chg: [instance:settings] Improved support of selects
parent
7c4b45a6e0
commit
99e3576221
|
@ -109,6 +109,11 @@ class SettingsProviderTable extends AppTable
|
|||
private function evaluateLeaf($setting, $settingSection)
|
||||
{
|
||||
$skipValidation = false;
|
||||
if ($setting['type'] == 'select') {
|
||||
if (!empty($setting['options']) && is_callable($setting['options'])) {
|
||||
$setting['options'] = $setting['options']($this);
|
||||
}
|
||||
}
|
||||
if (isset($setting['dependsOn'])) {
|
||||
$parentSetting = null;
|
||||
foreach ($settingSection as $settingSectionName => $settingSectionConfig) {
|
||||
|
@ -189,19 +194,30 @@ class SettingsProviderTable extends AppTable
|
|||
},
|
||||
'type' => 'string'
|
||||
],
|
||||
'to-del2' => [
|
||||
'description' => 'to del',
|
||||
'errorMessage' => 'to del',
|
||||
'sc2.hero' => [
|
||||
'description' => 'The true hero',
|
||||
'default' => '',
|
||||
'name' => 'To DEL 2',
|
||||
'type' => 'string'
|
||||
'options' => [
|
||||
'Jim Raynor' => 'Jim Raynor',
|
||||
'Sarah Kerrigan' => 'Sarah Kerrigan',
|
||||
'Artanis' => 'Artanis',
|
||||
'Zeratul' => 'Zeratul',
|
||||
],
|
||||
'name' => 'Hero',
|
||||
'type' => 'select'
|
||||
],
|
||||
'to-del3' => [
|
||||
'description' => 'to del',
|
||||
'errorMessage' => 'to del',
|
||||
'sc2.antagonist' => [
|
||||
'description' => 'The real bad guy',
|
||||
'default' => '',
|
||||
'name' => 'To DEL 2',
|
||||
'type' => 'string'
|
||||
'options' => function($settingsProviders) {
|
||||
return [
|
||||
'Amon' => 'Amon',
|
||||
'Sarah Kerrigan' => 'Sarah Kerrigan',
|
||||
'Narud' => 'Narud',
|
||||
];
|
||||
},
|
||||
'name' => 'Antagonist',
|
||||
'type' => 'select'
|
||||
],
|
||||
],
|
||||
'floating-setting' => [
|
||||
|
|
|
@ -243,12 +243,10 @@ function genSingleSetting($settingName, $setting, $appView)
|
|||
$appView->Bootstrap->genNode('a', [
|
||||
'class' => ['position-absolute', 'fas fa-times', 'p-abs-center-y', 'text-reset text-decoration-none', 'btn-reset-setting'],
|
||||
'href' => '#',
|
||||
'style' => 'left: -1.25em; z-index: 5;'
|
||||
]),
|
||||
$appView->Bootstrap->genNode('button', [
|
||||
'class' => ['btn', 'btn-success', 'btn-save-setting'],
|
||||
'type' => 'button',
|
||||
'style' => 'z-index: 5;'
|
||||
], __('save')),
|
||||
]));
|
||||
$inputGroup = $appView->Bootstrap->genNode('div', [
|
||||
|
@ -324,10 +322,37 @@ function genInputInteger($settingName, $setting, $appView)
|
|||
'aria-describedby' => "{$settingId}Help"
|
||||
]);
|
||||
}
|
||||
function genInputSelect($settingId, $setting, $appView)
|
||||
function genInputSelect($settingName, $setting, $appView)
|
||||
{
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
$setting['value'] = $setting['value'] ?? '';
|
||||
$options = [
|
||||
$appView->Bootstrap->genNode('option', ['value' => '-1', 'data-is-empty-option' => '1'], __('Select an option'))
|
||||
];
|
||||
foreach ($setting['options'] as $key => $value) {
|
||||
$options[] = $appView->Bootstrap->genNode('option', [
|
||||
'class' => [],
|
||||
'value' => $key,
|
||||
($setting['value'] == $value ? 'selected' : '') => $setting['value'] == $value ? 'selected' : '',
|
||||
], h($value));
|
||||
}
|
||||
$options = implode('', $options);
|
||||
return $appView->Bootstrap->genNode('select', [
|
||||
'class' => [
|
||||
'custom-select',
|
||||
'pr-4',
|
||||
(!empty($setting['error']) ? 'is-invalid' : ''),
|
||||
(!empty($setting['error']) ? "border-{$appView->get('variantFromSeverity')[$setting['severity']]}" : ''),
|
||||
(!empty($setting['error']) && $setting['severity'] == 'warning' ? 'warning' : ''),
|
||||
],
|
||||
'type' => 'text',
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
'placeholder' => $setting['default'] ?? '',
|
||||
'aria-describedby' => "{$settingId}Help"
|
||||
], $options);
|
||||
}
|
||||
function genInputMultiSelect($settingId, $setting, $appView)
|
||||
function genInputMultiSelect($settingName, $setting, $appView)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -422,7 +447,7 @@ function isLeaf($setting)
|
|||
$("#search-settings").val(null).trigger('change.select2');
|
||||
})
|
||||
|
||||
$('.tab-content input').on('input', function() {
|
||||
$('.tab-content input, .tab-content select').on('input', function() {
|
||||
if ($(this).attr('type') == 'checkbox') {
|
||||
const $input = $(this)
|
||||
const $inputGroup = $(this).closest('.form-group')
|
||||
|
@ -435,15 +460,18 @@ function isLeaf($setting)
|
|||
})
|
||||
|
||||
$('.tab-content .input-group-actions .btn-save-setting').click(function() {
|
||||
const $input = $(this).closest('.input-group').find('input')
|
||||
const $input = $(this).closest('.input-group').find('input, select')
|
||||
const settingName = $input.data('setting-name')
|
||||
const settingValue = $input.val()
|
||||
saveSetting(this, $input, settingName, settingValue)
|
||||
})
|
||||
$('.tab-content .input-group-actions .btn-reset-setting').click(function() {
|
||||
const $btn = $(this)
|
||||
const $input = $btn.closest('.input-group').find('input')
|
||||
const oldValue = settingsFlattened[$input.data('setting-name')].value
|
||||
const $input = $btn.closest('.input-group').find('input, select')
|
||||
let oldValue = settingsFlattened[$input.data('setting-name')].value
|
||||
if ($input.is('select')) {
|
||||
oldValue = oldValue !== undefined ? oldValue : -1
|
||||
}
|
||||
$input.val(oldValue)
|
||||
handleSettingValueChange($input)
|
||||
})
|
||||
|
@ -469,6 +497,72 @@ function isLeaf($setting)
|
|||
})
|
||||
}
|
||||
|
||||
function handleSettingValueChange($input) {
|
||||
const oldValue = settingsFlattened[$input.data('setting-name')].value
|
||||
const newValue = ($input.attr('type') == 'checkbox' ? $input.is(':checked') : $input.val())
|
||||
if (newValue == oldValue) {
|
||||
restoreWarnings($input)
|
||||
} else {
|
||||
removeWarnings($input)
|
||||
}
|
||||
}
|
||||
|
||||
function removeWarnings($input) {
|
||||
const $inputGroup = $input.closest('.input-group')
|
||||
const $inputGroupAppend = $inputGroup.find('.input-group-append')
|
||||
const $saveButton = $inputGroup.find('button.btn-save-setting')
|
||||
$input.removeClass(['is-invalid', 'border-warning', 'border-danger'])
|
||||
$inputGroupAppend.removeClass('d-none')
|
||||
if ($input.is('select') && $input.find('option:selected').data('is-empty-option') == 1) {
|
||||
$inputGroupAppend.addClass('d-none') // hide save button if empty selection picked
|
||||
}
|
||||
$inputGroup.parent().find('.invalid-feedback').removeClass('d-block')
|
||||
}
|
||||
|
||||
function restoreWarnings($input) {
|
||||
const $inputGroup = $input.closest('.input-group')
|
||||
const $inputGroupAppend = $inputGroup.find('.input-group-append')
|
||||
const $saveButton = $inputGroup.find('button.btn-save-setting')
|
||||
const setting = settingsFlattened[$input.data('setting-name')]
|
||||
if (setting.error) {
|
||||
borderVariant = setting.severity !== undefined ? variantFromSeverity[setting.severity] : 'warning'
|
||||
$input.addClass(['is-invalid', `border-${borderVariant}`])
|
||||
if (setting.severity == 'warning') {
|
||||
$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)
|
||||
$inputGroupAppend.addClass('d-none')
|
||||
}
|
||||
|
||||
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')
|
||||
})
|
||||
const severityMapping = {null: 0, info: 1, warning: 2, critical: 3}
|
||||
const severityMappingInverted = Object.assign({}, ...Object.entries(severityMapping).map(([k, v]) => ({[v]: k})))
|
||||
let highestSeverity = severityMapping[null]
|
||||
settingNames.forEach(name => {
|
||||
if (settingsFlattened[name].error) {
|
||||
highestSeverity = severityMapping[settingsFlattened[name].severity] > highestSeverity ? severityMapping[settingsFlattened[name].severity] : highestSeverity
|
||||
}
|
||||
});
|
||||
highestSeverity = severityMappingInverted[highestSeverity]
|
||||
$callout.removeClass(['callout', 'callout-danger', 'callout-warning', 'callout-info'])
|
||||
if (highestSeverity !== null) {
|
||||
$callout.addClass(['callout', `callout-${variantFromSeverity[highestSeverity]}`])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function settingMatcher(params, data) {
|
||||
if (params.term == null || params.term.trim() === '') {
|
||||
return data;
|
||||
|
@ -519,69 +613,6 @@ function isLeaf($setting)
|
|||
function formatSettingSearchSelection(state) {
|
||||
return state.text
|
||||
}
|
||||
|
||||
function handleSettingValueChange($input) {
|
||||
const oldValue = settingsFlattened[$input.data('setting-name')].value
|
||||
const newValue = ($input.attr('type') == 'checkbox' ? $input.is(':checked') : $input.val())
|
||||
if (newValue == oldValue) {
|
||||
restoreWarnings($input)
|
||||
} else {
|
||||
removeWarnings($input)
|
||||
}
|
||||
}
|
||||
|
||||
function removeWarnings($input) {
|
||||
const $inputGroup = $input.closest('.input-group')
|
||||
const $inputGroupAppend = $inputGroup.find('.input-group-append')
|
||||
const $saveButton = $inputGroup.find('button.btn-save-setting')
|
||||
$input.removeClass(['is-invalid', 'border-warning', 'border-danger'])
|
||||
$inputGroupAppend.removeClass('d-none')
|
||||
$inputGroup.parent().find('.invalid-feedback').removeClass('d-block')
|
||||
}
|
||||
|
||||
function restoreWarnings($input) {
|
||||
const $inputGroup = $input.closest('.input-group')
|
||||
const $inputGroupAppend = $inputGroup.find('.input-group-append')
|
||||
const $saveButton = $inputGroup.find('button.btn-save-setting')
|
||||
const setting = settingsFlattened[$input.data('setting-name')]
|
||||
if (setting.error) {
|
||||
borderVariant = setting.severity !== undefined ? variantFromSeverity[setting.severity] : 'warning'
|
||||
$input.addClass(['is-invalid', `border-${borderVariant}`])
|
||||
if (setting.severity == 'warning') {
|
||||
$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)
|
||||
$inputGroupAppend.addClass('d-none')
|
||||
}
|
||||
|
||||
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')
|
||||
})
|
||||
const severityMapping = {null: 0, info: 1, warning: 2, critical: 3}
|
||||
const severityMappingInverted = Object.assign({}, ...Object.entries(severityMapping).map(([k, v]) => ({[v]: k})))
|
||||
let highestSeverity = severityMapping[null]
|
||||
settingNames.forEach(name => {
|
||||
if (settingsFlattened[name].error) {
|
||||
highestSeverity = severityMapping[settingsFlattened[name].severity] > highestSeverity ? severityMapping[settingsFlattened[name].severity] : highestSeverity
|
||||
}
|
||||
});
|
||||
highestSeverity = severityMappingInverted[highestSeverity]
|
||||
$callout.removeClass(['callout', 'callout-danger', 'callout-warning', 'callout-info'])
|
||||
if (highestSeverity !== null) {
|
||||
$callout.addClass(['callout', `callout-${variantFromSeverity[highestSeverity]}`])
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -620,4 +651,14 @@ function isLeaf($setting)
|
|||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.input-group-actions {
|
||||
z-index: 5;
|
||||
}
|
||||
a.btn-reset-setting {
|
||||
left: -1.25em;
|
||||
}
|
||||
.custom-select ~ div > a.btn-reset-setting {
|
||||
left: -2.5em;
|
||||
}
|
||||
</style>
|
|
@ -141,6 +141,9 @@ div.progress-timeline .progress-line.progress-inactive {
|
|||
.form-control.is-invalid.warning {
|
||||
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-select.is-invalid.warning {
|
||||
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, 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") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
||||
}
|
||||
|
||||
.custom-control-input.is-invalid.warning ~ .custom-control-label {
|
||||
color: unset;
|
||||
|
|
Loading…
Reference in New Issue