Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 45 additions & 11 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,22 +216,44 @@ function clearSharedFilters() {
renderFilterDots();
updateFilterButton();
}
// --- Theme (dark / light) — persisted in localStorage, no backend needed ---
// --- Theme (dark / light / liquid-glass) — persisted in localStorage, no backend needed ---
const THEME_KEY = "tiol-theme";
const MENU_BG_KEY = "menu_bg_color";

function applyTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
if (theme === "liquid-glass") {
document.body.classList.add("theme-liquid-glass");
} else {
document.body.classList.remove("theme-liquid-glass");
}
}
function initTheme() {
let saved = "dark";
try { saved = localStorage.getItem(THEME_KEY) || "dark"; } catch (e) {}
applyTheme(saved);
}

async function applyMenuBgColor(color) {
if (!color) return;
document.documentElement.style.setProperty("--menu-bg-custom", color);
try { await invoke("set_setting", { key: MENU_BG_KEY, value: color }); } catch (e) {}
}

async function initMenuBgColor() {
let saved = "#ffffff";
try { saved = (await invoke("get_setting", { key: MENU_BG_KEY })) || "#ffffff"; } catch (e) {}
if (els.menuBgColor) els.menuBgColor.value = saved;
applyMenuBgColor(saved);
}

function renderThemeButtons() {
const cur = document.documentElement.getAttribute("data-theme") || "dark";
els.themeOptions.querySelectorAll("[data-theme]").forEach((btn) => {
btn.classList.toggle("btn--active", btn.dataset.theme === cur);
});
}

els.themeOptions.addEventListener("click", (ev) => {
const btn = ev.target.closest("[data-theme]");
if (!btn) return;
Expand Down Expand Up @@ -270,6 +292,10 @@ els.toggleFxShadow.addEventListener("click", () => {
try { localStorage.setItem(FX_SHADOW_KEY, next); } catch (e) {}
applyFx();
});

els.menuBgColor?.addEventListener("input", (ev) => {
applyMenuBgColor(ev.target.value);
});
els.navPhotos.addEventListener("click", () => {
switchView("photos");
onboardingOnPhotosClicked();
Expand Down Expand Up @@ -311,6 +337,7 @@ async function renderSettings() {
});
renderThemeButtons();
applyFx();
await initMenuBgColor();
renderHwDecode();
detectAndReportRenderer();
refreshModelStatus();
Expand Down Expand Up @@ -1176,12 +1203,16 @@ const COLOR_HEX = {
// The unfiltered result of the current query — filters re-apply to it.
let allPhotos = [];

// Star rating filter (C-17): only show photos with rating >= minRating.
// 0 = no filter. Driven by the #rating-filter dropdown in the search bar.
let minRating = 0;
// Star rating filter (C-17): show only photos whose rating is in the active
// set. Each checkbox toggles one rating (0–5). Unchecked ratings are hidden.
// Default: all ratings selected.
const activeRatings = new Set([0, 1, 2, 3, 4, 5]);
const ratingFilterEl = document.getElementById("rating-filter");
ratingFilterEl.addEventListener("change", () => {
minRating = parseInt(ratingFilterEl.value, 10) || 0;
activeRatings.clear();
ratingFilterEl.querySelectorAll('input[type="checkbox"]').forEach((cb) => {
if (cb.checked) activeRatings.add(parseInt(cb.value, 10));
});
renderPhotos(applyFilters(allPhotos));
});

Expand All @@ -1191,7 +1222,7 @@ function hasActiveFilters() {
activeLensFilters.size > 0 ||
focalMin != null ||
focalMax != null ||
minRating > 0
activeRatings.size < 6
);
}

Expand All @@ -1217,8 +1248,8 @@ function applyFilters(photos) {
if (focalMin != null && p.focal_length < focalMin) return false;
if (focalMax != null && p.focal_length > focalMax) return false;
}
// Star rating (C-17): photos without a rating (0) fail a >= filter.
if (minRating > 0 && (p.rating || 0) < minRating) return false;
// Star rating (C-17): photo must have a rating currently checked.
if (activeRatings.size < 6 && !activeRatings.has(p.rating || 0)) return false;
return true;
});
}
Expand Down Expand Up @@ -1410,8 +1441,11 @@ document.getElementById("btn-color-filter-clear").addEventListener("click", () =
focalMax = null;
filterFocalMin.value = "";
filterFocalMax.value = "";
minRating = 0;
ratingFilterEl.value = "0";
activeRatings.clear();
for (let i = 0; i <= 5; i++) activeRatings.add(i);
ratingFilterEl.querySelectorAll('input[type="checkbox"]').forEach((cb) => {
cb.checked = true;
});
renderFilterDots();
renderFilterLens();
updateFilterButton();
Expand Down Expand Up @@ -1735,7 +1769,7 @@ async function setPhotoRating(p, n) {
const stars = p._card.querySelector(".card__stars");
if (stars) renderCardStars(stars, p.rating);
}
if (minRating > 0) renderPhotos(applyFilters(allPhotos));
if (activeRatings.size < 6) renderPhotos(applyFilters(allPhotos));
} catch (e) {
alert(String(e));
}
Expand Down
36 changes: 23 additions & 13 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@
</select>
<input class="searchbar__input" id="semantic-search-input" type="text" data-i18n-placeholder="search.semantic.placeholder" />
<button class="btn btn--ghost searchbar__filter" id="btn-color-filter" data-i18n="photos.filterBtn" data-i18n-title="photos.filterColor">筛选</button>
<select class="searchbar__mode" id="rating-filter" data-i18n-title="photos.ratingFilter">
<option value="0" data-i18n="photos.ratingAll">全部</option>
<option value="1" data-i18n="photos.rating1">≥ 1 星</option>
<option value="2" data-i18n="photos.rating2">≥ 2 星</option>
<option value="3" data-i18n="photos.rating3">≥ 3 星</option>
<option value="4" data-i18n="photos.rating4">≥ 4 星</option>
<option value="5" data-i18n="photos.rating5">≥ 5 星</option>
</select>
<input class="searchbar__input" id="semantic-search-input" type="text" data-i18n-placeholder="search.semantic.placeholder" />
<button class="btn searchbar__select" id="btn-select-mode" data-i18n="photos.selectMode">多选</button>
</div>
<div class="grid" id="photo-grid"></div>
<div class="selection-bar glass" id="selection-bar" hidden>
<span class="selection-bar__count" id="selection-count"></span>
<span class="selection-bar__hint" id="selection-hint" hidden></span>
<span class="selection-bar__dots" id="selection-color-dots"></span>
<button class="btn btn--primary" id="btn-selection-tag" data-i18n="photos.addTagSelected" disabled>添加标签</button>
<button class="btn btn--danger" id="btn-selection-clear-tags" data-i18n="photos.deleteTags" disabled>删除标签</button>
<button class="btn btn--ghost" id="btn-selection-cancel" data-i18n="dialog.cancel">取消</button>
</div>
<div class="statusbar" id="photo-status">0 photos</div>
</section>

