Conversation
Deploying phizone-ui with
|
| Latest commit: |
06da116
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0d1449ca.phizone-ui.pages.dev |
There was a problem hiding this comment.
Pull request overview
This PR adds support for Milthm chart format to the PhiZone submission system. The implementation allows users to upload Milthm-formatted charts (using Rain Editor) in ZIP files, which are then parsed and processed alongside existing PE/RPE chart formats. The PR also updates Vite from 6.3.5 to 6.4.1 and adds JSZip as a dependency for ZIP file parsing.
Key Changes:
- Implemented
tryParseMilthmZipfunction to parse Milthm chart ZIP files with fallback logic for file discovery - Added new chart format enum value and level types (DZ, SK, CB, CL) to support Milthm difficulty levels
- Updated translation files across all locales to reflect support for Rain Editor/Milthm charts
- Modified submission workflow to handle Milthm charts differently (skip preview step, direct upload)
Reviewed changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/routes/(app)/studio/new-submission/+page.svelte | Added Milthm ZIP parsing logic, state management, and modified submission flow to handle Milthm charts |
| src/routes/(app)/studio/new-submission/+page.server.ts | Refactored schema creation to support locale-specific validation messages |
| src/routes/(app)/studio/chart-submissions/new/+page.svelte | Added .zip to accepted file types and fixed query enablement with browser check |
| src/lib/components/SongSubmissionForm.svelte | Added Milthm JSON parsing support for BPM extraction alongside existing RPE format |
| src/lib/utils.ts | Added color mappings for new Milthm level types (4-8) |
| src/lib/types.ts | Added MilthmMeta, MilthmBpm, and MilthmJson interfaces |
| src/lib/api/chart.ts | Added Milthm to ChartFormat enum and new level types to ChartLevel enum |
| src/lib/constants.ts | Extended LEVEL_TYPES array with Milthm difficulty levels |
| src/lib/translations/*.json | Updated translation strings to include Rain Editor/Milthm support |
| package.json | Added jszip dependency and upgraded Vite to 6.4.1 |
| .github/.vscode/settings.json | Added VSCode workspace settings (misplaced in .github directory) |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
| "chart_submissions": "谱面稿件", | ||
| "choose_chart": "选择谱面", | ||
| "choose_chart_description": "请在此处选择包含 RPE 或 PE 谱面的压缩包(.pez 或 .zip),并且须至少包含谱面、曲目、曲绘文件。", | ||
| "choose_chart_description": "PE / RPE / Rain Editor で作成された譜面のみ対応しています。50MB以下。", |
There was a problem hiding this comment.
The translation key milthm_preview_not_supported is missing for the ja-JP locale. This key is used at line 943 in the new-submission page but is only defined in en-US and zh-CN translation files. Please add: "milthm_preview_not_supported": "Milthmの譜面プレビューはまだサポートされていません" after line 32 in the session section.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| const imageFileObj = new File( | ||
| [imageBlob], | ||
| imageFile.name, | ||
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| // @ts-ignore | ||
| { type: 'image/' + imageFile.name.split('.').pop() }, |
There was a problem hiding this comment.
The MIME type construction is incorrect. Image extensions should use proper MIME types: jpg/jpeg → 'image/jpeg', png → 'image/png'. Using 'image/jpg' is technically incorrect, though browsers may handle it. Consider using a proper MIME type mapping.
| const imageFileObj = new File( | |
| [imageBlob], | |
| imageFile.name, | |
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
| // @ts-ignore | |
| { type: 'image/' + imageFile.name.split('.').pop() }, | |
| // Map image file extensions to correct MIME types | |
| const imageMimeTypes: Record<string, string> = { | |
| jpg: 'image/jpeg', | |
| jpeg: 'image/jpeg', | |
| png: 'image/png', | |
| gif: 'image/gif', | |
| bmp: 'image/bmp', | |
| webp: 'image/webp', | |
| svg: 'image/svg+xml', | |
| }; | |
| const imageExt = imageFile.name.split('.').pop()?.toLowerCase() || ''; | |
| const imageMimeType = imageMimeTypes[imageExt] || 'application/octet-stream'; | |
| const imageFileObj = new File( | |
| [imageBlob], | |
| imageFile.name, | |
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | |
| // @ts-ignore | |
| { type: imageMimeType }, |
| if (!songResp.ok) { | ||
| alert($t(`error.${(await songResp.json()).code}`)); | ||
| return; | ||
| } | ||
| processMatchResults((await songResp.json()).data); |
There was a problem hiding this comment.
The response is parsed twice: once to get the error code (line 450) and once to get the data (line 453). This is inefficient and could fail if the response body is already consumed. Store the parsed response in a variable and reuse it.
| if (!songResp.ok) { | |
| alert($t(`error.${(await songResp.json()).code}`)); | |
| return; | |
| } | |
| processMatchResults((await songResp.json()).data); | |
| const songRespJson = await songResp.json(); | |
| if (!songResp.ok) { | |
| alert($t(`error.${songRespJson.code}`)); | |
| return; | |
| } | |
| processMatchResults(songRespJson.data); |
| musicFile.name, | ||
| // eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
| // @ts-ignore | ||
| { type: 'audio/' + musicFile.name.split('.').pop() }, |
There was a problem hiding this comment.
The MIME type construction is incorrect. File extensions like 'wav' or 'ogg' should not be directly concatenated to 'audio/'. For example, OGG files should use 'audio/ogg' or 'audio/opus', not just 'audio/ogg'. Consider using a proper MIME type mapping or library. Valid MIME types: mp3 → 'audio/mpeg', wav → 'audio/wav', ogg → 'audio/ogg'.
| { type: 'audio/' + musicFile.name.split('.').pop() }, | |
| { type: ((): string => { | |
| const audioMimeTypes: Record<string, string> = { | |
| mp3: 'audio/mpeg', | |
| wav: 'audio/wav', | |
| ogg: 'audio/ogg', | |
| opus: 'audio/opus', | |
| flac: 'audio/flac', | |
| aac: 'audio/aac', | |
| m4a: 'audio/mp4', | |
| }; | |
| const ext = musicFile.name.split('.').pop()?.toLowerCase() ?? ''; | |
| return audioMimeTypes[ext] || 'audio/*'; | |
| })() }, |
| let lastBpm = 0; | ||
| let lastBeat = 0; | ||
| let lastTimeSec = 0; | ||
| bpmList.forEach((bpm, i) => { | ||
| bpm.startBeat = toBeats(bpm.startTime); | ||
| bpm.startTimeSec = | ||
| i === 0 ? lastTimeSec : lastTimeSec + ((bpm.startBeat - lastBeat) / lastBpm) * 60; | ||
| lastBpm = bpm.bpm; | ||
| lastBeat = bpm.startBeat; | ||
| lastTimeSec = bpm.startTimeSec; | ||
| }); | ||
|
|
||
| const bpmArr = bpmList.map((bpm) => bpm.bpm); | ||
| $form.MinBpm = Math.min(...bpmArr); | ||
| $form.MaxBpm = Math.max(...bpmArr); | ||
| if ($form.MinBpm === $form.MaxBpm) { | ||
| $form.Bpm = $form.MinBpm; | ||
| } | ||
| } else if ('lines' in chartJson && 'meta' in chartJson) { | ||
| const { bpms, meta } = chartJson as MilthmJson; | ||
| $form.Offset = Math.round(meta.offset * 100); | ||
|
|
||
| bpmList = bpms.map((b) => ({ | ||
| bpm: b.bpm, | ||
| startTime: b.time, | ||
| startBeat: 0, | ||
| startTimeSec: 0, | ||
| })); | ||
|
|
||
| let lastBpm = 0; | ||
| let lastBeat = 0; | ||
| let lastTimeSec = 0; | ||
| bpmList.forEach((bpm, i) => { | ||
| bpm.startBeat = toBeats(bpm.startTime); | ||
| bpm.startTimeSec = | ||
| i === 0 ? lastTimeSec : lastTimeSec + ((bpm.startBeat - lastBeat) / lastBpm) * 60; | ||
| lastBpm = bpm.bpm; | ||
| lastBeat = bpm.startBeat; | ||
| lastTimeSec = bpm.startTimeSec; | ||
| }); | ||
|
|
||
| const bpmArr = bpmList.map((bpm) => bpm.bpm); | ||
| $form.MinBpm = Math.min(...bpmArr); | ||
| $form.MaxBpm = Math.max(...bpmArr); | ||
| if ($form.MinBpm === $form.MaxBpm) { | ||
| $form.Bpm = $form.MinBpm; | ||
| } |
There was a problem hiding this comment.
[nitpick] There's significant code duplication between the RPE (lines 90-107) and Milthm (lines 119-136) chart processing branches. The BPM calculation logic is identical. Consider extracting this into a shared function to improve maintainability and reduce duplication.
| "chart_submissions": "譜面稿件", | ||
| "choose_chart": "选择谱面", | ||
| "choose_chart_description": "请在此处选择包含 RPE 或 PE 谱面的压缩包(.pez 或 .zip),并且须至少包含谱面、曲目、曲绘文件。", | ||
| "choose_chart_description": "僅支持 PE / RPE / Rain Editor 創作的譜面,不大於 50 MB", |
There was a problem hiding this comment.
The translation key milthm_preview_not_supported is missing for the zh-TW locale. This key is used at line 943 in the new-submission page but is only defined in en-US and zh-CN translation files. Please add: "milthm_preview_not_supported": "暫不支援 Milthm 譜面預覽" after line 32 in the session section.
…d zh-TW locales Co-authored-by: Naptie <57532127+Naptie@users.noreply.github.com>
Co-authored-by: Naptie <57532127+Naptie@users.noreply.github.com>
Add missing milthm_preview_not_supported translation key for ja-JP and zh-TW locales
…ServerLoad Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
No description provided.