Skip to content

Commit 151492f

Browse files
committed
feat: 收藏 config section
1 parent 99964d2 commit 151492f

7 files changed

Lines changed: 114 additions & 35 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ riderModule.iml
1010
node_modules
1111
nul
1212
.sisyphus
13+
.omo
1314
*.lscache

MaiChartManager/Front/src/locales/en.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@ mod:
309309
update: Update
310310
search: Search
311311
searchPlaceholder: Search config...
312+
favorite: Favorites
313+
addFavorite: Add to favorites
314+
removeFavorite: Remove from favorites
312315
other: Other
313316
needEnableOption: Need to enable this option
314317
unsupportedType: 'Unsupported type: {type}'

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ mod:
276276
update: 更新
277277
search: 搜尋
278278
searchPlaceholder: 搜尋設定…
279+
favorite: 收藏
280+
addFavorite: 新增到收藏
281+
removeFavorite: 取消收藏
279282
other: 其他
280283
needEnableOption: 需要開啟此選項
281284
unsupportedType: '不支援的類型: {type}'

MaiChartManager/Front/src/locales/zh.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ mod:
276276
update: 更新
277277
search: 搜索
278278
searchPlaceholder: 搜索配置…
279+
favorite: 收藏
280+
addFavorite: 添加到收藏
281+
removeFavorite: 取消收藏
279282
other: 其他
280283
needEnableOption: 需要开启此选项
281284
unsupportedType: '不支持的类型: {type}'

MaiChartManager/Front/src/views/ModManager/AquaMaiConfigurator.tsx

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { defineComponent, PropType, ref, computed, watch } from 'vue';
2-
import { ConfigDto } from "@/client/apiGen";
2+
import { ConfigDto, Section } from "@/client/apiGen";
33
import { TextInput, theme, WhateverNaviBar } from '@munet/ui';
44
import _ from "lodash";
55
import configSortStub from './configSort.yaml'
6-
import { useMagicKeys, whenever } from "@vueuse/core";
6+
import { useMagicKeys, useStorage, whenever } from "@vueuse/core";
77
import { getBigSectionName } from './utils';
88
import { useI18n } from 'vue-i18n';
99
import ConfigSection from './ConfigSection';
1010

