feat(1.0.14): тематики searchable, фикс лага confirm-диалога

- Confirm dialog: предзагрузка WebContentsView при старте приложения.
  Раньше каждое нажатие "Закрыть" создавало новый view с холодной
  загрузкой HTML+React → ~2с лаг и дубликаты от повторных кликов.
  Теперь view кэшируется, текст обновляется через IPC, повторные
  клики игнорируются пока диалог открыт.
- Темы: 14 → 71 (Война, Холодная война, Вьетнам, Призраки, Драконы,
  Шахматы, Самолёты, Поезда, Сёрфинг, Япония, ...). Все ID
  провалидированы probe-скриптом (≥50 фильмов на тематику).
- Chip-row заменён на SearchableSelect с поиском по подстроке —
  длинный список не помещается в чипы, а dropdown с фильтром
  гораздо удобнее. Заодно ушёл фиолетовый цвет чипа, плохо
  сочетавшийся с темой сайта.
This commit is contained in:
2026-05-17 11:29:39 +03:00
parent 8684eb7b67
commit 747b0f4c18
5 changed files with 265 additions and 45 deletions

58
main.js
View File

@@ -453,6 +453,10 @@ async function createWindow() {
} else {
mainWindow.loadFile(path.join(__dirname, 'dist', 'index.html'));
}
// Прогрев confirm-диалога: создаём WebContentsView один раз, чтобы убрать ~2с лаг
// при первом нажатии кнопки закрытия (и кнопки навигации с подтверждением).
preloadConfirmView();
}
// --- View helpers ---
@@ -544,22 +548,56 @@ function removeError() {
dialogFadeOut(view, () => { try { removeChild(view); view.webContents.destroy(); } catch (_) {} });
}
function setConfirm(text, actionOnYes) {
const view = makeDialogView();
confirmViews.push({ view, actionOnYes });
const query = new URLSearchParams({ text: text || '' }).toString();
view.webContents.once('did-finish-load', () => { addChild(view); });
// Confirm dialog: один кэшированный WebContentsView, переиспользуем для всех confirm'ов.
// Холодный старт WebContentsView с React-загрузкой занимает ~1-2с, отсюда был лаг
// при нажатии на кнопку закрытия. Теперь view создаётся при старте приложения и хранится готовым.
let confirmCachedView = null;
let confirmReady = false;
let activeConfirm = null; // { actionOnYes } если диалог открыт, иначе null
let pendingConfirm = null; // если showConfirm вызвали до того как view загрузился
function preloadConfirmView() {
if (confirmCachedView) return;
confirmCachedView = makeDialogView();
confirmCachedView.webContents.once('did-finish-load', () => {
confirmReady = true;
if (pendingConfirm) {
const p = pendingConfirm;
pendingConfirm = null;
setConfirm(p.text, p.actionOnYes);
}
});
if (isDev) {
view.webContents.loadURL(`${RENDERER_URL}/dialog-confirm.html?${query}`);
confirmCachedView.webContents.loadURL(`${RENDERER_URL}/dialog-confirm.html`);
} else {
view.webContents.loadFile(path.join(__dirname, 'dist', 'dialog-confirm.html'), { query: { text: text || '' } });
confirmCachedView.webContents.loadFile(path.join(__dirname, 'dist', 'dialog-confirm.html'));
}
}
function setConfirm(text, actionOnYes) {
// Гард: если уже открыт диалог — игнорируем повторные клики (никаких дубликатов).
if (activeConfirm) return;
if (!confirmReady) {
pendingConfirm = { text, actionOnYes };
return;
}
activeConfirm = { actionOnYes };
// confirmViews — для backwards compat с обработчиком action; держим в синхроне.
confirmViews.push({ view: confirmCachedView, actionOnYes });
confirmCachedView.webContents.send('dialog-confirm-set', { text, visible: true });
addChild(confirmCachedView);
}
function removeConfirm() {
if (!confirmViews.length) return;
const { view } = confirmViews.pop();
dialogFadeOut(view, () => { try { removeChild(view); view.webContents.destroy(); } catch (_) {} });
if (!activeConfirm) return;
activeConfirm = null;
confirmViews.pop();
confirmCachedView.webContents.send('dialog-confirm-set', { visible: false });
// Жду fade-out (≈220ms по dialogs.css), потом снимаю с DOM. View остаётся живой для следующего раза.
setTimeout(() => {
if (activeConfirm) return; // если за это время открыли новый — оставить
try { removeChild(confirmCachedView); } catch (_) {}
}, 250);
}