Skip to content

Commit 5a3fbe4

Browse files
committed
Update UI
Add 中原音韻 and 東干甘肅話 display modes
1 parent 9804f6e commit 5a3fbe4

File tree

13 files changed

+329
-56
lines changed

13 files changed

+329
-56
lines changed

src/app/index.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import "tailwindcss";
2+
@import "./scrollbar.css";
23
@import "./CharisSIL.css";
34
@import "./SBBWeb.css";
45

src/app/scrollbar.css

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* Firefox */
2+
:root {
3+
scrollbar-width: thin;
4+
scrollbar-color: #a1a1aa transparent; /* zinc-400 */
5+
}
6+
.dark :root,
7+
.dark {
8+
scrollbar-color: #52525b transparent; /* zinc-600 */
9+
}
10+
11+
/* WebKit (Chrome/Edge/Safari) */
12+
::-webkit-scrollbar {
13+
width: 10px;
14+
height: 10px;
15+
}
16+
17+
::-webkit-scrollbar-track {
18+
background: transparent;
19+
}
20+
21+
::-webkit-scrollbar-thumb {
22+
background-color: rgba(161, 161, 170, 0.8); /* zinc-400 */
23+
border-radius: 999px;
24+
border: 2px solid transparent;
25+
background-clip: content-box;
26+
}
27+
28+
.dark ::-webkit-scrollbar-thumb {
29+
background-color: rgba(82, 82, 91, 0.9); /* zinc-600 */
30+
}
31+
32+
::-webkit-scrollbar-thumb:hover {
33+
background-color: rgba(113, 113, 122, 0.9); /* zinc-500 */
34+
}
35+
36+
.dark ::-webkit-scrollbar-thumb:hover {
37+
background-color: rgba(113, 113, 122, 0.95); /* zinc-500 */
38+
}

src/components/Query.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { queryCharacters } from "@/lib/api";
4-
import { buildTableRows, parse廣韻字音 } from "@/lib/dataProcessor";
4+
import { buildTableRows, parse廣韻字音, parse中原音韻字音, parse東干甘肅話字音 } from "@/lib/dataProcessor";
55
import type { CharacterResult, ProcessedLanguage, TableRow } from "@/types";
66
import { useState, useEffect } from "react";
77
import { useApp } from "@/contexts/AppContext";
@@ -172,6 +172,18 @@ const Query = () => {
172172
isHTML = true; // Guangyun data contains HTML tags
173173
}
174174

175+
// Special handling for 中原音韻 data
176+
if (row.languageAbbr === "中原音韻" && 字音 !== "—") {
177+
字音 = parse中原音韻字音(字音, settings.中原音韻字段);
178+
isHTML = true; // 中原音韻 data contains HTML tags
179+
}
180+
181+
// Special handling for 東干甘肅話 data
182+
if (row.languageAbbr === "東干甘肅話" && 字音 !== "—") {
183+
字音 = parse東干甘肅話字音(字音, settings.東干甘肅話字段);
184+
isHTML = true; // 東干甘肅話 data contains HTML tags
185+
}
186+
175187
// Render with HTML or plain text
176188
if (isHTML) {
177189
return (
@@ -217,6 +229,6 @@ const Query = () => {
217229
<LanguageDetailModal language={selectedLanguage} onClose={() => setSelectedLanguage(null)} />
218230
</div>
219231
);
220-
}
232+
};
221233

222234
export default Query;

src/components/Settings.tsx

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useApp } from "@/contexts/AppContext";
44
import { getDisplayModeLabel } from "@/lib/dataProcessor";
5-
import { displayModes, 廣韻字段列表 } from "@/types";
5+
import { displayModes, 廣韻字段列表, 中原音韻字段列表, 東干甘肅話字段列表 } from "@/types";
66
import { useState, useMemo } from "react";
77
import { getTranslation } from "@/lib/i18n";
88
import { getTextColor } from "@/lib/utils";
@@ -17,9 +17,11 @@ export default function Settings() {
1717
selectAllLanguages,
1818
deselectAllLanguages,
1919
toggle廣韻字段,
20+
toggle中原音韻字段,
21+
toggle東干甘肅話字段,
2022
updateTheme,
2123
language,
22-
updateLanguage,
24+
setDisplayLanguage,
2325
} = useApp();
2426
const t = getTranslation(language);
2527

