├── .editorconfig ├── .eslintrc.cjs ├── .github └── workflows │ ├── build.yml │ └── lint.yml ├── .gitignore ├── .np-config.json ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .typesafe-i18n.json ├── LICENSE ├── README.md ├── api └── library.ts ├── docs └── CONTRIBUTE.md ├── jest.config.cjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.cjs ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ └── icon.png ├── resources │ ├── banner.png │ └── welcome.png ├── src │ └── main.rs └── tauri.conf.json ├── src ├── app.html ├── assets │ ├── BPM.png │ ├── TimeSignature.png │ ├── cursor │ │ ├── grab-cursor.png │ │ ├── move-cursor.png │ │ ├── resize-cursor.png │ │ └── select-cursor.png │ ├── fever_end.png │ ├── fever_start.png │ ├── homepage │ │ ├── editing.png │ │ ├── filesupport.png │ │ ├── funtionality.png │ │ ├── funtionality0.png │ │ ├── funtionality2.png │ │ ├── screenshot.png │ │ └── soundeffects.png │ ├── notes │ │ ├── noteC.png │ │ ├── noteF.png │ │ ├── noteL.png │ │ ├── noteN.png │ │ ├── notes_flick_arrow_01.png │ │ ├── notes_flick_arrow_01_diagonal.png │ │ ├── notes_flick_arrow_02.png │ │ ├── notes_flick_arrow_02_diagonal.png │ │ ├── notes_flick_arrow_03.png │ │ ├── notes_flick_arrow_03_diagonal.png │ │ ├── notes_flick_arrow_04.png │ │ ├── notes_flick_arrow_04_diagonal.png │ │ ├── notes_flick_arrow_05.png │ │ ├── notes_flick_arrow_05_diagonal.png │ │ ├── notes_flick_arrow_06.png │ │ ├── notes_flick_arrow_06_diagonal.png │ │ ├── notes_flick_arrow_crtcl_01.png │ │ ├── notes_flick_arrow_crtcl_01_diagonal.png │ │ ├── notes_flick_arrow_crtcl_02.png │ │ ├── notes_flick_arrow_crtcl_02_diagonal.png │ │ ├── notes_flick_arrow_crtcl_03.png │ │ ├── notes_flick_arrow_crtcl_03_diagonal.png │ │ ├── notes_flick_arrow_crtcl_04.png │ │ ├── notes_flick_arrow_crtcl_04_diagonal.png │ │ ├── notes_flick_arrow_crtcl_05.png │ │ ├── notes_flick_arrow_crtcl_05_diagonal.png │ │ ├── notes_flick_arrow_crtcl_06.png │ │ ├── notes_flick_arrow_crtcl_06_diagonal.png │ │ ├── notes_long_among.png │ │ └── notes_long_among_crtcl.png │ ├── playhead.png │ ├── select.png │ ├── skill.png │ ├── sound │ │ ├── connect.mp3 │ │ ├── connect_critical.mp3 │ │ ├── critical_flick.mp3 │ │ ├── critical_tap.mp3 │ │ ├── critical_tick.mp3 │ │ ├── flick.mp3 │ │ ├── good.mp3 │ │ ├── great.mp3 │ │ ├── perfect.mp3 │ │ ├── stage.mp3 │ │ └── tick.mp3 │ └── vector │ │ ├── curve-in.svg │ │ ├── curve-out.svg │ │ ├── diamond │ │ ├── ignored.svg │ │ ├── invisible.svg │ │ └── visible.svg │ │ └── straight.svg ├── global.d.ts ├── hooks.ts ├── i18n │ ├── en │ │ └── index.ts │ ├── formatters.ts │ ├── i18n-svelte.ts │ ├── i18n-types.ts │ ├── i18n-util.async.ts │ ├── i18n-util.sync.ts │ ├── i18n-util.ts │ ├── ja │ │ └── index.ts │ ├── ko │ │ └── index.ts │ ├── metadata.ts │ └── zh │ │ └── index.ts ├── lib │ ├── Canvas.svelte │ ├── PropertyBox.svelte │ ├── ToolBox.svelte │ ├── ZoomIndicator.svelte │ ├── api │ │ └── library.ts │ ├── audio │ │ ├── AudioManager.svelte │ │ └── scheduler.ts │ ├── basic │ │ ├── collections.ts │ │ ├── debug.ts │ │ ├── file.spec.ts │ │ ├── file.ts │ │ └── math.ts │ ├── colors.ts │ ├── consts.ts │ ├── control │ │ ├── ControlHandler.svelte │ │ ├── keyboard.ts │ │ └── pointer.ts │ ├── database.ts │ ├── dialogs │ │ ├── AboutDialog.svelte │ │ ├── BPMDialog.svelte │ │ ├── CustomSnappingDialog.svelte │ │ ├── ImageDialog.svelte │ │ ├── LibraryDialog.svelte │ │ ├── PreferencesDialog.svelte │ │ ├── ProjectCard.svelte │ │ ├── ProjectsDialog.svelte │ │ ├── TimeSignatureDialog.svelte │ │ └── index.ts │ ├── editing │ │ ├── clipboard.ts │ │ ├── flick.ts │ │ ├── flip.ts │ │ ├── history.ts │ │ ├── modes.ts │ │ ├── moving.ts │ │ ├── mutations.ts │ │ ├── playhead.ts │ │ ├── resizing.ts │ │ ├── scrolling.ts │ │ ├── selection.ts │ │ ├── slides.ts │ │ └── visibility.ts │ ├── menus │ │ └── CanvasContextMenu.svelte │ ├── position.ts │ ├── preferences.ts │ ├── render │ │ ├── BPM.svelte │ │ ├── DraggingSlide.svelte │ │ ├── Fever.svelte │ │ ├── Floating.svelte │ │ ├── FloatingNotes.svelte │ │ ├── Grid.svelte │ │ ├── Minimap.svelte │ │ ├── MovingNotes.svelte │ │ ├── Note.svelte │ │ ├── NoteControl.svelte │ │ ├── NoteError.svelte │ │ ├── PastingNotes.svelte │ │ ├── Playhead.svelte │ │ ├── ResizingNotes.svelte │ │ ├── Scrollbar.svelte │ │ ├── Selection.svelte │ │ ├── Single.svelte │ │ ├── Skill.svelte │ │ ├── Slide.svelte │ │ ├── SlidePath.svelte │ │ ├── SlideSteps.svelte │ │ ├── minimap.ts │ │ ├── note.ts │ │ └── renderer.ts │ ├── score │ │ ├── beatmap.ts │ │ └── susIO.ts │ ├── style.css │ ├── timing.ts │ └── ui │ │ ├── Button.svelte │ │ ├── Checkbox.svelte │ │ ├── ClickableIcon.svelte │ │ ├── DebugInfo.svelte │ │ ├── FileInput.svelte │ │ ├── KeyboardShortcut.svelte │ │ ├── Menu.svelte │ │ ├── MenuDivider.svelte │ │ ├── MenuItem.svelte │ │ ├── MenuTrigger.svelte │ │ ├── MenuWrapper.svelte │ │ ├── Modal.svelte │ │ ├── Select.svelte │ │ ├── TabContent.svelte │ │ ├── TabItem.svelte │ │ ├── TabSelect.svelte │ │ ├── Tabs.svelte │ │ ├── TextInput.svelte │ │ ├── ToolButton.svelte │ │ ├── Tooltip.svelte │ │ ├── UndoToast.svelte │ │ ├── Wrapper.svelte │ │ └── toast.ts └── routes │ ├── __layout.svelte │ ├── edit.svelte │ └── index.svelte ├── static ├── assets │ ├── fonts │ │ ├── Font.fnt │ │ └── Font.png │ └── textures │ │ ├── path.png │ │ ├── path_critical.png │ │ ├── spritesheet.json │ │ └── spritesheet.png └── favicon.png ├── svelte.config.js ├── tsconfig.json └── tsconfig.spec.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 5 | plugins: ['svelte3', '@typescript-eslint', 'no-array-concat'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript'), 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2019, 14 | project: './tsconfig.json', 15 | }, 16 | env: { 17 | browser: true, 18 | es2017: true, 19 | node: true, 20 | }, 21 | rules: { 22 | '@typescript-eslint/no-inferrable-types': 'off', 23 | '@typescript-eslint/no-non-null-assertion': 'off', 24 | 'no-restricted-globals': [ 25 | 'error', 26 | 'closed', 27 | 'event', 28 | 'fdescribe', 29 | 'name', 30 | 'length', 31 | 'location', 32 | 'parent', 33 | 'top', 34 | ], 35 | 'no-redeclare': 'off', 36 | 'no-import-assign': 'off', 37 | 'no-console': ['error', { allow: ['warn', 'error'] }], 38 | 'array-callback-return': 'error', 39 | 'no-array-concat/no-array-concat': 'error', 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build tauri 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | publish-tauri: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | platform: [macos-latest, ubuntu-latest, windows-latest] 14 | 15 | runs-on: ${{ matrix.platform }} 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: setup node 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 18 22 | - name: install Rust stable 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | toolchain: stable 26 | - name: install webkit2gtk (ubuntu only) 27 | if: matrix.platform == 'ubuntu-latest' 28 | run: | 29 | sudo apt-get update 30 | sudo apt-get install -y webkit2gtk-4.0 31 | - name: Setup pnpm 32 | uses: pnpm/action-setup@v2.1.0 33 | with: 34 | version: 6.0.2 35 | run_install: true 36 | - name: install app dependencies and build it 37 | run: pnpm build 38 | - uses: jdukewich/tauri-action@fix-windows-bundling 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | with: 42 | tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version 43 | releaseName: "PaletteWorks Editor v__VERSION__" 44 | releaseBody: "**Assets** からダウンロードしてインストールしてください。\n請從 **Assets** 文件下載安裝。\n**Assets** 에서 다운로드하여 설치하십시오.\nSee the **Assets** to download this version and install.\n**Assets**\n\n**Windows** .exe / .msi\n**Linux** .AppImage / .deb\n**macOS** .dmg\n\n**[Release Note](https://paletteworks.notion.site/PaletteWorks-Editor-7571ec4cffd4465f95ec0ff406bed54f)**" 45 | releaseDraft: true 46 | prerelease: false 47 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: ESLint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v2 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v2 19 | - name: Setup pnpm 20 | uses: pnpm/action-setup@v2.0.1 21 | with: 22 | version: 6.0.2 23 | run_install: true 24 | - name: Run ESLint 25 | run: pnpm lint 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /.svelte-kit 4 | /package 5 | 6 | temp/* 7 | 8 | .vercel_build_output 9 | .svelte-kit 10 | 11 | .env.local 12 | 13 | .vscode 14 | .vercel 15 | 16 | coverage 17 | 18 | build -------------------------------------------------------------------------------- /.np-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "yarn": false, 3 | "cleanup": false, 4 | "tests": false, 5 | "branch": "main", 6 | "message": "V: Release v%s" 7 | } 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github/** 2 | .svelte-kit/** 3 | .vercel/** 4 | .vscode/** 5 | .vercel_build_output 6 | static/** 7 | build/** 8 | coverage/** 9 | node_modules/** 10 | static/** 11 | temp/** 12 | src-tauri/** 13 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false 4 | } 5 | -------------------------------------------------------------------------------- /.typesafe-i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/typesafe-i18n@4.2.1/schema/typesafe-i18n.json", 3 | "baseLocale": "ja", 4 | "adapter": "svelte", 5 | "outputPath": "./src/i18n", 6 | "runAfterGenerator": "prettier --write src/i18n" 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 mkpoli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PaletteWorks Editor 2 | 3 | > [!IMPORTANT] 4 | > ✋ Hi! I am *mkpoli*, the author of **PaletteWorks Editor**. I'm really sorry for not communicating for such a long time — life took over with various responsibilities, including academic work and some health challenges. 5 | > 6 | > ⏳ This project has been inactive since 2022, but it's not fully abandoned. I may occasionally update it when I am motivated and well enough. 7 | > 8 | > 🙏 I genuinely appreciate everyone who’s shown interest, and I’m always open to feedback, discussions, or pull requests — as long as I’m feeling healthy and capable enough to respond. 9 | > 10 | > 🤝 If someone is motivated to continue developing PaletteWorks Editor, I’d be more than happy and honored to see it live on, and I’d gladly support however I can. 11 | > 12 | > 🔄 If you’re looking for similar tools or more actively maintained projects, there are modern alternative softwares such as [**MikuMikuWorld4CC**](https://github.com/sevenc-nanashi/MikuMikuWorld4CC). 13 | > 14 | > 2025-04-01 15 | 16 | 「プロジェクトセカイ カラフルステージ! feat. 初音ミク」の譜面エディターです。創作譜面をより作りやすくすることを目指しているものであります。 17 | 18 | 本程序爲「世界計劃:繽紛舞臺 feat. 初音未來」的自製譜面編輯器,目標是提供更好的創作體驗。 19 | 20 | “프로젝트 세카이 컬러풀 스테이지! feat.하츠네 미쿠” 의 자작채보 에디터입니다. 채보를 더 만들기 쉽게 하는 것을 목표로 하고 있습니다. 21 | (“프로젝트 세카이 컬러풀 스테이지! feat.하츠네 미쿠” 의 自作採譜 에디터입니다. 採譜를 더 만들기 쉽게 하는 것을 目標로 하고 있습니다.) 22 | 23 | ## Acknowledgements / 謝辞 / 致謝 / 사사(謝辭) 24 | 25 | ### Inspiration / インスピレーション / 啓發 / 영감(靈感) 26 | 27 | - [Ched](https://github.com/paralleltree/Ched) 28 | 29 | ### Special Thanks / スペシャルサンクス / 特別感謝 / 특별히 감사 (特別히 感謝) 30 | 31 | - ![Avatar of Burrito](https://images.weserv.nl/?url=avatars.githubusercontent.com/u/47196038?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d) [Burrito](https://github.com/NonSpicyBurrito) ![IBWT](https://cdn.discordapp.com/emojis/710979426623422594.png?size=128) 32 | - ![Avatar of 紫式部](https://images.weserv.nl/?url=user-images.githubusercontent.com/3502597/147320524-2e62a933-5423-4467-b65d-393286b89fcd.png&h=50&w=50&fit=cover&mask=circle&maxage=7d) [紫式部](https://twitter.com/purplepalettech) 33 | - ![Avatar of お窓](https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17107514?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d) [お窓](https://github.com/Dosugamea) 34 | - ![Avatar of 名無し。](https://images.weserv.nl/?url=avatars.githubusercontent.com/u/59691627?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d) [名無し。](https://github.com/sevenc-nanashi) 35 | - ![Avatar of よこしま。](https://images.weserv.nl/?url=user-images.githubusercontent.com/3502597/147321354-fafb4a9d-563c-4fc9-92af-8e6db515ee9f.png&h=50&w=50&fit=cover&mask=circle&maxage=7d) [よこしま。](https://www.youtube.com/c/よこしま) 36 | - ![Avatar of トトロっぽい何か](https://images.weserv.nl/?url=user-images.githubusercontent.com/3502597/147321415-a6b396dd-4295-46a0-969a-81a1df671cdf.png&h=50&w=50&fit=cover&mask=circle&maxage=7d) [トトロっぽい何か](https://youtube.com/c/トトロっぽい何か) 37 | - ![Avatar of jch](https://images.weserv.nl/?url=user-images.githubusercontent.com/3502597/147321520-7de435a2-a1bb-467b-9b98-2877794420e9.png?size=128?v=4&h=50&w=50&fit=cover&mask=circle&maxage=7d) jch 38 | - ![Avatar of Sora](https://images.weserv.nl/?url=user-images.githubusercontent.com/3502597/147321464-0e4ef37f-45ef-46cd-a7b3-2f727c88523d.png?size=128&h=50&w=50&fit=cover&mask=circle&maxage=7d) Sora 39 | 40 | and many others from 紫式部 / Sonolus Discord ... 41 | 42 | ### Contributors / コントリビューター / 貢獻者 / 기여자(寄與者) 43 | 44 | 45 | 46 | 47 | 48 | Made with [contrib.rocks](https://contrib.rocks). 49 | 50 | ## [Contribution Guide / コントリビュート方法 / 貢獻指南 / 기여하는 방법 (寄與하는 方法)](docs/CONTRIBUTE.md) 51 | 52 | ## LICENSE / ライセンス / 授權 / 사용권(使用權) 53 | 54 | MIT © 2021 mkpoli 55 | -------------------------------------------------------------------------------- /api/library.ts: -------------------------------------------------------------------------------- 1 | import type { VercelRequest, VercelResponse } from '@vercel/node' 2 | 3 | import { createClient } from '@libsql/client/web' 4 | import type { Single, Slide } from '$lib/score/beatmap' 5 | 6 | type LocaleStrings = { [key: string]: string } 7 | 8 | export type Item = { 9 | title: LocaleStrings 10 | description: LocaleStrings 11 | content: { 12 | singles?: Single[] 13 | slides?: Slide[] 14 | } 15 | } 16 | 17 | const client = createClient({ 18 | url: process.env['TURSO_DATABASE_URL']!, 19 | authToken: process.env['TURSO_AUTH_TOKEN']!, 20 | }) 21 | 22 | export async function list(): Promise { 23 | const res = await client.execute( 24 | 'SELECT title_ja, description_ja, content_json FROM items' 25 | ) 26 | 27 | return res.rows.map((row) => ({ 28 | title: { ja: row.title_ja as string }, 29 | description: { ja: row.description_ja as string }, 30 | content: JSON.parse(row.content_json as string), 31 | })) 32 | } 33 | 34 | export async function create(item: Item) { 35 | await client.execute({ 36 | sql: ` 37 | INSERT INTO items ( 38 | id, collection, ts_iso, title_ja, description_ja, content_json 39 | ) VALUES (?, ?, ?, ?, ?, ?) 40 | `, 41 | args: [ 42 | crypto.randomUUID(), 43 | 'items', 44 | new Date().toISOString(), 45 | item.title?.ja ?? null, 46 | item.description?.ja ?? null, 47 | JSON.stringify(item.content), 48 | ], 49 | }) 50 | } 51 | 52 | export default async (request: VercelRequest, response: VercelResponse) => { 53 | try { 54 | if (request.method === 'GET') { 55 | const data = await list() 56 | response.status(200).json(data) 57 | } else if (request.method === 'POST') { 58 | const data = await create(request.body) 59 | response.status(200).json(data) 60 | } else { 61 | response.status(405).end() 62 | } 63 | } catch (error) { 64 | console.error('❌ API error:', error) 65 | response.status(500).json({ message: 'Internal Server Error' }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docs/CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide / コントリビュート方法 / 貢獻指南 / 寄與하는 方法 2 | 3 | ## Developing / 開發 4 | 5 | ```bash 6 | pnpm install 7 | pnpm run dev -- --open 8 | pnpm run typesafe-i18n 9 | ``` 10 | 11 | ### Tauri Build 12 | 13 | ```bash 14 | pnpm run tauri dev 15 | ``` 16 | ## Releasing / 發佈 17 | 18 | 1. Clear current tree (commit changes) 19 | 2. Run pre-release check 20 | ```bash 21 | pnpm run beforeRelease 22 | ``` 23 | 3. Bump version numbers 24 | ```bash 25 | pnpx @rstacruz/bump-cli package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json # -M / -m / -p 26 | cargo update --manifest-path=src-tauri/Cargo.toml 27 | ``` 28 | 3. Commit ``V: Release v`` 29 | ```bash 30 | git add package.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.conf.json 31 | git commit -m "V: Release v" 32 | ``` 33 | 5. Tag the commit above 34 | ``` 35 | pnpm run tag 36 | ``` 37 | 5. Push to Remote 38 | ``` 39 | git push --follow-tags 40 | ``` 41 | 42 | ### Commit Message Naming Convention / コミットメッセージ命名規則 / 提交消息命名规则 / 커밋 메시지 명명 규칙 (커밋 메시지 命名 規則) 43 | 44 | ```regex 45 | (F|R|D|S|V|I): 46 | ``` 47 | 48 | - **F** for **F**eature (Additions, Fixes, Ajustments of functionalities, etc.) 49 | - **T** for **T**esting (New tests / specs, Test refactoring, etc.) 50 | - **R** for **R**efactor (Adjustments of code structure, naming, typing, comments, etc.) 51 | - **D** for **D**ocumentation (Documentation, README, etc.) 52 | - **S** for **S**tyle (Styling, Visual Design Adjustments, etc.) 53 | - **V** for **V**ersion (Versioning, Dependencies, Licensing, etc.) 54 | - **C** for **C**onfiguration (Building, Linting, CLI Tooling, etc.) 55 | - **I** for **I**18n (Translation, Localisation, etc.) 56 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | globals: { 4 | 'ts-jest': { 5 | tsconfig: './tsconfig.spec.json', 6 | }, 7 | }, 8 | testPathIgnorePatterns: [ 9 | '/node_modules/', 10 | '/build/', 11 | '/temp', 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paletteworks", 3 | "private": true, 4 | "version": "1.11.3", 5 | "scripts": { 6 | "dev": "svelte-kit dev", 7 | "clean": "rimraf build", 8 | "build:svelte-kit": "svelte-kit build", 9 | "build": "run-s clean build:svelte-kit", 10 | "preview": "svelte-kit preview", 11 | "test": "jest", 12 | "check": "svelte-check --tsconfig ./tsconfig.json", 13 | "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", 14 | "lint": "eslint --ignore-path .gitignore src", 15 | "typesafe-i18n": "typesafe-i18n", 16 | "beforeRelease": "run-s check lint test", 17 | "tag": "taggit", 18 | "format": "prettier --write --plugin-search-dir=. .", 19 | "tauri": "tauri" 20 | }, 21 | "license": "MIT", 22 | "devDependencies": { 23 | "@iconify/svelte": "2.1.2", 24 | "@mszu/pixi-ssr-shim": "1.0.2", 25 | "@pixi/events": "6.2.2", 26 | "@rstacruz/bump-cli": "2.0.1", 27 | "@sveltejs/adapter-static": "1.0.0-next.28", 28 | "@sveltejs/adapter-vercel": "1.0.0-next.43", 29 | "@sveltejs/kit": "1.0.0-next.278", 30 | "@tauri-apps/cli": "1.0.0", 31 | "@types/core-js": "2.5.5", 32 | "@types/jest": "27.4.1", 33 | "@types/msgpack-lite": "0.1.8", 34 | "@types/throttle-debounce": "2.1.0", 35 | "@typescript-eslint/eslint-plugin": "5.12.1", 36 | "@typescript-eslint/parser": "5.12.1", 37 | "@vercel/node": "1.13.0", 38 | "@zerodevx/svelte-toast": "0.7.0", 39 | "attr-accept": "2.2.2", 40 | "autoprefixer": "10.4.2", 41 | "bpm-detective": "2.0.5", 42 | "core-js": "3.21.1", 43 | "dexie": "3.2.1", 44 | "dexie-export-import": "1.0.3", 45 | "eslint": "8.9.0", 46 | "eslint-plugin-no-array-concat": "0.1.2", 47 | "eslint-plugin-svelte3": "3.4.0", 48 | "filesize": "8.0.7", 49 | "focus-trap": "6.7.3", 50 | "hotkeys-js": "3.8.7", 51 | "jest": "27.5.1", 52 | "msgpack-lite": "0.1.26", 53 | "npm-run-all": "4.1.5", 54 | "pixi.js": "~6.2.2", 55 | "postcss-viewport-height-correction": "1.1.1", 56 | "prettier": "2.6.0", 57 | "prettier-plugin-svelte": "2.6.0", 58 | "rimraf": "3.0.2", 59 | "sus-analyzer": "2.2.4", 60 | "sus-io": "1.1.0", 61 | "svelte": "3.46.4", 62 | "svelte-check": "2.4.5", 63 | "svelte-preprocess": "4.10.3", 64 | "taggit": "2.0.3", 65 | "throttle-debounce": "3.0.1", 66 | "tippy.js": "6.3.7", 67 | "ts-jest": "27.1.3", 68 | "tslib": "2.0.0", 69 | "typesafe-i18n": "4.2.1", 70 | "typescript": "4.5.5" 71 | }, 72 | "type": "module", 73 | "dependencies": { 74 | "@libsql/client": "^0.15.1", 75 | "@sveltejs/vite-plugin-svelte": "1.0.0-next.32", 76 | "@tauri-apps/api": "1.0.0-rc.1" 77 | }, 78 | "packageManager": "pnpm@9.10.0+sha1.216899f511c8dfde183c7cb50b69009c779534a8" 79 | } 80 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer'), 4 | require('postcss-viewport-height-correction'), 5 | ], 6 | } 7 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | WixTools 5 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "paletteworks" 3 | version = "1.11.3" 4 | description = "A score editor for Project Sekai: Colourful Stage feat. Hatsune Miku" 5 | authors = ["mkpoli "] 6 | license = "MIT" 7 | repository = "https://github.com/mkpoli/paletteworks-editor" 8 | default-run = "paletteworks" 9 | edition = "2021" 10 | rust-version = "1.57" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [build-dependencies] 15 | tauri-build = { version = "1.0.0", features = [] } 16 | 17 | [dependencies] 18 | serde_json = "1.0" 19 | serde = { version = "1.0", features = ["derive"] } 20 | tauri = { version = "1.0.0", features = ["dialog-all", "http-all", "shell-open"] } 21 | 22 | [features] 23 | # by default Tauri runs in production mode 24 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 25 | default = [ "custom-protocol" ] 26 | # this feature is used used for production builds where `devPath` points to the filesystem 27 | # DO NOT remove this 28 | custom-protocol = [ "tauri/custom-protocol" ] 29 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/resources/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/resources/banner.png -------------------------------------------------------------------------------- /src-tauri/resources/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src-tauri/resources/welcome.png -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | 6 | fn main() { 7 | tauri::Builder::default() 8 | .invoke_handler(tauri::generate_handler![write_file]) 9 | .run(tauri::generate_context!()) 10 | .expect("error while running tauri application"); 11 | } 12 | 13 | #[tauri::command] 14 | fn write_file(path: &str, data: Vec) { 15 | println!("writing file {}", path); 16 | std::fs::write(path, data).expect("error while writing file"); 17 | } 18 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "productName": "PaletteWorks Editor", 4 | "version": "1.11.3" 5 | }, 6 | "build": { 7 | "distDir": "../build", 8 | "devPath": "http://localhost:3000", 9 | "beforeDevCommand": "pnpm dev", 10 | "beforeBuildCommand": "pnpm build", 11 | "withGlobalTauri": true 12 | }, 13 | "tauri": { 14 | "bundle": { 15 | "active": true, 16 | "targets": "all", 17 | "identifier": "li.mkpo.paletteworks", 18 | "icon": [ 19 | "icons/32x32.png", 20 | "icons/128x128.png", 21 | "icons/128x128@2x.png", 22 | "icons/icon.icns", 23 | "icons/icon.ico" 24 | ], 25 | "resources": [], 26 | "externalBin": [], 27 | "copyright": "mkpoli 2021-2022", 28 | "category": "Productivity", 29 | "shortDescription": "A score editor for Project Sekai: Colourful Stage feat. Hatsune Miku", 30 | "longDescription": "This is a score editor for Project Sekai: Colorful Stage feat. Hatsune Miku. It aims to provide a better user experience to create custom maps.", 31 | "deb": { 32 | "depends": [] 33 | }, 34 | "macOS": { 35 | "frameworks": [], 36 | "minimumSystemVersion": "", 37 | "exceptionDomain": "", 38 | "signingIdentity": null, 39 | "providerShortName": null, 40 | "entitlements": null 41 | }, 42 | "windows": { 43 | "certificateThumbprint": null, 44 | "digestAlgorithm": "sha256", 45 | "timestampUrl": "", 46 | "wix": { 47 | "bannerPath": "./resources/banner.png", 48 | "dialogImagePath": "./resources/welcome.png", 49 | "language": ["en-US", "zh-CN", "zh-TW", "jp-JP", "ko-KO"] 50 | } 51 | } 52 | }, 53 | "updater": { 54 | "active": false 55 | }, 56 | "allowlist": { 57 | "http": { 58 | "all": true, 59 | "request": true, 60 | "scope": ["https://paletteworks.mkpo.li/*"] 61 | }, 62 | "dialog": { 63 | "all": true 64 | }, 65 | "shell": { 66 | "open": true 67 | } 68 | }, 69 | "windows": [ 70 | { 71 | "url": "/edit.html", 72 | "title": "PaletteWorks Editor", 73 | "width": 1024, 74 | "height": 768, 75 | "resizable": true, 76 | "fullscreen": false, 77 | "fileDropEnabled": false, 78 | "maximized": true 79 | } 80 | ], 81 | "security": { 82 | "csp": null 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %svelte.head% 8 | 9 | 10 |
%svelte.body%
11 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/assets/BPM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/BPM.png -------------------------------------------------------------------------------- /src/assets/TimeSignature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/TimeSignature.png -------------------------------------------------------------------------------- /src/assets/cursor/grab-cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/cursor/grab-cursor.png -------------------------------------------------------------------------------- /src/assets/cursor/move-cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/cursor/move-cursor.png -------------------------------------------------------------------------------- /src/assets/cursor/resize-cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/cursor/resize-cursor.png -------------------------------------------------------------------------------- /src/assets/cursor/select-cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/cursor/select-cursor.png -------------------------------------------------------------------------------- /src/assets/fever_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/fever_end.png -------------------------------------------------------------------------------- /src/assets/fever_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/fever_start.png -------------------------------------------------------------------------------- /src/assets/homepage/editing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/homepage/editing.png -------------------------------------------------------------------------------- /src/assets/homepage/filesupport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/homepage/filesupport.png -------------------------------------------------------------------------------- /src/assets/homepage/funtionality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/homepage/funtionality.png -------------------------------------------------------------------------------- /src/assets/homepage/funtionality0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/homepage/funtionality0.png -------------------------------------------------------------------------------- /src/assets/homepage/funtionality2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/homepage/funtionality2.png -------------------------------------------------------------------------------- /src/assets/homepage/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/homepage/screenshot.png -------------------------------------------------------------------------------- /src/assets/homepage/soundeffects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/homepage/soundeffects.png -------------------------------------------------------------------------------- /src/assets/notes/noteC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/noteC.png -------------------------------------------------------------------------------- /src/assets/notes/noteF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/noteF.png -------------------------------------------------------------------------------- /src/assets/notes/noteL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/noteL.png -------------------------------------------------------------------------------- /src/assets/notes/noteN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/noteN.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_01.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_01_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_01_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_02.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_02_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_02_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_03.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_03_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_03_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_04.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_04_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_04_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_05.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_05_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_05_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_06.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_06_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_06_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_01.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_01_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_01_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_02.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_02_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_02_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_03.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_03_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_03_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_04.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_04_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_04_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_05.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_05_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_05_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_06.png -------------------------------------------------------------------------------- /src/assets/notes/notes_flick_arrow_crtcl_06_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_flick_arrow_crtcl_06_diagonal.png -------------------------------------------------------------------------------- /src/assets/notes/notes_long_among.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_long_among.png -------------------------------------------------------------------------------- /src/assets/notes/notes_long_among_crtcl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/notes/notes_long_among_crtcl.png -------------------------------------------------------------------------------- /src/assets/playhead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/playhead.png -------------------------------------------------------------------------------- /src/assets/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/select.png -------------------------------------------------------------------------------- /src/assets/skill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/skill.png -------------------------------------------------------------------------------- /src/assets/sound/connect.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/connect.mp3 -------------------------------------------------------------------------------- /src/assets/sound/connect_critical.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/connect_critical.mp3 -------------------------------------------------------------------------------- /src/assets/sound/critical_flick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/critical_flick.mp3 -------------------------------------------------------------------------------- /src/assets/sound/critical_tap.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/critical_tap.mp3 -------------------------------------------------------------------------------- /src/assets/sound/critical_tick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/critical_tick.mp3 -------------------------------------------------------------------------------- /src/assets/sound/flick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/flick.mp3 -------------------------------------------------------------------------------- /src/assets/sound/good.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/good.mp3 -------------------------------------------------------------------------------- /src/assets/sound/great.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/great.mp3 -------------------------------------------------------------------------------- /src/assets/sound/perfect.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/perfect.mp3 -------------------------------------------------------------------------------- /src/assets/sound/stage.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/stage.mp3 -------------------------------------------------------------------------------- /src/assets/sound/tick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkpoli/paletteworks-editor/bf268907e8343ce58c61cdced64c75a6fba25c50/src/assets/sound/tick.mp3 -------------------------------------------------------------------------------- /src/assets/vector/curve-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/vector/curve-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/vector/diamond/ignored.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/vector/diamond/invisible.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/assets/vector/diamond/visible.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/vector/straight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | interface ImportMeta { 4 | env: { 5 | PACKAGE_VERSION: string 6 | DEV: boolean 7 | } 8 | } 9 | 10 | declare interface Window { 11 | __TAURI__ 12 | } 13 | 14 | declare module 'bpm-detective' { 15 | declare function detect(data: AudioBuffer): number 16 | export default detect 17 | } 18 | 19 | declare module 'core-js/actual/array/at.js' { 20 | /** 21 | * Takes an integer value and returns the item at that index, 22 | * allowing for positive and negative integers. 23 | * Negative integers count back from the last item in the array. 24 | */ 25 | declare function at(array: Array, index: number): T | undefined 26 | export default at 27 | } 28 | -------------------------------------------------------------------------------- /src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { detectLocale } from '$i18n/i18n-util' 2 | import type { GetSession, RequestEvent } from '@sveltejs/kit' 3 | import { initAcceptLanguageHeaderDetector } from 'typesafe-i18n/detectors' 4 | 5 | const getHeaders = (event: RequestEvent) => { 6 | const headers: Record = {} 7 | event.request.headers.forEach((value, key) => { headers[key] = value }) 8 | 9 | return headers 10 | } 11 | 12 | export const getSession: GetSession = (event) => { 13 | // detect the preferred language the user has configured in his browser 14 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language 15 | const headers = getHeaders(event) 16 | const acceptLanguageDetector = initAcceptLanguageHeaderDetector({ headers }) 17 | const locale = detectLocale(acceptLanguageDetector) 18 | 19 | return { 20 | locale, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/i18n/formatters.ts: -------------------------------------------------------------------------------- 1 | import type { FormattersInitializer } from 'typesafe-i18n' 2 | import type { Locales, Formatters } from './i18n-types' 3 | 4 | export const initFormatters: FormattersInitializer< 5 | Locales, 6 | Formatters 7 | > = async () => { 8 | const formatters: Formatters = { 9 | // add your formatter functions here 10 | } 11 | 12 | return formatters 13 | } 14 | -------------------------------------------------------------------------------- /src/i18n/i18n-svelte.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | 4 | import { initI18nSvelte } from 'typesafe-i18n/adapters/adapter-svelte' 5 | import type { Locales, Translations, TranslationFunctions, Formatters } from './i18n-types' 6 | import { loadedLocales, loadedFormatters } from './i18n-util' 7 | 8 | const { locale, LL, setLocale } = initI18nSvelte(loadedLocales, loadedFormatters) 9 | 10 | export { locale, LL, setLocale } 11 | 12 | export default LL 13 | -------------------------------------------------------------------------------- /src/i18n/i18n-util.async.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | 4 | import { initFormatters } from './formatters' 5 | import type { Locales, Translations } from './i18n-types' 6 | import { loadedFormatters, loadedLocales, locales } from './i18n-util' 7 | 8 | const localeTranslationLoaders = { 9 | en: () => import('./en'), 10 | ja: () => import('./ja'), 11 | ko: () => import('./ko'), 12 | zh: () => import('./zh'), 13 | } 14 | 15 | export const loadLocaleAsync = async (locale: Locales) => { 16 | if (loadedLocales[locale]) return 17 | 18 | loadedLocales[locale] = (await (localeTranslationLoaders[locale])()).default as unknown as Translations 19 | loadFormatters(locale) 20 | } 21 | 22 | export const loadAllLocalesAsync = () => Promise.all(locales.map(loadLocaleAsync)) 23 | 24 | export const loadFormatters = (locale: Locales) => { 25 | loadedFormatters[locale] = initFormatters(locale) 26 | } 27 | -------------------------------------------------------------------------------- /src/i18n/i18n-util.sync.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | 4 | import { initFormatters } from './formatters' 5 | import type { Locales, Translations } from './i18n-types' 6 | import { loadedFormatters, loadedLocales, locales } from './i18n-util' 7 | 8 | import en from './en' 9 | import ja from './ja' 10 | import ko from './ko' 11 | import zh from './zh' 12 | 13 | const localeTranslations = { 14 | en, 15 | ja, 16 | ko, 17 | zh, 18 | } 19 | 20 | export const loadLocale = (locale: Locales) => { 21 | if (loadedLocales[locale]) return 22 | 23 | loadedLocales[locale] = localeTranslations[locale] as unknown as Translations 24 | loadFormatters(locale) 25 | } 26 | 27 | export const loadAllLocales = () => locales.forEach(loadLocale) 28 | 29 | export const loadFormatters = (locale: Locales) => { 30 | loadedFormatters[locale] = initFormatters(locale) 31 | } 32 | -------------------------------------------------------------------------------- /src/i18n/i18n-util.ts: -------------------------------------------------------------------------------- 1 | // This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. 2 | /* eslint-disable */ 3 | 4 | import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' 5 | import type { LocaleDetector } from 'typesafe-i18n/detectors' 6 | import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' 7 | import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types' 8 | 9 | export const baseLocale: Locales = 'ja' 10 | 11 | export const locales: Locales[] = [ 12 | 'en', 13 | 'ja', 14 | 'ko', 15 | 'zh' 16 | ] 17 | 18 | export const loadedLocales = {} as Record 19 | 20 | export const loadedFormatters = {} as Record 21 | 22 | export const i18nString = (locale: Locales) => initI18nString(locale, loadedFormatters[locale]) 23 | 24 | export const i18nObject = (locale: Locales) => 25 | initI18nObject(locale, loadedLocales[locale], loadedFormatters[locale]) 26 | 27 | export const i18n = () => initI18n(loadedLocales, loadedFormatters) 28 | 29 | export const detectLocale = (...detectors: LocaleDetector[]) => detectLocaleFn(baseLocale, locales, ...detectors) 30 | -------------------------------------------------------------------------------- /src/i18n/metadata.ts: -------------------------------------------------------------------------------- 1 | export const LANGUAGE_ENDONYMS = { 2 | en: 'English', 3 | ja: '日本語', 4 | zh: '中文', 5 | ko: '한국어', 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/ZoomIndicator.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | { 21 | onclick(event, 1) 22 | }} 23 | /> 24 |
25 | 26 |
27 | { 31 | onclick(event, -1) 32 | }} 33 | /> 34 |
35 | {zoom.toFixed(1)}x 36 |
37 |
38 | 39 | 97 | -------------------------------------------------------------------------------- /src/lib/api/library.ts: -------------------------------------------------------------------------------- 1 | import type { Single, Slide } from '$lib/score/beatmap' 2 | 3 | // Types 4 | export type LocaleStrings = { 5 | [key: string]: string 6 | } 7 | 8 | export type Item = { 9 | title: LocaleStrings 10 | description: LocaleStrings 11 | content: { 12 | singles?: Single[] 13 | slides?: Slide[] 14 | } 15 | } 16 | 17 | // let fetch: ( 18 | // input: RequestInfo, 19 | // init?: RequestInit | undefined 20 | // ) => Promise 21 | 22 | const PREFIX = 'https://paletteworks.mkpo.li/' 23 | const LIBRARY_URL = '/api/library' 24 | 25 | export async function list(): Promise { 26 | if (!window.__TAURI__) { 27 | const res = await fetch(LIBRARY_URL) 28 | if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) 29 | return (await res.json()) as Item[] 30 | } else { 31 | const { http } = await import('@tauri-apps/api') 32 | return (await http.fetch(new URL(LIBRARY_URL, PREFIX).href)).data 33 | } 34 | } 35 | 36 | export async function create(item: Item): Promise { 37 | const res = await fetch(LIBRARY_URL, { 38 | method: 'POST', 39 | headers: { 40 | 'Content-Type': 'application/json', 41 | }, 42 | body: JSON.stringify(item), 43 | }) 44 | if (!res.ok) throw new Error(`${res.status} ${res.statusText}`) 45 | } 46 | -------------------------------------------------------------------------------- /src/lib/audio/scheduler.ts: -------------------------------------------------------------------------------- 1 | import type { EFFECT_SOUNDS } from '$lib/consts' 2 | export type Sound = keyof typeof EFFECT_SOUNDS 3 | export type AudioEvent = { 4 | time: number 5 | sound: Sound | 'bgm' 6 | loopTo?: number 7 | startFrom?: number 8 | } 9 | 10 | type EventCallback = (event: AudioEvent, offset: number) => void 11 | 12 | export type SchedulerOption = { 13 | scheduleInterval?: number 14 | scheduleLookahead?: number 15 | events?: Array 16 | callback?: EventCallback 17 | } 18 | 19 | export class AudioScheduler { 20 | audioContext: AudioContext 21 | audioNodes: AudioBufferSourceNode[] 22 | scheduleInterval: number 23 | scheduleLookahead: number 24 | events: AudioEvent[] 25 | callback: EventCallback 26 | eventsIndexNeedle: number 27 | timerID: number 28 | startTimeOffset: number 29 | 30 | constructor( 31 | audioContext: AudioContext, 32 | audioNodes: AudioBufferSourceNode[], 33 | { 34 | scheduleInterval = 50, // in milliseconds 35 | scheduleLookahead = 100, // in milliseconds 36 | events = [], 37 | callback = () => { 38 | /* empty */ 39 | }, 40 | }: SchedulerOption 41 | ) { 42 | this.audioContext = audioContext 43 | this.audioNodes = audioNodes // keep track of all created audio nodes so they can be stopped 44 | this.scheduleInterval = scheduleInterval // schedule new events at this interval 45 | this.scheduleLookahead = scheduleLookahead // schedule new events this far into the future 46 | this.events = events // an events object that must at least contain the time property for each event 47 | this.callback = callback // a function used to play the events 48 | 49 | this.eventsIndexNeedle = 0 // a needle used to go through the events index by index 50 | this.timerID = -1 // used by clearTimeout() to identify the setTimeout timer 51 | this.startTimeOffset = 0 // the offset between the time at audioContext creation and audioContext.currentTime 52 | } 53 | 54 | private _stopAllAudioNodes() { 55 | for (const node of this.audioNodes) { 56 | node.stop(0) 57 | } 58 | } 59 | 60 | stop() { 61 | if (this.audioContext.state === 'running') { 62 | this.audioContext.suspend() 63 | } 64 | this._stopAllAudioNodes() 65 | clearTimeout(this.timerID) 66 | } 67 | 68 | reset() { 69 | if (this.audioContext.state === 'suspended') { 70 | this.audioContext.resume() 71 | } 72 | this.eventsIndexNeedle = 0 73 | this.timerID = -1 74 | this.startTimeOffset = this.audioContext.currentTime 75 | } 76 | 77 | start() { 78 | this.stop() 79 | this.reset() 80 | this.play() 81 | } 82 | 83 | play() { 84 | while ( 85 | this.eventsIndexNeedle < this.events.length && 86 | typeof this.events[this.eventsIndexNeedle].time === 'number' && 87 | this.events[this.eventsIndexNeedle].time + this.startTimeOffset < 88 | this.audioContext.currentTime + this.scheduleLookahead / 1000 89 | ) { 90 | this.callback(this.events[this.eventsIndexNeedle], this.startTimeOffset) 91 | this.eventsIndexNeedle++ 92 | } 93 | this.timerID = window.setTimeout(() => { 94 | this.play() 95 | }, this.scheduleInterval) 96 | } 97 | 98 | // pause() { 99 | // if (this.audioContext.state === 'running') { 100 | // this.audioContext.suspend() 101 | // } else if(this.audioContext.state === 'suspended') { 102 | // this.audioContext.resume() 103 | // } 104 | // } 105 | } 106 | 107 | export function playOnce( 108 | audioContext: AudioContext, 109 | target: GainNode | AudioDestinationNode, 110 | buffer: AudioBuffer 111 | ) { 112 | const soundSource = audioContext.createBufferSource() 113 | soundSource.buffer = buffer 114 | soundSource.connect(target) 115 | soundSource.start() 116 | } 117 | -------------------------------------------------------------------------------- /src/lib/basic/collections.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | declare global { 4 | interface Array { 5 | pairwise(): [T, T][] 6 | rotateNext(cur: T): T 7 | rotatePrev(cur: T): T 8 | } 9 | 10 | interface Array { 11 | closest(num: T, smaller?: boolean): T | undefined 12 | } 13 | 14 | interface ReadonlyArray { 15 | pairwise(): [T, T][] 16 | rotateNext(cur: T): T 17 | rotatePrev(cur: T): T 18 | } 19 | } 20 | 21 | Array.prototype.pairwise = function pairwise(): [T, T][] { 22 | return this.slice(1).map((val: T, i: number) => [this[i], val]) 23 | } 24 | 25 | Array.prototype.rotateNext = function rotateNext(cur: T): T { 26 | return this[(this.indexOf(cur) + 1) % this.length] 27 | } 28 | 29 | Array.prototype.rotatePrev = function rotatePrev(cur: T): T { 30 | return this[(this.indexOf(cur) - 1 + this.length) % this.length] 31 | } 32 | 33 | Array.prototype.closest = function closest( 34 | num: number, 35 | smaller = true 36 | ): number | undefined { 37 | const sorted = [...this].sort((a, b) => a - b) 38 | return (smaller ? sorted.reverse() : sorted).find((e) => 39 | smaller ? e <= num : e >= num 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/basic/debug.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store' 2 | 3 | type Value = string | number | boolean | null | undefined 4 | 5 | export type DebugInfo = Map 6 | export const debugInfo = writable(new Map()) 7 | 8 | export function formatPoint(x: number, y: number) { 9 | return `(${x?.toFixed(3)}, ${y?.toFixed(3)})` 10 | } 11 | 12 | export function dbg(title: string, value: Value) { 13 | debugInfo.update((map: DebugInfo) => { 14 | map.set(title, value) 15 | return map 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/basic/file.spec.ts: -------------------------------------------------------------------------------- 1 | import { formatFilename } from './file' 2 | 3 | describe('formatFilename', () => { 4 | beforeAll(() => { 5 | jest.useFakeTimers('modern') 6 | jest.setSystemTime(new Date('2020-01-01').getTime()) 7 | }) 8 | 9 | const formatData = { 10 | project: 'project', 11 | title: 'title', 12 | artist: 'artist', 13 | author: 'author', 14 | } 15 | 16 | it('should replace {keyword} with real data', () => { 17 | expect(formatFilename('{artist}-{title}.txt', formatData)).toBe( 18 | 'artist-title.txt' 19 | ) 20 | }) 21 | 22 | it('should replace {date} with real date', () => { 23 | expect(formatFilename('{project}_{date}.sus', formatData)).toBe( 24 | 'project_2020-01-01.sus' 25 | ) 26 | }) 27 | 28 | it('should replace {datetime} with filename-safe time', () => { 29 | expect(formatFilename('{title}_{datetime}.sus', formatData)).toBe( 30 | 'title_2020-01-01T00-00-00.000Z.sus' 31 | ) 32 | }) 33 | 34 | it('should keep unknown {keyword} as is', () => { 35 | expect(formatFilename('{unknown}.txt', formatData)).toBe('{unknown}.txt') 36 | }) 37 | 38 | it('should keep other strings intact', () => { 39 | expect(formatFilename('Untitled.txt', formatData)).toBe('Untitled.txt') 40 | }) 41 | 42 | afterAll(() => { 43 | jest.useRealTimers() 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /src/lib/basic/file.ts: -------------------------------------------------------------------------------- 1 | import { default as accepted } from 'attr-accept' 2 | 3 | export async function download(b: Blob, filename: string): Promise { 4 | if (!window.__TAURI__) { 5 | const a = document.createElement('a') 6 | document.body.append(a) 7 | a.download = filename 8 | a.href = URL.createObjectURL(b) 9 | a.click() 10 | a.remove() 11 | } else { 12 | const { tauri, dialog } = await import('@tauri-apps/api') 13 | const defaultSavePath = window.localStorage.getItem('default-save-path') 14 | const path = await dialog.save({ 15 | defaultPath: defaultSavePath ? defaultSavePath + filename : filename, 16 | }) 17 | await tauri.invoke('write_file', { 18 | path, 19 | data: [...new Uint8Array(await b.arrayBuffer())], 20 | }) 21 | window.localStorage.setItem( 22 | 'default-save-path', 23 | path.slice(0, path.lastIndexOf('/')) 24 | ) 25 | // await fs.writeBinaryFile({ 26 | // contents: , 27 | // path, 28 | // }) 29 | } 30 | } 31 | 32 | export function toBlob(content: string) { 33 | const blob = new Blob([content], { type: 'text/sus+plain' }) 34 | return blob 35 | } 36 | 37 | export function dropHandler( 38 | accept: string, 39 | callback: (file: File) => void | Promise, 40 | onerror: () => void | Promise 41 | ): (event: DragEvent) => Promise { 42 | return async (event: DragEvent) => { 43 | if (!event.dataTransfer || event.dataTransfer.items.length === 0) return 44 | const item = event.dataTransfer.items[0] 45 | if (item.kind !== 'file') return 46 | const file = item.getAsFile() 47 | event.stopPropagation() 48 | if (file && accepted(file, accept)) { 49 | await callback(file) 50 | } else { 51 | await onerror() 52 | } 53 | } 54 | } 55 | 56 | export function dropHandlerMultiple( 57 | handlers: { accept: string; callback: (file: File) => void }[], 58 | onerror: () => void 59 | ): (event: DragEvent) => void { 60 | return (event: DragEvent) => { 61 | if (!event.dataTransfer || event.dataTransfer.items.length === 0) return 62 | const item = event.dataTransfer.items[0] 63 | if (item.kind !== 'file') return 64 | const file = item.getAsFile() 65 | event.stopPropagation() 66 | for (const { accept, callback } of handlers) { 67 | if (file && accepted(file, accept)) { 68 | callback(file) 69 | return 70 | } 71 | } 72 | onerror() 73 | } 74 | } 75 | 76 | export type FormatData = { 77 | project: string 78 | title: string 79 | artist: string 80 | author: string 81 | } 82 | export const SUPPORTED_FORMAT_KEYWORDS = [ 83 | 'project', 84 | 'title', 85 | 'artist', 86 | 'author', 87 | 'datetime', 88 | 'date', 89 | ] 90 | export function formatFilename(format: string, data: FormatData): string { 91 | return format.replace(/{([^}]+)}/g, (match, key: string | undefined) => { 92 | switch (key) { 93 | case 'project': 94 | case 'title': 95 | case 'artist': 96 | case 'author': 97 | return data[key] 98 | case 'datetime': 99 | return new Date().toISOString().replace(/:/g, '-') 100 | case 'date': 101 | return new Date().toISOString().split('T')[0] 102 | default: 103 | return match 104 | } 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /src/lib/basic/math.ts: -------------------------------------------------------------------------------- 1 | export function clamp(min: number, number: number, max: number): number { 2 | return Math.min(Math.max(number, min), max) 3 | } 4 | 5 | export function gcd(a: number, b: number): number { 6 | if (a < b) return gcd(b, a) 7 | if (b == 0) return a 8 | return gcd(b, a % b) 9 | } 10 | 11 | export function snap(y: number, step: number): number { 12 | return Math.floor(y / step) * step 13 | } 14 | 15 | export function minmax(a: number, b: number): [number, number] { 16 | return [Math.min(a, b), Math.max(a, b)] 17 | } 18 | 19 | export function between( 20 | a: number, 21 | x: number, 22 | b: number, 23 | includeMin = true, 24 | includeMax = true 25 | ): boolean { 26 | const [min, max] = minmax(a, b) 27 | return (includeMin ? x >= min : x > min) && (includeMax ? x <= max : x < max) 28 | } 29 | 30 | // from: https://stackoverflow.com/a/59906630/2719898 31 | type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' 32 | type FixedLengthArray]> = Pick< 33 | TObj, 34 | Exclude 35 | > & { 36 | readonly length: L 37 | [I: number]: T 38 | [Symbol.iterator]: () => IterableIterator 39 | } 40 | 41 | /** 42 | * Calculate the average of multiple vectors with the same dimension 43 | * @param values an array of vectors 44 | * @returns average of all vectors 45 | */ 46 | export function average( 47 | values: FixedLengthArray[] 48 | ): number[] { 49 | const total = values.reduce( 50 | (acc, v) => acc.map((a, i) => a + v[i]), 51 | values[0].map(() => 0) 52 | ) 53 | return total.map((v) => v / values.length) 54 | } 55 | 56 | export function lerp(x: number, y: number, a: number): number { 57 | return x * (1 - a) + y * a 58 | } 59 | 60 | export function easeInQuad(x: number): number { 61 | return x * x 62 | } 63 | 64 | export function easeOutQuad(x: number): number { 65 | return 1 - (1 - x) * (1 - x) 66 | } 67 | 68 | export function cartesianProduct(arr: T[][]): T[][] { 69 | if (arr.length === 1) { 70 | return arr[0].map((n) => [n]) 71 | } 72 | const result: T[][] = [] 73 | const rest = cartesianProduct(arr.slice(1)) 74 | for (const n of arr[0]) { 75 | for (const r of rest) { 76 | // eslint-disable-next-line no-array-concat/no-array-concat 77 | result.push([n].concat(r)) 78 | } 79 | } 80 | return result 81 | } 82 | -------------------------------------------------------------------------------- /src/lib/colors.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | COLOR_BPM: 0x03c04a, 3 | COLOR_TIME_SIGNATURE: 0xff8938, 4 | COLOR_BACKGROUND: 0x000000, 5 | COLOR_LANE_PRIMARY: 0xffffff, 6 | COLOR_LANE_SECONDARY: 0x4c3b5c, 7 | COLOR_BAR_PRIMARY: 0xffffff, 8 | COLOR_BAR_SECONDARY: 0xcccccc, 9 | COLOR_PLAYHEAD: 0xff153f, 10 | COLOR_SLIDE_PATH: 0xdafdf0, 11 | COLOR_SLIDE_PATH_CRITICAL: 0xfffccc, 12 | ALPHA_SLIDE_PATH: 0.9, 13 | COLOR_SLIDE_STEP: 0x24e0a1, 14 | ALPHA_SLIDE_STEP: 1, 15 | ALPHA_SLIDE_STEP_FILL: 0.65, 16 | COLOR_SELECTION: 0x007bf8, 17 | ALPHA_SELECTION: 0.25, 18 | COLOR_STACKED: 0xd50f13, 19 | ALPHA_STACKED: 0.5, 20 | COLOR_CORRUPTED: 0x000000, 21 | ALPHA_CORRUPTED: 0.5, // TODO: fix typo 22 | COLOR_WARNING: 0xffb943, 23 | ALPHA_WARNING: 0.4, 24 | COLOR_MULTI_TAP: 0x00e5ea, 25 | ALPHA_MULTI_TAP: 0.3, 26 | COLOR_MOVING_TINT: 0xced3e4, 27 | ALPHA_FLOATING: 0.5, 28 | } 29 | 30 | // next color hue from the source 31 | export function getColor(source: number, i: number) { 32 | const [l, c, h] = convertRGBToLCH(source) 33 | return convertLCHtoRGB([l, c, (h + i * 50) % 360]) 34 | } 35 | 36 | function convertRGBToLCH(color: number) { 37 | const [r, g, b] = [(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff] 38 | const l = 0.2126 * r + 0.7152 * g + 0.0722 * b 39 | const c = Math.sqrt(0.299 * r ** 2 + 0.587 * g ** 2 + 0.114 * b ** 2) 40 | const h = (Math.atan2(b, r) * 180) / Math.PI 41 | return [l, c, h] 42 | } 43 | 44 | function convertLCHtoRGB(lch: [number, number, number]): number { 45 | const [l, c, h] = lch 46 | const r = l + c * Math.cos((h * Math.PI) / 180) 47 | const b = l + c * Math.cos(((h + 120) * Math.PI) / 180) 48 | const g = l + c * Math.cos(((h + 240) * Math.PI) / 180) 49 | return ( 50 | (Math.round(r * 255) << 16) + 51 | (Math.round(g * 255) << 8) + 52 | Math.round(b * 255) 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/consts.ts: -------------------------------------------------------------------------------- 1 | export { default as COLORS } from './colors' 2 | 3 | export const RESOLUTION = 1 4 | 5 | export const BEAT_UNIT = 4 6 | export const BEAT_IN_MEASURE = 4 7 | export const TICK_PER_BEAT = 480 8 | export const TICK_PER_MEASURE = TICK_PER_BEAT * BEAT_IN_MEASURE 9 | 10 | export const MARGIN = 100 11 | export const MARGIN_BOTTOM = 30 12 | 13 | export const TEXT_MARGIN = 20 14 | 15 | export const LANE_WIDTH = 30 16 | export const LANE_COUNT_REAL = 12 17 | export const LANE_MIN = 2 18 | export const LANE_MAX = 13 19 | export const LANE_SUS_MIN = 0 20 | export const LANE_SUS_MAX = 16 21 | export const LANE_SIDE_MAX = LANE_MAX + 1 22 | export const LANE_COUNT = LANE_COUNT_REAL + 2 23 | export const LANE_FEVER = 15 24 | export const LANE_SKILL = 0 25 | export const MEASURE_HEIGHT = 300 26 | export const TICK_HEIGHT = 0.15625 27 | export const MAIN_WIDTH = MARGIN * 2 + 30 * LANE_COUNT 28 | export const SCROLLBAR_WIDTH = 13 29 | export const MINIMAP_RESOLUTION = 0.2 30 | export const MINIMAP_WIDTH = MAIN_WIDTH * MINIMAP_RESOLUTION 31 | 32 | export const WIDTH_DEFAULT = 3 33 | 34 | export const SNAPTO_DEFAULT = 8 35 | 36 | export const NOTE_PIVOT = [0.14971751412, 0.5] 37 | export const NOTE_WIDTH = 43 38 | export const NOTE_HEIGHT = 30 39 | 40 | export const DIAMOND_PIVOT = [0.15189873417, 0.5] 41 | export const DIAMOND_WIDTH = 30 42 | export const DIAMOND_HEIGHT = (30 / 158) * 160 43 | 44 | export const ZOOM_MIN = 0.1 45 | export const ZOOM_MAX = 10.0 46 | export const ZOOM_STEP = 0.1 47 | export const ZOOM_DEFAULT = 1 48 | 49 | export const TEXTURE_NAMES = [ 50 | 'noteC.png', 51 | 'noteF.png', 52 | 'noteL.png', 53 | 'noteN.png', 54 | 'notes_flick_arrow_01.png', 55 | 'notes_flick_arrow_01_diagonal.png', 56 | 'notes_flick_arrow_02.png', 57 | 'notes_flick_arrow_02_diagonal.png', 58 | 'notes_flick_arrow_03.png', 59 | 'notes_flick_arrow_03_diagonal.png', 60 | 'notes_flick_arrow_04.png', 61 | 'notes_flick_arrow_04_diagonal.png', 62 | 'notes_flick_arrow_05.png', 63 | 'notes_flick_arrow_05_diagonal.png', 64 | 'notes_flick_arrow_06.png', 65 | 'notes_flick_arrow_06_diagonal.png', 66 | 'notes_flick_arrow_crtcl_01.png', 67 | 'notes_flick_arrow_crtcl_01_diagonal.png', 68 | 'notes_flick_arrow_crtcl_02.png', 69 | 'notes_flick_arrow_crtcl_02_diagonal.png', 70 | 'notes_flick_arrow_crtcl_03.png', 71 | 'notes_flick_arrow_crtcl_03_diagonal.png', 72 | 'notes_flick_arrow_crtcl_04.png', 73 | 'notes_flick_arrow_crtcl_04_diagonal.png', 74 | 'notes_flick_arrow_crtcl_05.png', 75 | 'notes_flick_arrow_crtcl_05_diagonal.png', 76 | 'notes_flick_arrow_crtcl_06.png', 77 | 'notes_flick_arrow_crtcl_06_diagonal.png', 78 | 'notes_long_among.png', 79 | 'notes_long_among_crtcl.png', 80 | ] 81 | 82 | import tapPerfect from '$assets/sound/perfect.mp3' 83 | import tapCritical from '$assets/sound/critical_tap.mp3' 84 | import flickCritical from '$assets/sound/critical_flick.mp3' 85 | import flick from '$assets/sound/flick.mp3' 86 | import tick from '$assets/sound/tick.mp3' 87 | import tickCritical from '$assets/sound/critical_tick.mp3' 88 | import connect from '$assets/sound/connect.mp3' 89 | import connectCritical from '$assets/sound/connect_critical.mp3' 90 | import stage from '$assets/sound/stage.mp3' 91 | 92 | export const EFFECT_SOUNDS = { 93 | tapPerfect, 94 | tapCritical, 95 | flick, 96 | flickCritical, 97 | tick, 98 | tickCritical, 99 | connect, 100 | connectCritical, 101 | stage, 102 | } 103 | 104 | /** 105 | * Z-indicies 106 | **/ 107 | export enum Z_INDEX { 108 | GRID, 109 | BAR, 110 | GAMESCRIPT, 111 | FLOATING_BAR, 112 | PLAYHEAD, 113 | SLIDE_PATH, 114 | STEP, 115 | DIAMOND, 116 | NOTE, 117 | ARROW, 118 | CONTROL, 119 | CONTROL_INTERACTION, 120 | MINIMAP, 121 | ERROR, 122 | SELECTION, 123 | FLOATING_SLIDE_PATH, 124 | FLOATING_STEP, 125 | FLOATING_DIAMOND, 126 | FLOATING_NOTE, 127 | FLOATING_ARROW, 128 | } 129 | -------------------------------------------------------------------------------- /src/lib/control/ControlHandler.svelte: -------------------------------------------------------------------------------- 1 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/lib/control/keyboard.ts: -------------------------------------------------------------------------------- 1 | export const KEYBOARD_SHORTCUTS = { 2 | skipstart: [['backspace'], ['home'], ['shift', '`']], 3 | skipback: [['`'], ['\\']], 4 | playpause: [['space']], 5 | duplicate: [['ctrl', 'd']], 6 | flip: [ 7 | ['ctrl', 'h'], 8 | ['shift', 'h'], 9 | ], 10 | vflip: [['shift', 'v']], 11 | copy: [['ctrl', 'c']], 12 | cut: [['ctrl', 'x']], 13 | paste: [['ctrl', 'v']], 14 | flippaste: [['ctrl', 'alt', 'v']], 15 | undo: [['ctrl', 'z']], 16 | redo: [ 17 | ['ctrl', 'y'], 18 | ['ctrl', 'shift', 'z'], 19 | ], 20 | save: [['ctrl', 's']], 21 | export: [ 22 | ['ctrl', 'e'], 23 | ['ctrl', 'shift', 's'], 24 | ], 25 | open: [['ctrl', 'o']], 26 | image: [['ctrl', 'i']], 27 | new: [['ctrl', 'n']], 28 | selectall: [['ctrl', 'a']], 29 | unselectall: [['ctrl', 'shift', 'a']], 30 | delete: [['delete']], 31 | increaseSnapTo: [['alt', '=']], 32 | decreaseSnapTo: [['alt', '-']], 33 | pageup: [['pageup']], 34 | pagedown: [['pagedown']], 35 | gotoup: [['up']], 36 | gotodown: [['down']], 37 | gotoupfast: [['shift', 'up']], 38 | gotodownfast: [['shift', 'down']], 39 | openmainmenu: [['ctrl', 'm']], 40 | } as const 41 | 42 | export type KeyboardAction = keyof typeof KEYBOARD_SHORTCUTS 43 | 44 | import { writable } from 'svelte/store' 45 | 46 | export const shiftKey = writable(false) 47 | export const ctrlKey = writable(false) 48 | export const altKey = writable(false) 49 | -------------------------------------------------------------------------------- /src/lib/control/pointer.ts: -------------------------------------------------------------------------------- 1 | export const MOUSE_BUTTON = { 2 | LEFT: 0, 3 | MIDDLE: 1, 4 | RIGHT: 2, 5 | } 6 | 7 | import moveCursor from '$assets/cursor/move-cursor.png' 8 | import resizeCursor from '$assets/cursor/resize-cursor.png' 9 | import selectCursor from '$assets/cursor/select-cursor.png' 10 | import grabCursor from '$assets/cursor/grab-cursor.png' 11 | 12 | export const CURSOR_STYLES = { 13 | move: `url(${moveCursor}) 16 16, move`, 14 | resize: `url(${resizeCursor}) 16 16, ew-resize`, 15 | select: `url(${selectCursor}) 6 4, default`, 16 | grab: `url(${grabCursor}) 16 16, default`, 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/database.ts: -------------------------------------------------------------------------------- 1 | import type { Metadata, Score } from '$lib/score/beatmap' 2 | import { serialiseScore, deserialiseScore } from '$lib/score/beatmap' 3 | export interface Project { 4 | id?: number 5 | name: string | null 6 | created: Date 7 | updated: Date 8 | metadata: Metadata 9 | score: Score 10 | preview: Blob 11 | music: File | null 12 | } 13 | 14 | export interface Preferences { 15 | key?: string 16 | value: unknown 17 | } 18 | 19 | import { Dexie, liveQuery } from 'dexie' 20 | 21 | class Database extends Dexie { 22 | projects: Dexie.Table 23 | preferences: Dexie.Table 24 | constructor() { 25 | super('PaletteWorks') 26 | this.version(2).stores({ 27 | projects: '++id,name,created,updated,metadata,score,music,preview', 28 | preferences: 'key,value', 29 | }) 30 | this.projects = this.table('projects') 31 | this.preferences = this.table('preferences') 32 | } 33 | } 34 | 35 | export const db = new Database() 36 | 37 | export const projects = liveQuery(async () => 38 | (await db.projects.toArray()).reverse() 39 | ) 40 | export const preferences = liveQuery(async () => 41 | Object.fromEntries( 42 | (await db.preferences.toArray()).map(({ key, value }) => [key, value]) 43 | ) 44 | ) 45 | 46 | import msgpack from 'msgpack-lite' 47 | 48 | export async function seriliseProject(project: Project): Promise { 49 | const { name, created, updated, metadata, score, preview, music } = project 50 | const data = msgpack.encode({ 51 | version: 1, 52 | name, 53 | created: created.getTime(), 54 | updated: updated.getTime(), 55 | metadata, 56 | score: serialiseScore(score), 57 | preview: await preview.arrayBuffer(), 58 | music: music 59 | ? { 60 | data: await music.arrayBuffer(), 61 | name: music.name, 62 | type: music.type, 63 | lastModified: music.lastModified, 64 | } 65 | : null, 66 | }) 67 | return new Blob([data.buffer], { type: 'application/octet-binary' }) 68 | } 69 | 70 | export async function deserialiseProject(blob: Blob): Promise { 71 | const data = msgpack.decode(new Uint8Array(await blob.arrayBuffer())) 72 | const { version, name, created, updated, metadata, score, preview, music } = 73 | data 74 | if (version !== 1) throw new Error('Unsupported version') 75 | return { 76 | name, 77 | created: new Date(created), 78 | updated: new Date(updated), 79 | metadata, 80 | score: deserialiseScore(score), 81 | preview: new Blob([preview], { type: 'application/octet-binary' }), 82 | music: music 83 | ? new File([music.data], music.name, { 84 | type: music.type, 85 | lastModified: music.lastModified, 86 | }) 87 | : null, 88 | } 89 | } 90 | 91 | export const PROJECT_FILE_EXTENSION = '.pws' // PaletteWorks Score File 92 | -------------------------------------------------------------------------------- /src/lib/dialogs/AboutDialog.svelte: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 34 | { 37 | inputElement.focus() 38 | inputElement.select() 39 | }} 40 | > 41 |
42 |

{$LL.editor.dialog.about()}

43 |
44 | { 48 | dispatch('cancel') 49 | opened = false 50 | }} 51 | /> 52 |
53 |
54 | v{process.env.PACKAGE_VERSION} 55 |
56 | MIT License © 2021 mkpoli 57 |
58 | 95 |
96 | 97 | 107 |
108 |
109 | 110 | 171 | -------------------------------------------------------------------------------- /src/lib/dialogs/BPMDialog.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | { 23 | inputElement.focus() 24 | inputElement.select() 25 | }} 26 | > 27 |