Skip to content

Commit 68e61d2

Browse files
committed
feat: 游戏目录历史记录
1 parent 076c0d9 commit 68e61d2

12 files changed

Lines changed: 333 additions & 159 deletions

File tree

MaiChartManager/Controllers/App/OobeController.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public IActionResult SetGamePath([FromBody] string path)
3939

4040
StaticSettings.Config.GamePath = StaticSettings.GamePath;
4141
StaticSettings.Config.HistoryPath.Add(path);
42-
StaticSettings.Config.Save();
42+
// 这里不用持久化,最后保存的时候会持久化的
4343

4444
AppMain.UiContext?.Post(_ =>
4545
{
@@ -50,6 +50,20 @@ public IActionResult SetGamePath([FromBody] string path)
5050
return Ok();
5151
}
5252

53+
[HttpGet]
54+
public HashSet<string> GetGamePathHistory()
55+
{
56+
return StaticSettings.Config.HistoryPath;
57+
}
58+
59+
[HttpPost]
60+
public IActionResult DeleteGamePathHistory([FromBody] string path)
61+
{
62+
StaticSettings.Config.HistoryPath.Remove(path);
63+
StaticSettings.Config.Save();
64+
return Ok();
65+
}
66+
5367
[HttpPost]
5468
public void InitializeGameData()
5569
{

MaiChartManager/Front/src/client/apiGen.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,37 @@ export class Api<
22322232
...params,
22332233
}),
22342234

2235+
/**
2236+
* No description
2237+
*
2238+
* @tags Oobe
2239+
* @name GetGamePathHistory
2240+
* @request GET:/MaiChartManagerServlet/GetGamePathHistoryApi
2241+
*/
2242+
GetGamePathHistory: (params: RequestParams = {}) =>
2243+
this.request<string[], any>({
2244+
path: `/MaiChartManagerServlet/GetGamePathHistoryApi`,
2245+
method: "GET",
2246+
format: "json",
2247+
...params,
2248+
}),
2249+
2250+
/**
2251+
* No description
2252+
*
2253+
* @tags Oobe
2254+
* @name DeleteGamePathHistory
2255+
* @request POST:/MaiChartManagerServlet/DeleteGamePathHistoryApi
2256+
*/
2257+
DeleteGamePathHistory: (data: string, params: RequestParams = {}) =>
2258+
this.request<void, any>({
2259+
path: `/MaiChartManagerServlet/DeleteGamePathHistoryApi`,
2260+
method: "POST",
2261+
body: data,
2262+
type: ContentType.Json,
2263+
...params,
2264+
}),
2265+
22352266
/**
22362267
* No description
22372268
*

MaiChartManager/Front/src/locales/en.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,9 @@ settings:
561561
changingDirectory: Switching...
562562
changeDirectoryFailed: Failed to switch directory
563563
switchMode: Switch Mode
564+
historyPath: History
565+
deleteHistory: Delete
566+
noHistory: No history
564567
oobe:
565568
welcome: Welcome
566569
welcomeMessage: Welcome to MaiChartManager

MaiChartManager/Front/src/locales/zh-TW.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,9 @@ settings:
520520
changingDirectory: 正在切換...
521521
changeDirectoryFailed: 切換目錄失敗
522522
switchMode: 切換工作模式
523+
historyPath: 歷史記錄
524+
deleteHistory: 刪除
525+
noHistory: 暫無歷史記錄
523526
oobe:
524527
welcome: 歡迎
525528
welcomeMessage: 歡迎使用 MaiChartManager

MaiChartManager/Front/src/locales/zh.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,9 @@ settings:
526526
changingDirectory: 正在切换...
527527
changeDirectoryFailed: 切换目录失败
528528
switchMode: 切换工作模式
529+
historyPath: 历史记录
530+
deleteHistory: 删除
531+
noHistory: 暂无历史记录
529532
oobe:
530533
welcome: 欢迎
531534
welcomeMessage: 欢迎使用 MaiChartManager
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { defineComponent } from "vue";
2+
import { Select } from "@munet/ui";
3+
import { selectedThemeHue } from "@munet/ui";
4+
import { useI18n } from "vue-i18n";
5+
import { setLocale, availableLocales, locale } from '@/locales/index';
6+
import type { Locale } from '@/locales/index';
7+
import styles from "./ThemeSlider.module.scss";
8+
9+
export default defineComponent({
10+
setup() {
11+
const { t } = useI18n();
12+
13+
const localeLabels: Record<Locale, string> = {
14+
'zh': '简体中文',
15+
'zh-TW': '繁體中文',
16+
'en': 'English',
17+
};
18+
const localeOptions = availableLocales.map(l => ({
19+
label: localeLabels[l],
20+
value: l,
21+
}));
22+
23+
return () => (
24+
<div class="mb-6">
25+
<div class="text-lg font-semibold mb-3 text-[var(--link-color)]">{t('settings.appearance')}</div>
26+
<div class="rounded-xl bg-white/60 p-4 flex flex-col gap-4 border border-gray-200 border-solid">
27+
<div class="flex items-center gap-3">
28+
<div class="i-mdi-translate text-xl op-60" />
29+
<Select
30+
value={locale.value}
31+
options={localeOptions}
32+
onChange={(v: any) => setLocale(v as Locale)}
33+
/>
34+
</div>
35+
<div class="flex flex-col gap-2">
36+
<input
37+
type="range"
38+
min={0}
39+
max={360}
40+
step={1}
41+
value={selectedThemeHue.value}
42+
onInput={(e) => { selectedThemeHue.value = Number((e.target as HTMLInputElement).value); }}
43+
class={[styles['chromatic-hue-slider'], 'w-full']}
44+
/>
45+
<div class="flex items-center justify-end">
46+
<button
47+
onClick={() => { selectedThemeHue.value = 353; }}
48+
>
49+
{t('settings.resetDefault')}
50+
</button>
51+
</div>
52+
</div>
53+
</div>
54+
</div>
55+
);
56+
},
57+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { defineComponent } from "vue";
2+
import { Radio } from "@munet/ui";
3+
import { useI18n } from "vue-i18n";
4+
import { selectedChannel } from "@/views/ModManager/shouldShowUpdateController";
5+
6+
export default defineComponent({
7+
setup() {
8+
const { t } = useI18n();
9+
10+
return () => (
11+
<div class="mb-6">
12+
<div class="text-lg font-semibold mb-3 text-[var(--link-color)]">AquaMai</div>
13+
<div class="rounded-xl bg-white/60 p-4 flex flex-col gap-4 border border-gray-200 border-solid">
14+
<div class="flex gap-3">
15+
<span class="shrink-0 flex justify-end">{t('settings.updateChannel')}</span>
16+
<div class="flex flex-col gap-2">
17+
<div class="flex items-center gap-3">
18+
<Radio k={'slow'} v-model:value={selectedChannel.value}>{t('settings.updateChannelSlow')}</Radio>
19+
<Radio k={'ci'} v-model:value={selectedChannel.value}>{t('settings.updateChannelCi')}</Radio>
20+
</div>
21+
<span class="text-sm op-60">{t('settings.updateChannelDesc')}</span>
22+
</div>
23+
</div>
24+
</div>
25+
</div>
26+
);
27+
},
28+
});
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { defineComponent, ref, onMounted } from "vue";
2+
import { Button, Section } from "@munet/ui";
3+
import { theme } from "@munet/ui";
4+
import { useI18n } from "vue-i18n";
5+
import api from "@/client/api";
6+
import { updateAll } from "@/store/refs";
7+
8+
export default defineComponent({
9+
setup() {
10+
const { t } = useI18n();
11+
12+
const gamePath = ref('');
13+
const switching = ref(false);
14+
const error = ref('');
15+
const historyPaths = ref<string[]>([]);
16+
17+
onMounted(async () => {
18+
try {
19+
const res = await api.GetGamePath();
20+
gamePath.value = res.data || '';
21+
} catch {}
22+
try {
23+
const res = await api.GetGamePathHistory();
24+
historyPaths.value = res.data || [];
25+
} catch {}
26+
});
27+
28+
const handleChangeDirectory = async () => {
29+
error.value = '';
30+
try {
31+
const res = await api.OpenFolderDialog();
32+
if (!res.data) return;
33+
switching.value = true;
34+
await api.SetGamePath(res.data);
35+
await api.InitializeGameData();
36+
await updateAll();
37+
gamePath.value = res.data;
38+
if (!historyPaths.value.includes(res.data)) {
39+
historyPaths.value.push(res.data);
40+
}
41+
} catch (e: any) {
42+
error.value = t('settings.changeDirectoryFailed');
43+
} finally {
44+
switching.value = false;
45+
}
46+
};
47+
48+
const handleSwitchToHistory = async (path: string) => {
49+
if (path === gamePath.value) return;
50+
error.value = '';
51+
switching.value = true;
52+
try {
53+
await api.SetGamePath(path);
54+
await api.InitializeGameData();
55+
await updateAll();
56+
gamePath.value = path;
57+
} catch {
58+
error.value = t('settings.changeDirectoryFailed');
59+
} finally {
60+
switching.value = false;
61+
}
62+
};
63+
64+
const handleDeleteHistory = async (path: string) => {
65+
try {
66+
await api.DeleteGamePathHistory(path);
67+
historyPaths.value = historyPaths.value.filter(p => p !== path);
68+
} catch {}
69+
};
70+
71+
return () => (
72+
<div class="mb-6">
73+
<div class="text-lg font-semibold mb-3 text-[var(--link-color)]">{t('settings.gameDirectory')}</div>
74+
<div class="rounded-xl bg-white/60 p-4 flex flex-col gap-4 border border-gray-200 border-solid">
75+
<div class="flex items-center gap-3">
76+
<span class="shrink-0 op-60">{t('settings.currentPath')}</span>
77+
<span class="text-sm break-all">{gamePath.value || '—'}</span>
78+
</div>
79+
<div class="flex items-center gap-3">
80+
<Button
81+
disabled={switching.value}
82+
onClick={handleChangeDirectory}
83+
ing={switching.value}
84+
>
85+
{t('settings.changeDirectory')}
86+
</Button>
87+
<button
88+
onClick={()=>api.SwitchToSetMode()}
89+
>
90+
{t('settings.switchMode')}
91+
</button>
92+
{error.value && <span class="text-red-500 text-sm">{error.value}</span>}
93+
</div>
94+
{/* History */}
95+
<Section title={t('settings.historyPath')} icon="i-solar:history-linear">
96+
{historyPaths.value.length === 0
97+
? <span class="text-sm op-50">{t('settings.noHistory')}</span>
98+
: historyPaths.value.map(path => (
99+
<div
100+
class={[
101+
'flex items-center gap-2 px-3 py-1 rounded-lg cursor-pointer',
102+
theme.value.listItemHover,
103+
path === gamePath.value && theme.value.listItem,
104+
]}
105+
key={path}
106+
onClick={() => handleSwitchToHistory(path)}
107+
>
108+
<div class="i-material-symbols:folder-outline-rounded text-lg op-70 shrink-0" />
109+
<span class="text-sm break-all grow">{path}</span>
110+
<button
111+
onClick={(e: Event) => { e.stopPropagation(); handleDeleteHistory(path); }}
112+
>
113+
<div class="i-tabler:trash text-base op-80 text-sm" />
114+
</button>
115+
</div>
116+
))
117+
}
118+
</Section>
119+
</div>
120+
</div>
121+
);
122+
},
123+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { defineComponent } from "vue";
2+
import { CheckBox } from "@munet/ui";
3+
import { useI18n } from "vue-i18n";
4+
import { appSettings, saveSettings } from "@/store/settings";
5+
6+
export default defineComponent({
7+
setup() {
8+
const { t } = useI18n();
9+
10+
const onSettingChange = () => {
11+
saveSettings();
12+
};
13+
14+
return () => (
15+
<div class="mb-6">
16+
<div class="text-lg font-semibold mb-3 text-[var(--link-color)]">{t('settings.importOptions')}</div>
17+
<div class="rounded-xl bg-white/60 p-4 flex flex-col gap-4 border border-gray-200 border-solid">
18+
<CheckBox v-model:value={appSettings.value.ignoreLevel} onChange={onSettingChange}>{t('settings.ignoreLevel')}</CheckBox>
19+
<CheckBox v-model:value={appSettings.value.disableBga} onChange={onSettingChange}>{t('settings.disableBga')}</CheckBox>
20+
</div>
21+
</div>
22+
);
23+
},
24+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { defineComponent } from "vue";
2+
import { CheckBox, Select } from "@munet/ui";
3+
import { useI18n } from "vue-i18n";
4+
import { appSettings, saveSettings } from "@/store/settings";
5+
import { MovieCodec } from "@/client/apiGen";
6+
7+
export default defineComponent({
8+
setup() {
9+
const { t } = useI18n();
10+
11+
const movieCodecOptions = [
12+
{ label: t('chart.import.option.codecForceH264'), value: MovieCodec.ForceH264 },
13+
{ label: t('chart.import.option.codecForceVP9'), value: MovieCodec.ForceVP9 },
14+
];
15+
16+
const onSettingChange = () => {
17+
saveSettings();
18+
};
19+
20+
return () => (
21+
<div class="mb-6">
22+
<div class="text-lg font-semibold mb-3 text-[var(--link-color)]">{t('settings.videoOptions')}</div>
23+
<div class="rounded-xl bg-white/60 p-4 flex flex-col gap-4 border border-gray-200 border-solid">
24+
<div class="flex items-center gap-3">
25+
<span class="shrink-0">{t('settings.movieCodec')}</span>
26+
<Select v-model:value={appSettings.value.movieCodec} options={movieCodecOptions} onChange={onSettingChange} />
27+
</div>
28+
<CheckBox v-model:value={appSettings.value.yuv420p} onChange={onSettingChange}>{t('settings.yuv420p')}</CheckBox>
29+
<CheckBox v-model:value={appSettings.value.noScale} onChange={onSettingChange}>{t('settings.noScale')}</CheckBox>
30+
</div>
31+
</div>
32+
);
33+
},
34+
});

0 commit comments

Comments
 (0)