Refactor process picker into palette pattern, add notification app picker

- Refactor process-picker.ts into generic NamePalette with two concrete
  instances: ProcessPalette (running processes) and NotificationAppPalette
  (OS notification history apps)
- Notification color strip app colors and filter list now use
  NotificationAppPalette (shows display names like "Telegram" instead of
  process names like "telegram.exe")
- Fix case-insensitive matching for app_colors and app_filter_list in
  notification_stream.py
- Compact browse/remove buttons in notification app color rows with
  proper search icon
- Remove old inline process-picker HTML/CSS (replaced by palette overlay)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-21 01:23:08 +03:00
parent 5fa851618b
commit 07bb89e9b7
10 changed files with 5787 additions and 5397 deletions

View File

@@ -507,6 +507,60 @@ function _buildConditionTypeItems() {
}));
}
/** Wire up the custom time-range picker inputs → sync to hidden fields. */
function _wireTimeRangePicker(container: HTMLElement) {
const startH = container.querySelector('.tr-start-h') as HTMLInputElement;
const startM = container.querySelector('.tr-start-m') as HTMLInputElement;
const endH = container.querySelector('.tr-end-h') as HTMLInputElement;
const endM = container.querySelector('.tr-end-m') as HTMLInputElement;
const hiddenStart = container.querySelector('.condition-start-time') as HTMLInputElement;
const hiddenEnd = container.querySelector('.condition-end-time') as HTMLInputElement;
if (!startH || !startM || !endH || !endM) return;
const pad = (n: number) => String(n).padStart(2, '0');
function clamp(input: HTMLInputElement, min: number, max: number) {
let v = parseInt(input.value, 10);
if (isNaN(v)) v = min;
if (v < min) v = min;
if (v > max) v = max;
input.value = pad(v);
return v;
}
function sync() {
const sh = clamp(startH, 0, 23);
const sm = clamp(startM, 0, 59);
const eh = clamp(endH, 0, 23);
const em = clamp(endM, 0, 59);
hiddenStart.value = `${pad(sh)}:${pad(sm)}`;
hiddenEnd.value = `${pad(eh)}:${pad(em)}`;
}
[startH, startM, endH, endM].forEach(inp => {
inp.addEventListener('focus', () => inp.select());
inp.addEventListener('input', sync);
inp.addEventListener('blur', sync);
inp.addEventListener('keydown', (e) => {
const isHour = inp.dataset.role === 'hour';
const max = isHour ? 23 : 59;
if (e.key === 'ArrowUp') {
e.preventDefault();
let v = parseInt(inp.value, 10) || 0;
inp.value = pad(v >= max ? 0 : v + 1);
sync();
} else if (e.key === 'ArrowDown') {
e.preventDefault();
let v = parseInt(inp.value, 10) || 0;
inp.value = pad(v <= 0 ? max : v - 1);
sync();
}
});
});
sync();
}
function addAutomationConditionRow(condition: any) {
const list = document.getElementById('automation-conditions-list');
const row = document.createElement('div');
@@ -545,18 +599,35 @@ function addAutomationConditionRow(condition: any) {
if (type === 'time_of_day') {
const startTime = data.start_time || '00:00';
const endTime = data.end_time || '23:59';
const [sh, sm] = startTime.split(':').map(Number);
const [eh, em] = endTime.split(':').map(Number);
const pad = (n: number) => String(n).padStart(2, '0');
container.innerHTML = `
<div class="condition-fields">
<div class="condition-field">
<label>${t('automations.condition.time_of_day.start_time')}</label>
<input type="time" class="condition-start-time" value="${startTime}">
</div>
<div class="condition-field">
<label>${t('automations.condition.time_of_day.end_time')}</label>
<input type="time" class="condition-end-time" value="${endTime}">
<input type="hidden" class="condition-start-time" value="${startTime}">
<input type="hidden" class="condition-end-time" value="${endTime}">
<div class="time-range-picker">
<div class="time-range-slot">
<span class="time-range-label">${t('automations.condition.time_of_day.start_time')}</span>
<div class="time-range-input-wrap">
<input type="number" class="tr-start-h" min="0" max="23" value="${sh}" data-role="hour">
<span class="time-range-colon">:</span>
<input type="number" class="tr-start-m" min="0" max="59" value="${pad(sm)}" data-role="minute">
</div>
</div>
<div class="time-range-arrow">→</div>
<div class="time-range-slot">
<span class="time-range-label">${t('automations.condition.time_of_day.end_time')}</span>
<div class="time-range-input-wrap">
<input type="number" class="tr-end-h" min="0" max="23" value="${eh}" data-role="hour">
<span class="time-range-colon">:</span>
<input type="number" class="tr-end-m" min="0" max="59" value="${pad(em)}" data-role="minute">
</div>
</div>
</div>
<small class="condition-always-desc">${t('automations.condition.time_of_day.overnight_hint')}</small>
</div>`;
_wireTimeRangePicker(container);
return;
}
if (type === 'system_idle') {
@@ -660,10 +731,6 @@ function addAutomationConditionRow(condition: any) {
<button type="button" class="btn-browse-apps" title="${t('automations.condition.application.browse')}">${t('automations.condition.application.browse')}</button>
</div>
<textarea class="condition-apps" rows="3" placeholder="firefox.exe&#10;chrome.exe">${escapeHtml(appsValue)}</textarea>
<div class="process-picker" style="display:none">
<input type="text" class="process-picker-search" placeholder="${t('automations.condition.application.search')}" autocomplete="off">
<div class="process-picker-list"></div>
</div>
</div>
</div>
`;