@@ -55,6 +57,13 @@ export default function Settings() {
5557
const selectedCount = settings.selectedLanguages.size;
5658
const totalCount = processedLanguages.length;
5759

60+
const displayLanguageList = [
61+
{ code: "zh_HK", label: "繁體中文" },
62+
{ code: "zh_CN", label: "简体中文" },
63+
{ code: "ja", label: "日本語" },
64+
{ code: "en_GB", label: "English" },
65+
] as const;
66+
5867
return (
5968
<div className="min-h-screen bg-background">
6069
<div className="max-w-7xl mx-auto p-4">
@@ -84,34 +93,16 @@ export default function Settings() {
8493
<section className="mb-4 bg-card p-4 shadow-sm">
8594
<h2 className="text-lg font-bold mb-3 text-foreground">{t.settings.interfaceLanguage}</h2>
8695
<div className="flex gap-2 flex-wrap">
87-
<button
88-
onClick={() => updateLanguage("zh_HK")}
89-
className={`px-6 py-1.5 text-sm font-medium transition-colors rounded-full ${
90-
language === "zh_HK" ? "bg-[#EB0000] text-white" : "bg-card text-card-foreground hover:bg-secondary"
91-
}`}>
92-
中文(繁體)
93-
</button>
94-
<button
95-
onClick={() => updateLanguage("zh_CN")}
96-
className={`px-6 py-1.5 text-sm font-medium transition-colors rounded-full ${
97-
language === "zh_CN" ? "bg-[#EB0000] text-white" : "bg-card text-card-foreground hover:bg-secondary"
98-
}`}>
99-
中文(简体)
100-
</button>
101-
<button
102-
onClick={() => updateLanguage("ja")}
103-
className={`px-6 py-1.5 text-sm font-medium transition-colors rounded-full ${
104-
language === "ja" ? "bg-[#EB0000] text-white" : "bg-card text-card-foreground hover:bg-secondary"
105-
}`}>
106-
日本語
107-
</button>
108-
<button
109-
onClick={() => updateLanguage("en_GB")}
110-
className={`px-6 py-1.5 text-sm font-medium transition-colors rounded-full ${
111-
language === "en_GB" ? "bg-[#EB0000] text-white" : "bg-card text-card-foreground hover:bg-secondary"
112-
}`}>
113-
English
114-
</button>
96+
{displayLanguageList.map(lang => (
97+
<button
98+
key={lang.code}
99+
onClick={() => setDisplayLanguage(lang.code)}
100+
className={`px-6 py-1.5 text-sm font-medium transition-colors rounded-full ${
101+
language === lang.code ? "bg-[#EB0000] text-white" : "bg-card text-card-foreground hover:bg-secondary"
102+
}`}>
103+
{lang.label}
104+
</button>
105+
))}
115106
</div>
116107
</section>
117108

@@ -209,8 +200,60 @@ export default function Settings() {
209200
</div>
210201
</section>
211202

212-
{/* Language Selection Section */}
203+
{/* Zhongyuan Yinyun Display Section */}
213204
<section className="mb-4 bg-card p-4 shadow-sm">
205+
<div className="flex items-center justify-between mb-3">
206+
<div>
207+
<h2 className="text-lg font-bold text-foreground">{t.settings.zhongyuanDisplay}</h2>
208+
</div>
209+
</div>
210+
211+
{/* Zhongyuan Yinyun Fields Grid */}
212+
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 2xl:grid-cols-12">
213+
{中原音韻字段列表.map(field => (
214+
<label
215+
key={field}
216+
className="flex items-start gap-1 p-1 hover:bg-secondary cursor-pointer border-r border-b border text-xs leading-tight">
217+
<input
218+
type="checkbox"
219+
checked={settings.中原音韻字段.has(field)}
220+
onChange={() => toggle中原音韻字段(field)}
221+
className="w-3 h-3 mt-0.5 flex-shrink-0 accent-primary"
222+
/>
223+
<span className="min-w-0 break-words text-foreground">{field}</span>
224+
</label>
225+
))}
226+
</div>
227+
</section>
228+
229+
{/* Donggan Gansu Dialect Display Section */}
230+
<section className="mb-4 bg-card p-4 shadow-sm">
231+
<div className="flex items-center justify-between mb-3">
232+
<div>
233+
<h2 className="text-lg font-bold text-foreground">{t.settings.dunganGansuDisplay}</h2>
234+
</div>
235+
</div>
236+
237+
{/* Donggan Gansu Dialect Fields Grid */}
238+
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 2xl:grid-cols-12">
239+
{東干甘肅話字段列表.map(field => (
240+
<label
241+
key={field}
242+
className="flex items-start gap-1 p-1 hover:bg-secondary cursor-pointer border-r border-b border text-xs leading-tight">
243+
<input
244+
type="checkbox"
245+
checked={settings.東干甘肅話字段.has(field)}
246+
onChange={() => toggle東干甘肅話字段(field)}
247+
className="w-3 h-3 mt-0.5 flex-shrink-0 accent-primary"
248+
/>
249+
<span className="min-w-0 break-words text-foreground">{field}</span>
250+
</label>
251+
))}
252+
</div>
253+
</section>
254+
255+
{/* Language Selection Section */}
256+
<section className="mb-4 bg-card p-4 shadow-sm max-h-[600px] overflow-y-auto">
214257
<div className="flex items-center justify-between mb-3">
215258
<h2 className="text-lg font-bold text-foreground">
216259
{t.settings.languageSelection} ({selectedCount}/{totalCount})

src/contexts/AppContext.tsx

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { processLanguages } from "@/lib/dataProcessor";
44
import type {
55
DisplayMode,
66
廣韻字段,
7+
中原音韻字段,
8+
東干甘肅話字段,
79
Language,
810
LanguageInfo,
911
ProcessedLanguage,
@@ -29,6 +31,8 @@ interface AppContextValue {
2931
selectAllLanguages: () => void;
3032
deselectAllLanguages: () => void;
3133
toggle廣韻字段: (field: 廣韻字段) => void;
34+
toggle中原音韻字段: (field: 中原音韻字段) => void;
35+
toggle東干甘肅話字段: (field: 東干甘肅話字段) => void;
3236
updateTheme: (theme: Theme) => void;
3337

3438
// Current page
@@ -37,7 +41,7 @@ interface AppContextValue {
3741

3842
// UI language
3943
language: Language;
40-
updateLanguage: (lang: Language) => void;
44+
setDisplayLanguage: (lang: Language) => void;
4145

4246
// Query state
4347
queryInput: string;
@@ -48,34 +52,51 @@ interface AppContextValue {
4852

4953
const AppContext = createContext<AppContextValue | undefined>(undefined);
5054

55+
const detectSystemTheme = (): Theme => {
56+
try {
57+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
58+
return prefersDark ? "dark" : "light";
59+
} catch {
60+
return "light";
61+
}
62+
};
63+
64+
// https://github.com/osfans/MCPDict/blob/aee3606025410afc1be5c6f5bf1bd95ae8f237bf/app/src/main/res/values/strings.xml#L204-L206
65+
const DEFAULT_中原音韻字段: 中原音韻字段[] = ["unt"];
66+
// https://github.com/osfans/MCPDict/blob/aee3606025410afc1be5c6f5bf1bd95ae8f237bf/app/src/main/res/values/strings.xml#L218-L221
67+
const DEFAULT_東干甘肅話字段: 東干甘肅話字段[] = ["音標", "西里爾字母"];
68+
5169
const DEFAULT_SETTINGS: UserSettings = {
5270
displayMode: "地圖集二",
5371
selectedLanguages: new Set<number>(),
54-
廣韻字段: new Set<廣韻字段>(["切韻拼音", "切韻音系描述", "unt(2022)擬音", "反切"]), // Default fields
55-
theme: "light", // Default theme
72+
廣韻字段: new Set<廣韻字段>(["切韻拼音", "切韻音系描述", "unt(2022)擬音"]), // Default fields
73+
中原音韻字段: new Set<中原音韻字段>(DEFAULT_中原音韻字段),
74+
東干甘肅話字段: new Set<東干甘肅話字段>(DEFAULT_東干甘肅話字段),
75+
theme: detectSystemTheme(), // Default theme based on system preference
5676
};
5777

5878
const DEFAULT_LANGUAGE: Language = "zh_HK";
5979

80+
const DISPLAY_LANGUAGE_KEY = "yindian-language";
81+
82+
export const getCachedDisplayLanguage = (): Language | null => {
83+
try {
84+
const cached = localStorage.getItem(DISPLAY_LANGUAGE_KEY);
85+
if (!cached) return null;
86+
return cached as Language;
87+
} catch {
88+
return null;
89+
}
90+
};
91+
6092
export function AppProvider({ children }: { children: ReactNode }) {
6193
const [rawLanguages, setRawLanguages] = useState<LanguageInfo[]>([]);
6294
const [isLoadingLanguages, setIsLoadingLanguages] = useState(true);
6395
const [languagesError, setLanguagesError] = useState<Error | null>(null);
6496
const [queryInput, setQueryInput] = useState<string>("");
6597
const [queryResults, setQueryResults] = useState<CharacterResult[] | null>(null);
6698
const [page, setPage] = useState<Pages>("query");
67-
const [language, setLanguageState] = useState<Language>(() => {
68-
// Try to load language from localStorage
69-
try {
70-
const saved = localStorage.getItem("yindian-language");
71-
if (saved) {
72-
return saved as Language;
73-
}
74-
} catch (e) {
75-
console.error("Failed to load language:", e);
76-
}
77-
return DEFAULT_LANGUAGE;
78-
});
99+
const [language, setLanguageState] = useState<Language>(getCachedDisplayLanguage() || DEFAULT_LANGUAGE);
79100
const [settings, setSettings] = useState<UserSettings>(() => {
80101
// Try to load settings from localStorage
81102
try {
@@ -85,8 +106,10 @@ export function AppProvider({ children }: { children: ReactNode }) {
85106
return {
86107
displayMode: parsed.displayMode as DisplayMode,
87108
selectedLanguages: new Set(parsed.selectedLanguages || []),
88-
廣韻字段: new Set<廣韻字段>(parsed.廣韻字段 || ["切韻拼音", "切韻音系描述"]),
89-
theme: parsed.theme || "light",
109+
廣韻字段: new Set<廣韻字段>(parsed.廣韻字段 || ["切韻拼音", "切韻音系描述", "unt(2022)擬音"]),
110+
中原音韻字段: new Set<中原音韻字段>(parsed.中原音韻字段 || DEFAULT_中原音韻字段),
111+
東干甘肅話字段: new Set<東干甘肅話字段>(parsed.東干甘肅話字段 || DEFAULT_東干甘肅話字段),
112+
theme: parsed.theme || detectSystemTheme(),
90113
};
91114
}
92115
} catch (e) {
@@ -104,6 +127,8 @@ export function AppProvider({ children }: { children: ReactNode }) {
104127
displayMode: settings.displayMode,
105128
selectedLanguages: Array.from(settings.selectedLanguages),
106129
廣韻字段: Array.from(settings.廣韻字段),
130+
中原音韻字段: Array.from(settings.中原音韻字段),
131+
東干甘肅話字段: Array.from(settings.東干甘肅話字段),
107132
theme: settings.theme,
108133
}),
109134
);
@@ -179,14 +204,38 @@ export function AppProvider({ children }: { children: ReactNode }) {
179204
});
180205
};
181206

207+
const toggle中原音韻字段 = (field: 中原音韻字段) => {
208+
setSettings(prev => {
209+
const newFields = new Set(prev.中原音韻字段);
210+
if (newFields.has(field)) {
211+
newFields.delete(field);
212+
} else {
213+
newFields.add(field);
214+
}
215+
return { ...prev, 中原音韻字段: newFields };
216+
});
217+
};
218+
219+
const toggle東干甘肅話字段 = (field: 東干甘肅話字段) => {
220+
setSettings(prev => {
221+
const newFields = new Set(prev.東干甘肅話字段);
222+
if (newFields.has(field)) {
223+
newFields.delete(field);
224+
} else {
225+
newFields.add(field);
226+
}
227+
return { ...prev, 東干甘肅話字段: newFields };
228+
});
229+
};
230+
182231
const updateTheme = (theme: Theme) => {
183232
setSettings(prev => ({ ...prev, theme }));
184233
};
185234

186-
const setLanguage = (lang: Language) => {
235+
const setDisplayLanguage = (lang: Language) => {
187236
setLanguageState(lang);
188237
try {
189-
localStorage.setItem("yindian-language", lang);
238+
localStorage.setItem(DISPLAY_LANGUAGE_KEY, lang);
190239
} catch (e) {
191240
console.error("Failed to save language:", e);
192241
}
@@ -205,9 +254,11 @@ export function AppProvider({ children }: { children: ReactNode }) {
205254
selectAllLanguages,
206255
deselectAllLanguages,
207256
toggle廣韻字段,
257+
toggle中原音韻字段,
258+
toggle東干甘肅話字段,
208259
updateTheme,
209260
language,
210-
updateLanguage: setLanguage,
261+
setDisplayLanguage,
211262
queryInput,
212263
setQueryInput,
213264
queryResults,

0 commit comments

Comments
 (0)