Skip to content

Commit 16c7715

Browse files
committed
feat: 设置预览判断支持输入数字
1 parent dc85a4e commit 16c7715

12 files changed

Lines changed: 115 additions & 21 deletions

File tree

AquaMai

MaiChartManager/Front/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"@iconify/json": "^2.2.443",
1717
"@microsoft/fetch-event-source": "^2.0.1",
1818
"@modyfi/vite-plugin-yaml": "^1.1.1",
19-
"@munet/ui": "^1.0.12",
19+
"@munet/ui": "^1.0.13",
2020
"@sentry/vite-plugin": "^5.1.0",
2121
"@sentry/vue": "^10.40.0",
2222
"@types/color": "^4.2.0",

MaiChartManager/Front/pnpm-lock.yaml

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

MaiChartManager/Front/src/locales/en.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ music:
127127
previewChartWarning: Chart preview does not represent actual gameplay, please test in-game
128128
audioPreviewCtrlShiftClick: Ctrl / Shift + Click to set start / end time directly at click position
129129
audioPreviewSelectRegion: Selection
130+
audioPreviewStart: Start Time (s)
131+
audioPreviewEnd: End Time (s)
130132
includeB35: Include in B35
131133
includeB15: Include in B15
132134
versionHint: >-

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ music:
121121
previewChartWarning: 譜面預覽不代表上機效果,請以實際為準
122122
audioPreviewCtrlShiftClick: Ctrl / Shift + 點擊可直接將點擊位置設為開始 / 結束時間
123123
audioPreviewSelectRegion: 選區
124+
audioPreviewStart: 起點時間(秒)
125+
audioPreviewEnd: 終點時間(秒)
124126
includeB35: 計入 B35
125127
includeB15: 計入 B15
126128
versionHint: >-

MaiChartManager/Front/src/locales/zh.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ music:
121121
previewChartWarning: 谱面预览不代表上机效果,请以实际为准
122122
audioPreviewCtrlShiftClick: Ctrl / Shift + 点击可直接将点击位置设为开始 / 结束时间
123123
audioPreviewSelectRegion: 选区
124+
audioPreviewStart: 起点时间(秒)
125+
audioPreviewEnd: 终点时间(秒)
124126
includeB35: 计入 B35
125127
includeB15: 计入 B15
126128
versionHint: >-
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* fetchEventSource 的自定义 onopen 处理器
3+
* 当响应不是 SSE 格式时(如 ASP.NET 返回 ProblemDetails JSON),
4+
* 读取响应体获取实际的错误信息,而非仅报 content-type 不匹配
5+
*/
6+
export async function handleSseOpen(response: Response) {
7+
if (response.ok && response.headers.get('content-type')?.includes('text/event-stream')) return;
8+
9+
let errorMessage: string | undefined;
10+
try {
11+
const text = await response.text();
12+
if (text) {
13+
try {
14+
const json = JSON.parse(text);
15+
errorMessage = json.detail || json.title || json.message;
16+
} catch {
17+
errorMessage = text;
18+
}
19+
}
20+
} catch {
21+
}
22+
23+
throw new Error(errorMessage || `HTTP ${response.status}: ${response.statusText}`);
24+
}

