chg: [instance:settings] UI refactoring
parent
cf793b6742
commit
ef86e77e41
|
@ -277,10 +277,9 @@ class SettingsProviderTable extends AppTable
|
|||
'Security' => [
|
||||
'Development' => [
|
||||
'Debugging' => [
|
||||
'Debug' => [
|
||||
'app.security.debug' => [
|
||||
'description' => __('The debug level of the instance'),
|
||||
'default' => 0,
|
||||
'dependsOn' => 'host',
|
||||
'name' => __('Debug Level'),
|
||||
'test' => function($value, $setting, $validator) {
|
||||
$validator->range('value', [0, 3]);
|
||||
|
|
|
@ -1,99 +1,45 @@
|
|||
<?php
|
||||
// debug($settings);
|
||||
// debug($settingsProvider);
|
||||
// debug($notices);
|
||||
$mainNoticeHeading = [
|
||||
'critical' => __('Your Cerebrate instance requires immediate attention.'),
|
||||
'warning' => __('Issues found, it is recommended that you resolve them.'),
|
||||
'info' => __('There are some optional settings that are incorrect or not set.'),
|
||||
];
|
||||
$noticeDescriptionPerLevel = [
|
||||
'critical' => __('Cerebrate will not operate correctly or will be unsecure until these issues are resolved.'),
|
||||
'warning' => __('Some of the features of Cerebrate cannot be utilised until these issues are resolved.'),
|
||||
'info' => __('There are some optional tweaks that could be done to improve the looks of your Cerebrate instance.'),
|
||||
];
|
||||
$headingPerLevel = [
|
||||
'critical' => __('Critical settings'),
|
||||
'warning' => __('Warning settings'),
|
||||
'info' => __('Info settings'),
|
||||
];
|
||||
$variantFromSeverity = [
|
||||
'critical' => 'danger',
|
||||
'warning' => 'warning',
|
||||
'info' => 'info',
|
||||
];
|
||||
$this->set('variantFromSeverity', $variantFromSeverity);
|
||||
|
||||
$alertVariant = 'info';
|
||||
$alertBody = '';
|
||||
$skipHeading = false;
|
||||
$tableItems = [];
|
||||
foreach (array_keys($mainNoticeHeading) as $level) {
|
||||
if(!empty($notices[$level])) {
|
||||
$variant = $variantFromSeverity[$level];
|
||||
if (!$skipHeading) {
|
||||
$alertBody .= sprintf('<h5 class="alert-heading">%s</h5>', $mainNoticeHeading[$level]);
|
||||
$alertVariant = $variant;
|
||||
$skipHeading = true;
|
||||
}
|
||||
$tableItems[] = [
|
||||
'severity' => $headingPerLevel[$level],
|
||||
'issues' => count($notices[$level]),
|
||||
'badge-variant' => $variant,
|
||||
'description' => $noticeDescriptionPerLevel[$level],
|
||||
];
|
||||
}
|
||||
}
|
||||
$alertBody .= $this->Bootstrap->table([
|
||||
'small' => true,
|
||||
'striped' => false,
|
||||
'hover' => false,
|
||||
'borderless' => true,
|
||||
'bordered' => false,
|
||||
'tableClass' => 'mb-0'
|
||||
], [
|
||||
'fields' => [
|
||||
['key' => 'severity', 'label' => __('Severity')],
|
||||
['key' => 'issues', 'label' => __('Issues'), 'formatter' => function($count, $row) {
|
||||
return $this->Bootstrap->badge([
|
||||
'variant' => $row['badge-variant'],
|
||||
'text' => $count
|
||||
]);
|
||||
}],
|
||||
['key' => 'description', 'label' => __('Description')]
|
||||
],
|
||||
'items' => $tableItems,
|
||||
]);
|
||||
$settingNotice = $this->Bootstrap->alert([
|
||||
'dismissible' => false,
|
||||
'variant' => $alertVariant,
|
||||
'html' => $alertBody
|
||||
]);
|
||||
$settingNotice = sprintf('<div class="mt-3">%s</div>', $settingNotice);
|
||||
$this->set('settingNotice', $settingNotice);
|
||||
$settingTable = genLevel0($settingsProvider, $this);
|
||||
$settingTable = genNavcard($settingsProvider, $this);
|
||||
?>
|
||||
|
||||
<script>
|
||||
const variantFromSeverity = <?= json_encode($variantFromSeverity) ?>;
|
||||
const settingsFlattened = <?= json_encode($settingsFlattened) ?>;
|
||||
</script>
|
||||
|
||||
<div class="px-5">
|
||||
<div class="mb-3">
|
||||
<select id="search-settings" class="d-block w-100" aria-describedby="<?= __('Search setting input') ?>"><option></option></select>
|
||||
<?=
|
||||
$this->element('Settings/search', [
|
||||
]);
|
||||
?>
|
||||
</div>
|
||||
<?= $settingTable; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
function genLevel0($settingsProvider, $appView)
|
||||
function genNavcard($settingsProvider, $appView)
|
||||
{
|
||||
$content0 = [];
|
||||
$level0 = array_keys($settingsProvider);
|
||||
foreach ($settingsProvider as $level1Name => $level1Setting) {
|
||||
if (!empty($level1Setting)) {
|
||||
$content0[] = genLevel1($level1Setting, $appView);
|
||||
$cardContent = [];
|
||||
$cardNavs = array_keys($settingsProvider);
|
||||
foreach ($settingsProvider as $navName => $sectionSettings) {
|
||||
if (!empty($sectionSettings)) {
|
||||
$cardContent[] = genContentForNav($sectionSettings, $appView);
|
||||
} else {
|
||||
$content0[] = __('No Settings available yet');
|
||||
$cardContent[] = __('No Settings available yet');
|
||||
}
|
||||
}
|
||||
array_unshift($level0, __('Settings Diagnostic'));
|
||||
array_unshift($content0, $appView->get('settingNotice'));
|
||||
array_unshift($cardNavs, __('Settings Diagnostic'));
|
||||
$notice = $appView->element('Settings/notice', [
|
||||
'variantFromSeverity' => $appView->get('variantFromSeverity'),
|
||||
]);
|
||||
array_unshift($cardContent, $notice);
|
||||
$tabsOptions0 = [
|
||||
// 'vertical' => true,
|
||||
// 'vertical-size' => 2,
|
||||
|
@ -102,33 +48,35 @@ function genLevel0($settingsProvider, $appView)
|
|||
'justify' => 'center',
|
||||
'nav-class' => ['settings-tabs'],
|
||||
'data' => [
|
||||
'navs' => $level0,
|
||||
'content' => $content0
|
||||
'navs' => $cardNavs,
|
||||
'content' => $cardContent
|
||||
]
|
||||
];
|
||||
$table0 = $appView->Bootstrap->tabs($tabsOptions0);
|
||||
return $table0;
|
||||
}
|
||||
|
||||
function genLevel1($level1Setting, $appView)
|
||||
function genContentForNav($sectionSettings, $appView)
|
||||
{
|
||||
$content1 = [];
|
||||
$nav1 = [];
|
||||
foreach ($level1Setting as $level2Name => $level2Setting) {
|
||||
if (!empty($level2Setting)) {
|
||||
$content1[] = genLevel2($level2Name, $level2Setting, $appView);
|
||||
$groupedContent = [];
|
||||
$groupedSetting = [];
|
||||
foreach ($sectionSettings as $sectionName => $subSectionSettings) {
|
||||
if (!empty($subSectionSettings)) {
|
||||
$groupedContent[] = genSection($sectionName, $subSectionSettings, $appView);
|
||||
} else {
|
||||
$content1[] = '';
|
||||
$groupedContent[] = '';
|
||||
}
|
||||
$nav1[$level2Name] = array_filter( // only show grouped settings
|
||||
array_keys($level2Setting),
|
||||
function ($settingGroupName) use ($level2Setting) {
|
||||
return !isLeaf($level2Setting[$settingGroupName]) && !empty($level2Setting[$settingGroupName]);
|
||||
$groupedSetting[$sectionName] = array_filter( // only show grouped settings
|
||||
array_keys($subSectionSettings),
|
||||
function ($settingGroupName) use ($subSectionSettings) {
|
||||
return !isLeaf($subSectionSettings[$settingGroupName]) && !empty($subSectionSettings[$settingGroupName]);
|
||||
}
|
||||
);
|
||||
}
|
||||
$contentHtml = implode('', $content1);
|
||||
$scrollspyNav = genScrollspyNav($nav1);
|
||||
$contentHtml = implode('', $groupedContent);
|
||||
$scrollspyNav = $appView->element('Settings/scrollspyNav', [
|
||||
'groupedSetting' => $groupedSetting
|
||||
]);
|
||||
$mainPanelHeight = 'calc(100vh - 42px - 1rem - 56px - 38px - 1rem)';
|
||||
$container = '<div class="d-flex">';
|
||||
$container .= "<div class=\"\" style=\"flex: 0 0 10em;\">{$scrollspyNav}</div>";
|
||||
|
@ -137,319 +85,42 @@ function genLevel1($level1Setting, $appView)
|
|||
return $container;
|
||||
}
|
||||
|
||||
function genLevel2($level2Name, $level2Setting, $appView)
|
||||
function genSection($sectionName, $subSectionSettings, $appView)
|
||||
{
|
||||
foreach ($level2Setting as $level3Name => $level3Setting) {
|
||||
if (!empty($level3Setting)) {
|
||||
$level3 = genLevel3($level2Name, $level3Name, $level3Setting, $appView);
|
||||
$content2[] = sprintf('<div id="%s">%s</div>', sprintf('sp-%s', h($level2Name)), $level3);
|
||||
$sectionContent = [];
|
||||
$sectionContent[] = sprintf('<div id="%s">', sprintf('sp-%s', h($sectionName)));
|
||||
foreach ($subSectionSettings as $panelName => $panelSettings) {
|
||||
if (!empty($panelSettings)) {
|
||||
$panelHTML = $appView->element('Settings/panel', [
|
||||
'sectionName' => $sectionName,
|
||||
'panelName' => $panelName,
|
||||
'panelSettings' => $panelSettings,
|
||||
]);
|
||||
$sectionContent[] = $panelHTML;
|
||||
} else {
|
||||
$content2[] = '';
|
||||
$sectionContent[] = '';
|
||||
}
|
||||
}
|
||||
return implode('', $content2);
|
||||
}
|
||||
|
||||
function genLevel3($level2Name, $settingGroupName, $setting, $appView)
|
||||
{
|
||||
$settingGroup = '';
|
||||
if (isLeaf($setting)) {
|
||||
$tmp = genSingleSetting($settingGroupName, $setting, $appView);
|
||||
$settingGroup = "<div>{$tmp}</div>";
|
||||
} else {
|
||||
$tmpID = sprintf('sp-%s-%s', h($level2Name), h($settingGroupName));
|
||||
$settingGroup .= sprintf('<h4 id="%s"><a class="text-reset text-decoration-none" href="#%s">%s</a></h4>', $tmpID, $tmpID, h($settingGroupName));
|
||||
$groupIssueSeverity = false;
|
||||
foreach ($setting as $singleSettingName => $singleSetting) {
|
||||
$tmp = genSingleSetting($singleSettingName, $singleSetting, $appView);
|
||||
$settingGroup .= sprintf('<div class="ml-3">%s</div>', $tmp);
|
||||
if (!empty($singleSetting['error'])) {
|
||||
$settingVariant = $appView->get('variantFromSeverity')[$singleSetting['severity']];
|
||||
if ($groupIssueSeverity != 'danger') {
|
||||
if ($groupIssueSeverity != 'warning') {
|
||||
$groupIssueSeverity = $settingVariant;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$settingGroup = $appView->Bootstrap->genNode('div', [
|
||||
'class' => [
|
||||
'shadow',
|
||||
'p-2',
|
||||
'mb-4',
|
||||
'rounded',
|
||||
'settings-group',
|
||||
(!empty($groupIssueSeverity) ? "callout callout-${groupIssueSeverity}" : ''),
|
||||
($appView->get('darkMode') ? 'bg-dark' : 'bg-light')
|
||||
],
|
||||
], $settingGroup);
|
||||
}
|
||||
return $settingGroup;
|
||||
}
|
||||
|
||||
function genSingleSetting($settingName, $setting, $appView)
|
||||
{
|
||||
$dependsOnHtml = '';
|
||||
if (!empty($setting['dependsOn'])) {
|
||||
$dependsOnHtml = $appView->Bootstrap->genNode('span', [
|
||||
], $appView->Bootstrap->genNode('sup', [
|
||||
'class' => [
|
||||
$appView->FontAwesome->getClass('info'),
|
||||
'ml-1',
|
||||
],
|
||||
'title' => __('This setting depends on the validity of: {0}', h($setting['dependsOn']))
|
||||
]));
|
||||
}
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
$label = $appView->Bootstrap->genNode('label', [
|
||||
'class' => ['font-weight-bolder', 'mb-0'],
|
||||
'for' => $settingId
|
||||
], h($setting['name']) . $dependsOnHtml);
|
||||
$description = '';
|
||||
if (!empty($setting['description'])) {
|
||||
$description = $appView->Bootstrap->genNode('small', [
|
||||
'class' => ['form-text', 'text-muted', 'mt-0'],
|
||||
'id' => "{$settingId}Help"
|
||||
], h($setting['description']));
|
||||
}
|
||||
$textColor = 'text-warning';
|
||||
if (!empty($setting['severity'])) {
|
||||
$textColor = "text-{$appView->get('variantFromSeverity')[$setting['severity']]}";
|
||||
}
|
||||
$error = $appView->Bootstrap->genNode('div', [
|
||||
'class' => ['d-block', 'invalid-feedback', $textColor],
|
||||
], (!empty($setting['error']) ? h($setting['errorMessage']) : ''));
|
||||
if (empty($setting['type'])) {
|
||||
$setting['type'] = 'string';
|
||||
}
|
||||
if ($setting['type'] == 'string') {
|
||||
$input = genInputString($settingName, $setting, $appView);
|
||||
} elseif ($setting['type'] == 'boolean') {
|
||||
$input = genInputCheckbox($settingName, $setting, $appView);
|
||||
$description = '';
|
||||
} elseif ($setting['type'] == 'integer') {
|
||||
$input = genInputInteger($settingName, $setting, $appView);
|
||||
} elseif ($setting['type'] == 'select') {
|
||||
$input = genInputSelect($settingName, $setting, $appView);
|
||||
} elseif ($setting['type'] == 'multi-select') {
|
||||
$input = genInputMultiSelect($settingName, $setting, $appView);
|
||||
} else {
|
||||
$input = genInputString($settingName, $setting, $appView);
|
||||
}
|
||||
|
||||
$inputGroupSave = $appView->Bootstrap->genNode('div', [
|
||||
'class' => ['input-group-append', 'd-none', 'position-relative', 'input-group-actions'],
|
||||
], implode('', [
|
||||
$appView->Bootstrap->genNode('a', [
|
||||
'class' => ['position-absolute', 'fas fa-times', 'p-abs-center-y', 'text-reset text-decoration-none', 'btn-reset-setting'],
|
||||
'href' => '#',
|
||||
]),
|
||||
$appView->Bootstrap->genNode('button', [
|
||||
'class' => ['btn', 'btn-success', 'btn-save-setting'],
|
||||
'type' => 'button',
|
||||
], __('save')),
|
||||
]));
|
||||
$inputGroup = $appView->Bootstrap->genNode('div', [
|
||||
'class' => ['input-group'],
|
||||
], implode('', [$input, $inputGroupSave]));
|
||||
|
||||
$container = $appView->Bootstrap->genNode('div', [
|
||||
'class' => ['form-group', 'mb-2']
|
||||
], implode('', [$label, $inputGroup, $description, $error]));
|
||||
return $container;
|
||||
}
|
||||
|
||||
function genInputString($settingName, $setting, $appView)
|
||||
{
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
return $appView->Bootstrap->genNode('input', [
|
||||
'class' => [
|
||||
'form-control',
|
||||
'pr-4',
|
||||
(!empty($setting['error']) ? 'is-invalid' : ''),
|
||||
(!empty($setting['error']) ? "border-{$appView->get('variantFromSeverity')[$setting['severity']]}" : ''),
|
||||
(!empty($setting['error']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
|
||||
],
|
||||
'type' => 'text',
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
'value' => isset($setting['value']) ? $setting['value'] : "",
|
||||
'placeholder' => $setting['default'] ?? '',
|
||||
'aria-describedby' => "{$settingId}Help"
|
||||
]);
|
||||
}
|
||||
function genInputCheckbox($settingName, $setting, $appView)
|
||||
{
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
$switch = $appView->Bootstrap->genNode('input', [
|
||||
'class' => [
|
||||
'custom-control-input',
|
||||
(!empty($setting['error']) ? 'is-invalid' : ''),
|
||||
(!empty($setting['error']) && $setting['severity'] == 'warning' ? 'warning' : ''),
|
||||
],
|
||||
'type' => 'checkbox',
|
||||
'value' => !empty($setting['value']) ? 1 : 0,
|
||||
(!empty($setting['value']) ? 'checked' : '') => !empty($setting['value']) ? 'checked' : '',
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
]);
|
||||
$label = $appView->Bootstrap->genNode('label', [
|
||||
'class' => [
|
||||
'custom-control-label'
|
||||
],
|
||||
'for' => $settingId,
|
||||
], h($setting['description']));
|
||||
$container = $appView->Bootstrap->genNode('div', [
|
||||
'class' => [
|
||||
'custom-control',
|
||||
'custom-switch',
|
||||
],
|
||||
], implode('', [$switch, $label]));
|
||||
return $container;
|
||||
}
|
||||
function genInputInteger($settingName, $setting, $appView)
|
||||
{
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
return $appView->Bootstrap->genNode('input', [
|
||||
'class' => [
|
||||
'form-control',
|
||||
(!empty($setting['error']) ? 'is-invalid' : ''),
|
||||
(!empty($setting['error']) ? "border-{$appView->get('variantFromSeverity')[$setting['severity']]}" : ''),
|
||||
(!empty($setting['error']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
|
||||
],
|
||||
'type' => 'number',
|
||||
'min' => '0',
|
||||
'step' => 1,
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
'aria-describedby' => "{$settingId}Help"
|
||||
]);
|
||||
}
|
||||
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']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
|
||||
],
|
||||
'type' => 'text',
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
'placeholder' => $setting['default'] ?? '',
|
||||
'aria-describedby' => "{$settingId}Help"
|
||||
], $options);
|
||||
}
|
||||
function genInputMultiSelect($settingName, $setting, $appView)
|
||||
{
|
||||
}
|
||||
|
||||
function genScrollspyNav($nav1)
|
||||
{
|
||||
$nav = '<nav id="navbar-scrollspy-setting" class="navbar">';
|
||||
$nav .= '<nav class="nav nav-pills flex-column">';
|
||||
foreach ($nav1 as $group => $sections) {
|
||||
$nav .= sprintf('<a class="nav-link main-group text-reset p-1" href="#%s">%s</a>', sprintf('sp-%s', h($group)), h($group));
|
||||
$nav .= sprintf('<nav class="nav nav-pills sub-group collapse flex-column" data-maingroup="%s">', sprintf('sp-%s', h($group)));
|
||||
foreach ($sections as $section) {
|
||||
$nav .= sprintf('<a class="nav-link nav-link-group text-reset ml-3 my-1 p-1" href="#%s">%s</a>', sprintf('sp-%s-%s', h($group), h($section)), h($section));
|
||||
}
|
||||
$nav .= '</nav>';
|
||||
}
|
||||
$nav .= '</nav>';
|
||||
$nav .= '</nav>';
|
||||
return $nav;
|
||||
$sectionContent[] = '</div>';
|
||||
return implode('', $sectionContent);
|
||||
}
|
||||
|
||||
function isLeaf($setting)
|
||||
{
|
||||
return !empty($setting['name']) && !empty($setting['type']);
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
|
||||
<script>
|
||||
const variantFromSeverity = <?= json_encode($variantFromSeverity) ?>;
|
||||
const settingsFlattened = <?= json_encode($settingsFlattened) ?>;
|
||||
let selectData = []
|
||||
for (const settingName in settingsFlattened) {
|
||||
if (Object.hasOwnProperty.call(settingsFlattened, settingName)) {
|
||||
const setting = settingsFlattened[settingName];
|
||||
const selectID = settingName.replaceAll('.', '_')
|
||||
selectData.push({
|
||||
id: selectID,
|
||||
text: setting.name,
|
||||
setting: setting
|
||||
})
|
||||
}
|
||||
}
|
||||
$(document).ready(function() {
|
||||
$('[data-spy="scroll"]').on('activate.bs.scrollspy', function(evt, {relatedTarget}) {
|
||||
const $associatedLink = $(`#navbar-scrollspy-setting nav.nav-pills .nav-link[href="${relatedTarget}"]`)
|
||||
let $associatedNav
|
||||
if ($associatedLink.hasClass('main-group')) {
|
||||
$associatedNav = $associatedLink.next()
|
||||
} else {
|
||||
$associatedNav = $associatedLink.parent()
|
||||
}
|
||||
const $allNavs = $('#navbar-scrollspy-setting nav.nav-pills.sub-group')
|
||||
$allNavs.removeClass('group-active').hide()
|
||||
$associatedNav.addClass('group-active').show()
|
||||
$('.depends-on-icon').tooltip({
|
||||
placement: 'right',
|
||||
})
|
||||
|
||||
$('.settings-tabs a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
|
||||
$('[data-spy="scroll"]').trigger('scroll.bs.scrollspy')
|
||||
})
|
||||
|
||||
$("#search-settings").select2({
|
||||
data: selectData,
|
||||
placeholder: '<?= __('Search setting by typing here...') ?>',
|
||||
templateResult: formatSettingSearchResult,
|
||||
templateSelection: formatSettingSearchSelection,
|
||||
matcher: settingMatcher,
|
||||
sorter: settingSorter,
|
||||
})
|
||||
.on('select2:select', function (e) {
|
||||
const selected = e.params.data
|
||||
const settingPath = selected.setting['setting-path']
|
||||
const settingPathTokenized = settingPath.split('.')
|
||||
const tabName = settingPathTokenized[0]
|
||||
const IDtoFocus = 'sp-' + settingPathTokenized.slice(1).join('-')
|
||||
const $navController = $('.settings-tabs').find('a.nav-link').filter(function() {
|
||||
return $(this).text() == tabName
|
||||
})
|
||||
if ($navController.length == 1) {
|
||||
$toFocus = $(`#${IDtoFocus}`).parent()
|
||||
if ($navController.hasClass('active')) {
|
||||
$toFocus[0].scrollIntoView()
|
||||
$toFocus.find(`input#${selected.id}`).focus()
|
||||
} else {
|
||||
$navController.on('shown.bs.tab.after-selection', () => {
|
||||
$toFocus[0].scrollIntoView()
|
||||
$toFocus.find(`input#${selected.id}`).focus()
|
||||
$navController.off('shown.bs.tab.after-selection')
|
||||
}).tab('show')
|
||||
}
|
||||
}
|
||||
$("#search-settings").val(null).trigger('change.select2');
|
||||
})
|
||||
|
||||
$('.tab-content input, .tab-content select').on('input', function() {
|
||||
if ($(this).attr('type') == 'checkbox') {
|
||||
const $input = $(this)
|
||||
|
@ -516,7 +187,7 @@ function isLeaf($setting)
|
|||
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'])
|
||||
$input.removeClass(['is-invalid', 'border-warning', 'border-danger', 'border-info'])
|
||||
$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
|
||||
|
@ -566,97 +237,9 @@ function isLeaf($setting)
|
|||
$callout.addClass(['callout', `callout-${variantFromSeverity[highestSeverity]}`])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function settingMatcher(params, data) {
|
||||
if (params.term == null || params.term.trim() === '') {
|
||||
return data;
|
||||
}
|
||||
if (data.text === undefined || data.setting === undefined) {
|
||||
return null;
|
||||
}
|
||||
let modifiedData = $.extend({}, data, true);
|
||||
const loweredTerms = params.term.trim().toLowerCase().split(' ')
|
||||
for (let i = 0; i < loweredTerms.length; i++) {
|
||||
const loweredTerm = loweredTerms[i];
|
||||
const settingNameMatch = data.setting['true-name'].toLowerCase().indexOf(loweredTerm) > -1 || data.text.toLowerCase().indexOf(loweredTerm) > -1
|
||||
const settingGroupMatch = data.setting['setting-path'].toLowerCase().indexOf(loweredTerm) > -1
|
||||
const settingDescMatch = data.setting.description.toLowerCase().indexOf(loweredTerm) > -1
|
||||
if (settingNameMatch || settingGroupMatch || settingDescMatch) {
|
||||
modifiedData.matchPriority = (settingNameMatch ? 10 : 0) + (settingGroupMatch ? 5 : 0) + (settingDescMatch ? 1 : 0)
|
||||
}
|
||||
}
|
||||
if (modifiedData.matchPriority > 0) {
|
||||
return modifiedData;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function settingSorter(data) {
|
||||
let sortedData = data.slice(0)
|
||||
sortedData = sortedData.sort((a, b) => {
|
||||
return a.matchPriority == b.matchPriority ? 0 : (b.matchPriority - a.matchPriority)
|
||||
})
|
||||
return sortedData;
|
||||
}
|
||||
|
||||
function formatSettingSearchResult(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const $state = $('<div/>').append(
|
||||
$('<div/>').addClass('d-flex justify-content-between')
|
||||
.append(
|
||||
$('<span/>').addClass('font-weight-bold').text(state.text),
|
||||
$('<span/>').addClass('font-weight-light').text(state.setting['setting-path'].replaceAll('.', ' ▸ '))
|
||||
),
|
||||
$('<div/>').addClass('font-italic font-weight-light ml-3').text(state.setting['description'])
|
||||
)
|
||||
return $state
|
||||
}
|
||||
|
||||
function formatSettingSearchSelection(state) {
|
||||
return state.text
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link {
|
||||
background-color: unset !important;
|
||||
color: black;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link:not(.main-group).active {
|
||||
color: #007bff !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group:before {
|
||||
margin-right: 0.25em;
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-weight: 900;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
text-rendering: auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group.active:before {
|
||||
content: "\f0d7";
|
||||
}
|
||||
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group:before {
|
||||
content: "\f0da";
|
||||
}
|
||||
|
||||
.select2-container {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.input-group-actions {
|
||||
z-index: 5;
|
||||
}
|
||||
|
@ -666,8 +249,7 @@ function isLeaf($setting)
|
|||
.custom-select ~ div > a.btn-reset-setting {
|
||||
left: -2.5em;
|
||||
}
|
||||
|
||||
.form-control ~ div > a.btn-reset-setting {
|
||||
.form-control[type="number"] ~ div > a.btn-reset-setting {
|
||||
left: -3em;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
if ($setting['type'] == 'string' || empty($setting['type'])) {
|
||||
$input = (function ($settingName, $setting, $appView) {
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
return $appView->Bootstrap->genNode('input', [
|
||||
'class' => [
|
||||
'form-control',
|
||||
'pr-4',
|
||||
(!empty($setting['error']) ? 'is-invalid' : ''),
|
||||
(!empty($setting['error']) ? "border-{$appView->get('variantFromSeverity')[$setting['severity']]}" : ''),
|
||||
(!empty($setting['error']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
|
||||
],
|
||||
'type' => 'text',
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
'value' => isset($setting['value']) ? $setting['value'] : "",
|
||||
'placeholder' => $setting['default'] ?? '',
|
||||
'aria-describedby' => "{$settingId}Help"
|
||||
]);
|
||||
})($settingName, $setting, $this);
|
||||
|
||||
} elseif ($setting['type'] == 'boolean') {
|
||||
$input = (function ($settingName, $setting, $appView) {
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
$switch = $appView->Bootstrap->genNode('input', [
|
||||
'class' => [
|
||||
'custom-control-input',
|
||||
(!empty($setting['error']) ? 'is-invalid' : ''),
|
||||
(!empty($setting['error']) && $setting['severity'] == 'warning' ? 'warning' : ''),
|
||||
],
|
||||
'type' => 'checkbox',
|
||||
'value' => !empty($setting['value']) ? 1 : 0,
|
||||
(!empty($setting['value']) ? 'checked' : '') => !empty($setting['value']) ? 'checked' : '',
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
]);
|
||||
$label = $appView->Bootstrap->genNode('label', [
|
||||
'class' => [
|
||||
'custom-control-label'
|
||||
],
|
||||
'for' => $settingId,
|
||||
], h($setting['description']));
|
||||
$container = $appView->Bootstrap->genNode('div', [
|
||||
'class' => [
|
||||
'custom-control',
|
||||
'custom-switch',
|
||||
],
|
||||
], implode('', [$switch, $label]));
|
||||
return $container;
|
||||
})($settingName, $setting, $this);
|
||||
$description = '';
|
||||
|
||||
} elseif ($setting['type'] == 'integer') {
|
||||
$input = (function ($settingName, $setting, $appView) {
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
return $appView->Bootstrap->genNode('input', [
|
||||
'class' => [
|
||||
'form-control',
|
||||
(!empty($setting['error']) ? 'is-invalid' : ''),
|
||||
(!empty($setting['error']) ? "border-{$appView->get('variantFromSeverity')[$setting['severity']]}" : ''),
|
||||
(!empty($setting['error']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
|
||||
],
|
||||
'type' => 'number',
|
||||
'min' => '0',
|
||||
'step' => 1,
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
'aria-describedby' => "{$settingId}Help"
|
||||
]);
|
||||
})($settingName, $setting, $this);
|
||||
|
||||
} elseif ($setting['type'] == 'select') {
|
||||
$input = (function ($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']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
|
||||
],
|
||||
'type' => 'text',
|
||||
'id' => $settingId,
|
||||
'data-setting-name' => $settingName,
|
||||
'placeholder' => $setting['default'] ?? '',
|
||||
'aria-describedby' => "{$settingId}Help"
|
||||
], $options);
|
||||
})($settingName, $setting, $this);
|
||||
|
||||
} elseif ($setting['type'] == 'multi-select') {
|
||||
$input = (function ($settingName, $setting, $appView) {
|
||||
return '';
|
||||
})($settingName, $setting, $this);
|
||||
}
|
||||
echo $input;
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
$settingId = str_replace('.', '_', $settingName);
|
||||
|
||||
$dependsOnHtml = '';
|
||||
if (!empty($setting['dependsOn'])) {
|
||||
$dependsOnHtml = $this->Bootstrap->genNode('span', [
|
||||
'class' => [
|
||||
'ml-1',
|
||||
'd-inline-block',
|
||||
'depends-on-icon'
|
||||
],
|
||||
'style' => 'min-width: 0.75em;',
|
||||
'title' => __('This setting depends on the validity of: {0}', h($setting['dependsOn'])),
|
||||
], $this->Bootstrap->genNode('sup', [
|
||||
'class' => $this->FontAwesome->getClass('info'),
|
||||
]));
|
||||
}
|
||||
$label = $this->Bootstrap->genNode('label', [
|
||||
'class' => ['font-weight-bolder', 'mb-0'],
|
||||
'for' => $settingId
|
||||
], h($setting['name']) . $dependsOnHtml);
|
||||
|
||||
$description = '';
|
||||
if (!empty($setting['description']) && (empty($setting['type']) || $setting['type'] != 'boolean')) {
|
||||
$description = $this->Bootstrap->genNode('small', [
|
||||
'class' => ['form-text', 'text-muted', 'mt-0'],
|
||||
'id' => "{$settingId}Help"
|
||||
], h($setting['description']));
|
||||
}
|
||||
$textColor = 'text-warning';
|
||||
if (!empty($setting['severity'])) {
|
||||
$textColor = "text-{$this->get('variantFromSeverity')[$setting['severity']]}";
|
||||
}
|
||||
$validationError = $this->Bootstrap->genNode('div', [
|
||||
'class' => ['d-block', 'invalid-feedback', $textColor],
|
||||
], (!empty($setting['error']) ? h($setting['errorMessage']) : ''));
|
||||
|
||||
$input = $this->element('Settings/field', [
|
||||
'setting' => $setting,
|
||||
'settingName' => $settingName,
|
||||
]);
|
||||
|
||||
$inputGroupSave = $this->Bootstrap->genNode('div', [
|
||||
'class' => ['input-group-append', 'd-none', 'position-relative', 'input-group-actions'],
|
||||
], implode('', [
|
||||
$this->Bootstrap->genNode('a', [
|
||||
'class' => ['position-absolute', 'fas fa-times', 'p-abs-center-y', 'text-reset text-decoration-none', 'btn-reset-setting'],
|
||||
'href' => '#',
|
||||
]),
|
||||
$this->Bootstrap->genNode('button', [
|
||||
'class' => ['btn', 'btn-success', 'btn-save-setting'],
|
||||
'type' => 'button',
|
||||
], __('save')),
|
||||
]));
|
||||
$inputGroup = $this->Bootstrap->genNode('div', [
|
||||
'class' => ['input-group'],
|
||||
], implode('', [$input, $inputGroupSave]));
|
||||
|
||||
$container = $this->Bootstrap->genNode('div', [
|
||||
'class' => ['form-group', 'mb-2']
|
||||
], implode('', [$label, $inputGroup, $description, $validationError]));
|
||||
|
||||
echo $container;
|
||||
?>
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
$mainNoticeHeading = [
|
||||
'critical' => __('Your Cerebrate instance requires immediate attention.'),
|
||||
'warning' => __('Issues found, it is recommended that you resolve them.'),
|
||||
'info' => __('There are some optional settings that are incorrect or not set.'),
|
||||
];
|
||||
$headingPerLevel = [
|
||||
'critical' => __('Critical settings'),
|
||||
'warning' => __('Warning settings'),
|
||||
'info' => __('Info settings'),
|
||||
];
|
||||
$noticeDescriptionPerLevel = [
|
||||
'critical' => __('Cerebrate will not operate correctly or will be unsecure until these issues are resolved.'),
|
||||
'warning' => __('Some of the features of Cerebrate cannot be utilised until these issues are resolved.'),
|
||||
'info' => __('There are some optional tweaks that could be done to improve the looks of your Cerebrate instance.'),
|
||||
];
|
||||
|
||||
$alertVariant = 'info';
|
||||
$skipHeading = false;
|
||||
$alertBody = '';
|
||||
$tableItems = [];
|
||||
foreach (array_keys($mainNoticeHeading) as $level) {
|
||||
if(!empty($notices[$level])) {
|
||||
$variant = $variantFromSeverity[$level];
|
||||
if (!$skipHeading) {
|
||||
$alertBody .= sprintf('<h5 class="alert-heading">%s</h5>', $mainNoticeHeading[$level]);
|
||||
$alertVariant = $variant;
|
||||
$skipHeading = true;
|
||||
}
|
||||
$tableItems[] = [
|
||||
'severity' => $headingPerLevel[$level],
|
||||
'issues' => count($notices[$level]),
|
||||
'badge-variant' => $variant,
|
||||
'description' => $noticeDescriptionPerLevel[$level],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$alertBody = $this->Bootstrap->table([
|
||||
'small' => true,
|
||||
'striped' => false,
|
||||
'hover' => false,
|
||||
'borderless' => true,
|
||||
'bordered' => false,
|
||||
'tableClass' => 'mb-0'
|
||||
], [
|
||||
'fields' => [
|
||||
['key' => 'severity', 'label' => __('Severity')],
|
||||
['key' => 'issues', 'label' => __('Issues'), 'formatter' => function($count, $row) {
|
||||
return $this->Bootstrap->badge([
|
||||
'variant' => $row['badge-variant'],
|
||||
'text' => $count
|
||||
]);
|
||||
}],
|
||||
['key' => 'description', 'label' => __('Description')]
|
||||
],
|
||||
'items' => $tableItems,
|
||||
]);
|
||||
|
||||
$settingNotice = $this->Bootstrap->alert([
|
||||
'dismissible' => false,
|
||||
'variant' => $alertVariant,
|
||||
'html' => $alertBody
|
||||
]);
|
||||
$settingNotice = sprintf('<div class="mt-3">%s</div>', $settingNotice);
|
||||
echo $settingNotice;
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
$panelHTML = '';
|
||||
if (isLeaf($panelSettings)) {
|
||||
$singleSetting = $this->element('Settings/fieldGroup', [
|
||||
'panelName' => $panelName,
|
||||
'panelSettings' => $panelSettings,
|
||||
'settingName' => $panelName,
|
||||
'setting' => $panelSettings,
|
||||
]);
|
||||
$panelHTML = "<div>{$singleSetting}</div>";
|
||||
} else {
|
||||
$panelID = sprintf('sp-%s-%s', h($sectionName), h($panelName));
|
||||
$panelHTML .= sprintf('<h4 id="%s"><a class="text-reset text-decoration-none" href="#%s">%s</a></h4>', $panelID, $panelID, h($panelName));
|
||||
$groupIssueSeverity = false;
|
||||
foreach ($panelSettings as $singleSettingName => $singleSetting) {
|
||||
$singleSettingHTML = $this->element('Settings/fieldGroup', [
|
||||
'panelName' => $panelName,
|
||||
'panelSettings' => $panelSettings,
|
||||
'settingName' => $singleSettingName,
|
||||
'setting' => $singleSetting,
|
||||
]);
|
||||
$panelHTML .= sprintf('<div class="ml-3">%s</div>', $singleSettingHTML);
|
||||
if (!empty($singleSetting['error'])) {
|
||||
$settingVariant = $this->get('variantFromSeverity')[$singleSetting['severity']];
|
||||
if ($groupIssueSeverity != 'danger') {
|
||||
if ($groupIssueSeverity != 'warning') {
|
||||
$groupIssueSeverity = $settingVariant;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$panelHTML = $this->Bootstrap->genNode('div', [
|
||||
'class' => [
|
||||
'shadow',
|
||||
'p-2',
|
||||
'mb-4',
|
||||
'rounded',
|
||||
'settings-group',
|
||||
(!empty($groupIssueSeverity) ? "callout callout-${groupIssueSeverity}" : ''),
|
||||
($this->get('darkMode') ? 'bg-dark' : 'bg-light')
|
||||
],
|
||||
], $panelHTML);
|
||||
}
|
||||
echo $panelHTML;
|
|
@ -0,0 +1,63 @@
|
|||
<nav id="navbar-scrollspy-setting" class="navbar">
|
||||
<nav class="nav nav-pills flex-column">
|
||||
<?php foreach ($groupedSetting as $group => $sections): ?>
|
||||
<a class="nav-link main-group text-reset p-1" href="#<?= sprintf('sp-%s', h($group)) ?>"><?= h($group) ?></a>
|
||||
<nav class="nav nav-pills sub-group collapse flex-column" data-maingroup="<?= sprintf('sp-%s', h($group)) ?>">
|
||||
<?php foreach ($sections as $section): ?>
|
||||
<a class="nav-link nav-link-group text-reset ml-3 my-1 p-1" href="#<?= sprintf('sp-%s-%s', h($group), h($section)) ?>"><?= h($section) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('[data-spy="scroll"]').on('activate.bs.scrollspy', function(evt, {relatedTarget}) {
|
||||
const $associatedLink = $(`#navbar-scrollspy-setting nav.nav-pills .nav-link[href="${relatedTarget}"]`)
|
||||
let $associatedNav
|
||||
if ($associatedLink.hasClass('main-group')) {
|
||||
$associatedNav = $associatedLink.next()
|
||||
} else {
|
||||
$associatedNav = $associatedLink.parent()
|
||||
}
|
||||
const $allNavs = $('#navbar-scrollspy-setting nav.nav-pills.sub-group')
|
||||
$allNavs.removeClass('group-active').hide()
|
||||
$associatedNav.addClass('group-active').show()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link {
|
||||
background-color: unset !important;
|
||||
color: black;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link:not(.main-group).active {
|
||||
color: #007bff !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group:before {
|
||||
margin-right: 0.25em;
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-weight: 900;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
text-rendering: auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group.active:before {
|
||||
content: "\f0d7";
|
||||
}
|
||||
|
||||
#navbar-scrollspy-setting nav.nav-pills .nav-link.main-group:before {
|
||||
content: "\f0da";
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,109 @@
|
|||
<select id="search-settings" class="d-block w-100" aria-describedby="<?= __('Search setting input') ?>"><option></option></select>
|
||||
|
||||
<script>
|
||||
let selectData = []
|
||||
for (const settingName in settingsFlattened) {
|
||||
if (Object.hasOwnProperty.call(settingsFlattened, settingName)) {
|
||||
const setting = settingsFlattened[settingName];
|
||||
const selectID = settingName.replaceAll('.', '_')
|
||||
selectData.push({
|
||||
id: selectID,
|
||||
text: setting.name,
|
||||
setting: setting
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$("#search-settings").select2({
|
||||
data: selectData,
|
||||
placeholder: '<?= __('Search setting by typing here...') ?>',
|
||||
templateResult: formatSettingSearchResult,
|
||||
templateSelection: formatSettingSearchSelection,
|
||||
matcher: settingMatcher,
|
||||
sorter: settingSorter,
|
||||
})
|
||||
.on('select2:select', function (e) {
|
||||
const selected = e.params.data
|
||||
const settingPath = selected.setting['setting-path']
|
||||
const settingPathTokenized = settingPath.split('.')
|
||||
const tabName = settingPathTokenized[0]
|
||||
const IDtoFocus = 'sp-' + settingPathTokenized.slice(1).join('-')
|
||||
const $navController = $('.settings-tabs').find('a.nav-link').filter(function() {
|
||||
return $(this).text() == tabName
|
||||
})
|
||||
if ($navController.length == 1) {
|
||||
$toFocus = $(`#${IDtoFocus}`).parent()
|
||||
if ($navController.hasClass('active')) {
|
||||
$toFocus[0].scrollIntoView()
|
||||
$toFocus.find(`input#${selected.id}`).focus()
|
||||
} else {
|
||||
$navController.on('shown.bs.tab.after-selection', () => {
|
||||
$toFocus[0].scrollIntoView()
|
||||
$toFocus.find(`input#${selected.id}`).focus()
|
||||
$navController.off('shown.bs.tab.after-selection')
|
||||
}).tab('show')
|
||||
}
|
||||
}
|
||||
$("#search-settings").val(null).trigger('change.select2');
|
||||
})
|
||||
})
|
||||
|
||||
function settingMatcher(params, data) {
|
||||
if (params.term == null || params.term.trim() === '') {
|
||||
return data;
|
||||
}
|
||||
if (data.text === undefined || data.setting === undefined) {
|
||||
return null;
|
||||
}
|
||||
let modifiedData = $.extend({}, data, true);
|
||||
const loweredTerms = params.term.trim().toLowerCase().split(' ')
|
||||
for (let i = 0; i < loweredTerms.length; i++) {
|
||||
const loweredTerm = loweredTerms[i];
|
||||
const settingNameMatch = data.setting['true-name'].toLowerCase().indexOf(loweredTerm) > -1 || data.text.toLowerCase().indexOf(loweredTerm) > -1
|
||||
const settingGroupMatch = data.setting['setting-path'].toLowerCase().indexOf(loweredTerm) > -1
|
||||
const settingDescMatch = data.setting.description.toLowerCase().indexOf(loweredTerm) > -1
|
||||
if (settingNameMatch || settingGroupMatch || settingDescMatch) {
|
||||
modifiedData.matchPriority = (settingNameMatch ? 10 : 0) + (settingGroupMatch ? 5 : 0) + (settingDescMatch ? 1 : 0)
|
||||
}
|
||||
}
|
||||
if (modifiedData.matchPriority > 0) {
|
||||
return modifiedData;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function settingSorter(data) {
|
||||
let sortedData = data.slice(0)
|
||||
sortedData = sortedData.sort((a, b) => {
|
||||
return a.matchPriority == b.matchPriority ? 0 : (b.matchPriority - a.matchPriority)
|
||||
})
|
||||
return sortedData;
|
||||
}
|
||||
|
||||
function formatSettingSearchResult(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const $state = $('<div/>').append(
|
||||
$('<div/>').addClass('d-flex justify-content-between')
|
||||
.append(
|
||||
$('<span/>').addClass('font-weight-bold').text(state.text),
|
||||
$('<span/>').addClass('font-weight-light').text(state.setting['setting-path'].replaceAll('.', ' ▸ '))
|
||||
),
|
||||
$('<div/>').addClass('font-italic font-weight-light ml-3').text(state.setting['description'])
|
||||
)
|
||||
return $state
|
||||
}
|
||||
|
||||
function formatSettingSearchSelection(state) {
|
||||
return state.text
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.select2-container {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue