Skip to content

Commit 05b0192

Browse files
feat: config panel
1 parent 683f074 commit 05b0192

File tree

14 files changed

+1475
-17
lines changed

14 files changed

+1475
-17
lines changed

src/shell/script/script.js

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/shell/script/ts/build.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ const setTimeout = __mshell.infra.setTimeout;
1919
const clearTimeout = __mshell.infra.clearTimeout;
2020
`
2121
},
22-
alias: {
23-
react: 'react',
24-
'react-reconciler': 'react-reconciler',
25-
}
22+
jsx: "transform",
23+
tsconfigRaw: "{}",
24+
jsxFactory: "h",
25+
jsxFragment: "Fragment"
2626
});
27+
2728
console.log('Build completed successfully');
2829
} catch (error) {
2930
console.error('Build failed:', error);
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import * as shell from "mshell";
2+
import { WINDOW_WIDTH, WINDOW_HEIGHT, SIDEBAR_WIDTH } from "./constants";
3+
import { loadConfig, saveConfig } from "./utils";
4+
import {
5+
ContextMenuContext,
6+
DebugConsoleContext,
7+
PluginLoadOrderContext,
8+
UpdateDataContext,
9+
NotificationContext,
10+
PluginSourceContext
11+
} from "./contexts";
12+
import Sidebar from "./Sidebar";
13+
import ContextMenuConfig from "./ContextMenuConfig";
14+
import UpdatePage from "./UpdatePage";
15+
import PluginStore from "./PluginStore";
16+
import PluginConfig from "./PluginConfig";
17+
import { useState, useEffect } from "react";
18+
19+
export const ConfigApp = () => {
20+
const [activePage, setActivePage] = useState('context-menu');
21+
const [contextMenuConfig, setContextMenuConfig] = useState<any>({});
22+
const [debugConsole, setDebugConsole] = useState<boolean>(false);
23+
const [pluginLoadOrder, setPluginLoadOrder] = useState<any[]>([]);
24+
const [updateData, setUpdateData] = useState<any>(null);
25+
const [config, setConfig] = useState<any>({});
26+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
27+
const [loadingMessage, setLoadingMessage] = useState<string | null>(null);
28+
const [currentPluginSource, setCurrentPluginSource] = useState<string>('Enlysure');
29+
const [cachedPluginIndex, setCachedPluginIndex] = useState<any>(null);
30+
31+
useEffect(() => {
32+
const current_config_path = shell.breeze.data_directory() + '/config.json';
33+
const current_config = shell.fs.read(current_config_path);
34+
const parsed = JSON.parse(current_config);
35+
setConfig(parsed);
36+
setContextMenuConfig(parsed.context_menu || {});
37+
setDebugConsole(parsed.debug_console || false);
38+
setPluginLoadOrder(parsed.plugin_load_order || []);
39+
}, []);
40+
41+
const updateContextMenu = (newConfig: any) => {
42+
setContextMenuConfig(newConfig);
43+
const newGlobal = { ...config, context_menu: newConfig };
44+
setConfig(newGlobal);
45+
saveConfig(newGlobal);
46+
};
47+
48+
const updateDebugConsole = (value: boolean) => {
49+
setDebugConsole(value);
50+
const newGlobal = { ...config, debug_console: value };
51+
setConfig(newGlobal);
52+
saveConfig(newGlobal);
53+
};
54+
55+
const updatePluginLoadOrder = (order: any[]) => {
56+
setPluginLoadOrder(order);
57+
const newGlobal = { ...config, plugin_load_order: order };
58+
setConfig(newGlobal);
59+
saveConfig(newGlobal);
60+
};
61+
62+
return (
63+
<ContextMenuContext.Provider value={{ config: contextMenuConfig, update: updateContextMenu }}>
64+
<DebugConsoleContext.Provider value={{ value: debugConsole, update: updateDebugConsole }}>
65+
<PluginLoadOrderContext.Provider value={{ order: pluginLoadOrder, update: updatePluginLoadOrder }}>
66+
<UpdateDataContext.Provider value={{ updateData, setUpdateData }}>
67+
<NotificationContext.Provider value={{
68+
errorMessage,
69+
setErrorMessage,
70+
loadingMessage,
71+
setLoadingMessage
72+
}}>
73+
<PluginSourceContext.Provider value={{
74+
currentPluginSource,
75+
setCurrentPluginSource,
76+
cachedPluginIndex,
77+
setCachedPluginIndex
78+
}}>
79+
<flex horizontal width={WINDOW_WIDTH} height={WINDOW_HEIGHT} autoSize={false} gap={10}>
80+
<Sidebar
81+
activePage={activePage}
82+
setActivePage={setActivePage}
83+
sidebarWidth={SIDEBAR_WIDTH}
84+
windowHeight={WINDOW_HEIGHT}
85+
/>
86+
<flex padding={20}>
87+
{activePage === 'context-menu' && <ContextMenuConfig />}
88+
{activePage === 'update' && <UpdatePage />}
89+
{activePage === 'plugin-store' && <PluginStore />}
90+
{activePage === 'plugin-config' && <PluginConfig />}
91+
</flex>
92+
</flex>
93+
</PluginSourceContext.Provider>
94+
</NotificationContext.Provider>
95+
</UpdateDataContext.Provider>
96+
</PluginLoadOrderContext.Provider>
97+
</DebugConsoleContext.Provider>
98+
</ContextMenuContext.Provider>
99+
);
100+
};
101+
102+
export default ConfigApp;
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import * as shell from "mshell";
2+
import { Button, Text, Toggle } from "./components";
3+
import { ContextMenuContext, DebugConsoleContext } from "./contexts";
4+
import { useTranslation, getNestedValue, setNestedValue } from "./utils";
5+
import { theme_presets, animation_presets } from "./constants";
6+
import { memo, useContext } from "react";
7+
const ContextMenuConfig = memo(() => {
8+
const { config, update } = useContext(ContextMenuContext)!;
9+
const { value: debugConsole, update: updateDebugConsole } = useContext(DebugConsoleContext)!;
10+
const { t } = useTranslation();
11+
12+
const currentTheme = config?.theme;
13+
const currentAnimation = config?.theme?.animation;
14+
15+
const getAllSubkeys = (presets: any) => {
16+
if (!presets) return [];
17+
const keys = new Set();
18+
for (const v of Object.values(presets)) {
19+
if (v)
20+
for (const key of Object.keys(v)) {
21+
keys.add(key);
22+
}
23+
}
24+
return [...keys];
25+
};
26+
27+
const applyPreset = (preset: any, origin: any, presets: any) => {
28+
const allSubkeys = getAllSubkeys(presets);
29+
const newPreset = preset ? { ...preset } : {};
30+
for (let key in origin) {
31+
if (allSubkeys.includes(key)) continue;
32+
newPreset[key] = origin[key];
33+
}
34+
return newPreset;
35+
};
36+
37+
const checkPresetMatch = (current: any, preset: any) => {
38+
if (!current) return false;
39+
if (!preset) return false;
40+
return Object.keys(preset).every(key => JSON.stringify(current[key]) === JSON.stringify(preset[key]));
41+
};
42+
43+
const getCurrentPreset = (current: any, presets: any) => {
44+
if (!current) return "默认";
45+
for (const [name, preset] of Object.entries(presets)) {
46+
if (preset && checkPresetMatch(current, preset)) {
47+
return name;
48+
}
49+
}
50+
return "自定义";
51+
};
52+
53+
const currentThemePreset = getCurrentPreset(currentTheme, theme_presets);
54+
const currentAnimationPreset = getCurrentPreset(currentAnimation, animation_presets);
55+
56+
return (
57+
<flex gap={20} alignItems="stretch" width={500} autoSize={false}>
58+
<Text fontSize={24}>{t("Breeze 设置")}</Text>
59+
<flex />
60+
<flex gap={10}>
61+
<Text fontSize={18}>{t("主题")}</Text>
62+
<flex horizontal gap={10}>
63+
{Object.keys(theme_presets).map(name => (
64+
<Button
65+
key={name}
66+
selected={name === currentThemePreset}
67+
onClick={() => {
68+
try {
69+
let newTheme;
70+
if (!theme_presets[name]) {
71+
newTheme = undefined;
72+
} else {
73+
newTheme = applyPreset(theme_presets[name], config?.theme, theme_presets);
74+
}
75+
update(newTheme ? { ...config, theme: newTheme } : { ...config, theme: undefined });
76+
} catch (e) {
77+
shell.println(e);
78+
}
79+
}}
80+
>
81+
<Text fontSize={14}>{name}</Text>
82+
</Button>
83+
))}
84+
</flex>
85+
</flex>
86+
87+
<flex gap={10}>
88+
<Text fontSize={18}>{t("动画")}</Text>
89+
<flex horizontal gap={10}>
90+
{Object.keys(animation_presets).map(name => (
91+
<Button
92+
key={name}
93+
onClick={() => {
94+
try {
95+
let newAnimation;
96+
if (!animation_presets[name]) {
97+
newAnimation = undefined;
98+
} else {
99+
newAnimation = animation_presets[name];
100+
}
101+
update({ ...config, theme: { ...config.theme, animation: newAnimation } });
102+
} catch (e) {
103+
shell.println(e);
104+
}
105+
}}
106+
>
107+
<Text fontSize={14}>{name}</Text>
108+
</Button>
109+
))}
110+
</flex>
111+
</flex>
112+
113+
<flex gap={10} alignItems="stretch" justifyContent="center">
114+
<Text fontSize={18}>{t("杂项")}</Text>
115+
<Toggle label={t("调试控制台")} value={debugConsole} onChange={updateDebugConsole} />
116+
<Toggle label={t("垂直同步")} value={getNestedValue(config, "vsync") ?? true} onChange={(v) => {
117+
const newConfig = { ...config };
118+
setNestedValue(newConfig, "vsync", v);
119+
update(newConfig);
120+
}} />
121+
<Toggle label={t("忽略自绘菜单")} value={getNestedValue(config, "ignore_owner_draw") ?? true} onChange={(v) => {
122+
const newConfig = { ...config };
123+
setNestedValue(newConfig, "ignore_owner_draw", v);
124+
update(newConfig);
125+
}} />
126+
<Toggle label={t("向上展开时反向排列")} value={getNestedValue(config, "reverse_if_open_to_up") ?? true} onChange={(v) => {
127+
const newConfig = { ...config };
128+
setNestedValue(newConfig, "reverse_if_open_to_up", v);
129+
update(newConfig);
130+
}} />
131+
<Toggle label={t("尝试使用 Windows 11 圆角")} value={getNestedValue(config, "theme.use_dwm_if_available") ?? true} onChange={(v) => {
132+
const newConfig = { ...config };
133+
setNestedValue(newConfig, "theme.use_dwm_if_available", v);
134+
update(newConfig);
135+
}} />
136+
<Toggle label={t("亚克力背景效果")} value={getNestedValue(config, "theme.acrylic") ?? true} onChange={(v) => {
137+
const newConfig = { ...config };
138+
setNestedValue(newConfig, "theme.acrylic", v);
139+
update(newConfig);
140+
}} />
141+
<Toggle label={t("键盘热键")} value={getNestedValue(config, "hotkeys") ?? true} onChange={(v) => {
142+
const newConfig = { ...config };
143+
setNestedValue(newConfig, "hotkeys", v);
144+
update(newConfig);
145+
}} />
146+
</flex>
147+
</flex>
148+
);
149+
});
150+
151+
export default ContextMenuConfig;
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import * as shell from "mshell";
2+
import { showMenu } from "./utils";
3+
import { Text, PluginItem } from "./components";
4+
import { PluginLoadOrderContext } from "./contexts";
5+
import { useTranslation, loadPlugins, togglePlugin, deletePlugin } from "./utils";
6+
import { memo, useContext, useEffect, useState } from "react";
7+
8+
const PluginConfig = memo(() => {
9+
const { order, update } = useContext(PluginLoadOrderContext)!;
10+
const { t } = useTranslation();
11+
12+
const [installedPlugins, setInstalledPlugins] = useState<string[]>([]);
13+
14+
useEffect(() => {
15+
reloadPluginsList();
16+
}, []);
17+
18+
const reloadPluginsList = () => {
19+
const plugins = loadPlugins();
20+
setInstalledPlugins(plugins);
21+
};
22+
23+
const handleTogglePlugin = (name: string) => {
24+
togglePlugin(name);
25+
reloadPluginsList();
26+
};
27+
28+
const handleDeletePlugin = (name: string) => {
29+
deletePlugin(name);
30+
reloadPluginsList();
31+
};
32+
33+
const isPrioritized = (name: string) => {
34+
return order?.includes(name) || false;
35+
};
36+
37+
const togglePrioritize = (name: string) => {
38+
const newOrder = [...(order || [])];
39+
if (newOrder.includes(name)) {
40+
const index = newOrder.indexOf(name);
41+
newOrder.splice(index, 1);
42+
} else {
43+
newOrder.unshift(name);
44+
}
45+
update(newOrder);
46+
};
47+
48+
const showContextMenu = (pluginName: string) => {
49+
showMenu(menu => {
50+
menu.append_menu({
51+
name: isPrioritized(pluginName) ? '取消优先加载' : '设为优先加载',
52+
action() {
53+
togglePrioritize(pluginName);
54+
menu.close();
55+
}
56+
});
57+
menu.append_menu({
58+
name: t("删除"),
59+
action() {
60+
handleDeletePlugin(pluginName);
61+
menu.close();
62+
}
63+
});
64+
if (on_plugin_menu[pluginName]) {
65+
on_plugin_menu[pluginName](menu)
66+
}
67+
});
68+
};
69+
70+
const prioritizedPlugins = installedPlugins.filter(name => isPrioritized(name));
71+
const regularPlugins = installedPlugins.filter(name => !isPrioritized(name));
72+
73+
return (
74+
<flex gap={20} width={580} height={550} autoSize={false} alignItems="stretch">
75+
<Text fontSize={24}>{t("插件配置")}</Text>
76+
77+
<flex enableScrolling maxHeight={500} alignItems="stretch">
78+
{prioritizedPlugins.length > 0 && (
79+
<flex gap={10} alignItems="stretch" paddingBottom={10} paddingTop={10}>
80+
<Text fontSize={16}>{t("优先加载插件")}</Text>
81+
{prioritizedPlugins.map(name => {
82+
const isEnabled = shell.fs.exists(shell.breeze.data_directory() + '/scripts/' + name + '.js');
83+
return (
84+
<PluginItem
85+
key={name}
86+
name={name}
87+
isEnabled={isEnabled}
88+
isPrioritized={true}
89+
onToggle={() => handleTogglePlugin(name)}
90+
onMoreClick={showContextMenu}
91+
/>
92+
);
93+
})}
94+
95+
<flex height={1} autoSize={false} backgroundColor={shell.breeze.is_light_theme() ? '#E0E0E0' : '#505050'} />
96+
</flex>
97+
)}
98+
<flex gap={10} alignItems="stretch">
99+
{prioritizedPlugins.length === 0 && <Text fontSize={16}>{t("已安装插件")}</Text>}
100+
{regularPlugins.map(name => {
101+
const isEnabled = shell.fs.exists(shell.breeze.data_directory() + '/scripts/' + name + '.js');
102+
return (
103+
<PluginItem
104+
key={name}
105+
name={name}
106+
isEnabled={isEnabled}
107+
isPrioritized={false}
108+
onToggle={() => handleTogglePlugin(name)}
109+
onMoreClick={showContextMenu}
110+
/>
111+
);
112+
})}
113+
</flex>
114+
</flex>
115+
116+
</flex>
117+
);
118+
});
119+
120+
export default PluginConfig;

0 commit comments

Comments
 (0)