diff --git a/src/Model/Table/SettingsProviderTable.php b/src/Model/Table/SettingsProviderTable.php
index e0cdc9f..08ea323 100644
--- a/src/Model/Table/SettingsProviderTable.php
+++ b/src/Model/Table/SettingsProviderTable.php
@@ -248,12 +248,15 @@ class SettingsProviderTable extends AppTable
],
'UI' => [
'General' => [
- ],
- 'app.ui.dark' => [
- 'description' => __('Enable the dark theme of the application'),
- 'default' => false,
- 'name' => __('Dark theme'),
- 'type' => 'boolean',
+ 'app.ui.dark' => [
+ 'description' => __('Enable the dark theme of the application'),
+ 'default' => false,
+ 'name' => __('Dark theme'),
+ 'test' => function() {
+ return 'Fake error';
+ },
+ 'type' => 'boolean',
+ ],
],
],
],
diff --git a/templates/Instance/settings.php b/templates/Instance/settings.php
index ca8a639..44327ff 100644
--- a/templates/Instance/settings.php
+++ b/templates/Instance/settings.php
@@ -129,7 +129,7 @@ function genLevel1($level1Setting, $appView)
}
$contentHtml = implode('', $content1);
$scrollspyNav = genScrollspyNav($nav1);
- $mainPanelHeight = 'calc(100vh - 8px - 42px - 1rem - 56px - 38px - 1rem)';
+ $mainPanelHeight = 'calc(100vh - 42px - 1rem - 56px - 38px - 1rem)';
$container = '
';
$container .= "
{$scrollspyNav}
";
$container .= "
{$contentHtml}
";
@@ -285,11 +285,13 @@ function genInputCheckbox($settingName, $setting, $appView)
$settingId = str_replace('.', '_', $settingName);
$switch = $appView->Bootstrap->genNode('input', [
'class' => [
- 'custom-control-input'
+ 'custom-control-input',
+ (!empty($setting['error']) ? 'is-invalid' : ''),
+ (!empty($setting['error']) && $setting['severity'] == 'warning' ? 'warning' : ''),
],
'type' => 'checkbox',
'value' => !empty($setting['value']) ? 1 : 0,
- 'checked' => !empty($setting['value']) ? 'checked' : '',
+ (!empty($setting['value']) ? 'checked' : '') => !empty($setting['value']) ? 'checked' : '',
'id' => $settingId,
'data-setting-name' => $settingName,
]);
@@ -421,26 +423,22 @@ function isLeaf($setting)
})
$('.tab-content input').on('input', function() {
- handleSettingValueChange($(this))
+ if ($(this).attr('type') == 'checkbox') {
+ const $input = $(this)
+ const $inputGroup = $(this).closest('.form-group')
+ const settingName = $(this).data('setting-name')
+ const settingValue = $(this).is(':checked')
+ saveSetting($inputGroup[0], $input, settingName, settingValue)
+ } else {
+ handleSettingValueChange($(this))
+ }
})
$('.tab-content .input-group-actions .btn-save-setting').click(function() {
const $input = $(this).closest('.input-group').find('input')
const settingName = $input.data('setting-name')
const settingValue = $input.val()
- const url = '/instance/saveSetting/'
- const data = {
- name: settingName,
- value: settingValue,
- }
- const APIOptions = {
- statusNode: this
- }
- AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((result) => {
- settingsFlattened[settingName] = result.data
- $input.val(result.data.value)
- handleSettingValueChange($input)
- })
+ saveSetting(this, $input, settingName, settingValue)
})
$('.tab-content .input-group-actions .btn-reset-setting').click(function() {
const $btn = $(this)
@@ -451,6 +449,26 @@ function isLeaf($setting)
})
})
+ function saveSetting(statusNode, $input, settingName, settingValue) {
+ const url = '/instance/saveSetting/'
+ const data = {
+ name: settingName,
+ value: settingValue,
+ }
+ const APIOptions = {
+ statusNode: statusNode,
+ }
+ AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((result) => {
+ settingsFlattened[settingName] = result.data
+ if ($input.attr('type') == 'checkbox') {
+ $input.prop('checked', result.data.value)
+ } else {
+ $input.val(result.data.value)
+ }
+ handleSettingValueChange($input)
+ })
+ }
+
function settingMatcher(params, data) {
if (params.term == null || params.term.trim() === '') {
return data;
@@ -504,7 +522,8 @@ function isLeaf($setting)
function handleSettingValueChange($input) {
const oldValue = settingsFlattened[$input.data('setting-name')].value
- if ($input.val() == oldValue) {
+ const newValue = ($input.attr('type') == 'checkbox' ? $input.is(':checked') : $input.val())
+ if (newValue == oldValue) {
restoreWarnings($input)
} else {
removeWarnings($input)
@@ -532,6 +551,8 @@ function isLeaf($setting)
$input.addClass('warning')
}
$inputGroup.parent().find('.invalid-feedback').addClass('d-block').text(setting.errorMessage)
+ } else {
+ removeWarnings($input)
}
const $callout = $input.closest('.settings-group')
updateCalloutColors($callout)
@@ -539,6 +560,9 @@ function isLeaf($setting)
}
function updateCalloutColors($callout) {
+ if ($callout.length == 0) {
+ return
+ }
const $settings = $callout.find('input')
const settingNames = Array.from($settings).map((i) => {
return $(i).data('setting-name')
diff --git a/webroot/css/bootstrap-additional.css b/webroot/css/bootstrap-additional.css
index 00171be..63c730f 100644
--- a/webroot/css/bootstrap-additional.css
+++ b/webroot/css/bootstrap-additional.css
@@ -142,6 +142,22 @@ div.progress-timeline .progress-line.progress-inactive {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23ffc107' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ffc107' stroke='none'/%3e%3c/svg%3e")
}
+.custom-control-input.is-invalid.warning ~ .custom-control-label {
+ color: unset;
+}
+.custom-control-input.is-invalid.warning ~ .custom-control-label::before {
+ border-color: #ffc107;
+}
+.custom-control-input.is-invalid.warning:checked ~ .custom-control-label::before {
+ background-color: #ffc107;
+}
+.custom-control-input.is-invalid.warning:focus:not(:checked) ~ .custom-control-label::before {
+ border-color: #ffc107;
+}
+.custom-control-input.is-invalid.warning:focus ~ .custom-control-label::before {
+ box-shadow: 0 0 0 0.2rem #ffc10740;
+}
+
.p-abs-center-y {
top: 50%;
transform: translateY(-50%);
diff --git a/webroot/js/bootstrap-helper.js b/webroot/js/bootstrap-helper.js
index 80af3e8..61bca3f 100644
--- a/webroot/js/bootstrap-helper.js
+++ b/webroot/js/bootstrap-helper.js
@@ -833,7 +833,7 @@ class OverlayFactory {
* @property {string=('primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark'|'white'|'transparent')} variant - The variant of the overlay
* @property {number} opacity - The opacity of the overlay
* @property {boolean} rounded - If the overlay should be rounded
- * @property {number} auto - Whether overlay and spinner options should be adapted automatically based on the node
+ * @property {boolean} auto - Whether overlay and spinner options should be adapted automatically based on the node
* @property {string=('primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark'|'white'|'transparent')} spinnerVariant - The variant of the spinner
* @property {boolean} spinnerSmall - If the spinner inside the overlay should be small
* @property {string=('border'|'grow')} spinnerSmall - If the spinner inside the overlay should be small