Frontend performance and code quality improvements
Performance: cache getBoundingClientRect in card-glare and drag-drop, build adjacency Maps for O(1) graph BFS, batch WebGL uniform uploads, cache matchMedia/search text in card-sections, use Map in graph-layout. Code quality: extract shared FPS chart factory (chart-utils.js) and FilterListManager (filter-list.js), replace 14-way CSS editor dispatch with type handler registry, move state to state.js, fix layer violation in api.js, add i18n for hardcoded strings, sync 53 missing locale keys, add HTTP error logging in DataCache. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
_streamModalPPTemplates, set_streamModalPPTemplates,
|
||||
_modalFilters, set_modalFilters,
|
||||
_ppTemplateNameManuallyEdited, set_ppTemplateNameManuallyEdited,
|
||||
currentTestingTemplate, setCurrentTestingTemplate,
|
||||
_currentTestStreamId, set_currentTestStreamId,
|
||||
_currentTestPPTemplateId, set_currentTestPPTemplateId,
|
||||
_lastValidatedImageSource, set_lastValidatedImageSource,
|
||||
@@ -59,7 +60,7 @@ import { wrapCard } from '../core/card-colors.js';
|
||||
import { TagInput, renderTagChips } from '../core/tag-input.js';
|
||||
import { IconSelect } from '../core/icon-select.js';
|
||||
import { EntitySelect } from '../core/entity-palette.js';
|
||||
import * as P from '../core/icon-paths.js';
|
||||
import { FilterListManager } from '../core/filter-list.js';
|
||||
|
||||
// ── TagInput instances for modals ──
|
||||
let _captureTemplateTagsInput = null;
|
||||
@@ -315,7 +316,7 @@ export async function showTestTemplateModal(templateId) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.currentTestingTemplate = template;
|
||||
setCurrentTestingTemplate(template);
|
||||
await loadDisplaysForTest();
|
||||
restoreCaptureDuration();
|
||||
|
||||
@@ -328,7 +329,7 @@ export async function showTestTemplateModal(templateId) {
|
||||
|
||||
export function closeTestTemplateModal() {
|
||||
testTemplateModal.forceClose();
|
||||
window.currentTestingTemplate = null;
|
||||
setCurrentTestingTemplate(null);
|
||||
}
|
||||
|
||||
async function loadAvailableEngines() {
|
||||
@@ -470,7 +471,7 @@ function collectEngineConfig() {
|
||||
async function loadDisplaysForTest() {
|
||||
try {
|
||||
// Use engine-specific display list for engines with own devices (camera, scrcpy)
|
||||
const engineType = window.currentTestingTemplate?.engine_type;
|
||||
const engineType = currentTestingTemplate?.engine_type;
|
||||
const engineHasOwnDisplays = availableEngines.find(e => e.type === engineType)?.has_own_displays || false;
|
||||
const url = engineHasOwnDisplays
|
||||
? `/config/displays?engine_type=${engineType}`
|
||||
@@ -508,7 +509,7 @@ async function loadDisplaysForTest() {
|
||||
}
|
||||
|
||||
export function runTemplateTest() {
|
||||
if (!window.currentTestingTemplate) {
|
||||
if (!currentTestingTemplate) {
|
||||
showToast(t('templates.test.error.no_engine'), 'error');
|
||||
return;
|
||||
}
|
||||
@@ -521,7 +522,7 @@ export function runTemplateTest() {
|
||||
return;
|
||||
}
|
||||
|
||||
const template = window.currentTestingTemplate;
|
||||
const template = currentTestingTemplate;
|
||||
localStorage.setItem('lastTestDisplayIndex', displayIndex);
|
||||
|
||||
const previewWidth = Math.round(Math.min(window.innerWidth * 0.8, 1920) * Math.min(window.devicePixelRatio || 1, 2));
|
||||
@@ -559,7 +560,7 @@ function buildTestStatsHtml(result) {
|
||||
<div class="stat-item"><span>${t('templates.test.results.avg_capture_time')}:</span> <strong>${Number(avgMs).toFixed(1)}ms</strong></div>`;
|
||||
}
|
||||
html += `
|
||||
<div class="stat-item"><span>Resolution:</span> <strong>${res}</strong></div>`;
|
||||
<div class="stat-item"><span>${t('templates.test.results.resolution')}</span> <strong>${res}</strong></div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
@@ -1672,7 +1673,7 @@ export function onStreamDisplaySelected(displayIndex, display) {
|
||||
|
||||
export function onTestDisplaySelected(displayIndex, display) {
|
||||
document.getElementById('test-template-display').value = displayIndex;
|
||||
const engineType = window.currentTestingTemplate?.engine_type || null;
|
||||
const engineType = currentTestingTemplate?.engine_type || null;
|
||||
document.getElementById('test-display-picker-label').textContent = formatDisplayLabel(displayIndex, display, engineType);
|
||||
}
|
||||
|
||||
@@ -2242,169 +2243,22 @@ function _getStripFilterName(filterId) {
|
||||
return translated;
|
||||
}
|
||||
|
||||
let _filterIconSelect = null;
|
||||
// ── PP FilterListManager instance ──
|
||||
const ppFilterManager = new FilterListManager({
|
||||
getFilters: () => _modalFilters,
|
||||
getFilterDefs: () => _availableFilters,
|
||||
getFilterName: _getFilterName,
|
||||
selectId: 'pp-add-filter-select',
|
||||
containerId: 'pp-filter-list',
|
||||
prefix: '',
|
||||
editingIdInputId: 'pp-template-id',
|
||||
selfRefFilterId: 'filter_template',
|
||||
autoNameFn: () => _autoGeneratePPTemplateName(),
|
||||
initDrag: _initFilterDragForContainer,
|
||||
initPaletteGrids: _initFilterPaletteGrids,
|
||||
});
|
||||
|
||||
const _FILTER_ICONS = {
|
||||
brightness: P.sunDim,
|
||||
saturation: P.palette,
|
||||
gamma: P.sun,
|
||||
downscaler: P.monitor,
|
||||
pixelate: P.layoutDashboard,
|
||||
auto_crop: P.target,
|
||||
flip: P.rotateCw,
|
||||
color_correction: P.palette,
|
||||
filter_template: P.fileText,
|
||||
frame_interpolation: P.fastForward,
|
||||
noise_gate: P.volume2,
|
||||
palette_quantization: P.sparkles,
|
||||
css_filter_template: P.fileText,
|
||||
};
|
||||
|
||||
function _populateFilterSelect() {
|
||||
const select = document.getElementById('pp-add-filter-select');
|
||||
select.innerHTML = `<option value="">${t('filters.select_type')}</option>`;
|
||||
const items = [];
|
||||
for (const f of _availableFilters) {
|
||||
const name = _getFilterName(f.filter_id);
|
||||
select.innerHTML += `<option value="${f.filter_id}">${name}</option>`;
|
||||
const pathData = _FILTER_ICONS[f.filter_id] || P.wrench;
|
||||
items.push({
|
||||
value: f.filter_id,
|
||||
icon: `<svg class="icon" viewBox="0 0 24 24">${pathData}</svg>`,
|
||||
label: name,
|
||||
desc: t(`filters.${f.filter_id}.desc`),
|
||||
});
|
||||
}
|
||||
if (_filterIconSelect) {
|
||||
_filterIconSelect.updateItems(items);
|
||||
} else if (items.length > 0) {
|
||||
_filterIconSelect = new IconSelect({
|
||||
target: select,
|
||||
items,
|
||||
columns: 3,
|
||||
placeholder: t('filters.select_type'),
|
||||
onChange: () => addFilterFromSelect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic filter list renderer — shared by PP template and CSPT modals.
|
||||
* @param {string} containerId - DOM container ID for filter cards
|
||||
* @param {Array} filtersArr - mutable array of {filter_id, options, _expanded}
|
||||
* @param {Array} filterDefs - available filter definitions (with options_schema)
|
||||
* @param {string} prefix - handler prefix: '' for PP, 'cspt' for CSPT
|
||||
* @param {string} editingIdInputId - ID of hidden input holding the editing template ID
|
||||
* @param {string} selfRefFilterId - filter_id that should exclude self ('filter_template' or 'css_filter_template')
|
||||
*/
|
||||
function _renderFilterListGeneric(containerId, filtersArr, filterDefs, prefix, editingIdInputId, selfRefFilterId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (filtersArr.length === 0) {
|
||||
container.innerHTML = `<div class="pp-filter-empty">${t('filters.empty')}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const toggleFn = prefix ? `${prefix}ToggleFilterExpand` : 'toggleFilterExpand';
|
||||
const removeFn = prefix ? `${prefix}RemoveFilter` : 'removeFilter';
|
||||
const updateFn = prefix ? `${prefix}UpdateFilterOption` : 'updateFilterOption';
|
||||
const inputPrefix = prefix ? `${prefix}-filter` : 'filter';
|
||||
const nameFn = prefix ? _getStripFilterName : _getFilterName;
|
||||
|
||||
let html = '';
|
||||
filtersArr.forEach((fi, index) => {
|
||||
const filterDef = filterDefs.find(f => f.filter_id === fi.filter_id);
|
||||
const filterName = nameFn(fi.filter_id);
|
||||
const isExpanded = fi._expanded === true;
|
||||
|
||||
let summary = '';
|
||||
if (filterDef && !isExpanded) {
|
||||
summary = filterDef.options_schema.map(opt => {
|
||||
const val = fi.options[opt.key] !== undefined ? fi.options[opt.key] : opt.default;
|
||||
return val;
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
html += `<div class="pp-filter-card${isExpanded ? ' expanded' : ''}" data-filter-index="${index}">
|
||||
<div class="pp-filter-card-header" onclick="${toggleFn}(${index})">
|
||||
<span class="pp-filter-drag-handle" title="${t('filters.drag_to_reorder')}">⠇</span>
|
||||
<span class="pp-filter-card-chevron">${isExpanded ? '▼' : '▶'}</span>
|
||||
<span class="pp-filter-card-name">${escapeHtml(filterName)}</span>
|
||||
${summary ? `<span class="pp-filter-card-summary">${escapeHtml(summary)}</span>` : ''}
|
||||
<div class="pp-filter-card-actions" onclick="event.stopPropagation()">
|
||||
<button type="button" class="btn-filter-action btn-filter-remove" onclick="${removeFn}(${index})" title="${t('filters.remove')}">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pp-filter-card-options"${isExpanded ? '' : ' style="display:none"'}>`;
|
||||
|
||||
if (filterDef) {
|
||||
for (const opt of filterDef.options_schema) {
|
||||
const currentVal = fi.options[opt.key] !== undefined ? fi.options[opt.key] : opt.default;
|
||||
const inputId = `${inputPrefix}-${index}-${opt.key}`;
|
||||
if (opt.type === 'bool') {
|
||||
const checked = currentVal === true || currentVal === 'true';
|
||||
html += `<div class="pp-filter-option pp-filter-option-bool">
|
||||
<label for="${inputId}">
|
||||
<span>${escapeHtml(opt.label)}</span>
|
||||
<input type="checkbox" id="${inputId}" ${checked ? 'checked' : ''}
|
||||
onchange="${updateFn}(${index}, '${opt.key}', this.checked)">
|
||||
</label>
|
||||
</div>`;
|
||||
} else if (opt.type === 'select' && Array.isArray(opt.choices)) {
|
||||
const editingId = document.getElementById(editingIdInputId)?.value || '';
|
||||
const filteredChoices = (fi.filter_id === selfRefFilterId && opt.key === 'template_id' && editingId)
|
||||
? opt.choices.filter(c => c.value !== editingId)
|
||||
: opt.choices;
|
||||
let selectVal = currentVal;
|
||||
if (filteredChoices.length > 0 && !filteredChoices.some(c => c.value === selectVal)) {
|
||||
selectVal = filteredChoices[0].value;
|
||||
fi.options[opt.key] = selectVal;
|
||||
}
|
||||
const hasPaletteColors = filteredChoices.some(c => c.colors);
|
||||
const options = filteredChoices.map(c =>
|
||||
`<option value="${escapeHtml(c.value)}"${c.value === selectVal ? ' selected' : ''}>${escapeHtml(c.label)}</option>`
|
||||
).join('');
|
||||
const gridAttr = hasPaletteColors ? ` data-palette-grid="${escapeHtml(JSON.stringify(filteredChoices))}"` : '';
|
||||
const isTemplateRef = opt.key === 'template_id';
|
||||
const entityAttr = isTemplateRef ? ' data-entity-select="template"' : '';
|
||||
html += `<div class="pp-filter-option">
|
||||
<label for="${inputId}"><span>${escapeHtml(opt.label)}:</span></label>
|
||||
<select id="${inputId}"${gridAttr}${entityAttr}
|
||||
onchange="${updateFn}(${index}, '${opt.key}', this.value)">
|
||||
${options}
|
||||
</select>
|
||||
</div>`;
|
||||
} else if (opt.type === 'string') {
|
||||
const maxLen = opt.max_length || 500;
|
||||
html += `<div class="pp-filter-option">
|
||||
<label for="${inputId}"><span>${escapeHtml(opt.label)}:</span></label>
|
||||
<input type="text" id="${inputId}" value="${escapeHtml(String(currentVal))}"
|
||||
maxlength="${maxLen}" class="pp-filter-text-input"
|
||||
onchange="${updateFn}(${index}, '${opt.key}', this.value)">
|
||||
</div>`;
|
||||
} else {
|
||||
html += `<div class="pp-filter-option">
|
||||
<label for="${inputId}">
|
||||
<span>${escapeHtml(opt.label)}:</span>
|
||||
<span id="${inputId}-display">${currentVal}</span>
|
||||
</label>
|
||||
<input type="range" id="${inputId}"
|
||||
min="${opt.min_value}" max="${opt.max_value}" step="${opt.step}" value="${currentVal}"
|
||||
oninput="${updateFn}(${index}, '${opt.key}', this.value); document.getElementById('${inputId}-display').textContent = this.value;">
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html += `</div></div>`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
_initFilterDragForContainer(containerId, filtersArr, () => {
|
||||
_renderFilterListGeneric(containerId, filtersArr, filterDefs, prefix, editingIdInputId, selfRefFilterId);
|
||||
});
|
||||
// Initialize palette icon grids on select elements
|
||||
_initFilterPaletteGrids(container);
|
||||
}
|
||||
// _renderFilterListGeneric has been replaced by FilterListManager.render()
|
||||
|
||||
/** Stored IconSelect instances for filter option selects (keyed by select element id). */
|
||||
const _filterOptionIconSelects = {};
|
||||
@@ -2449,11 +2303,11 @@ function _initFilterPaletteGrids(container) {
|
||||
}
|
||||
|
||||
export function renderModalFilterList() {
|
||||
_renderFilterListGeneric('pp-filter-list', _modalFilters, _availableFilters, '', 'pp-template-id', 'filter_template');
|
||||
ppFilterManager.render();
|
||||
}
|
||||
|
||||
export function renderCSPTModalFilterList() {
|
||||
_renderFilterListGeneric('cspt-filter-list', _csptModalFilters, _stripFilters, 'cspt', 'cspt-id', 'css_filter_template');
|
||||
csptFilterManager.render();
|
||||
}
|
||||
|
||||
/* ── Generic filter drag-and-drop reordering ── */
|
||||
@@ -2611,104 +2465,23 @@ function _filterAutoScroll(clientY, ds) {
|
||||
ds.scrollRaf = requestAnimationFrame(scroll);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic: add a filter from a select element into a filters array.
|
||||
*/
|
||||
function _addFilterGeneric(selectId, filtersArr, filterDefs, iconSelect, renderFn, autoNameFn) {
|
||||
const select = document.getElementById(selectId);
|
||||
const filterId = select.value;
|
||||
if (!filterId) return;
|
||||
// _addFilterGeneric and _updateFilterOptionGeneric have been replaced by FilterListManager methods
|
||||
|
||||
const filterDef = filterDefs.find(f => f.filter_id === filterId);
|
||||
if (!filterDef) return;
|
||||
// ── PP filter actions (delegate to ppFilterManager) ──
|
||||
export function addFilterFromSelect() { ppFilterManager.addFromSelect(); }
|
||||
export function toggleFilterExpand(index) { ppFilterManager.toggleExpand(index); }
|
||||
export function removeFilter(index) { ppFilterManager.remove(index); }
|
||||
export function moveFilter(index, direction) { ppFilterManager.move(index, direction); }
|
||||
export function updateFilterOption(filterIndex, optionKey, value) { ppFilterManager.updateOption(filterIndex, optionKey, value); }
|
||||
|
||||
const options = {};
|
||||
for (const opt of filterDef.options_schema) {
|
||||
if (opt.type === 'select' && !opt.default && Array.isArray(opt.choices) && opt.choices.length > 0) {
|
||||
options[opt.key] = opt.choices[0].value;
|
||||
} else {
|
||||
options[opt.key] = opt.default;
|
||||
}
|
||||
}
|
||||
|
||||
filtersArr.push({ filter_id: filterId, options, _expanded: true });
|
||||
select.value = '';
|
||||
if (iconSelect) iconSelect.setValue('');
|
||||
renderFn();
|
||||
if (autoNameFn) autoNameFn();
|
||||
}
|
||||
|
||||
function _updateFilterOptionGeneric(filterIndex, optionKey, value, filtersArr, filterDefs) {
|
||||
if (filtersArr[filterIndex]) {
|
||||
const fi = filtersArr[filterIndex];
|
||||
const filterDef = filterDefs.find(f => f.filter_id === fi.filter_id);
|
||||
if (filterDef) {
|
||||
const optDef = filterDef.options_schema.find(o => o.key === optionKey);
|
||||
if (optDef && optDef.type === 'bool') {
|
||||
fi.options[optionKey] = !!value;
|
||||
} else if (optDef && optDef.type === 'select') {
|
||||
fi.options[optionKey] = String(value);
|
||||
} else if (optDef && optDef.type === 'string') {
|
||||
fi.options[optionKey] = String(value);
|
||||
} else if (optDef && optDef.type === 'int') {
|
||||
fi.options[optionKey] = parseInt(value);
|
||||
} else {
|
||||
fi.options[optionKey] = parseFloat(value);
|
||||
}
|
||||
} else {
|
||||
fi.options[optionKey] = parseFloat(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── PP filter actions ──
|
||||
export function addFilterFromSelect() {
|
||||
_addFilterGeneric('pp-add-filter-select', _modalFilters, _availableFilters, _filterIconSelect, renderModalFilterList, _autoGeneratePPTemplateName);
|
||||
}
|
||||
|
||||
export function toggleFilterExpand(index) {
|
||||
if (_modalFilters[index]) { _modalFilters[index]._expanded = !_modalFilters[index]._expanded; renderModalFilterList(); }
|
||||
}
|
||||
|
||||
export function removeFilter(index) {
|
||||
_modalFilters.splice(index, 1); renderModalFilterList(); _autoGeneratePPTemplateName();
|
||||
}
|
||||
|
||||
export function moveFilter(index, direction) {
|
||||
const newIndex = index + direction;
|
||||
if (newIndex < 0 || newIndex >= _modalFilters.length) return;
|
||||
const tmp = _modalFilters[index];
|
||||
_modalFilters[index] = _modalFilters[newIndex];
|
||||
_modalFilters[newIndex] = tmp;
|
||||
renderModalFilterList(); _autoGeneratePPTemplateName();
|
||||
}
|
||||
|
||||
export function updateFilterOption(filterIndex, optionKey, value) {
|
||||
_updateFilterOptionGeneric(filterIndex, optionKey, value, _modalFilters, _availableFilters);
|
||||
}
|
||||
|
||||
// ── CSPT filter actions ──
|
||||
export function csptAddFilterFromSelect() {
|
||||
_addFilterGeneric('cspt-add-filter-select', _csptModalFilters, _stripFilters, _csptFilterIconSelect, renderCSPTModalFilterList, _autoGenerateCSPTName);
|
||||
}
|
||||
|
||||
export function csptToggleFilterExpand(index) {
|
||||
if (_csptModalFilters[index]) { _csptModalFilters[index]._expanded = !_csptModalFilters[index]._expanded; renderCSPTModalFilterList(); }
|
||||
}
|
||||
|
||||
export function csptRemoveFilter(index) {
|
||||
_csptModalFilters.splice(index, 1); renderCSPTModalFilterList(); _autoGenerateCSPTName();
|
||||
}
|
||||
|
||||
export function csptUpdateFilterOption(filterIndex, optionKey, value) {
|
||||
_updateFilterOptionGeneric(filterIndex, optionKey, value, _csptModalFilters, _stripFilters);
|
||||
}
|
||||
// ── CSPT filter actions (delegate to csptFilterManager) ──
|
||||
export function csptAddFilterFromSelect() { csptFilterManager.addFromSelect(); }
|
||||
export function csptToggleFilterExpand(index) { csptFilterManager.toggleExpand(index); }
|
||||
export function csptRemoveFilter(index) { csptFilterManager.remove(index); }
|
||||
export function csptUpdateFilterOption(filterIndex, optionKey, value) { csptFilterManager.updateOption(filterIndex, optionKey, value); }
|
||||
|
||||
function collectFilters() {
|
||||
return _modalFilters.map(fi => ({
|
||||
filter_id: fi.filter_id,
|
||||
options: { ...fi.options },
|
||||
}));
|
||||
return ppFilterManager.collect();
|
||||
}
|
||||
|
||||
function _autoGeneratePPTemplateName() {
|
||||
@@ -2743,7 +2516,7 @@ export async function showAddPPTemplateModal(cloneData = null) {
|
||||
}
|
||||
document.getElementById('pp-template-name').oninput = () => { set_ppTemplateNameManuallyEdited(true); };
|
||||
|
||||
_populateFilterSelect();
|
||||
ppFilterManager.populateSelect(() => addFilterFromSelect());
|
||||
renderModalFilterList();
|
||||
|
||||
// Pre-fill from clone data after form is set up
|
||||
@@ -2780,7 +2553,7 @@ export async function editPPTemplate(templateId) {
|
||||
options: { ...fi.options },
|
||||
})));
|
||||
|
||||
_populateFilterSelect();
|
||||
ppFilterManager.populateSelect(() => addFilterFromSelect());
|
||||
renderModalFilterList();
|
||||
|
||||
// Tags
|
||||
@@ -2894,7 +2667,20 @@ export async function closePPTemplateModal() {
|
||||
|
||||
// ===== Color Strip Processing Templates (CSPT) =====
|
||||
|
||||
let _csptFilterIconSelect = null;
|
||||
// ── CSPT FilterListManager instance ──
|
||||
const csptFilterManager = new FilterListManager({
|
||||
getFilters: () => _csptModalFilters,
|
||||
getFilterDefs: () => _stripFilters,
|
||||
getFilterName: _getStripFilterName,
|
||||
selectId: 'cspt-add-filter-select',
|
||||
containerId: 'cspt-filter-list',
|
||||
prefix: 'cspt',
|
||||
editingIdInputId: 'cspt-id',
|
||||
selfRefFilterId: 'css_filter_template',
|
||||
autoNameFn: () => _autoGenerateCSPTName(),
|
||||
initDrag: _initFilterDragForContainer,
|
||||
initPaletteGrids: _initFilterPaletteGrids,
|
||||
});
|
||||
|
||||
async function loadStripFilters() {
|
||||
await stripFiltersCache.fetch();
|
||||
@@ -2910,34 +2696,6 @@ async function loadCSPTemplates() {
|
||||
}
|
||||
}
|
||||
|
||||
function _populateCSPTFilterSelect() {
|
||||
const select = document.getElementById('cspt-add-filter-select');
|
||||
select.innerHTML = `<option value="">${t('filters.select_type')}</option>`;
|
||||
const items = [];
|
||||
for (const f of _stripFilters) {
|
||||
const name = _getStripFilterName(f.filter_id);
|
||||
select.innerHTML += `<option value="${f.filter_id}">${name}</option>`;
|
||||
const pathData = _FILTER_ICONS[f.filter_id] || P.wrench;
|
||||
items.push({
|
||||
value: f.filter_id,
|
||||
icon: `<svg class="icon" viewBox="0 0 24 24">${pathData}</svg>`,
|
||||
label: name,
|
||||
desc: t(`filters.${f.filter_id}.desc`),
|
||||
});
|
||||
}
|
||||
if (_csptFilterIconSelect) {
|
||||
_csptFilterIconSelect.updateItems(items);
|
||||
} else if (items.length > 0) {
|
||||
_csptFilterIconSelect = new IconSelect({
|
||||
target: select,
|
||||
items,
|
||||
columns: 3,
|
||||
placeholder: t('filters.select_type'),
|
||||
onChange: () => csptAddFilterFromSelect(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _autoGenerateCSPTName() {
|
||||
if (_csptNameManuallyEdited) return;
|
||||
if (document.getElementById('cspt-id').value) return;
|
||||
@@ -2951,10 +2709,7 @@ function _autoGenerateCSPTName() {
|
||||
}
|
||||
|
||||
function collectCSPTFilters() {
|
||||
return _csptModalFilters.map(fi => ({
|
||||
filter_id: fi.filter_id,
|
||||
options: { ...fi.options },
|
||||
}));
|
||||
return csptFilterManager.collect();
|
||||
}
|
||||
|
||||
export async function showAddCSPTModal(cloneData = null) {
|
||||
@@ -2977,7 +2732,7 @@ export async function showAddCSPTModal(cloneData = null) {
|
||||
}
|
||||
document.getElementById('cspt-name').oninput = () => { set_csptNameManuallyEdited(true); };
|
||||
|
||||
_populateCSPTFilterSelect();
|
||||
csptFilterManager.populateSelect(() => csptAddFilterFromSelect());
|
||||
renderCSPTModalFilterList();
|
||||
|
||||
if (cloneData) {
|
||||
@@ -3012,7 +2767,7 @@ export async function editCSPT(templateId) {
|
||||
options: { ...fi.options },
|
||||
})));
|
||||
|
||||
_populateCSPTFilterSelect();
|
||||
csptFilterManager.populateSelect(() => csptAddFilterFromSelect());
|
||||
renderCSPTModalFilterList();
|
||||
|
||||
if (_csptTagsInput) { _csptTagsInput.destroy(); _csptTagsInput = null; }
|
||||
|
||||
Reference in New Issue
Block a user