Skip to content

Commit 9f11e12

Browse files
committed
Improve UX on language and theme selection
1 parent 252d55f commit 9f11e12

11 files changed

Lines changed: 524 additions & 73 deletions

File tree

languages/de.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@
197197
"appearance.syncHint": "Designs und Sprachen aus /themes und /languages im Projektverzeichnis laden",
198198
"appearance.sync": "Synchronisieren",
199199
"appearance.synced": "Synchronisiert",
200+
"appearance.localOnly": "Nur lokal",
201+
"appearance.localOverride": "Überschreibt Remote",
200202

201203
"settings.advanced": "Erweitert",
202204
"settings.devMode": "Entwickleroptionen",
@@ -234,6 +236,7 @@
234236
"settings.version": "Version",
235237
"settings.stack": "Technologie",
236238
"settings.configPath": "Konfiguration",
239+
"settings.openConfigFolder": "Konfigurationsordner öffnen",
237240

238241
"release.title": "Release-Details",
239242
"release.preRelease": "Vorabversion",

src/main/AssetManager.ts

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import https from 'https';
55
import { GITHUB_CONFIG } from './shared/config/GitHub.config';
66
import { BUILTIN_THEME, THEME_GITHUB_PATH } from './shared/config/Theme.config';
77
import { ENGLISH } from './shared/config/DefaultLanguage.config';
8-
import type { ThemeDefinition, LocalThemeState } from './shared/types/Theme.types';
9-
import type { LanguageDefinition, LocalLanguageState } from './shared/types/Language.types';
8+
import type { ThemeDefinition, LocalThemeState, ThemePreview } from './shared/types/Theme.types';
9+
import type {
10+
LanguageDefinition,
11+
LocalLanguageState,
12+
LanguagePreview,
13+
} from './shared/types/Language.types';
1014

1115
function dataDir(): string {
1216
return app.getPath('userData');
@@ -93,6 +97,53 @@ export async function fetchRemoteThemes(): Promise<{
9397
}
9498
}
9599

100+
export async function fetchRemoteThemePreviews(): Promise<{
101+
ok: boolean;
102+
themes?: ThemePreview[];
103+
error?: string;
104+
}> {
105+
try {
106+
const listing = await httpsGetJson(contentsUrl(THEME_GITHUB_PATH));
107+
if (!Array.isArray(listing)) return { ok: false, error: 'Themes folder not found' };
108+
const themes: ThemePreview[] = [];
109+
for (const f of (listing as Array<{ name: string }>).filter((f) => f.name.endsWith('.json'))) {
110+
try {
111+
const theme = (await httpsGetJson(rawUrl(THEME_GITHUB_PATH, f.name))) as ThemeDefinition;
112+
if (theme.id && theme.name && theme.colors) {
113+
themes.push({
114+
id: theme.id,
115+
name: theme.name,
116+
filename: f.name,
117+
previewColors: {
118+
accent: theme.colors.accent,
119+
'base-900': theme.colors['base-900'],
120+
'surface-raised': theme.colors['surface-raised'],
121+
'text-primary': theme.colors['text-primary'],
122+
},
123+
});
124+
}
125+
} catch {
126+
/* skip */
127+
}
128+
}
129+
return { ok: true, themes };
130+
} catch (e) {
131+
return { ok: false, error: String(e) };
132+
}
133+
}
134+
135+
export async function fetchRemoteThemeByFile(
136+
filename: string
137+
): Promise<{ ok: boolean; theme?: ThemeDefinition; error?: string }> {
138+
try {
139+
const theme = (await httpsGetJson(rawUrl(THEME_GITHUB_PATH, filename))) as ThemeDefinition;
140+
if (theme.id && theme.name && theme.colors) return { ok: true, theme };
141+
return { ok: false, error: 'Invalid theme' };
142+
} catch (e) {
143+
return { ok: false, error: String(e) };
144+
}
145+
}
146+
96147
// ─── Languages ────────────────────────────────────────────────────────────────
97148

98149
const LANG_FILE = 'language-state.json';
@@ -142,6 +193,47 @@ export async function fetchRemoteLanguages(): Promise<{
142193
}
143194
}
144195