Expand Down Expand Up @@ -181,7 +182,7 @@ <h2 class="settings__title" data-i18n="settings.title">设置</h2>

<!-- Image preview (modal, right panel) -->
<div class="preview-overlay" id="preview-overlay" hidden>
<div class="preview" id="preview-panel">
<div class="preview glass" id="preview-panel">
<div class="preview__header">
<button class="preview__close" id="preview-close" data-i18n-title="preview.close" aria-label="Close">✕</button>
<span class="preview__name" id="preview-name"></span>
Expand All @@ -196,7 +197,7 @@ <h2 class="settings__title" data-i18n="settings.title">设置</h2>

<!-- Tag edit dialog (list of current tags + picker of existing tags) -->
<div class="dialog-overlay" id="edit-overlay" hidden>
<div class="dialog" role="dialog" aria-modal="true">
<div class="dialog glass" role="dialog" aria-modal="true">
<div class="dialog__title" data-i18n="card.edit.title">编辑标签</div>
<div class="dialog__label" data-i18n="card.edit.current">当前标签</div>
<div class="edit-chips" id="edit-chips"></div>
Expand All @@ -212,7 +213,7 @@ <h2 class="settings__title" data-i18n="settings.title">设置</h2>

<!-- Multi-select "add tag" panel (pick one existing tag for the selection) -->
<div class="dialog-overlay" id="tagpick-overlay" hidden>
<div class="dialog" role="dialog" aria-modal="true">
<div class="dialog glass" role="dialog" aria-modal="true">
<div class="dialog__title" data-i18n="tags.pickTitle">为选中的照片添加标签</div>
<input class="dialog__input" id="tagpick-search" type="text" data-i18n-placeholder="tags.pickSearch" />
<div class="tagpick-list" id="tagpick-list"></div>
Expand All @@ -223,7 +224,7 @@ <h2 class="settings__title" data-i18n="settings.title">设置</h2>
</div>

<!-- Color/lens/focal filter dropdown (multi-select, all intersect) -->
<div class="color-filter" id="color-filter" hidden>
<div class="color-filter glass" id="color-filter" hidden>
<div class="color-filter__title" data-i18n="photos.filterColor">颜色筛选</div>
<div class="color-filter__dots" id="color-filter-dots"></div>
<div class="color-filter__title" data-i18n="photos.filterLens">镜头</div>
Expand All @@ -234,6 +235,15 @@ <h2 class="settings__title" data-i18n="settings.title">设置</h2>
<span class="color-filter__focal-sep">–</span>
<input class="color-filter__focal-input" id="filter-focal-max" type="number" min="0" step="1" placeholder="max mm" data-i18n-placeholder="photos.filterFocalMax" />
</div>
<div class="color-filter__title" data-i18n="photos.ratingFilter">按评分筛选</div>
<div class="color-filter__rating" id="rating-filter">
<label><input type="checkbox" value="0" checked><span data-i18n="photos.rating0">无星</span></label>
<label><input type="checkbox" value="1" checked><span data-i18n="photos.rating1">1星</span></label>
<label><input type="checkbox" value="2" checked><span data-i18n="photos.rating2">2星</span></label>
<label><input type="checkbox" value="3" checked><span data-i18n="photos.rating3">3星</span></label>
<label><input type="checkbox" value="4" checked><span data-i18n="photos.rating4">4星</span></label>
<label><input type="checkbox" value="5" checked><span data-i18n="photos.rating5">5星</span></label>
</div>
<button class="btn btn--ghost color-filter__clear" id="btn-color-filter-clear" data-i18n="photos.filterClear">清除</button>
</div>