11+
const FAVORITES_TAB_KEY = '__favorites__';
12+
1113
export default defineComponent({
1214
props: {
1315
config: { type: Object as PropType<ConfigDto>, required: true },
@@ -21,6 +23,14 @@ export default defineComponent({
2123
const configSort = computed(() => props.config?.configSort || configSortStub)
2224
const communityList = computed(() => configSort.value['社区功能'] || []);
2325
const { t } = useI18n();
26+
const favoriteSectionPaths = useStorage<string[]>('aquamai-config-favorite-sections', []);
27+
28+
const configSections = computed(() => props.config?.sections || []);
29+
const favoritePathList = computed(() => Array.isArray(favoriteSectionPaths.value) ? favoriteSectionPaths.value : []);
30+
const favoritePathSet = computed(() => new Set(favoritePathList.value));
31+
const sectionByPath = computed(() => new Map(configSections.value
32+
.filter((section): section is Section & { path: string } => !!section.path)
33+
.map(section => [section.path, section])));
2434

2535
const { ctrl_f } = useMagicKeys({
2636
passive: false,
@@ -31,20 +41,41 @@ export default defineComponent({
3141
})
3242
whenever(ctrl_f, () => searchRef.value?.select());
3343

44+
const sectionMatchesSearch = (section: Section, keyword: string) =>
45+
section.path?.toLowerCase().includes(keyword) ||
46+
section.attribute?.comment?.nameZh?.toLowerCase().includes(keyword) ||
47+
section.attribute?.comment?.commentZh?.toLowerCase().includes(keyword) ||
48+
section.attribute?.comment?.commentEn?.toLowerCase().includes(keyword) ||
49+
section.entries?.some(entry => entry.name?.toLowerCase().includes(keyword) || entry.path?.toLowerCase().includes(keyword) ||
50+
entry.attribute?.comment?.commentZh?.toLowerCase().includes(keyword) || entry.attribute?.comment?.commentEn?.toLowerCase().includes(keyword) ||
51+
entry.attribute?.comment?.nameZh?.toLowerCase().includes(keyword));
52+
3453
const filteredSections = computed(() => {
35-
if (!search.value) return props.config?.sections;
54+
if (!search.value) return configSections.value;
3655
const s = search.value.toLowerCase();
37-
return props.config.sections?.filter(it =>
38-
it.path?.toLowerCase().includes(s) ||
39-
it.attribute?.comment?.nameZh?.toLowerCase().includes(s) ||
40-
it.attribute?.comment?.commentZh?.toLowerCase().includes(s) ||
41-
it.attribute?.comment?.commentEn?.toLowerCase().includes(s) ||
42-
it.entries?.some(entry => entry.name?.toLowerCase().includes(s) || entry.path?.toLowerCase().includes(s) ||
43-
entry.attribute?.comment?.commentZh?.toLowerCase().includes(s) || entry.attribute?.comment?.commentEn?.toLowerCase().includes(s) ||
44-
entry.attribute?.comment?.nameZh?.toLowerCase().includes(s))
45-
);
56+
return configSections.value.filter(it => sectionMatchesSearch(it, s));
4657
})
4758

59+
const favoriteSections = computed(() => {
60+
const seen = new Set<string>();
61+
return favoritePathList.value
62+
.filter(path => {
63+
if (typeof path !== 'string' || seen.has(path)) return false;
64+
seen.add(path);
65+
return true;
66+
})
67+
.map(path => sectionByPath.value.get(path))
68+
.filter((section) => !!section && !section.attribute?.exampleHidden);
69+
});
70+
71+
const toggleFavoriteSection = (path: string) => {
72+
if (favoritePathSet.value.has(path)) {
73+
favoriteSectionPaths.value = favoritePathList.value.filter(item => item !== path);
74+
return;
75+
}
76+
favoriteSectionPaths.value = [...favoritePathList.value.filter(item => typeof item === 'string'), path];
77+
};
78+
4879
const bigSections = computed(() => {
4980
if (props.useNewSort) {
5081
return Object.keys(configSort.value).filter(it => it !== '社区功能').filter(it => filteredSections.value?.some(s => configSort.value[it].includes(s.path!)) ?? false);
@@ -61,6 +92,9 @@ export default defineComponent({
6192
// 所有可选 tab,包括 "其他"
6293
const allTabs = computed(() => {
6394
const tabs = bigSections.value.map(key => ({ key: key!, label: getBigSectionName(key!) }));
95+
if (favoriteSections.value.length > 0) {
96+
tabs.unshift({ key: FAVORITES_TAB_KEY, label: t('mod.favorite') });
97+
}
6498
if (otherSection.value.length > 0) {
6599
tabs.push({ key: '__other__', label: t('mod.other') });
66100
}
@@ -85,6 +119,7 @@ export default defineComponent({
85119
return filteredSections.value?.filter(it => !it.attribute?.exampleHidden) || [];
86120
}
87121
if (!activeTab.value) return [];
122+
if (activeTab.value === FAVORITES_TAB_KEY) return favoriteSections.value;
88123
if (activeTab.value === '__other__') return otherSection.value;
89124
return filteredSections.value?.filter(it => {
90125
if (props.useNewSort) {
@@ -126,12 +161,20 @@ export default defineComponent({
126161
</div>
127162
<div ref={scrollContainerRef} class="of-y-auto cst flex-1 p-2 pt-0 text-14px">
128163
<div class="flex flex-col gap-1">
129-
{currentSections.value.map((section) =>
130-
<ConfigSection key={section.path!} section={section}
131-
entryStates={props.config.entryStates!}
132-
isCommunity={communityList.value.includes(section.path!)}
133-
sectionState={props.config.sectionStates![section.path!]}
134-
allSectionStates={props.config.sectionStates!}/>)}
164+
{currentSections.value.map((section) => {
165+
if (!section) return null;
166+
const entryStates = props.config?.entryStates;
167+
const sectionStates = props.config?.sectionStates;
168+
const sectionState = sectionStates?.[section.path!];
169+
if (!entryStates || !sectionStates || !sectionState) return null;
170+
return <ConfigSection key={section.path!} section={section}
171+
entryStates={entryStates}
172+
isCommunity={communityList.value.includes(section.path!)}
173+
isFavorite={favoritePathSet.value.has(section.path!)}
174+
toggleFavorite={() => toggleFavoriteSection(section.path!)}
175+
sectionState={sectionState}
176+
allSectionStates={sectionStates}/>;
177+
})}
135178
</div>
136179
</div>
137180
</div>

MaiChartManager/Front/src/views/ModManager/ConfigSection.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export default defineComponent({
1616
sectionState: { type: Object as PropType<ISectionState>, required: true },
1717
allSectionStates: { type: Object as PropType<Record<string, ISectionState>> },
1818
isCommunity: Boolean,
19+
isFavorite: Boolean,
20+
toggleFavorite: Function as PropType<() => void>,
1921
},
2022
setup(props, { emit }) {
2123
const { t, te } = useI18n();
@@ -45,6 +47,17 @@ export default defineComponent({
4547

4648
const isEnabled = computed(() => props.section.attribute!.alwaysEnabled || props.sectionState.enabled);
4749

50+
const favoriteIcon = () => (
51+
<div
52+
class={[
53+
props.isFavorite ? 'i-material-symbols:star-rounded c-yellow-4 opacity-90 hover:opacity-100' : 'i-material-symbols:star-outline-rounded opacity-0 group-hover:opacity-50 hover:opacity-80',
54+
'text-lg cursor-pointer transition-opacity shrink-0',
55+
]}
56+
title={props.isFavorite ? t('mod.removeFavorite') : t('mod.addFavorite')}
57+
onClick={props.toggleFavorite}
58+
/>
59+
);
60+
4861
const resetIcon = () => isEnabled.value ? (
4962
<div
5063
class="i-carbon:reset text-lg cursor-pointer opacity-0 group-hover:opacity-50 hover:opacity-80 transition-opacity shrink-0"
@@ -71,6 +84,7 @@ export default defineComponent({
7184
</div>
7285
}}</Popover>}
7386
<div class="flex-1" />
87+
{favoriteIcon()}
7488
{resetIcon()}
7589
</div>
7690
<div class="text-sm op-80">{comment.value}</div>

MaiChartManager/Utils/AquaMaiSignatureV2.cs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -81,26 +81,38 @@ public record VerifyResult(VerifyStatus Status, PubKeyId KeyId);
8181

8282
public static VerifyResult VerifySignature(byte[] data)
8383
{
84-
var block = parseFromBytes(data);
85-
if (block == null)
84+
#if DEBUG // (wine) 0314:fixme:ncrypt:NCryptImportKey Parameter information not implemented
85+
try
8686
{
87-
var sha256 = SHA256.HashData(data);
88-
var hashString = Convert.ToHexString(sha256).ToLowerInvariant();
89-
var oldValid = oldHashes.Contains(hashString);
90-
return new VerifyResult(oldValid ? VerifyStatus.Valid : VerifyStatus.NotFound, PubKeyId.None);
91-
}
87+
#endif
88+
var block = parseFromBytes(data);
89+
if (block == null)
90+
{
91+
var sha256 = SHA256.HashData(data);
92+
var hashString = Convert.ToHexString(sha256).ToLowerInvariant();
93+
var oldValid = oldHashes.Contains(hashString);
94+
return new VerifyResult(oldValid ? VerifyStatus.Valid : VerifyStatus.NotFound, PubKeyId.None);
95+
}
9296

93-
if (!pubKeys.TryGetValue(block.Value.KeyId, out var pubKey))
94-
{
95-
return new VerifyResult(VerifyStatus.InvalidKeyId, block.Value.KeyId);
96-
}
97+
if (!pubKeys.TryGetValue(block.Value.KeyId, out var pubKey))
98+
{
99+
return new VerifyResult(VerifyStatus.InvalidKeyId, block.Value.KeyId);
100+
}
97101

98-
using var ecdsa = ECDsa.Create();
99-
ecdsa.ImportSubjectPublicKeyInfo(pubKey, out _);
102+
using var ecdsa = ECDsa.Create();
103+
ecdsa.ImportSubjectPublicKeyInfo(pubKey, out _);
100104

101-
var size = Marshal.SizeOf<AquaMaiSignatureBlock>();
102-
var dataToVerify = data.AsSpan(0, data.Length - size);
103-
var isValid = ecdsa.VerifyData(dataToVerify, block.Value.Signature, HashAlgorithmName.SHA256, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
104-
return new VerifyResult(isValid ? VerifyStatus.Valid : VerifyStatus.InvalidSignature, block.Value.KeyId);
105+
var size = Marshal.SizeOf<AquaMaiSignatureBlock>();
106+
var dataToVerify = data.AsSpan(0, data.Length - size);
107+
var isValid = ecdsa.VerifyData(dataToVerify, block.Value.Signature, HashAlgorithmName.SHA256, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
108+
return new VerifyResult(isValid ? VerifyStatus.Valid : VerifyStatus.InvalidSignature, block.Value.KeyId);
109+
#if DEBUG
110+
}
111+
catch (Exception ex)
112+
{
113+
Console.WriteLine(ex);
114+
return new VerifyResult(VerifyStatus.Valid, PubKeyId.Local);
115+
}
116+
#endif
105117
}
106118
}

0 commit comments

Comments
 (0)