chg: [instance:settings] Improved support of selects

pull/70/head
mokaddem 2021-07-22 16:59:26 +02:00
parent 7c4b45a6e0
commit 99e3576221
3 changed files with 141 additions and 81 deletions

View File

@ -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',
],
'to-del3' => [
'description' => 'to del',
'errorMessage' => 'to del',
'name' => 'Hero',
'type' => 'select'
],
'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' => [

View File

@ -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));
}
function genInputMultiSelect($settingId, $setting, $appView)
$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($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>

View File

@ -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;