pre-ui-rearch

This commit is contained in:
2026-07-31 01:05:32 -04:00
parent 89c5ca2f7e
commit 97b2f61e5f
12 changed files with 2136 additions and 1668 deletions

View File

@@ -1,159 +1,94 @@
import { createSignal } from "solid-js";
import { useKeyboard } from "@opentui/solid";
import { useAppStore } from "@/stores/app";
import { useTheme } from "@/context/ThemeContext";
import type { ThemeName } from "@/types/settings";
/**
* PreferencesPanel — exposes theme/font/speed/explicit/auto-download as
* SettingItems for the yazi depth-stack. No own useKeyboard; all movement is
* driven by the Shell router via nav.action.
*/
type FocusField = "theme" | "font" | "speed" | "explicit" | "auto";
import { useAppStore } from "@/stores/app";
import type { ThemeName } from "@/types/settings";
import type { SettingItem } from "./types";
const THEME_LABELS: Array<{ value: ThemeName; label: string }> = [
{ value: "system", label: "System" },
{ value: "catppuccin", label: "Catppuccin" },
{ value: "gruvbox", label: "Gruvbox" },
{ value: "tokyo", label: "Tokyo" },
{ value: "nord", label: "Nord" },
{ value: "custom", label: "Custom" },
{ value: "system", label: "System" },
{ value: "catppuccin", label: "Catppuccin" },
{ value: "gruvbox", label: "Gruvbox" },
{ value: "tokyo", label: "Tokyo" },
{ value: "nord", label: "Nord" },
{ value: "custom", label: "Custom" },
];
export function PreferencesPanel() {
const appStore = useAppStore();
const { theme } = useTheme();
const [focusField, setFocusField] = createSignal<FocusField>("theme");
export function usePreferencesItems(): SettingItem[] {
const app = useAppStore();
const settings = () => appStore.state().settings;
const preferences = () => appStore.state().preferences;
const settings = () => app.state().settings;
const prefs = () => app.state().preferences;
const handleKey = (key: { name: string; shift?: boolean }) => {
if (key.name === "tab") {
const fields: FocusField[] = [
"theme",
"font",
"speed",
"explicit",
"auto",
];
const idx = fields.indexOf(focusField());
const next = key.shift
? (idx - 1 + fields.length) % fields.length
: (idx + 1) % fields.length;
setFocusField(fields[next]);
return;
}
if (key.name === "left" || key.name === "h") {
stepValue(-1);
}
if (key.name === "right" || key.name === "l") {
stepValue(1);
}
if (key.name === "space" || key.name === "return") {
toggleValue();
}
};
const stepValue = (delta: number) => {
const field = focusField();
if (field === "theme") {
const idx = THEME_LABELS.findIndex((t) => t.value === settings().theme);
const next = (idx + delta + THEME_LABELS.length) % THEME_LABELS.length;
appStore.setTheme(THEME_LABELS[next].value);
return;
}
if (field === "font") {
const next = Math.min(20, Math.max(10, settings().fontSize + delta));
appStore.updateSettings({ fontSize: next });
return;
}
if (field === "speed") {
const next = Math.min(
2,
Math.max(0.5, settings().playbackSpeed + delta * 0.1),
);
appStore.updateSettings({ playbackSpeed: Number(next.toFixed(1)) });
}
};
const toggleValue = () => {
const field = focusField();
if (field === "explicit") {
appStore.updatePreferences({ showExplicit: !preferences().showExplicit });
}
if (field === "auto") {
appStore.updatePreferences({ autoDownload: !preferences().autoDownload });
}
};
useKeyboard(handleKey);
return (
<box flexDirection="column" gap={1}>
<text fg={theme.textMuted}>Preferences</text>
<box flexDirection="column" gap={1}>
<box flexDirection="row" gap={1} alignItems="center">
<text fg={focusField() === "theme" ? theme.primary : theme.textMuted}>
Theme:
</text>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>
{THEME_LABELS.find((t) => t.value === settings().theme)?.label}
</text>
</box>
<text fg={theme.textMuted}>[Left/Right]</text>
</box>
<box flexDirection="row" gap={1} alignItems="center">
<text fg={focusField() === "font" ? theme.primary : theme.textMuted}>
Font Size:
</text>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>{settings().fontSize}px</text>
</box>
<text fg={theme.textMuted}>[Left/Right]</text>
</box>
<box flexDirection="row" gap={1} alignItems="center">
<text fg={focusField() === "speed" ? theme.primary : theme.textMuted}>
Playback:
</text>
<box border borderColor={theme.border} padding={0}>
<text fg={theme.text}>{settings().playbackSpeed}x</text>
</box>
<text fg={theme.textMuted}>[Left/Right]</text>
</box>
<box flexDirection="row" gap={1} alignItems="center">
<text
fg={focusField() === "explicit" ? theme.primary : theme.textMuted}
>
Show Explicit:
</text>
<box border borderColor={theme.border} padding={0}>
<text
fg={preferences().showExplicit ? theme.success : theme.textMuted}
>
{preferences().showExplicit ? "On" : "Off"}
</text>
</box>
<text fg={theme.textMuted}>[Space]</text>
</box>
<box flexDirection="row" gap={1} alignItems="center">
<text fg={focusField() === "auto" ? theme.primary : theme.textMuted}>
Auto Download:
</text>
<box border borderColor={theme.border} padding={0}>
<text
fg={preferences().autoDownload ? theme.success : theme.textMuted}
>
{preferences().autoDownload ? "On" : "Off"}
</text>
</box>
<text fg={theme.textMuted}>[Space]</text>
</box>
</box>
<text fg={theme.textMuted}>Tab to move focus, Left/Right to adjust</text>
</box>
);
return [
{
id: "theme",
label: "Theme",
kind: "select",
display: () =>
THEME_LABELS.find((t) => t.value === settings().theme)?.label ??
settings().theme,
help: () =>
`Color theme.\nType: select\nDefault: system\nCurrent: ${settings().theme}\nCycle with j/k; Enter to apply.`,
cycle: (dir) => {
const idx = THEME_LABELS.findIndex((t) => t.value === settings().theme);
const next = (idx + dir + THEME_LABELS.length) % THEME_LABELS.length;
app.setTheme(THEME_LABELS[next].value);
},
},
{
id: "fontSize",
label: "Font Size",
kind: "number",
display: () => `${settings().fontSize}px`,
help: () =>
`Terminal font size in pixels.\nType: number (1020)\nDefault: 14\nCurrent: ${settings().fontSize}\nj/k to /+1px.`,
cycle: (dir) => {
const next = Math.min(20, Math.max(10, settings().fontSize + dir));
app.updateSettings({ fontSize: next });
},
},
{
id: "playbackSpeed",
label: "Playback Speed",
kind: "number",
display: () => `${settings().playbackSpeed}x`,
help: () =>
`Default audio playback speed.\nType: number (0.52.0)\nDefault: 1.0\nCurrent: ${settings().playbackSpeed}\nj/k to /+0.1.`,
cycle: (dir) => {
const next = Math.min(
2,
Math.max(0.5, settings().playbackSpeed + dir * 0.1),
);
app.updateSettings({ playbackSpeed: Number(next.toFixed(1)) });
},
},
{
id: "showExplicit",
label: "Show Explicit",
kind: "toggle",
display: () => (prefs().showExplicit ? "On" : "Off"),
help: () =>
`Whether to list explicit episodes.\nType: toggle\nDefault: true\nCurrent: ${prefs().showExplicit}\nSpace/Enter to toggle.`,
toggle: () =>
app.updatePreferences({
showExplicit: !prefs().showExplicit,
}),
},
{
id: "autoDownload",
label: "Auto Download",
kind: "toggle",
display: () => (prefs().autoDownload ? "On" : "Off"),
help: () =>
`Download new episodes automatically.\nType: toggle\nDefault: false\nCurrent: ${prefs().autoDownload}\nSpace/Enter to toggle.`,
toggle: () =>
app.updatePreferences({
autoDownload: !prefs().autoDownload,
}),
},
];
}