196+
export async function fetchRemoteLanguagePreviews(): Promise<{
197+
ok: boolean;
198+
languages?: LanguagePreview[];
199+
error?: string;
200+
}> {
201+
try {
202+
const listing = await httpsGetJson(contentsUrl(GITHUB_CONFIG.languagesPath));
203+
if (!Array.isArray(listing)) return { ok: false, error: 'Languages folder not found' };
204+
const languages: LanguagePreview[] = [];
205+
for (const f of (listing as Array<{ name: string }>).filter((f) => f.name.endsWith('.json'))) {
206+
try {
207+
const lang = (await httpsGetJson(
208+
rawUrl(GITHUB_CONFIG.languagesPath, f.name)
209+
)) as LanguageDefinition;
210+
if (lang.id && lang.name) {
211+
languages.push({ id: lang.id, name: lang.name, filename: f.name });
212+
}
213+
} catch {
214+
/* skip */
215+
}
216+
}
217+
return { ok: true, languages };
218+
} catch (e) {
219+
return { ok: false, error: String(e) };
220+
}
221+
}
222+
223+
export async function fetchRemoteLanguageByFile(
224+
filename: string
225+
): Promise<{ ok: boolean; language?: LanguageDefinition; error?: string }> {
226+
try {
227+
const lang = (await httpsGetJson(
228+
rawUrl(GITHUB_CONFIG.languagesPath, filename)
229+
)) as LanguageDefinition;
230+
if (lang.id && lang.name && lang.strings) return { ok: true, language: lang };
231+
return { ok: false, error: 'Invalid language' };
232+
} catch (e) {
233+
return { ok: false, error: String(e) };
234+
}
235+
}
236+
145237
// ─── Dev mode: load from local project directories ────────────────────────────
146238