MaiChartManager/Front/src/views/Charts/ImportCreateChartButton/ImportChartButton/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ImportStepDisplay from "./ImportStepDisplay";
1111
import { useStorage } from "@vueuse/core";
1212
import { captureException } from "@sentry/vue";
1313
import { fetchEventSource } from "@microsoft/fetch-event-source";
14+
import { handleSseOpen } from "@/utils/sseOpen";
1415
import { defaultSavedOptions, defaultTempOptions, dummyMeta, IMPORT_STEP, ImportChartMessageEx, ImportMeta, STEP } from "./types";
1516
import getNextUnusedMusicId from "@/utils/getNextUnusedMusicId";
1617
import { useI18n } from 'vue-i18n';
@@ -102,6 +103,7 @@ export default defineComponent({
102103
signal: controller.signal,
103104
method: 'PUT',
104105
body,
106+
onopen: handleSseOpen,
105107
onerror(e) {
106108
reject(e);
107109
controller.abort();

MaiChartManager/Front/src/views/Charts/MusicEdit/AudioPreviewEditor.tsx

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { computed, defineComponent, onMounted, ref } from "vue";
2-
import { Button, addToast } from "@munet/ui";
1+
import { computed, defineComponent, onMounted, ref, watch } from "vue";
2+
import { Button, NumberInput, addToast } from "@munet/ui";
33
import WaveSurfer from "wavesurfer.js";
44
import { globalCapture, selectedADir, selectMusicId } from "@/store/refs";
55
import ZoomPlugin from 'wavesurfer.js/dist/plugins/zoom'
@@ -15,7 +15,7 @@ export default defineComponent({
1515
props: {
1616
closeModel: {type: Function, required: true}
1717
},
18-
setup(props, {emit}) {
18+
setup(props, {emit, expose}) {
1919
const waveSurferContainer = ref()
2020
const region = ref<Region>()
2121
const ws = ref<WaveSurfer>()
@@ -24,6 +24,42 @@ export default defineComponent({
2424
const load = ref(false)
2525
const dataLoad = ref(true)
2626
const {ctrl, shift} = useMagicKeys()
27+
const startTime = ref(0)
28+
const endTime = ref(0)
29+
const duration = ref(0)
30+
// 标记是否正在由输入框触发 region 更新,防止循环
31+
const updatingFromInput = ref(false)
32+
33+
const syncTimesFromRegion = () => {
34+
if (region.value && !updatingFromInput.value) {
35+
startTime.value = region.value.start
36+
endTime.value = region.value.end
37+
}
38+
}
39+
40+
const onStartTimeChange = () => {
41+
if (!region.value) return
42+
if (startTime.value >= endTime.value) {
43+
addToast({message: t('music.edit.audioPreviewStartGtEnd'), type: 'warning'})
44+
startTime.value = region.value.start
45+
return
46+
}
47+
updatingFromInput.value = true
48+
region.value.setOptions({start: startTime.value})
49+
updatingFromInput.value = false
50+
}
51+
52+
const onEndTimeChange = () => {
53+
if (!region.value) return
54+
if (endTime.value <= startTime.value) {
55+
addToast({message: t('music.edit.audioPreviewEndLtStart'), type: 'warning'})
56+
endTime.value = region.value.end
57+
return
58+
}
59+
updatingFromInput.value = true
60+
region.value.setOptions({end: endTime.value, start: region.value.start})
61+
updatingFromInput.value = false
62+
}
2763

2864

2965
onMounted(async () => {
@@ -57,15 +93,20 @@ export default defineComponent({
5793
],
5894
})
5995

60-
ws.value.on('decode', (duration) => {
96+
ws.value.on('decode', (dur) => {
97+
duration.value = dur
6198
// Regions
6299
region.value = regions.addRegion({
63100
start: savedRegion!.startTime! >= 0 ? savedRegion.startTime! : 0,
64-
end: savedRegion!.endTime! >= 0 ? savedRegion.endTime! : duration,
101+
end: savedRegion!.endTime! >= 0 ? savedRegion.endTime! : dur,
65102
drag: true,
66103
resize: true,
67104
id: 'selection',
68105
})
106+
syncTimesFromRegion()
107+
108+
region.value.on('update', syncTimesFromRegion)
109+
region.value.on('update-end', syncTimesFromRegion)
69110
})
70111

71112
ws.value.on('click', (e) => {
@@ -76,12 +117,14 @@ export default defineComponent({
76117
return
77118
}
78119
region.value!.setOptions({start: time})
120+
syncTimesFromRegion()
79121
} else if (shift.value) {
80122
if (time <= region.value!.start) {
81123
addToast({message: t('music.edit.audioPreviewEndLtStart'), type: 'warning'})
82124
return
83125
}
84126
region.value!.setOptions({end: time, start: region.value!.start})
127+
syncTimesFromRegion()
85128
}
86129
})
87130

@@ -110,6 +153,10 @@ export default defineComponent({
110153

111154
const playIcon = computed(() => isPlaying.value ? 'i-mdi-pause' : 'i-mdi-play')
112155

156+
expose({
157+
save, load
158+
})
159+
113160
return () => <div class="relative">
114161
{dataLoad.value && <div class="absolute inset-0 flex items-center justify-center bg-black/10 z-10"><div class="i-mdi-loading animate-spin text-2xl"/></div>}
115162
<div class="flex flex-col gap-3">
@@ -133,13 +180,15 @@ export default defineComponent({
133180
{t('music.edit.audioPreviewSelectRegion')}
134181
</Button>
135182
</div>
136-
<div class="flex gap-2 justify-end">
137-
<Button variant="secondary" danger onClick={props.closeModel as any} disabled={load.value}>
138-
{t('common.dismiss')}
139-
</Button>
140-
<Button variant="secondary" onClick={save} ing={load.value}>
141-
{t('common.save')}
142-
</Button>
183+
<div class="flex gap-4 items-center">
184+
<div class="flex flex-col gap-1 w-0 grow">
185+
<div class="ml-1 text-sm">{t('music.edit.audioPreviewStart')}</div>
186+
<NumberInput v-model:value={startTime.value} min={0} max={duration.value} step={0.001} decimal={3} onChange={onStartTimeChange}/>
187+
</div>
188+
<div class="flex flex-col gap-1 w-0 grow">
189+
<div class="ml-1 text-sm">{t('music.edit.audioPreviewEnd')}</div>
190+
<NumberInput v-model:value={endTime.value} min={0} max={duration.value} step={0.001} decimal={3} onChange={onEndTimeChange}/>
191+
</div>
143192
</div>
144193
</div>
145194
</div>;

MaiChartManager/Front/src/views/Charts/MusicEdit/AudioPreviewEditorButton.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export default defineComponent({
2020
show.value = true
2121
}
2222

23+
const editorRef = ref<{save: () => Promise<void>, load: boolean}>()
24+
2325
return () => <Button variant="secondary" onClick={handleClick} disabled={props.disabled}>
2426
{t('music.edit.editPreview')}
2527

@@ -29,8 +31,15 @@ export default defineComponent({
2931
v-model:show={show.value}
3032
esc={false}
3133
>{{
32-
default: () =>
33-
<AudioPreviewEditor closeModel={() => show.value = false}/>,
34+
default: () => <AudioPreviewEditor ref={editorRef} closeModel={() => show.value = false} />,
35+
actions: () => <>
36+
<Button variant="secondary" danger onClick={() => show.value = false} disabled={editorRef.value?.load}>
37+
{t('common.dismiss')}
38+
</Button>
39+
<Button variant="secondary" onClick={editorRef.value?.save} ing={editorRef.value?.load}>
40+
{t('common.save')}
41+
</Button>
42+
</>
3443
}}</Modal>
3544
</Button>;
3645
}

0 commit comments

Comments
 (0)