主题与字号从页面顶栏搬进设置页的「外观」,顶部导航加上预设入口。
顶栏那套汉堡菜单在每页都要单独挂载、样式也重复,收进设置页后只剩一处改;顺手把「整组都没有曾用型号」的配置组整组收起,不再留空卡片。
This commit is contained in:
+112
-14
@@ -9,6 +9,17 @@ import {
|
||||
sortByApiProfileUsage,
|
||||
} from "./api-profile-usage.js";
|
||||
import { readSniffModels, writeSniffModels } from "./model-sniff-cache.js";
|
||||
import {
|
||||
applyColorTheme,
|
||||
applyUiFontScale,
|
||||
bumpUiFontScale,
|
||||
COLOR_THEMES,
|
||||
getStoredColorTheme,
|
||||
getStoredUiFontScale,
|
||||
UI_FONT_SCALE_DEFAULT,
|
||||
UI_FONT_SCALE_MAX,
|
||||
UI_FONT_SCALE_MIN,
|
||||
} from "./theme.js";
|
||||
|
||||
let state = {
|
||||
settings: { activeProfileId: null, activePresetId: null },
|
||||
@@ -29,6 +40,8 @@ let sniffBootId = "";
|
||||
let sniffRestoreSeq = 0;
|
||||
/** 组 id → 是否展开显示该组全部配置(默认只显示曾选用);有搜索词时按匹配显示 */
|
||||
const showAllProfilesByGroup = new Set();
|
||||
/** 上一轮渲染里因为一个曾用型号都没有而整组收起的组 id */
|
||||
let lastHiddenGroupIds = [];
|
||||
let profileSearchQuery = "";
|
||||
let editingPersonaId = null;
|
||||
let activeSection = "api";
|
||||
@@ -59,6 +72,12 @@ const sectionPreferenceEl = document.getElementById("section-preference");
|
||||
const sectionPresetEl = document.getElementById("section-preset");
|
||||
const sectionStorageEl = document.getElementById("section-storage");
|
||||
const sectionChatEl = document.getElementById("section-chat");
|
||||
const sectionAppearanceEl = document.getElementById("section-appearance");
|
||||
const appearanceThemesEl = document.getElementById("appearance-themes");
|
||||
const appearanceScaleValueEl = document.getElementById("appearance-scale-value");
|
||||
const appearanceScaleMinusEl = document.getElementById("appearance-scale-minus");
|
||||
const appearanceScalePlusEl = document.getElementById("appearance-scale-plus");
|
||||
const appearanceScaleResetEl = document.getElementById("appearance-scale-reset");
|
||||
const preferenceCollectEveryEl = document.getElementById("preference-collect-every");
|
||||
const activeSettingsBarEl = document.getElementById("active-settings-bar");
|
||||
const settingsToastEl = document.getElementById("settings-toast");
|
||||
@@ -126,6 +145,35 @@ function showToast(message, isError = false) {
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
/** 外观(主题/字号)只存在本机浏览器里,不走服务端设置。 */
|
||||
const APPEARANCE_STORAGE_KEYS = ["wa-color-theme", "wa-ui-font-scale"];
|
||||
|
||||
function renderAppearanceThemes() {
|
||||
if (!appearanceThemesEl || appearanceThemesEl.childElementCount) return;
|
||||
appearanceThemesEl.innerHTML = COLOR_THEMES.map(
|
||||
(theme) => `<button type="button" class="appearance-theme" data-appearance-theme="${theme.id}" role="radio" aria-checked="false">
|
||||
<span class="appearance-theme-icon" aria-hidden="true">${theme.icon}</span>
|
||||
<span class="appearance-theme-copy">
|
||||
<span class="appearance-theme-name">${escapeHtml(theme.name)}</span>
|
||||
<span class="appearance-theme-desc">${escapeHtml(theme.description)}</span>
|
||||
</span>
|
||||
</button>`,
|
||||
).join("");
|
||||
}
|
||||
|
||||
function syncAppearanceUi() {
|
||||
const activeTheme = getStoredColorTheme();
|
||||
appearanceThemesEl?.querySelectorAll("[data-appearance-theme]").forEach((btn) => {
|
||||
const on = btn.getAttribute("data-appearance-theme") === activeTheme;
|
||||
btn.classList.toggle("is-active", on);
|
||||
btn.setAttribute("aria-checked", String(on));
|
||||
});
|
||||
const percent = getStoredUiFontScale();
|
||||
if (appearanceScaleValueEl) appearanceScaleValueEl.textContent = `${percent}%`;
|
||||
if (appearanceScaleMinusEl) appearanceScaleMinusEl.disabled = percent <= UI_FONT_SCALE_MIN;
|
||||
if (appearanceScalePlusEl) appearanceScalePlusEl.disabled = percent >= UI_FONT_SCALE_MAX;
|
||||
}
|
||||
|
||||
function renderActiveBar() {
|
||||
const profile = state.profiles.find(
|
||||
(p) => p.id === state.settings.activeProfileId,
|
||||
@@ -159,6 +207,8 @@ function switchSection(section) {
|
||||
sectionPresetEl.classList.toggle("hidden", section !== "preset");
|
||||
sectionStorageEl?.classList.toggle("hidden", section !== "storage");
|
||||
sectionChatEl?.classList.toggle("hidden", section !== "chat");
|
||||
sectionAppearanceEl?.classList.toggle("hidden", section !== "appearance");
|
||||
if (section === "appearance") syncAppearanceUi();
|
||||
renderPanelHeader();
|
||||
}
|
||||
|
||||
@@ -197,6 +247,9 @@ function renderPanelHeader() {
|
||||
btn.textContent = "导入 JSON";
|
||||
btn.addEventListener("click", () => presetFileEl.click());
|
||||
panelActionsEl.appendChild(btn);
|
||||
} else if (activeSection === "appearance") {
|
||||
panelTitleEl.textContent = "外观";
|
||||
panelSubtitleEl.textContent = "主题与字号,只影响这台浏览器";
|
||||
} else if (activeSection === "chat") {
|
||||
panelTitleEl.textContent = "直聊";
|
||||
panelSubtitleEl.textContent = "卡片拖动的跟手倍率";
|
||||
@@ -338,6 +391,7 @@ function renderProfiles() {
|
||||
? state.groups
|
||||
: [{ id: "", name: "未分组", baseUrl: "" }];
|
||||
const seen = new Set();
|
||||
const hiddenGroupIds = [];
|
||||
let shownGroups = 0;
|
||||
|
||||
for (const group of groups) {
|
||||
@@ -352,6 +406,11 @@ function renderProfiles() {
|
||||
activeId,
|
||||
);
|
||||
if (query && !visible.length) continue;
|
||||
// 默认「只看曾用」时,整组都没有曾用型号就整组收起,不留空卡片。
|
||||
if (!showAll && !visible.length) {
|
||||
hiddenGroupIds.push(group.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasActive = members.some((p) => p.id === activeId);
|
||||
const sample = members[0];
|
||||
@@ -409,19 +468,10 @@ function renderProfiles() {
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (!visible.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "profile-group-empty";
|
||||
empty.textContent = query
|
||||
? "没有匹配的模型"
|
||||
: "本组还没有曾选用过的型号。点「显示全部」,或「添加型号」。";
|
||||
card.appendChild(empty);
|
||||
} else {
|
||||
const rows = document.createElement("div");
|
||||
rows.className = "profile-model-rows";
|
||||
for (const p of visible) rows.appendChild(renderModelRow(p));
|
||||
card.appendChild(rows);
|
||||
}
|
||||
const rows = document.createElement("div");
|
||||
rows.className = "profile-model-rows";
|
||||
for (const p of visible) rows.appendChild(renderModelRow(p));
|
||||
card.appendChild(rows);
|
||||
|
||||
profilesListEl.appendChild(card);
|
||||
shownGroups += 1;
|
||||
@@ -452,7 +502,17 @@ function renderProfiles() {
|
||||
shownGroups += 1;
|
||||
}
|
||||
|
||||
if (!shownGroups) {
|
||||
lastHiddenGroupIds = hiddenGroupIds;
|
||||
// 一个曾用型号都没有的组整组收起后,留一行提示,需要时还能展开。
|
||||
if (hiddenGroupIds.length) {
|
||||
const hint = document.createElement("p");
|
||||
hint.className = "empty-hint profile-hidden-groups";
|
||||
hint.innerHTML = `已自动隐藏 ${hiddenGroupIds.length} 个还没有曾用型号的组
|
||||
<button type="button" class="profile-group-toggle" data-action="reveal-hidden-groups">显示这些组</button>`;
|
||||
profilesListEl.appendChild(hint);
|
||||
}
|
||||
|
||||
if (!shownGroups && !hiddenGroupIds.length) {
|
||||
profilesListEl.innerHTML = query
|
||||
? `<p class="empty-hint">没有匹配「${escapeHtml(profileSearchQuery.trim())}」的配置。</p>`
|
||||
: '<p class="empty-hint">暂无配置,点击右上角「新增配置」。</p>';
|
||||
@@ -1525,6 +1585,13 @@ profilesListEl.addEventListener("click", async (e) => {
|
||||
const action = btn.dataset.action;
|
||||
|
||||
try {
|
||||
if (action === "reveal-hidden-groups") {
|
||||
for (const hiddenId of lastHiddenGroupIds) {
|
||||
showAllProfilesByGroup.add(hiddenId);
|
||||
}
|
||||
renderProfiles();
|
||||
return;
|
||||
}
|
||||
if (action === "toggle-group-all") {
|
||||
if (showAllProfilesByGroup.has(id)) showAllProfilesByGroup.delete(id);
|
||||
else showAllProfilesByGroup.add(id);
|
||||
@@ -1924,6 +1991,37 @@ document.getElementById("btn-clear-context")?.addEventListener("click", async ()
|
||||
}
|
||||
});
|
||||
|
||||
appearanceThemesEl?.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("[data-appearance-theme]");
|
||||
if (!btn) return;
|
||||
applyColorTheme(btn.getAttribute("data-appearance-theme"));
|
||||
syncAppearanceUi();
|
||||
});
|
||||
|
||||
appearanceScaleMinusEl?.addEventListener("click", () => {
|
||||
bumpUiFontScale(-1);
|
||||
syncAppearanceUi();
|
||||
});
|
||||
|
||||
appearanceScalePlusEl?.addEventListener("click", () => {
|
||||
bumpUiFontScale(1);
|
||||
syncAppearanceUi();
|
||||
});
|
||||
|
||||
appearanceScaleResetEl?.addEventListener("click", () => {
|
||||
applyColorTheme("modern");
|
||||
applyUiFontScale(UI_FONT_SCALE_DEFAULT);
|
||||
syncAppearanceUi();
|
||||
showToast("外观已恢复默认");
|
||||
});
|
||||
|
||||
window.addEventListener("storage", (e) => {
|
||||
if (APPEARANCE_STORAGE_KEYS.includes(e.key)) syncAppearanceUi();
|
||||
});
|
||||
|
||||
renderAppearanceThemes();
|
||||
syncAppearanceUi();
|
||||
|
||||
renderPanelHeader();
|
||||
loadAll().catch((err) => {
|
||||
showToast(`加载设置失败: ${err.message}`, true);
|
||||
|
||||
Reference in New Issue
Block a user