147239
function projectRoot(): string {

src/main/ipc/Asset.ipc.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import {
33
getActiveTheme,
44
setActiveTheme,
55
fetchRemoteThemes,
6+
fetchRemoteThemePreviews,
7+
fetchRemoteThemeByFile,
68
getActiveLanguage,
79
setActiveLanguage,
810
fetchRemoteLanguages,
11+
fetchRemoteLanguagePreviews,
12+
fetchRemoteLanguageByFile,
913
loadDevAssets,
1014
} from '../AssetManager';
1115
import type { ThemeDefinition } from '../shared/types/Theme.types';
@@ -28,6 +32,16 @@ export const AssetIPC = {
2832
channel: 'asset:fetchThemes',
2933
handler: () => fetchRemoteThemes(),
3034
},
35+
fetchThemePreviews: {
36+
type: 'invoke',
37+
channel: 'asset:themePreviews',
38+
handler: () => fetchRemoteThemePreviews(),
39+
},
40+
fetchThemeByFile: {
41+
type: 'invoke',
42+
channel: 'asset:themeByFile',
43+
handler: (_e: any, filename: string) => fetchRemoteThemeByFile(filename),
44+
},
3145

3246
// Languages
3347
getActiveLanguage: {
@@ -45,6 +59,16 @@ export const AssetIPC = {
4559
channel: 'asset:fetchLangs',
4660
handler: () => fetchRemoteLanguages(),
4761
},
62+
fetchLanguagePreviews: {
63+
type: 'invoke',
64+
channel: 'asset:langPreviews',
65+
handler: () => fetchRemoteLanguagePreviews(),
66+
},
67+
fetchLanguageByFile: {
68+
type: 'invoke',
69+
channel: 'asset:langByFile',
70+
handler: (_e: any, filename: string) => fetchRemoteLanguageByFile(filename),
71+
},
4872

4973
// Dev
5074
loadDevAssets: {

src/main/ipc/System.ipc.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { dialog, shell } from 'electron';
1+
import { app, dialog, shell } from 'electron';
22
import { restApiServer } from '../RestAPI';
33
import type { RouteMap } from '../IPCController';
44
import type { AppSettings, JRCEnvironment } from '../shared/types/App.types';
@@ -65,4 +65,10 @@ export const SystemIPC = {
6565
channel: 'shell:openExternal',
6666
handler: (_e: any, url: string) => shell.openExternal(url),
6767
},
68+
69+
openConfigFolder: {
70+
type: 'invoke',
71+
channel: 'shell:openConfigFolder',
72+
handler: () => shell.openPath(app.getPath('userData')),
73+
},
6874
} satisfies RouteMap;

src/main/shared/config/DefaultLanguage.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ const ENGLISH_STRINGS = {
219219
'Load themes and languages from /themes and /languages in the project root',
220220
'appearance.sync': 'Sync',
221221
'appearance.synced': 'Synced',
222+
'appearance.localOnly': 'Local only',
223+
'appearance.localOverride': 'Overrides remote',
222224

223225
// Settings: Advanced
224226
'settings.advanced': 'Advanced',
@@ -259,6 +261,7 @@ const ENGLISH_STRINGS = {
259261
'settings.version': 'Version',
260262
'settings.stack': 'Stack',
261263
'settings.configPath': 'Config',
264+
'settings.openConfigFolder': 'Open config folder',
262265

263266
// Release modal
264267
'release.title': 'Release Details',

src/main/shared/types/Language.types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ export interface LocalLanguageState {
1010
activeLanguageId: string;
1111
activeLanguage: LanguageDefinition;
1212
}
13+
14+
export interface LanguagePreview {
15+
id: string;
16+
name: string;
17+
filename: string;
18+
}

src/main/shared/types/Theme.types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,15 @@ export interface LocalThemeState {
2626
activeThemeId: string;
2727
activeTheme: ThemeDefinition;
2828
}
29+
30+
export type ThemePreviewColors = Pick<
31+
ThemeColors,
32+
'accent' | 'base-900' | 'surface-raised' | 'text-primary'
33+
>;
34+
35+
export interface ThemePreview {
36+
id: string;
37+
name: string;
38+
filename: string;
39+
previewColors: ThemePreviewColors;
40+
}

src/renderer/components/common/ContextMenu.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ export function ContextMenu({ x, y, items, onClose }: Props) {
2828
};
2929
document.addEventListener('mousedown', handleOutside);
3030
document.addEventListener('keydown', handleKey);
31+
document.addEventListener('scroll', onClose, true);
3132
return () => {
3233
document.removeEventListener('mousedown', handleOutside);
3334
document.removeEventListener('keydown', handleKey);
35+
document.removeEventListener('scroll', onClose, true);
3436
};
3537
}, [onClose]);
3638

src/renderer/components/settings/VersionChecker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Button } from '../common/Button';
44
import { Tooltip } from '../common/Tooltip';
55
import { ReleaseModal } from './ReleaseModal';
66
import { VscCheck, VscWarning, VscSync, VscCircleSlash } from 'react-icons/vsc';
7-
import { GitHubRelease } from '../../../../main/shared/types/GitHub.types';
7+
import { GitHubRelease } from '../../../main/shared/types/GitHub.types';
88

99
interface Props {
1010
currentVersion: string;

src/renderer/components/settings/sections/AboutSection.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react';
22
import { useTranslation } from '../../../i18n/I18nProvider';
33
import { Section, Row } from '../SettingsRow';
4+
import { Tooltip } from '../../common/Tooltip';
45
import { VersionChecker } from '../VersionChecker';
6+
import { VscFolderOpened } from 'react-icons/vsc';
57
import { version } from '../../../../../package.json';
68

79
export function AboutSection() {
@@ -13,7 +15,17 @@ export function AboutSection() {
1315
<span className="font-mono text-xs text-text-secondary">Electron · React · TypeScript</span>
1416
</Row>
1517
<Row label={t('settings.configPath')}>
16-
<span className="font-mono text-xs text-text-muted">%APPDATA%\java-runner-client</span>
18+
<div className="flex items-center gap-2">
19+
<span className="font-mono text-xs text-text-muted">%APPDATA%\java-runner-client</span>
20+
<Tooltip content={t('settings.openConfigFolder')} side="left" delay={300}>
21+
<button
22+
onClick={() => window.api.openConfigFolder()}
23+
className="p-1 rounded text-text-muted hover:text-text-primary hover:bg-surface-raised transition-colors"
24+
>
25+
<VscFolderOpened size={14} />
26+
</button>
27+
</Tooltip>
28+
</div>
1729
</Row>
1830
</Section>
1931
);

0 commit comments

Comments
 (0)