Expand All @@ -258,7 +268,7 @@ <h2 class="settings__title" data-i18n="settings.title">设置</h2>

<!-- Generic confirm dialog (clears tags, etc.) -->
<div class="dialog-overlay" id="confirm-overlay" hidden>
<div class="dialog" role="dialog" aria-modal="true">
<div class="dialog glass" role="dialog" aria-modal="true">
<div class="dialog__title" data-i18n="dialog.confirmTitle">确认操作</div>
<div class="dialog__text" id="confirm-text"></div>
<div class="dialog__actions">
Expand Down
12 changes: 7 additions & 5 deletions src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@
"filterFocalMax": "Max mm",
"filterEmpty": "No photos match the filters",
"ratingFilter": "Filter by rating",
"ratingAll": "All",
"rating1": "1+ star",
"rating2": "2+ stars",
"rating3": "3+ stars",
"rating4": "4+ stars",
"rating0": "Unrated",
"rating1": "1 star",
"rating2": "2 stars",
"rating3": "3 stars",
"rating4": "4 stars",
"rating5": "5 stars",
"status": {
"count": "{count} photos",
Expand Down Expand Up @@ -178,6 +178,8 @@
"theme": "Theme",
"themeDark": "Dark",
"themeLight": "Light",
"themeLiquidGlass": "Liquid Glass",
"menuBgColor": "Menu background",
"title": "Settings",
"language": "Language",
"languageZh": "中文",
Expand Down
26 changes: 15 additions & 11 deletions src/locales/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ export const MESSAGES = {
"filterFocalMax": "最大 mm",
"filterEmpty": "没有符合筛选条件的照片",
"ratingFilter": "按评分筛选",
"ratingAll": "全部",
"rating1": "≥ 1 星",
"rating2": "≥ 2 星",
"rating3": "≥ 3 星",
"rating4": "≥ 4 星",
"rating5": "≥ 5 星",
"rating0": "无星",
"rating1": "1星",
"rating2": "2星",
"rating3": "3星",
"rating4": "4星",
"rating5": "5星",
"status": {
"count": "{count} 张照片",
"partial": "{shown} / {total} 张照片"
Expand Down Expand Up @@ -181,6 +181,8 @@ export const MESSAGES = {
"theme": "主题",
"themeDark": "深色",
"themeLight": "浅色",
"themeLiquidGlass": "液态玻璃",
"menuBgColor": "菜单背景色",
"title": "设置",
"language": "语言",
"languageZh": "中文",
Expand Down Expand Up @@ -272,11 +274,11 @@ export const MESSAGES = {
"filterFocalMax": "Max mm",
"filterEmpty": "No photos match the filters",
"ratingFilter": "Filter by rating",
"ratingAll": "All",
"rating1": "1+ star",
"rating2": "2+ stars",
"rating3": "3+ stars",
"rating4": "4+ stars",
"rating0": "Unrated",
"rating1": "1 star",
"rating2": "2 stars",
"rating3": "3 stars",
"rating4": "4 stars",
"rating5": "5 stars",
"status": {
"count": "{count} photos",
Expand Down Expand Up @@ -392,6 +394,8 @@ export const MESSAGES = {
"theme": "Theme",
"themeDark": "Dark",
"themeLight": "Light",
"themeLiquidGlass": "Liquid Glass",
"menuBgColor": "Menu background",
"title": "Settings",
"language": "Language",
"languageZh": "中文",
Expand Down
14 changes: 8 additions & 6 deletions src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@
"filterFocalMax": "最大 mm",
"filterEmpty": "没有符合筛选条件的照片",
"ratingFilter": "按评分筛选",
"ratingAll": "全部",
"rating1": "≥ 1 星",
"rating2": "≥ 2 星",
"rating3": "≥ 3 星",
"rating4": "≥ 4 星",
"rating5": "≥ 5 星",
"rating0": "无星",
"rating1": "1星",
"rating2": "2星",
"rating3": "3星",
"rating4": "4星",
"rating5": "5星",
"status": {
"count": "{count} 张照片",
"partial": "{shown} / {total} 张照片"
Expand Down Expand Up @@ -178,6 +178,8 @@
"theme": "主题",
"themeDark": "深色",
"themeLight": "浅色",
"themeLiquidGlass": "液态玻璃",
"menuBgColor": "菜单背景色",
"title": "设置",
"language": "语言",
"languageZh": "中文",
Expand Down
Loading
Loading