Compare commits

...

9 Commits
0.3.0 ... 0.5.0

Author SHA1 Message Date
b10c797d52 docs: update README.md 2025-04-10 14:10:47 +00:00
ff0a37dfdc feat: skeleton of proper local package support 2025-04-10 14:07:44 +00:00
d63d81e349 feat: also copy aime.txt 2025-04-10 13:53:20 +00:00
9ea66dbeab feat: segatools.ini loading 2025-04-10 13:32:49 +00:00
4e795257ad feat: add dont_switch_primary 2025-04-08 21:49:15 +00:00
41fbef0260 chore: bump ver 2025-04-04 22:20:05 +00:00
93e0a7e313 feat: new error banner, qol 2025-04-04 22:14:09 +00:00
ca871f069f feat: autoupdate toggle 2025-04-04 19:41:38 +00:00
8c3f9762a4 feat: ui scaling, update all 2025-04-04 14:37:16 +00:00
40 changed files with 1537 additions and 438 deletions

View File

@ -1,6 +1,6 @@
# STARTLINER
A simple and easy to use launcher, configuration tool and mod manager for [many games](https://silentblue.remywiki.com/ONGEKI:bright_MEMORY) (more to come) using [Rainycolor Watercolor](https://rainy.patafour.zip).
A simple and easy to use launcher, configuration tool and mod manager for O.N.G.E.K.I. and CHUNITHM, using [Rainycolor Watercolor](https://rainy.patafour.zip).
Made with Rust (Tauri) and Vue. Technically multiplatform. Contributions welcome.
@ -23,7 +23,7 @@ bun install
bun run tauri build
```
Create a profile, then click on things in the configuration tab (game path, `amfs` and network at the least). STARTLINER expects clean data with unpacked binaries. Anything else you can have in the game directory (segatools, BepInEx, etc.) can be present, but will not be used.
Create a profile, then click on things in the configuration tab (game path, `amfs` and network at the least). STARTLINER expects clean data with unpacked binaries. Anything else you may have in the game directory (segatools, BepInEx, etc.) can be present, but will not be used.
Once a profile has been set up, it is possible to bypass the GUI:

242
bun.lock
View File

@ -5,40 +5,42 @@
"name": "startliner",
"dependencies": {
"@mdi/font": "7.4.47",
"@primevue/forms": "^4.3.1",
"@primevue/themes": "^4.3.1",
"@tailwindcss/vite": "^4.0.9",
"@tauri-apps/api": "^2.3.0",
"@tauri-apps/plugin-deep-link": "~2.2.0",
"@tauri-apps/plugin-dialog": "~2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
"@primevue/forms": "^4.3.3",
"@primevue/themes": "^4.3.3",
"@tailwindcss/vite": "^4.1.2",
"@tauri-apps/api": "^2.4.1",
"@tauri-apps/plugin-cli": "^2.2.0",
"@tauri-apps/plugin-deep-link": "~2.2.1",
"@tauri-apps/plugin-dialog": "~2.2.1",
"@tauri-apps/plugin-fs": "^2.2.1",
"@tauri-apps/plugin-opener": "^2.2.6",
"@tauri-apps/plugin-shell": "~2.2.0",
"@tauri-apps/plugin-shell": "~2.2.1",
"@tauri-apps/plugin-updater": "^2.7.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"pinia": "^3.0.1",
"primeicons": "^7.0.0",
"primevue": "^4.3.1",
"primevue": "^4.3.3",
"roboto-fontface": "^0.10.0",
"tailwindcss": "^4.0.9",
"tailwindcss": "^4.1.2",
"tailwindcss-primeui": "^0.4.0",
"vue": "^3.5.13",
"vuetify": "^3.7.14",
"vuetify": "^3.8.0",
},
"devDependencies": {
"@tauri-apps/cli": "^2.3.1",
"@tsconfig/node22": "^22.0.0",
"@types/node": "^22.13.9",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-typescript": "^14.4.0",
"@tauri-apps/cli": "^2.4.1",
"@tsconfig/node22": "^22.0.1",
"@types/node": "^22.14.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/eslint-config-typescript": "^14.5.0",
"@vue/tsconfig": "^0.5.1",
"npm-run-all2": "^7.0.2",
"sass": "1.77.8",
"sass-embedded": "^1.85.1",
"sass-embedded": "^1.86.3",
"typescript": "^5.8.2",
"unplugin-fonts": "^1.3.1",
"unplugin-vue-components": "^0.27.5",
"vite": "^6.2.0",
"vite-plugin-vuetify": "^2.1.0",
"vite": "^6.2.5",
"vite-plugin-vuetify": "^2.1.1",
"vue-tsc": "^2.2.8",
},
},
@ -166,13 +168,13 @@
"@primeuix/utils": ["@primeuix/utils@0.5.1", "", {}, "sha512-/bYirtF3gJOGrRQfQ5tUyQOLEria7wg/UCqvpIydTAxLmj/UWgWwh2kAjYVp49eldm1+2sk4+TDkbAz8XcPpew=="],
"@primevue/core": ["@primevue/core@4.3.1", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/utils": "^0.5.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Z2JfYk7I477qdlWHH5yiqUK0cwNe5joZJLwFtSEFkmBi/ocXvkGNAYk8XYNCz6UTDePUQSHKseKJxMkFHlfRtw=="],
"@primevue/core": ["@primevue/core@4.3.3", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/utils": "^0.5.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-kSkN5oourG7eueoFPIqiNX3oDT/f0I5IRK3uOY/ytz+VzTZp5yuaCN0Nt42ZQpVXjDxMxDvUhIdaXVrjr58NhQ=="],
"@primevue/forms": ["@primevue/forms@4.3.1", "", { "dependencies": { "@primeuix/forms": "^0.0.4", "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.1" } }, "sha512-2IWIrRCV82ey7fUb0dQhnJX8iqddcyZNajsEQpRMw1MO8NchN+vT26H2H1AYDI5uwwmv1FCqr+MiU9wk39Ub2g=="],
"@primevue/forms": ["@primevue/forms@4.3.3", "", { "dependencies": { "@primeuix/forms": "^0.0.4", "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.3" } }, "sha512-GZYMd8wp+7/4DVMoGGUtRkAHw352peT3pgwgzaFYQqNIjxxGw9eI253XTxrppRCowrGJ2jEe80p9WfHi087B1g=="],
"@primevue/icons": ["@primevue/icons@4.3.1", "", { "dependencies": { "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.1" } }, "sha512-67GFk/NdbVDuPx4tlbO01BBWujLiZTJJJSce63dvLr7082YukPfrQq4Kru+y5Qmrfkq0uaP1I3+Ut9Skr6ATfQ=="],
"@primevue/icons": ["@primevue/icons@4.3.3", "", { "dependencies": { "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.3" } }, "sha512-ouQaxHyeFB6MSfEGGbjaK5Qv9efS1xZGetZoU5jcPm090MSYLFtroP1CuK3lZZAQals06TZ6T6qcoNukSHpK5w=="],
"@primevue/themes": ["@primevue/themes@4.3.1", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/themes": "^1.0.0" } }, "sha512-aC7V5BkMoMxEMoq7hIf+PNEaY/AS5EAgVg96Gf3bvV1JqZ36hSQ2CV1SfuNsWeEvIPF/u4L5qvjwC/LGjT4qgw=="],
"@primevue/themes": ["@primevue/themes@4.3.3", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/themes": "^1.0.0" } }, "sha512-LiYlSXsHeA8DFm8+yGyiDFQc3SEQwHcESTN1/rV+rrZ+UPuPisHY9fNIGRFQKA5XUQPDTQDQjtwYGx25Jikwhg=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
@ -214,95 +216,101 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.6", "", { "os": "win32", "cpu": "x64" }, "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w=="],
"@tailwindcss/node": ["@tailwindcss/node@4.0.9", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "tailwindcss": "4.0.9" } }, "sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.2", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.29.2", "tailwindcss": "4.1.2" } }, "sha512-ZwFnxH+1z8Ehh8bNTMX3YFrYdzAv7JLY5X5X7XSFY+G9QGJVce/P9xb2mh+j5hKt8NceuHmdtllJvAHWKtsNrQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.9", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.9", "@tailwindcss/oxide-darwin-arm64": "4.0.9", "@tailwindcss/oxide-darwin-x64": "4.0.9", "@tailwindcss/oxide-freebsd-x64": "4.0.9", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.9", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.9", "@tailwindcss/oxide-linux-arm64-musl": "4.0.9", "@tailwindcss/oxide-linux-x64-gnu": "4.0.9", "@tailwindcss/oxide-linux-x64-musl": "4.0.9", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.9", "@tailwindcss/oxide-win32-x64-msvc": "4.0.9" } }, "sha512-eLizHmXFqHswJONwfqi/WZjtmWZpIalpvMlNhTM99/bkHtUs6IqgI1XQ0/W5eO2HiRQcIlXUogI2ycvKhVLNcA=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.2", "@tailwindcss/oxide-darwin-arm64": "4.1.2", "@tailwindcss/oxide-darwin-x64": "4.1.2", "@tailwindcss/oxide-freebsd-x64": "4.1.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.2", "@tailwindcss/oxide-linux-arm64-musl": "4.1.2", "@tailwindcss/oxide-linux-x64-gnu": "4.1.2", "@tailwindcss/oxide-linux-x64-musl": "4.1.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.2", "@tailwindcss/oxide-win32-x64-msvc": "4.1.2" } }, "sha512-Zwz//1QKo6+KqnCKMT7lA4bspGfwEgcPAHlSthmahtgrpKDfwRGk8PKQrW8Zg/ofCDIlg6EtjSTKSxxSufC+CQ=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.9", "", { "os": "android", "cpu": "arm64" }, "sha512-YBgy6+2flE/8dbtrdotVInhMVIxnHJPbAwa7U1gX4l2ThUIaPUp18LjB9wEH8wAGMBZUb//SzLtdXXNBHPUl6Q=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.2", "", { "os": "android", "cpu": "arm64" }, "sha512-IxkXbntHX8lwGmwURUj4xTr6nezHhLYqeiJeqa179eihGv99pRlKV1W69WByPJDQgSf4qfmwx904H6MkQqTA8w=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-pWdl4J2dIHXALgy2jVkwKBmtEb73kqIfMpYmcgESr7oPQ+lbcQ4+tlPeVXaSAmang+vglAfFpXQCOvs/aGSqlw=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZRtiHSnFYHb4jHKIdzxlFm6EDfijTCOT4qwUhJ3GWxfDoW2yT3z/y8xg0nE7e72unsmSj6dtfZ9Y5r75FIrlpA=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4Dq3lKp0/C7vrRSkNPtBGVebEyWt9QPPlQctxJ0H3MDyiQYvzVYf8jKow7h5QkWNe8hbatEqljMj/Y0M+ERYJg=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BiKUNZf1A0pBNzndBvnPnBxonCY49mgbOsPfILhcCE5RM7pQlRoOgN7QnwNhY284bDbfQSEOWnFR0zbPo6IDTw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-k7U1RwRODta8x0uealtVt3RoWAWqA+D5FAOsvVGpYoI6ObgmnzqWW6pnVwz70tL8UZ/QXjeMyiICXyjzB6OGtQ=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Z30VcpUfRGkiddj4l5NRCpzbSGjhmmklVoqkVQdkEC0MOelpY+fJrVhzSaXHmWrmSvnX8yiaEqAbdDScjVujYQ=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9", "", { "os": "linux", "cpu": "arm" }, "sha512-NDDjVweHz2zo4j+oS8y3KwKL5wGCZoXGA9ruJM982uVJLdsF8/1AeKvUwKRlMBpxHt1EdWJSAh8a0Mfhl28GlQ=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.2", "", { "os": "linux", "cpu": "arm" }, "sha512-w3wsK1ChOLeQ3gFOiwabtWU5e8fY3P1Ss8jR3IFIn/V0va3ir//hZ8AwURveS4oK1Pu6b8i+yxesT4qWnLVUow=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-jk90UZ0jzJl3Dy1BhuFfRZ2KP9wVKMXPjmCtY4U6fF2LvrjP5gWFJj5VHzfzHonJexjrGe1lMzgtjriuZkxagg=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oY/u+xJHpndTj7B5XwtmXGk8mQ1KALMfhjWMMpE8pdVAznjJsF5KkCceJ4Fmn5lS1nHMCwZum5M3/KzdmwDMdw=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-3eMjyTC6HBxh9nRgOHzrc96PYh1/jWOwHZ3Kk0JN0Kl25BJ80Lj9HEvvwVDNTgPg154LdICwuFLuhfgH9DULmg=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-k7G6vcRK/D+JOWqnKzKN/yQq1q4dCkI49fMoLcfs2pVcaUAXEqCP9NmA8Jv+XahBv5DtDjSAY3HJbjosEdKczg=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.9", "", { "os": "linux", "cpu": "x64" }, "sha512-v0D8WqI/c3WpWH1kq/HP0J899ATLdGZmENa2/emmNjubT0sWtEke9W9+wXeEoACuGAhF9i3PO5MeyditpDCiWQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.2", "", { "os": "linux", "cpu": "x64" }, "sha512-fLL+c678TkYKgkDLLNxSjPPK/SzTec7q/E5pTwvpTqrth867dftV4ezRyhPM5PaiCqX651Y8Yk0wRQMcWUGnmQ=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.9", "", { "os": "linux", "cpu": "x64" }, "sha512-Kvp0TCkfeXyeehqLJr7otsc4hd/BUPfcIGrQiwsTVCfaMfjQZCG7DjI+9/QqPZha8YapLA9UoIcUILRYO7NE1Q=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0tU1Vjd1WucZ2ooq6y4nI9xyTSaH2g338bhrqk+2yzkMHskBm+pMsOCfY7nEIvALkA1PKPOycR4YVdlV7Czo+A=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-m3+60T/7YvWekajNq/eexjhV8z10rswcz4BC9bioJ7YaN+7K8W2AmLmG0B79H14m6UHE571qB0XsPus4n0QVgQ=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-r8QaMo3QKiHqUcn+vXYCypCEha+R0sfYxmaZSgZshx9NfkY+CHz91aS2xwNV/E4dmUDkTPUag7sSdiCHPzFVTg=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.9", "", { "os": "win32", "cpu": "x64" }, "sha512-dpc05mSlqkwVNOUjGu/ZXd5U1XNch1kHFJ4/cHkZFvaW1RzbHmRt24gvM8/HC6IirMxNarzVw4IXVtvrOoZtxA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.2", "", { "os": "win32", "cpu": "x64" }, "sha512-lYCdkPxh9JRHXoBsPE8Pu/mppUsC2xihYArNAESub41PKhHTnvn6++5RpmFM+GLSt3ewyS8fwCVvht7ulWm6cw=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.0.9", "", { "dependencies": { "@tailwindcss/node": "4.0.9", "@tailwindcss/oxide": "4.0.9", "lightningcss": "^1.29.1", "tailwindcss": "4.0.9" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-BIKJO+hwdIsN7V6I7SziMZIVHWWMsV/uCQKYEbeiGRDRld+TkqyRRl9+dQ0MCXbhcVr+D9T/qX2E84kT7V281g=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.2", "", { "dependencies": { "@tailwindcss/node": "4.1.2", "@tailwindcss/oxide": "4.1.2", "tailwindcss": "4.1.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-3r/ZdMW0gxY8uOx1To0lpYa4coq4CzINcCX4laM1rS340Kcn0ac4A/MMFfHN8qba51aorZMYwMcOxYk4wJ9FYg=="],
"@tauri-apps/api": ["@tauri-apps/api@2.3.0", "", {}, "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA=="],
"@tauri-apps/api": ["@tauri-apps/api@2.4.1", "", {}, "sha512-5sYwZCSJb6PBGbBL4kt7CnE5HHbBqwH+ovmOW6ZVju3nX4E3JX6tt2kRklFEH7xMOIwR0btRkZktuLhKvyEQYg=="],
"@tauri-apps/cli": ["@tauri-apps/cli@2.3.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.3.1", "@tauri-apps/cli-darwin-x64": "2.3.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.3.1", "@tauri-apps/cli-linux-arm64-gnu": "2.3.1", "@tauri-apps/cli-linux-arm64-musl": "2.3.1", "@tauri-apps/cli-linux-x64-gnu": "2.3.1", "@tauri-apps/cli-linux-x64-musl": "2.3.1", "@tauri-apps/cli-win32-arm64-msvc": "2.3.1", "@tauri-apps/cli-win32-ia32-msvc": "2.3.1", "@tauri-apps/cli-win32-x64-msvc": "2.3.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-xewcw/ZsCqgilTy2h7+pp2Baxoy7zLR2wXOV7SZLzkb6SshHVbm1BFAjn8iFATURRW85KLzl6wSGJ2dQHjVHqw=="],
"@tauri-apps/cli": ["@tauri-apps/cli@2.4.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.4.1", "@tauri-apps/cli-darwin-x64": "2.4.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.4.1", "@tauri-apps/cli-linux-arm64-gnu": "2.4.1", "@tauri-apps/cli-linux-arm64-musl": "2.4.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.4.1", "@tauri-apps/cli-linux-x64-gnu": "2.4.1", "@tauri-apps/cli-linux-x64-musl": "2.4.1", "@tauri-apps/cli-win32-arm64-msvc": "2.4.1", "@tauri-apps/cli-win32-ia32-msvc": "2.4.1", "@tauri-apps/cli-win32-x64-msvc": "2.4.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-9Ta81jx9+57FhtU/mPIckDcOBtPTUdKM75t4+aA0X84b8Sclb0jy1xA8NplmcRzp2fsfIHNngU2NiRxsW5+yOQ=="],
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.3.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TOhSdsXYt+f+asRU+Dl+Wufglj/7+CX9h8RO4hl5k7D6lR4L8yTtdhpS7btaclOMmjYC4piNfJE70GoxhOoYWw=="],
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.4.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QME7s8XQwy3LWClTVlIlwXVSLKkeJ/z88pr917Mtn9spYOjnBfsgHAgGdmpWD3NfJxjg7CtLbhH49DxoFL+hLg=="],
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.3.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-LDwGg3AuBQ3aCeMAFaFwt0MSGOVFoXuXEe0z4QxQ7jZE5tdAOhKABaq4i569V5lShCgQZ6nLD/tmA5+GipvHnA=="],
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.4.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/r89IcW6Ya1sEsFUEH7wLNruDTj7WmDWKGpPy7gATFtQr5JEY4heernqE82isjTUimnHZD8SCr0jA3NceI4ybw=="],
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.3.1", "", { "os": "linux", "cpu": "arm" }, "sha512-hu3HpbbtJBvHXw5i54QHwLxOUoXWqhf7CL2YYSPOrWEEQo10NKddulP61L5gfr5z+bSSaitfLwqgTidgnaNJCA=="],
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.4.1", "", { "os": "linux", "cpu": "arm" }, "sha512-9tDijkRB+CchAGjXxYdY9l/XzFpLp1yihUtGXJz9eh+3qIoRI043n3e+6xmU8ZURr7XPnu+R4sCmXs6HD+NCEQ=="],
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mEGgwkiGSKYXWHhGodo7zU9PCd2I/d6KkR+Wp1nzK+DxsCrEK6yJ5XxYLSQSDcKkM4dCxpVEPUiVMbDhmn08jg=="],
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.4.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-pnFGDEXBAzS4iDYAVxTRhAzNu3K2XPGflYyBc0czfHDBXopqRgMyj5Q9Wj7HAwv6cM8BqzXINxnb2ZJFGmbSgA=="],
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.3.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-tqQkafikGfnc7ISnGjSYkbpnzJKEyO8XSa0YOXTAL3J8R5Pss5ZIZY7G8kq1mwQSR/dPVR1ZLTVXgZGuysjP8w=="],
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.4.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Hp0zXgeZNKmT+eoJSCxSBUm2QndNuRxR55tmIeNm3vbyUMJN/49uW7nurZ5fBPsacN4Pzwlx1dIMK+Gnr9A69w=="],
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-I3puDJ2wGEauXlXbzIHn2etz78TaWs1cpN6zre02maHr6ZR7nf7euTCOGPhhfoMG0opA5mT/eLuYpVw648/VAA=="],
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.4.1", "", { "os": "linux", "cpu": "none" }, "sha512-3T3bo2E4fdYRvzcXheWUeQOVB+LunEEi92iPRgOyuSVexVE4cmHYl+MPJF+EUV28Et0hIVTsHibmDO0/04lAFg=="],
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.3.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rbWiCOBuQN7tPySkUyBs914uUikE3mEUOqV/IFospvKESw4UC3G1DL5+ybfXH7Orb8/in3JpJuVzYQjo+OSbBA=="],
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.4.1", "", { "os": "linux", "cpu": "x64" }, "sha512-kLN0FdNONO+2i+OpU9+mm6oTGufRC00e197TtwjpC0N6K2K8130w7Q3FeODIM2CMyg0ov3tH+QWqKW7GNhHFzg=="],
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.3.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-PdTmUzSeTHjJuBpCV7L+V29fPhPtToU+NZU46slHKSA1aT38MiFDXBZ/6P5Zudrt9QPMfIubqnJKbK8Ivvv7Ww=="],
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.4.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8exvA5Ub9eg66a6hsMQKJIkf63QAf9OdiuFKOsEnKZkNN2x0NLgfvEcqdw88VY0UMs9dBoZ1AGbWMeYnLrLwQ=="],
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.3.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-K/Xa97kspWT4UWj3t26lL2D3QsopTAxS7kWi5kObdqtAGn3qD52qBi24FH38TdvHYz4QlnLIb30TukviCgh4gw=="],
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.4.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-4JFrslsMCJQG1c573T9uqQSAbF3j/tMKkMWzsIssv8jvPiP++OG61A2/F+y9te9/Q/O95cKhDK63kaiO5xQaeg=="],
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.3.1", "", { "os": "win32", "cpu": "x64" }, "sha512-RgwzXbP8gAno3kQEsybMtgLp6D1Z1Nec2cftryYbPTJmoMJs6e4qgtxuTSbUz5SKnHe8rGgMiFSvEGoHvbG72Q=="],
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.4.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-9eXfFORehYSCRwxg2KodfmX/mhr50CI7wyBYGbPLePCjr5z0jK/9IyW6r0tC+ZVjwpX48dkk7hKiUgI25jHjzA=="],
"@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-H6mkxr2KZ3XJcKL44tiq6cOjCw9DL8OgU1xjn3j26Qsn+H/roPFiyhR7CHuB8Ar+sQFj4YVlfmJwtBajK2FETQ=="],
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.4.1", "", { "os": "win32", "cpu": "x64" }, "sha512-60a4Ov7Jrwqz2hzDltlS7301dhSAmM9dxo+IRBD3xz7yobKrgaHXYpWvnRomYItHcDd51VaKc9292H8/eE/gsw=="],
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-6bLkYK68zyK31418AK5fNccCdVuRnNpbxquCl8IqgFByOgWFivbiIlvb79wpSXi0O+8k8RCSsIpOquebusRVSg=="],
"@tauri-apps/plugin-cli": ["@tauri-apps/plugin-cli@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-rvNhMog9rHr01Xk+trBFKJ0eZICIvPkm9GX6ogB89/0hROU/lf+a/sb4vC0wtSeR7zrJuCSxwxYuvHCZheaYFA=="],
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-+08mApuONKI8/sCNEZ6AR8vf5vI9DXD4YfrQ9NQmhRxYKMLVhRW164vdW5BSLmMpuevftpQ2FVoL9EFkfG9Z+g=="],
"@tauri-apps/plugin-deep-link": ["@tauri-apps/plugin-deep-link@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-8skZ6qIH/kWaV8d6jj3aPvvkIOuqkVk0APRDey9n9N3Ueu3n4MIbuxpAKR2EdoAyQxnXxPTNVyjw2D35/vfGyg=="],
"@tauri-apps/plugin-dialog": ["@tauri-apps/plugin-dialog@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-wZmCouo4PgTosh/UoejPw9DPs6RllS5Pp3fuOV2JobCu36mR5AXU2MzU9NZiVaFi/5Zfc8RN0IhcZHnksJ1o8A=="],
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-KdGzvvA4Eg0Dhw55MwczFbjxLxsTx0FvwwC/0StXlr6IxwPUxh5ziZQoaugkBFs8t+wfebdQrjBEzd8NmmDXNw=="],
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.2.6", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-bSdkuP71ZQRepPOn8BOEdBKYJQvl6+jb160QtJX/i2H9BF6ZySY/kYljh76N2Ne5fJMQRge7rlKoStYQY5Jq1w=="],
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.2.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA=="],
"@tauri-apps/plugin-shell": ["@tauri-apps/plugin-shell@2.2.1", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-G1GFYyWe/KlCsymuLiNImUgC8zGY0tI0Y3p8JgBCWduR5IEXlIJS+JuG1qtveitwYXlfJrsExt3enhv5l2/yhA=="],
"@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.7.0", "", { "dependencies": { "@tauri-apps/api": "^2.0.0" } }, "sha512-oBug5UCH2wOsoYk0LW5LEMAT51mszjg11s8eungRH26x/qOrEjLvnuJJoxVVr9nsWowJ6vnpXKS+lUMfFTlvHQ=="],
"@trivago/prettier-plugin-sort-imports": ["@trivago/prettier-plugin-sort-imports@5.2.2", "", { "dependencies": { "@babel/generator": "^7.26.5", "@babel/parser": "^7.26.7", "@babel/traverse": "^7.26.7", "@babel/types": "^7.26.7", "javascript-natural-sort": "^0.7.1", "lodash": "^4.17.21" }, "peerDependencies": { "@vue/compiler-sfc": "3.x", "prettier": "2.x - 3.x", "prettier-plugin-svelte": "3.x", "svelte": "4.x || 5.x" }, "optionalPeers": ["@vue/compiler-sfc", "prettier-plugin-svelte", "svelte"] }, "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA=="],
"@tsconfig/node22": ["@tsconfig/node22@22.0.0", "", {}, "sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg=="],
"@tsconfig/node22": ["@tsconfig/node22@22.0.1", "", {}, "sha512-VkgOa3n6jvs1p+r3DiwBqeEwGAwEvnVCg/hIjiANl5IEcqP3G0u5m8cBJspe1t9qjZRlZ7WFgqq5bJrGdgAKMg=="],
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
"@types/node": ["@types/node@22.14.0", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.24.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/type-utils": "8.24.0", "@typescript-eslint/utils": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.29.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/type-utils": "8.29.0", "@typescript-eslint/utils": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.24.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/types": "8.24.0", "@typescript-eslint/typescript-estree": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.29.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/types": "8.29.0", "@typescript-eslint/typescript-estree": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0" } }, "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0" } }, "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.24.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.24.0", "@typescript-eslint/utils": "8.24.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.29.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.29.0", "@typescript-eslint/utils": "8.29.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.24.0", "", {}, "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.29.0", "", {}, "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "@typescript-eslint/visitor-keys": "8.24.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "@typescript-eslint/visitor-keys": "8.29.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.24.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.24.0", "@typescript-eslint/types": "8.24.0", "@typescript-eslint/typescript-estree": "8.24.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.29.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.29.0", "@typescript-eslint/types": "8.29.0", "@typescript-eslint/typescript-estree": "8.29.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.24.0", "", { "dependencies": { "@typescript-eslint/types": "8.24.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.29.0", "", { "dependencies": { "@typescript-eslint/types": "8.29.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.1", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ=="],
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.3", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg=="],
"@volar/language-core": ["@volar/language-core@2.4.11", "", { "dependencies": { "@volar/source-map": "2.4.11" } }, "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg=="],
@ -326,7 +334,7 @@
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.2", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA=="],
"@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.4.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.23.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.23.0", "vue-eslint-parser": "^9.4.3" }, "peerDependencies": { "eslint": "^9.10.0", "eslint-plugin-vue": "^9.28.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-daU+eAekEeVz3CReE4PRW25fe+OJDKwE28jHN6LimDEnuFMbJ6H4WGogEpNof276wVP6UvzOeJQfLFjB5mW29A=="],
"@vue/eslint-config-typescript": ["@vue/eslint-config-typescript@14.5.0", "", { "dependencies": { "@typescript-eslint/utils": "^8.26.0", "fast-glob": "^3.3.3", "typescript-eslint": "^8.26.0", "vue-eslint-parser": "^10.1.1" }, "peerDependencies": { "eslint": "^9.10.0", "eslint-plugin-vue": "^9.28.0 || ^10.0.0", "typescript": ">=4.8.4" }, "optionalPeers": ["typescript"] }, "sha512-5oPOyuwkw++AP5gHDh5YFmST50dPfWOcm3/W7Nbh42IK5O3H74ytWAw0TrCRTaBoD/02khnWXuZf1Bz1xflavQ=="],
"@vue/language-core": ["@vue/language-core@2.2.8", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^1.0.3", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ=="],
@ -402,7 +410,7 @@
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
@ -510,27 +518,27 @@
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lightningcss": ["lightningcss@1.29.1", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.1", "lightningcss-darwin-x64": "1.29.1", "lightningcss-freebsd-x64": "1.29.1", "lightningcss-linux-arm-gnueabihf": "1.29.1", "lightningcss-linux-arm64-gnu": "1.29.1", "lightningcss-linux-arm64-musl": "1.29.1", "lightningcss-linux-x64-gnu": "1.29.1", "lightningcss-linux-x64-musl": "1.29.1", "lightningcss-win32-arm64-msvc": "1.29.1", "lightningcss-win32-x64-msvc": "1.29.1" } }, "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q=="],
"lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.1", "", { "os": "linux", "cpu": "arm" }, "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.2", "", { "os": "linux", "cpu": "arm" }, "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.1", "", { "os": "win32", "cpu": "x64" }, "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
"local-pkg": ["local-pkg@0.5.1", "", { "dependencies": { "mlly": "^1.7.3", "pkg-types": "^1.2.1" } }, "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ=="],
@ -608,7 +616,7 @@
"primeicons": ["primeicons@7.0.0", "", {}, "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="],
"primevue": ["primevue@4.3.1", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/styles": "^1.0.0", "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.1", "@primevue/icons": "4.3.1" } }, "sha512-NSUpcWf2WpXgqOvjgXu5zQM3E5UEXoA2iXLi6xV+h1SBZ1TmgNfrjme96KRzfUY2RBsI0rTSUuPv0I+fXvtcmA=="],
"primevue": ["primevue@4.3.3", "", { "dependencies": { "@primeuix/styled": "^0.5.0", "@primeuix/styles": "^1.0.0", "@primeuix/utils": "^0.5.1", "@primevue/core": "4.3.3", "@primevue/icons": "4.3.3" } }, "sha512-nooYVoEz5CdP3EhUkD6c3qTdRmpLHZh75fBynkUkl46K8y5rksHTjdSISiDijwTA5STQIOkyqLb+RM+HQ6nC1Q=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
@ -634,47 +642,47 @@
"sass": ["sass@1.77.8", "", { "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { "sass": "sass.js" } }, "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ=="],
"sass-embedded": ["sass-embedded@1.85.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.0.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-android-arm": "1.85.1", "sass-embedded-android-arm64": "1.85.1", "sass-embedded-android-ia32": "1.85.1", "sass-embedded-android-riscv64": "1.85.1", "sass-embedded-android-x64": "1.85.1", "sass-embedded-darwin-arm64": "1.85.1", "sass-embedded-darwin-x64": "1.85.1", "sass-embedded-linux-arm": "1.85.1", "sass-embedded-linux-arm64": "1.85.1", "sass-embedded-linux-ia32": "1.85.1", "sass-embedded-linux-musl-arm": "1.85.1", "sass-embedded-linux-musl-arm64": "1.85.1", "sass-embedded-linux-musl-ia32": "1.85.1", "sass-embedded-linux-musl-riscv64": "1.85.1", "sass-embedded-linux-musl-x64": "1.85.1", "sass-embedded-linux-riscv64": "1.85.1", "sass-embedded-linux-x64": "1.85.1", "sass-embedded-win32-arm64": "1.85.1", "sass-embedded-win32-ia32": "1.85.1", "sass-embedded-win32-x64": "1.85.1" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-0i+3h2Df/c71afluxC1SXqyyMmJlnKWfu9ZGdzwuKRM1OftEa2XM2myt5tR36CF3PanYrMjFKtRIj8PfSf838w=="],
"sass-embedded": ["sass-embedded@1.86.3", "", { "dependencies": { "@bufbuild/protobuf": "^2.0.0", "buffer-builder": "^0.2.0", "colorjs.io": "^0.5.0", "immutable": "^5.0.2", "rxjs": "^7.4.0", "supports-color": "^8.1.1", "sync-child-process": "^1.0.2", "varint": "^6.0.0" }, "optionalDependencies": { "sass-embedded-android-arm": "1.86.3", "sass-embedded-android-arm64": "1.86.3", "sass-embedded-android-ia32": "1.86.3", "sass-embedded-android-riscv64": "1.86.3", "sass-embedded-android-x64": "1.86.3", "sass-embedded-darwin-arm64": "1.86.3", "sass-embedded-darwin-x64": "1.86.3", "sass-embedded-linux-arm": "1.86.3", "sass-embedded-linux-arm64": "1.86.3", "sass-embedded-linux-ia32": "1.86.3", "sass-embedded-linux-musl-arm": "1.86.3", "sass-embedded-linux-musl-arm64": "1.86.3", "sass-embedded-linux-musl-ia32": "1.86.3", "sass-embedded-linux-musl-riscv64": "1.86.3", "sass-embedded-linux-musl-x64": "1.86.3", "sass-embedded-linux-riscv64": "1.86.3", "sass-embedded-linux-x64": "1.86.3", "sass-embedded-win32-arm64": "1.86.3", "sass-embedded-win32-ia32": "1.86.3", "sass-embedded-win32-x64": "1.86.3" }, "bin": { "sass": "dist/bin/sass.js" } }, "sha512-3pZSp24ibO1hdopj+W9DuiWsZOb2YY6AFRo/jjutKLBkqJGM1nJjXzhAYfzRV+Xn5BX1eTI4bBTE09P0XNHOZg=="],
"sass-embedded-android-arm": ["sass-embedded-android-arm@1.85.1", "", { "os": "android", "cpu": "arm" }, "sha512-GkcgUGMZtEF9gheuE1dxCU0ZSAifuaFXi/aX7ZXvjtdwmTl9Zc/OHR9oiUJkc8IW9UI7H8TuwlTAA8+SwgwIeQ=="],
"sass-embedded-android-arm": ["sass-embedded-android-arm@1.86.3", "", { "os": "android", "cpu": "arm" }, "sha512-UyeXrFzZSvrGbvrWUBcspbsbivGgAgebLGJdSqJulgSyGbA6no3DWQ5Qpdd6+OAUC39BlpPu74Wx9s4RrVuaFw=="],
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.85.1", "", { "os": "android", "cpu": "arm64" }, "sha512-27oRheqNA3SJM2hAxpVbs7mCKUwKPWmEEhyiNFpBINb5ELVLg+Ck5RsGg+SJmo130ul5YX0vinmVB5uPWc8X5w=="],
"sass-embedded-android-arm64": ["sass-embedded-android-arm64@1.86.3", "", { "os": "android", "cpu": "arm64" }, "sha512-q+XwFp6WgAv+UgnQhsB8KQ95kppvWAB7DSoJp+8Vino8b9ND+1ai3cUUZPE5u4SnLZrgo5NtrbPvN5KLc4Pfyg=="],
"sass-embedded-android-ia32": ["sass-embedded-android-ia32@1.85.1", "", { "os": "android", "cpu": "ia32" }, "sha512-f3x16NyRgtXFksIaO/xXKrUhttUBv8V0XsAR2Dhdb/yz4yrDrhzw9Wh8fmw7PlQqECcQvFaoDr3XIIM6lKzasw=="],
"sass-embedded-android-ia32": ["sass-embedded-android-ia32@1.86.3", "", { "os": "android", "cpu": "ia32" }, "sha512-gTJjVh2cRzvGujXj5ApPk/owUTL5SiO7rDtNLrzYAzi1N5HRuLYXqk3h1IQY3+eCOBjGl7mQ9XyySbJs/3hDvg=="],
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.85.1", "", { "os": "android", "cpu": "none" }, "sha512-IP6OijpJ8Mqo7XqCe0LsuZVbAxEFVboa0kXqqR5K55LebEplsTIA2GnmRyMay3Yr/2FVGsZbCb6Wlgkw23eCiA=="],
"sass-embedded-android-riscv64": ["sass-embedded-android-riscv64@1.86.3", "", { "os": "android", "cpu": "none" }, "sha512-Po3JnyiCS16kd6REo1IMUbFGYtvL9O0rmKaXx5vOuBaJD1LPy2LiSSp7TU7wkJ9IxsTDGzFaSeP1I9qb6D8VVg=="],
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.85.1", "", { "os": "android", "cpu": "x64" }, "sha512-Mh7CA53wR3ADvXAYipFc/R3vV4PVOzoKwWzPxmq+7i8UZrtsVjKONxGtqWe9JG1mna0C9CRZAx0sv/BzbOJxWg=="],
"sass-embedded-android-x64": ["sass-embedded-android-x64@1.86.3", "", { "os": "android", "cpu": "x64" }, "sha512-+7h3jdDv/0kUFx0BvxYlq2fa7CcHiDPlta6k5OxO5K6jyqJwo9hc0Z052BoYEauWTqZ+vK6bB5rv2BIzq4U9nA=="],
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.85.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-msWxzhvcP9hqGVegxVePVEfv9mVNTlUgGr6k7O7Ihji702mbtrH/lKwF4aRkkt4g1j7tv10+JtQXmTNi/pi9kA=="],
"sass-embedded-darwin-arm64": ["sass-embedded-darwin-arm64@1.86.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EgLwV4ORm5Hr0DmIXo0Xw/vlzwLnfAiqD2jDXIglkBsc5czJmo4/IBdGXOP65TRnsgJEqvbU3aQhuawX5++x9A=="],
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.85.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-J4UFHUiyI9Z+mwYMwz11Ky9TYr3hY1fCxeQddjNGL/+ovldtb0yAIHvoVM0BGprQDm5JqhtUk8KyJ3RMJqpaAA=="],
"sass-embedded-darwin-x64": ["sass-embedded-darwin-x64@1.86.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-dfKhfrGPRNLWLC82vy/vQGmNKmAiKWpdFuWiePRtg/E95pqw+sCu6080Y6oQLfFu37Iq3MpnXiSpDuSo7UnPWA=="],
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.85.1", "", { "os": "linux", "cpu": "arm" }, "sha512-X0fDh95nNSw1wfRlnkE4oscoEA5Au4nnk785s9jghPFkTBg+A+5uB6trCjf0fM22+Iw6kiP4YYmDdw3BqxAKLQ=="],
"sass-embedded-linux-arm": ["sass-embedded-linux-arm@1.86.3", "", { "os": "linux", "cpu": "arm" }, "sha512-+fVCIH+OR0SMHn2NEhb/VfbpHuUxcPtqMS34OCV3Ka99LYZUJZqth4M3lT/ppGl52mwIVLNYzR4iLe6mdZ6mYA=="],
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.85.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jGadetB03BMFG2rq3OXub/uvC/lGpbQOiLGEz3NLb2nRZWyauRhzDtvZqkr6BEhxgIWtMtz2020yD8ZJSw/r2w=="],
"sass-embedded-linux-arm64": ["sass-embedded-linux-arm64@1.86.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-tYq5rywR53Qtc+0KI6pPipOvW7a47ETY69VxfqI9BR2RKw2hBbaz0bIw6OaOgEBv2/XNwcWb7a4sr7TqgkqKAA=="],
"sass-embedded-linux-ia32": ["sass-embedded-linux-ia32@1.85.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-7HlYY90d9mitDtNi5s+S+5wYZrTVbkBH2/kf7ixrzh2BFfT0YM81UHLJRnGX93y9aOMBL6DSZAIfkt1RsV9bkQ=="],
"sass-embedded-linux-ia32": ["sass-embedded-linux-ia32@1.86.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-CmQ5OkqnaeLdaF+bMqlYGooBuenqm3LvEN9H8BLhjkpWiFW8hnYMetiqMcJjhrXLvDw601KGqA5sr/Rsg5s45g=="],
"sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.85.1", "", { "os": "linux", "cpu": "arm" }, "sha512-5vcdEqE8QZnu6i6shZo7x2N36V7YUoFotWj2rGekII5ty7Nkaj+VtZhUEOp9tAzEOlaFuDp5CyO1kUCvweT64A=="],
"sass-embedded-linux-musl-arm": ["sass-embedded-linux-musl-arm@1.86.3", "", { "os": "linux", "cpu": "arm" }, "sha512-SEm65SQknI4pl+mH5Xf231hOkHJyrlgh5nj4qDbiBG6gFeutaNkNIeRgKEg3cflXchCr8iV/q/SyPgjhhzQb7w=="],
"sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.85.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-FLkIT0p18XOkR6wryJ13LqGBDsrYev2dRk9dtiU18NCpNXruKsdBQ1ZnWHVKB3h1dA9lFyEEisC0sooKdNfeOQ=="],
"sass-embedded-linux-musl-arm64": ["sass-embedded-linux-musl-arm64@1.86.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-4zOr2C/eW89rxb4ozTfn7lBzyyM5ZigA1ZSRTcAR26Qbg/t2UksLdGnVX9/yxga0d6aOi0IvO/7iM2DPPRRotg=="],
"sass-embedded-linux-musl-ia32": ["sass-embedded-linux-musl-ia32@1.85.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-N1093T84zQJor1yyIAdYScB5eAuQarGK1tKgZ4uTnxVlgA7Xi1lXV8Eh7ox9sDqKCaWkVQ3MjqU26vYRBeRWyw=="],
"sass-embedded-linux-musl-ia32": ["sass-embedded-linux-musl-ia32@1.86.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-84Tcld32LB1loiqUvczWyVBQRCChm0wNLlkT59qF29nxh8njFIVf9yaPgXcSyyjpPoD9Tu0wnq3dvVzoMCh9AQ=="],
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.85.1", "", { "os": "linux", "cpu": "none" }, "sha512-WRsZS/7qlfYXsa93FBpSruieuURIu7ySfFhzYfF1IbKrNAGwmbduutkHZh2ddm5/vQMvQ0Rdosgv+CslaQHMcw=="],
"sass-embedded-linux-musl-riscv64": ["sass-embedded-linux-musl-riscv64@1.86.3", "", { "os": "linux", "cpu": "none" }, "sha512-IxEqoiD7vdNpiOwccybbV93NljBy64wSTkUOknGy21SyV43C8uqESOwTwW9ywa3KufImKm8L3uQAW/B0KhJMWg=="],
"sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.85.1", "", { "os": "linux", "cpu": "x64" }, "sha512-+OlLIilA5TnP0YEqTQ8yZtkW+bJIQYvzoGoNLUEskeyeGuOiIyn2CwL6G4JQB4xZQFaxPHb7JD3EueFkQbH0Pw=="],
"sass-embedded-linux-musl-x64": ["sass-embedded-linux-musl-x64@1.86.3", "", { "os": "linux", "cpu": "x64" }, "sha512-ePeTPXUxPK6JgHcUfnrkIyDtyt+zlAvF22mVZv6y1g/PZFm1lSfX+Za7TYHg9KaYqaaXDiw6zICX4i44HhR8rA=="],
"sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.85.1", "", { "os": "linux", "cpu": "none" }, "sha512-mKKlOwMGLN7yP1p0gB5yG/HX4fYLnpWaqstNuOOXH+fOzTaNg0+1hALg0H0CDIqypPO74M5MS9T6FAJZGdT6dQ=="],
"sass-embedded-linux-riscv64": ["sass-embedded-linux-riscv64@1.86.3", "", { "os": "linux", "cpu": "none" }, "sha512-NuXQ72dwfNLe35E+RaXJ4Noq4EkFwM65eWwCwxEWyJO9qxOx1EXiCAJii6x8kkOh5daWuMU0VAI1B9RsJaqqQQ=="],
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.85.1", "", { "os": "linux", "cpu": "x64" }, "sha512-uKRTv0z8NgtHV7xSren78+yoWB79sNi7TMqI7Bxd8fcRNIgHQSA8QBdF8led2ETC004hr8h71BrY60RPO+SSvA=="],
"sass-embedded-linux-x64": ["sass-embedded-linux-x64@1.86.3", "", { "os": "linux", "cpu": "x64" }, "sha512-t8be9zJ5B82+og9bQmIQ83yMGYZMTMrlGA+uGWtYacmwg6w3093dk91Fx0YzNSZBp3Tk60qVYjCZnEIwy60x0g=="],
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.85.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-/GMiZXBOc6AEMBC3g25Rp+x8fq9Z6Ql7037l5rajBPhZ+DdFwtdHY0Ou3oIU6XuWUwD06U3ii4XufXVFhsP6PA=="],
"sass-embedded-win32-arm64": ["sass-embedded-win32-arm64@1.86.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-4ghuAzjX4q8Nksm0aifRz8hgXMMxS0SuymrFfkfJlrSx68pIgvAge6AOw0edoZoe0Tf5ZbsWUWamhkNyNxkTvw=="],
"sass-embedded-win32-ia32": ["sass-embedded-win32-ia32@1.85.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-L+4BWkKKBGFOKVQ2PQ5HwFfkM5FvTf1Xx2VSRvEWt9HxPXp6SPDho6zC8fqNQ3hSjoaoASEIJcSvgfdQYO0gdg=="],
"sass-embedded-win32-ia32": ["sass-embedded-win32-ia32@1.86.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-tCaK4zIRq9mLRPxLzBAdYlfCuS/xLNpmjunYxeWkIwlJo+k53h1udyXH/FInnQ2GgEz0xMXyvH3buuPgzwWYsw=="],
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.85.1", "", { "os": "win32", "cpu": "x64" }, "sha512-/FO0AGKWxVfCk4GKsC0yXWBpUZdySe3YAAbQQL0lL6xUd1OiUY8Kow6g4Kc1TB/+z0iuQKKTqI/acJMEYl4iTQ=="],
"sass-embedded-win32-x64": ["sass-embedded-win32-x64@1.86.3", "", { "os": "win32", "cpu": "x64" }, "sha512-zS+YNKfTF4SnOfpC77VTb0qNZyTXrxnAezSoRV0xnw6HlY+1WawMSSB6PbWtmbvyfXNgpmJUttoTtsvJjRCucg=="],
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
@ -698,7 +706,7 @@
"sync-message-port": ["sync-message-port@1.1.3", "", {}, "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg=="],
"tailwindcss": ["tailwindcss@4.0.9", "", {}, "sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw=="],
"tailwindcss": ["tailwindcss@4.1.2", "", {}, "sha512-VCsK+fitIbQF7JlxXaibFhxrPq4E2hDcG8apzHUdWFMCQWD8uLdlHg4iSkZ53cgLCCcZ+FZK7vG8VjvLcnBgKw=="],
"tailwindcss-primeui": ["tailwindcss-primeui@0.4.0", "", { "peerDependencies": { "tailwindcss": ">=3.1.0" } }, "sha512-YYC7B7Yyzm1/4pEGgpf1ABAhbrKY++LuPoUamnKE7fTPO5Ct/Qr/dT+Uq2yiVhQnaW1zHQpYnThxfksaxhlDfQ=="],
@ -716,11 +724,11 @@
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"typescript-eslint": ["typescript-eslint@8.24.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.24.0", "@typescript-eslint/parser": "8.24.0", "@typescript-eslint/utils": "8.24.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ=="],
"typescript-eslint": ["typescript-eslint@8.29.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.29.0", "@typescript-eslint/parser": "8.29.0", "@typescript-eslint/utils": "8.29.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg=="],
"ufo": ["ufo@1.5.4", "", {}, "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unplugin": ["unplugin@2.0.0-beta.1", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-2qzQo5LN2DmUZXkWDHvGKLF5BP0WN+KthD6aPnPJ8plRBIjv4lh5O07eYcSxgO2znNw9s4MNhEO1sB+JDllDbQ=="],
@ -736,19 +744,19 @@
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
"vite": ["vite@6.2.0", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ=="],
"vite": ["vite@6.2.5", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA=="],
"vite-plugin-vuetify": ["vite-plugin-vuetify@2.1.0", "", { "dependencies": { "@vuetify/loader-shared": "^2.1.0", "debug": "^4.3.3", "upath": "^2.0.1" }, "peerDependencies": { "vite": ">=5", "vue": "^3.0.0", "vuetify": "^3.0.0" } }, "sha512-4wEAQtZaigPpwbFcZbrKpYwutOsWwWdeXn22B9XHzDPQNxVsKT+K9lKcXZnI5JESO1Iaql48S9rOk8RZZEt+Mw=="],
"vite-plugin-vuetify": ["vite-plugin-vuetify@2.1.1", "", { "dependencies": { "@vuetify/loader-shared": "^2.1.0", "debug": "^4.3.3", "upath": "^2.0.1" }, "peerDependencies": { "vite": ">=5", "vue": "^3.0.0", "vuetify": "^3.0.0" } }, "sha512-Pb7bKhQH8qPMzURmEGq2aIqCJkruFNsyf1NcrrtnjsOIkqJPMcBbiP0oJoO8/uAmyB5W/1JTbbUEsyXdMM0QHQ=="],
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
"vue": ["vue@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="],
"vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="],
"vue-eslint-parser": ["vue-eslint-parser@10.1.2", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.6.0", "lodash": "^4.17.21", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-1guOfYgNlD7JH2popr/bt5vc7Mzt6quRCnEbqLgpMHvoHEGV1oImzdqrLd+oMD76cHt8ilBP4cda9WA72TLFDQ=="],
"vue-tsc": ["vue-tsc@2.2.8", "", { "dependencies": { "@volar/typescript": "~2.4.11", "@vue/language-core": "2.2.8" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ=="],
"vuetify": ["vuetify@3.7.15", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=1.0.0", "vue": "^3.3.0", "webpack-plugin-vuetify": ">=2.0.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-kBZzwXI5EcAMiW5TRMgK1reXQd0K/PpUt+ekX4Alvm7n09uzJ1my1TLNbX1sQ8/0KYgoxOf17C8qOJzBGkT+PA=="],
"vuetify": ["vuetify@3.8.0", "", { "peerDependencies": { "typescript": ">=4.7", "vite-plugin-vuetify": ">=2.1.0", "vue": "^3.5.0", "webpack-plugin-vuetify": ">=3.1.0" }, "optionalPeers": ["typescript", "vite-plugin-vuetify", "webpack-plugin-vuetify"] }, "sha512-ROC0Xq2G/25ZyUpQMhaynMyXZBJY1WbOGlqOB810yubp8hfY8RlrOw+mzXJonOq6jylCY32muQ9xiJF1JPTLVA=="],
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
@ -776,13 +784,7 @@
"@primeuix/forms/@primeuix/utils": ["@primeuix/utils@0.4.1", "", {}, "sha512-5+1NLfyna+gLRPeFTo+xlR0tfPVLuVdidbeahAMLkQga5Rw0LxyUBCyD2/Zv2JkV69o2T+hpEDyddl3VdnYoBw=="],
"@tauri-apps/plugin-deep-link/@tauri-apps/api": ["@tauri-apps/api@2.2.0", "", {}, "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="],
"@tauri-apps/plugin-dialog/@tauri-apps/api": ["@tauri-apps/api@2.2.0", "", {}, "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="],
"@tauri-apps/plugin-fs/@tauri-apps/api": ["@tauri-apps/api@2.2.0", "", {}, "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="],
"@tauri-apps/plugin-shell/@tauri-apps/api": ["@tauri-apps/api@2.2.0", "", {}, "sha512-R8epOeZl1eJEl603aUMIGb4RXlhPjpgxbGVEaqY+0G5JG9vzV/clNlzTeqc+NLYXVqXcn8mb4c5b9pJIUDEyAg=="],
"@tauri-apps/plugin-opener/@tauri-apps/api": ["@tauri-apps/api@2.3.0", "", {}, "sha512-33Z+0lX2wgZbx1SPFfqvzI6su63hCBkbzv+5NexeYjIx7WA9htdOKoRR7Dh3dJyltqS5/J8vQFyybiRoaL0hlA=="],
"@vue/compiler-sfc/postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="],
@ -798,6 +800,8 @@
"eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"eslint-plugin-vue/vue-eslint-parser": ["vue-eslint-parser@9.4.3", "", { "dependencies": { "debug": "^4.3.4", "eslint-scope": "^7.1.1", "eslint-visitor-keys": "^3.3.0", "espree": "^9.3.1", "esquery": "^1.4.0", "lodash": "^4.17.21", "semver": "^7.3.6" }, "peerDependencies": { "eslint": ">=6.0.0" } }, "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@ -808,18 +812,18 @@
"unplugin-vue-components/unplugin": ["unplugin@1.16.1", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w=="],
"vue-eslint-parser/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
"vue-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"vue-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
"@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"eslint-plugin-vue/vue-eslint-parser/eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="],
"eslint-plugin-vue/vue-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"eslint-plugin-vue/vue-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="],
"eslint/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
}
}

View File

@ -11,42 +11,42 @@
},
"dependencies": {
"@mdi/font": "7.4.47",
"@primevue/forms": "^4.3.1",
"@primevue/themes": "^4.3.1",
"@tailwindcss/vite": "^4.0.9",
"@tauri-apps/api": "^2.3.0",
"@primevue/forms": "^4.3.3",
"@primevue/themes": "^4.3.3",
"@tailwindcss/vite": "^4.1.2",
"@tauri-apps/api": "^2.4.1",
"@tauri-apps/plugin-cli": "^2.2.0",
"@tauri-apps/plugin-deep-link": "~2.2.0",
"@tauri-apps/plugin-dialog": "~2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
"@tauri-apps/plugin-deep-link": "~2.2.1",
"@tauri-apps/plugin-dialog": "~2.2.1",
"@tauri-apps/plugin-fs": "^2.2.1",
"@tauri-apps/plugin-opener": "^2.2.6",
"@tauri-apps/plugin-shell": "~2.2.0",
"@tauri-apps/plugin-updater": "^2.6.1",
"@tauri-apps/plugin-shell": "~2.2.1",
"@tauri-apps/plugin-updater": "^2.7.0",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"pinia": "^3.0.1",
"primeicons": "^7.0.0",
"primevue": "^4.3.1",
"primevue": "^4.3.3",
"roboto-fontface": "^0.10.0",
"tailwindcss": "^4.0.9",
"tailwindcss": "^4.1.2",
"tailwindcss-primeui": "^0.4.0",
"vue": "^3.5.13",
"vuetify": "^3.7.15"
"vuetify": "^3.8.0"
},
"devDependencies": {
"@tauri-apps/cli": "^2.3.1",
"@tsconfig/node22": "^22.0.0",
"@types/node": "^22.13.9",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-typescript": "^14.4.0",
"@tauri-apps/cli": "^2.4.1",
"@tsconfig/node22": "^22.0.1",
"@types/node": "^22.14.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/eslint-config-typescript": "^14.5.0",
"@vue/tsconfig": "^0.5.1",
"npm-run-all2": "^7.0.2",
"sass": "1.77.8",
"sass-embedded": "^1.85.1",
"sass-embedded": "^1.86.3",
"typescript": "^5.8.2",
"unplugin-fonts": "^1.3.1",
"unplugin-vue-components": "^0.27.5",
"vite": "^6.2.0",
"vite-plugin-vuetify": "^2.1.0",
"vite": "^6.2.5",
"vite-plugin-vuetify": "^2.1.1",
"vue-tsc": "^2.2.8"
}
}

View File

@ -12,6 +12,9 @@
"core:window:allow-set-focus",
"core:window:allow-hide",
"core:window:allow-show",
"core:window:allow-set-size",
"core:window:allow-inner-size",
"core:window:allow-set-min-size",
"core:app:allow-app-hide",
"shell:default",
"dialog:default",

View File

@ -1,8 +1,11 @@
use ini::Ini;
use log;
use std::collections::HashMap;
use std::path::PathBuf;
use tokio::sync::Mutex;
use tokio::fs;
use tauri::{AppHandle, Manager, State};
use crate::model::config::GlobalConfigField;
use crate::model::misc::Game;
use crate::pkg::{Package, PkgKey};
use crate::pkg_store::{InstallResult, PackageStore};
@ -62,7 +65,7 @@ pub async fn startline(app: AppHandle, refresh: bool) -> Result<(), String> {
if let Some(p) = &mut appd.profile {
log::debug!("{}", hash);
p.line_up(hash, refresh, app.clone()).await
.map_err(|e| format!("Lineup failed:\n{}", e))?;
.map_err(|e| e.to_string())?;
let app_clone = app.clone();
let p_clone = p.clone();
tauri::async_runtime::spawn(async move {
@ -318,7 +321,7 @@ pub async fn get_current_profile(state: State<'_, Mutex<AppData>>) -> Result<Opt
#[tauri::command]
pub async fn sync_current_profile(state: State<'_, Mutex<AppData>>, data: ProfileData) -> Result<(), String> {
log::debug!("invoke: sync_current_profile");
log::debug!("invoke: sync_current_profile {:?}", data);
let mut appd = state.lock().await;
if let Some(p) = &mut appd.profile {
@ -344,6 +347,27 @@ pub async fn save_current_profile(state: State<'_, Mutex<AppData>>) -> Result<()
}
}
#[tauri::command]
pub async fn load_segatools_ini(state: State<'_, Mutex<AppData>>, path: PathBuf) -> Result<(), String> {
log::debug!("invoke: load_segatools_ini({:?})", path);
let mut appd = state.lock().await;
if let Some(p) = &mut appd.profile {
let str = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
// Stupid path escape hack for the ini reader
let str = str.replace("\\", "\\\\").replace("\\\\\\\\", "\\\\");
let ini = Ini::load_from_str(&str).map_err(|e| e.to_string())?;
p.data.sgt.load_from_ini(&ini, p.config_dir()).map_err(|e| e.to_string())?;
p.data.network.load_from_ini(&ini).map_err(|e| e.to_string())?;
if let Some(kb) = &mut p.data.keyboard {
kb.load_from_ini(&ini).map_err(|e| e.to_string())?;
}
p.save().map_err(|e| e.to_string())?;
}
Ok(())
}
#[tauri::command]
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
log::debug!("invoke: list_platform_capabilities");
@ -355,6 +379,29 @@ pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
return Ok(vec!["wine".to_owned()]);
}
#[tauri::command]
pub async fn get_global_config(state: State<'_, Mutex<AppData>>, field: GlobalConfigField) -> Result<bool, ()> {
log::debug!("invoke: get_global_config({field:?})");
let appd = state.lock().await;
match field {
GlobalConfigField::OfflineMode => Ok(appd.cfg.offline_mode),
GlobalConfigField::EnableAutoupdates => Ok(appd.cfg.enable_autoupdates)
}
}
#[tauri::command]
pub async fn set_global_config(state: State<'_, Mutex<AppData>>, field: GlobalConfigField, value: bool) -> Result<(), String> {
log::debug!("invoke: set_global_config({field:?}, {value})");
let mut appd = state.lock().await;
match field {
GlobalConfigField::OfflineMode => appd.cfg.offline_mode = value,
GlobalConfigField::EnableAutoupdates => appd.cfg.enable_autoupdates = value
};
appd.write().map_err(|e| e.to_string())
}
#[tauri::command]
#[cfg(target_os = "windows")]
pub async fn list_displays() -> Result<Vec<(String, String)>, String> {
@ -389,4 +436,10 @@ pub async fn list_directories() -> Result<util::Dirs, ()> {
log::debug!("invoke: list_directores");
Ok(util::all_dirs().clone())
}
// Tauri fs api is useless
#[tauri::command]
pub async fn file_exists(path: String) -> Result<bool, ()> {
Ok(std::fs::exists(path).unwrap_or(false))
}

View File

@ -71,8 +71,8 @@ pub async fn run(_args: Vec<String>) {
} else {
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
.title("STARTLINER")
.inner_size(720f64, 480f64)
.min_inner_size(720f64, 480f64)
.inner_size(900f64, 480f64)
.min_inner_size(900f64, 480f64)
.build()?;
start_immediately = false;
}
@ -199,10 +199,15 @@ pub async fn run(_args: Vec<String>) {
cmd::get_current_profile,
cmd::sync_current_profile,
cmd::save_current_profile,
cmd::load_segatools_ini,
cmd::get_global_config,
cmd::set_global_config,
cmd::list_displays,
cmd::list_platform_capabilities,
cmd::list_directories,
cmd::file_exists,
])
.build(tauri::generate_context!())
.expect("error while building tauri application");
@ -262,22 +267,28 @@ fn deep_link(app: AppHandle, args: Vec<String>) {
}
async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> {
let mutex = app.state::<Mutex<AppData>>();
let appd = mutex.lock().await;
if !appd.cfg.enable_autoupdates {
log::info!("skipping autoupdate");
return Ok(());
}
if let Some(update) = app.updater()?.check().await? {
let mut downloaded = 0;
update
.download_and_install(
|chunk_length, content_length| {
downloaded += chunk_length;
log::debug!("downloaded {downloaded} from {content_length:?}");
},
|| {
log::info!("download finished");
},
let mut downloaded = 0;
update.download_and_install(
|chunk_length, content_length| {
downloaded += chunk_length;
log::debug!("downloaded {downloaded} from {content_length:?}");
},
|| {
log::info!("download finished");
},
)
.await?;
log::info!("update installed");
app.restart();
log::info!("update installed");
app.restart();
}
Ok(())

View File

@ -1,10 +1,25 @@
use serde::{Deserialize, Serialize};
use super::misc::Game;
#[derive(Serialize, Deserialize, Clone, Default)]
#[derive(Serialize, Deserialize, Clone)]
pub struct GlobalConfig {
pub recent_profile: Option<(Game, String)>,
#[serde(default)]
pub offline_mode: bool,
pub enable_autoupdates: bool,
}
impl Default for GlobalConfig {
fn default() -> Self {
Self {
recent_profile: Default::default(),
offline_mode: false,
enable_autoupdates: true
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum GlobalConfigField {
OfflineMode,
EnableAutoupdates
}

View File

@ -65,8 +65,8 @@ impl Game {
pub fn has_module(&self, module: ProfileModule) -> bool {
match self {
Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini}),
Game::Chunithm => make_bitflags!(ProfileModule::{Segatools | Network}),
Game::Ongeki => make_bitflags!(ProfileModule::{Segatools | Display | Network | BepInEx | Mu3Ini | Keyboard}),
Game::Chunithm => make_bitflags!(ProfileModule::{Segatools | Network | Keyboard}),
}.contains(module)
}
}

View File

@ -75,6 +75,12 @@ pub struct Display {
pub rotation: i32,
pub frequency: i32,
pub borderless_fullscreen: bool,
#[serde(default)]
pub dont_switch_primary: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub monitor_index_override: Option<i32>,
}
impl Display {
@ -92,6 +98,8 @@ impl Display {
Game::Ongeki => 60,
},
borderless_fullscreen: true,
dont_switch_primary: false,
monitor_index_override: None,
}
}
}
@ -154,6 +162,75 @@ pub struct Mu3Ini {
pub blacklist: Option<(i32, i32)>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct OngekiKeyboard {
pub use_mouse: bool,
pub coin: i32,
pub svc: i32,
pub test: i32,
pub lmenu: i32,
pub rmenu: i32,
pub l1: i32,
pub l2: i32,
pub l3: i32,
pub r1: i32,
pub r2: i32,
pub r3: i32,
pub lwad: i32,
pub rwad: i32,
}
impl Default for OngekiKeyboard {
fn default() -> Self {
Self {
use_mouse: true,
test: 0x70,
svc: 0x71,
coin: 0x72,
lmenu: 0x55,
rmenu: 0x4F,
lwad: 0x01,
rwad: 0x02,
l1: 0x41,
l2: 0x53,
l3: 0x44,
r1: 0x4A,
r2: 0x4B,
r3: 0x4C
}
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct ChunithmKeyboard {
pub split_ir: bool,
pub coin: i32,
pub svc: i32,
pub test: i32,
pub cell: [i32; 32],
pub ir: [i32; 6],
}
impl Default for ChunithmKeyboard {
fn default() -> Self {
Self {
split_ir: false,
test: 0x70,
svc: 0x71,
coin: 0x72,
cell: Default::default(),
ir: Default::default(),
}
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(tag = "game", content = "data")]
pub enum Keyboard {
Ongeki(OngekiKeyboard),
Chunithm(ChunithmKeyboard),
}
#[bitflags]
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -162,5 +239,6 @@ pub enum ProfileModule {
Network,
Display,
BepInEx,
Mu3Ini
Mu3Ini,
Keyboard,
}

View File

@ -38,49 +38,7 @@ cabLedOutputSerial=0
; Output slider LED data to the named pipe
controllerLedOutputPipe=1
; Output slider LED data to the serial port
controllerLedOutputSerial=0
[io4]
; Test button virtual-key code. Default is the F1 key.
test=0x70
; Service button virtual-key code. Default is the F2 key.
service=0x71
; Keyboard button to increment coin counter. Default is the F3 key.
coin=0x72
; Set \"1\" to enable mouse lever emulation, \"0\" to use XInput
mouse=1
; XInput input bindings
;
; Left Stick Lever
; Left Trigger Lever (move to the left)
; Right Trigger Lever (move to the right)
; Left Left red button
; Up Left green button
; Right Left blue button
; Left Shoulder Left side button
; Right Shoulder Right side button
; X Right red button
; Y Right green button
; A Right blue button
; Back Left menu button
; Start Right menu button
; Keyboard input bindings
left1=0x41 ; A
left2=0x53 ; S
left3=0x44 ; D
leftSide=0x01 ; Mouse Left
rightSide=0x02 ; Mouse Right
right1=0x4A ; J
right2=0x4B ; K
right3=0x4C ; L
leftMenu=0x55 ; U
rightMenu=0x4F ; O".to_owned(),
controllerLedOutputSerial=0".to_owned(),
Game::Chunithm => "
[vfd]
; Enable VFD emulation. Disable to use a real VFD
@ -179,120 +137,6 @@ controllerLedOutputOpeNITHM=0
; x86 chuniio to path32, x64 to path64. Both are necessary.
;path32=
;path64=
; -----------------------------------------------------------------------------
; Input settings
; -----------------------------------------------------------------------------
; Keyboard bindings are specified as hexadecimal (prefixed with 0x) or decimal
; (not prefixed with 0x) virtual-key codes, a list of which can be found here:
;
; https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
;
; This is, admittedly, not the most user-friendly configuration method in the
; world. An improved solution will be provided later.
[io3]
test=0x31
service=0x32
coin=0x33
ir=0x00
ir6=0x39
ir5=0x38
ir4=0x37
ir3=0x36
ir2=0x35
ir1=0x34
[ir]
ir6=0x39
ir5=0x38
ir4=0x37
ir3=0x36
ir2=0x35
ir1=0x34
[slider]
cell32=0x51
cell30=0x5A
cell28=0x53
cell26=0x45
cell24=0x43
cell22=0x46
cell20=0x54
cell18=0x42
cell16=0x48
cell14=0x55
cell12=0x4D
cell10=0x4B
cell8=0x4F
cell6=190
cell4=186
cell2=219
cell31=0x41
cell29=0x57
cell27=0x58
cell25=0x44
cell23=0x52
cell21=0x56
cell19=0x47
cell17=0x59
cell15=0x4E
cell13=0x4A
cell11=0x49
cell9=188
cell7=0x4C
cell5=0x50
cell3=191
cell1=222
".to_owned()
}
}

View File

@ -1,4 +1,3 @@
use crate::model::profile::{Display, DisplayMode};
use anyhow::Result;
use displayz::{query_displays, DisplaySet};
@ -7,14 +6,14 @@ use tauri::{AppHandle, Listener};
#[derive(Clone)]
pub struct DisplayInfo {
pub primary: String,
pub set: Option<DisplaySet>
pub set: Option<DisplaySet>,
}
impl Default for DisplayInfo {
fn default() -> Self {
DisplayInfo {
primary: "default".to_owned(),
set: query_displays().ok()
set: query_displays().ok(),
}
}
}
@ -52,14 +51,16 @@ impl Display {
.find(|display| display.name() == self.target)
.ok_or_else(|| anyhow!("Display {} not found", self.target))?;
target.set_primary()?;
if !self.dont_switch_primary {
target.set_primary()?;
}
let settings = target.settings()
.as_ref()
.ok_or_else(|| anyhow!("Unable to query display settings"))?;
let res = DisplayInfo {
primary: primary.name().to_owned(),
set: Some(display_set.clone())
set: Some(display_set.clone()),
};
if self.rotation == 90 || self.rotation == 270 {
@ -88,7 +89,9 @@ impl Display {
settings.borrow_mut().resolution = Resolution::new(width, height);
}
display_set.apply()?;
display_set.apply().map_err(
|_| anyhow!("The selected monitor has been disconnected or doesn't support the chosen display mode")
)?;
displayz::refresh()?;
log::debug!("prepare display: done");

View File

@ -0,0 +1,120 @@
use ini::Ini;
use anyhow::Result;
use crate::model::profile::Keyboard;
macro_rules! parse_int_field {
($section:expr,$sgt:expr,$sl:expr) => {
if let Some(field) = $section.get($sgt) {
let field = &field[0..field.chars().position(|c| c == ';').unwrap_or(field.len())].trim();
log::debug!("loading {}={}", $sgt, field);
let res = if field.starts_with("0x") {
i32::from_str_radix(&field.trim()[2..], 16)
} else {
field.trim().parse::<i32>()
};
match res {
Ok(v) => $sl = v,
Err(e) => log::warn!("unable to read a segatools.ini field key={} value={}: {:?}", $sgt, field.trim(), e)
};
} else {
log::debug!("unable to load {}", $sgt);
}
}
}
impl Keyboard {
pub fn load_from_ini(&mut self, ini: &Ini) -> Result<()> {
log::debug!("loading kb");
match self {
Keyboard::Ongeki(kb) => {
if let Some(s) = ini.section(Some("io4")) {
parse_int_field!(s, "test", kb.test);
parse_int_field!(s, "service", kb.svc);
parse_int_field!(s, "coin", kb.coin);
parse_int_field!(s, "left1", kb.l1);
parse_int_field!(s, "left2", kb.l2);
parse_int_field!(s, "left3", kb.l3);
parse_int_field!(s, "right1", kb.r1);
parse_int_field!(s, "right2", kb.r2);
parse_int_field!(s, "right3", kb.r3);
parse_int_field!(s, "leftMenu", kb.lmenu);
parse_int_field!(s, "rightMenu", kb.rmenu);
parse_int_field!(s, "leftSide", kb.lwad);
parse_int_field!(s, "rightSide", kb.rwad);
let mut mouse: i32 = 1;
parse_int_field!(s, "mouse", mouse);
kb.use_mouse = if mouse == 1 { true } else { false };
}
}
Keyboard::Chunithm(kb) => {
if let Some(s) = ini.section(Some("io3")) {
parse_int_field!(s, "test", kb.test);
parse_int_field!(s, "service", kb.svc);
parse_int_field!(s, "coin", kb.coin);
let mut ir: i32 = 1;
parse_int_field!(s, "ir", ir);
kb.split_ir = if ir == 0 { true } else { false };
}
if let Some(s) = ini.section(Some("slider")) {
for i in 0..kb.cell.len() {
parse_int_field!(s, format!("cell{}", i + 1), kb.cell[i]);
}
}
if let Some(s) = ini.section(Some("ir")) {
for i in 0..kb.ir.len() {
parse_int_field!(s, format!("ir{}", i + 1), kb.ir[i]);
}
}
}
}
Ok(())
}
// This is assumed to run in sync after the segatools module
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
match self {
Keyboard::Ongeki(kb) => {
ini.with_section(Some("io4"))
.set("test", kb.test.to_string())
.set("service", kb.svc.to_string())
.set("coin", kb.coin.to_string())
.set("left1", kb.l1.to_string())
.set("left2", kb.l2.to_string())
.set("left3", kb.l3.to_string())
.set("right1", kb.r1.to_string())
.set("right2", kb.r2.to_string())
.set("right3", kb.r3.to_string())
.set("leftSide", kb.lwad.to_string())
.set("rightSide", kb.rwad.to_string())
.set("leftMenu", kb.lmenu.to_string())
.set("rightMenu", kb.rmenu.to_string())
.set("mouse", if kb.use_mouse { "1" } else { "0" });
}
Keyboard::Chunithm(kb) => {
for (i, cell) in kb.cell.iter().enumerate() {
ini.with_section(Some("slider")).set(format!("cell{}", i + 1), cell.to_string());
}
if kb.split_ir {
for (i, ir) in kb.ir.iter().enumerate() {
ini.with_section(Some("ir")).set(format!("ir{}", i + 1), ir.to_string());
}
} else {
ini.with_section(Some("io3")).set("ir", kb.ir[0].to_string());
}
ini.with_section(Some("io3"))
.set("test", kb.test.to_string())
.set("service", kb.svc.to_string())
.set("coin", kb.coin.to_string());
}
}
Ok(())
}
}

View File

@ -3,6 +3,7 @@ pub mod segatools;
pub mod network;
pub mod bepinex;
pub mod mu3ini;
pub mod keyboard;
#[cfg(target_os = "windows")]
pub mod display_windows;

View File

@ -5,6 +5,32 @@ use ini::Ini;
use crate::model::profile::{Network, NetworkType};
impl Network {
pub fn load_from_ini(&mut self, ini: &Ini) -> Result<()> {
log::debug!("loading network");
if let Some(s) = ini.section(Some("dns")) {
if let Some(default) = s.get("default") {
if default.starts_with("192.") || default.starts_with("127.") {
self.network_type = NetworkType::Artemis;
} else {
self.network_type = NetworkType::Remote;
self.remote_address = default.to_owned();
}
}
}
if let Some(s) = ini.section(Some("netenv")) {
s.get("addrSuffix").map(|v|
self.suffix = v.parse::<i32>().ok()
);
}
if let Some(s) = ini.section(Some("keychip")) {
s.get("subnet").map(|v| self.subnet = v.to_owned());
s.get("id").map(|v| self.keychip = v.to_owned());
}
Ok(())
}
pub fn line_up(&self, ini: &mut Ini) -> Result<()> {
log::debug!("begin line-up: network");

View File

@ -1,5 +1,4 @@
use std::path::PathBuf;
use std::path::{PathBuf, Path};
use anyhow::{anyhow, Result};
use ini::Ini;
use crate::{model::{misc::Game, profile::{Aime, Segatools}, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
@ -31,6 +30,31 @@ impl Segatools {
_ => {},
}
}
pub fn load_from_ini(&mut self, ini: &Ini, config_dir: impl AsRef<Path>) -> Result<()> {
log::debug!("loading sgt");
if let Some(s) = ini.section(Some("vfs")) {
s.get("amfs").map(|v| self.amfs = PathBuf::from(v));
s.get("appdata").map(|v| self.appdata = PathBuf::from(v));
s.get("option").map(|v| self.option = PathBuf::from(v));
}
if let Some(s) = ini.section(Some("aime")) {
if s.get("enable").unwrap_or("0") == "1" {
if let Some(aime_path) = s.get("aimePath") {
if let Some(game_dir) = self.target.parent() {
let target = game_dir.join(aime_path);
std::fs::copy(target, config_dir.as_ref().join("aime.txt"))?;
} else {
log::error!("profile doesn't have a game directory");
}
} else {
log::warn!("aime emulation is enabled, but no aimePath specified");
}
}
}
Ok(())
}
pub async fn line_up(&self, p: &impl ProfilePaths, game: Game) -> Result<Ini> {
log::debug!("begin line-up: segatools");

View File

@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, path::{Path, PathBuf}};
use tokio::fs;
use enumflags2::{bitflags, make_bitflags, BitFlags};
use crate::{model::{local::{self, PackageManifest}, rainy}, util};
use crate::{model::{local::{self, PackageManifest}, misc::Game, rainy}, util};
// {namespace}-{name}
#[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Serialize, Deserialize, Display, Debug)]
@ -14,6 +14,12 @@ pub struct PkgKey(pub String);
#[derive(Eq, Hash, PartialEq, PartialOrd, Ord, Clone, Serialize, Deserialize, Display, Debug)]
pub struct PkgKeyVersion(String);
#[derive(Copy, Clone, Display, Debug, Serialize, Deserialize, Default)]
pub enum PackageSource {
#[default] Rainy,
Local(Game)
}
#[derive(Clone, Default, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct Package {
@ -21,7 +27,8 @@ pub struct Package {
pub name: String,
pub description: String,
pub loc: Option<Local>,
pub rmt: Option<Remote>
pub rmt: Option<Remote>,
pub source: PackageSource,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
@ -88,11 +95,12 @@ impl Package {
version: v.version_number,
categories: p.categories,
dependencies: Self::sanitize_deps(v.dependencies)
})
}),
source: PackageSource::Rainy,
})
}
pub async fn from_dir(dir: PathBuf) -> Result<Package> {
pub async fn from_dir(dir: PathBuf, source: PackageSource) -> Result<Package> {
let str = fs::read_to_string(dir.join("manifest.json")).await?;
let mft: local::PackageManifest = serde_json::from_str(&str)?;
@ -116,7 +124,8 @@ impl Package {
status,
dependencies
}),
rmt: None
rmt: None,
source
})
}
@ -125,7 +134,15 @@ impl Package {
}
pub fn path(&self) -> PathBuf {
util::pkg_dir().join(self.key().0)
match self.source {
PackageSource::Rainy => util::pkg_dir().join(self.key().0),
PackageSource::Local(game) =>
util::pkg_dir()
.parent()
.unwrap()
.join(format!("pkg-{game}"))
.join(&self.name),
}
}
pub fn _dir_to_key(dir: &Path) -> Result<String> {

View File

@ -8,7 +8,7 @@ use tokio::task::JoinSet;
use crate::model::local::{PackageList, PackageListEntry};
use crate::model::misc::Game;
use crate::model::rainy;
use crate::pkg::{Package, PkgKey, Remote, Status};
use crate::pkg::{Package, PackageSource, PkgKey, Remote, Status};
use crate::util;
use crate::download_handler::DownloadHandler;
@ -69,7 +69,7 @@ impl PackageStore {
pub async fn reload_package(&mut self, key: PkgKey) {
let dir = util::pkg_dir().join(&key.0);
if let Ok(pkg) = Package::from_dir(dir).await {
if let Ok(pkg) = Package::from_dir(dir, PackageSource::Rainy).await {
self.update_nonremote(key, pkg);
} else {
log::error!("couldn't reload {}", key);
@ -83,7 +83,7 @@ impl PackageStore {
for dir in dirents {
if let Ok(dir) = dir {
let path = dir.path();
futures.spawn(Package::from_dir(path));
futures.spawn(Package::from_dir(path, PackageSource::Rainy));
}
}

View File

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use tauri::AppHandle;
use std::{collections::BTreeSet, path::{Path, PathBuf}};
use crate::{model::{misc::Game, profile::{Aime, Mu3Ini, ProfileModule}}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
use crate::{model::{misc::Game, profile::{Aime, ChunithmKeyboard, Keyboard, Mu3Ini, OngekiKeyboard, ProfileModule}}, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
use tauri::Emitter;
use std::process::Stdio;
use crate::model::profile::BepInEx;
@ -54,7 +54,10 @@ pub struct ProfileData {
pub wine: crate::model::profile::Wine,
#[serde(skip_serializing_if = "Option::is_none")]
pub mu3_ini: Option<Mu3Ini>
pub mu3_ini: Option<Mu3Ini>,
#[serde(skip_serializing_if = "Option::is_none")]
pub keyboard: Option<Keyboard>
}
impl Profile {
@ -74,6 +77,12 @@ impl Profile {
#[cfg(not(target_os = "windows"))]
wine: crate::model::profile::Wine::default(),
mu3_ini: if meta.game == Game::Ongeki { Some(Mu3Ini { audio: None, blacklist: None }) } else { None },
keyboard:
if meta.game == Game::Ongeki {
Some(Keyboard::Ongeki(OngekiKeyboard::default()))
} else {
Some(Keyboard::Chunithm(ChunithmKeyboard::default()))
},
},
meta: meta.clone()
};
@ -87,11 +96,18 @@ impl Profile {
pub fn load(game: Game, name: String) -> Result<Self> {
let path = util::profile_config_dir(game, &name).join("profile.json");
if let Ok(s) = std::fs::read_to_string(&path) {
let data = serde_json::from_str::<ProfileData>(&s)
let mut data = serde_json::from_str::<ProfileData>(&s)
.map_err(|e| anyhow!("Unable to parse {:?}: {:?}", path, e))?;
log::debug!("{:?}", data);
if game == Game::Ongeki && data.keyboard.is_none() {
data.keyboard = Some(Keyboard::Ongeki(OngekiKeyboard::default()));
}
if game == Game::Chunithm && data.keyboard.is_none() {
data.keyboard = Some(Keyboard::Chunithm(ChunithmKeyboard::default()));
}
Ok(Profile {
meta: ProfileMeta {
game, name
@ -163,6 +179,10 @@ impl Profile {
if self.meta.game.has_module(ProfileModule::Mu3Ini) && source.mu3_ini.is_some() {
self.data.mu3_ini = source.mu3_ini;
}
if self.meta.game.has_module(ProfileModule::Keyboard) && source.keyboard.is_some() {
self.data.keyboard = source.keyboard;
}
}
pub async fn line_up(&self, pkg_hash: String, refresh: bool, _app: AppHandle) -> Result<()> {
let info = match &self.data.display {
@ -200,6 +220,10 @@ impl Profile {
.map_err(|e| anyhow!("segatools configuration failed:\n{:?}", e))?;
self.data.network.line_up(&mut ini)?;
if let Some(keyboard) = &self.data.keyboard {
keyboard.line_up(&mut ini)?;
}
ini.write_to_file(self.data_dir().join("segatools.ini"))
.map_err(|e| anyhow!("Error writing segatools.ini: {}", e))?;
@ -266,8 +290,12 @@ impl Profile {
.arg(self.meta.game.exe());
if let Some(display) = &self.data.display {
if display.dont_switch_primary && display.target != "default" {
game_builder.args(["-monitor", &display.monitor_index_override.unwrap_or_else(|| 1).to_string()]);
} else {
game_builder.args(["-monitor", "1"]);
}
game_builder.args([
"-monitor 1",
"-screen-width", &display.rez.0.to_string(),
"-screen-height", &display.rez.1.to_string(),
"-screen-fullscreen", if display.mode == DisplayMode::Fullscreen { "1" } else { "0" }

View File

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "STARTLINER",
"version": "0.2.0",
"version": "0.5.0",
"identifier": "zip.patafour.startliner",
"build": {
"beforeDevCommand": "bun run dev",

View File

@ -1,25 +1,36 @@
<script setup lang="ts">
import { Ref, computed, onMounted, ref } from 'vue';
import Button from 'primevue/button';
import ConfirmDialog from 'primevue/confirmdialog';
import Dialog from 'primevue/dialog';
import InputIcon from 'primevue/inputicon';
import InputText from 'primevue/inputtext';
import ScrollPanel from 'primevue/scrollpanel';
import Tab from 'primevue/tab';
import TabList from 'primevue/tablist';
import TabPanel from 'primevue/tabpanel';
import TabPanels from 'primevue/tabpanels';
import Tabs from 'primevue/tabs';
import { listen } from '@tauri-apps/api/event';
import ModList from './ModList.vue';
import ModStore from './ModStore.vue';
import OptionList from './OptionList.vue';
import ProfileList from './ProfileList.vue';
import StartButton from './StartButton.vue';
import { invoke } from '../invoke';
import { useGeneralStore, usePkgStore, usePrfStore } from '../stores';
import {
useClientStore,
useGeneralStore,
usePkgStore,
usePrfStore,
} from '../stores';
import { Dirs } from '../types';
import { messageSplit } from '../util';
const pkg = usePkgStore();
const prf = usePrfStore();
const general = useGeneralStore();
const client = useClientStore();
pkg.setupListeners();
@ -31,6 +42,7 @@ const isProfileDisabled = computed(() => prf.current === null);
onMounted(async () => {
invoke('list_directories').then((d) => {
general.dirs = d as Dirs;
client.load();
});
const fetch_promise = pkg.fetch(true);
@ -54,38 +66,102 @@ onMounted(async () => {
});
});
const errorVisible = ref(false);
const errorMessage = ref('No error');
const errorHeader = ref('No header');
listen<{ message: string; header: string }>('invoke-error', (event) => {
errorVisible.value = true;
errorMessage.value = event.payload.message;
errorHeader.value = event.payload.header;
});
</script>
<template>
<main>
<main
:class="
client.scaleFactor === 's'
? 'main-scale-s'
: client.scaleFactor === 'm'
? 'main-scale-m'
: client.scaleFactor === 'l'
? 'main-scale-l'
: 'main-scale-xl'
"
>
<ConfirmDialog>
<template #message="{ message }">
<ScrollPanel
v-if="messageSplit(message).length > 5"
style="width: 100%; height: 40vh"
>
<p v-for="m in messageSplit(message)">
{{ m }}
</p></ScrollPanel
>
<div v-else>
<p v-for="m in messageSplit(message)">
{{ m }}
</p>
</div>
</template>
</ConfirmDialog>
<Dialog
modal
:visible="errorVisible"
:closable="false /*this shit doesn't work */"
:header="errorHeader"
:style="{ width: '50vw' }"
>
<div class="flex flex-col gap-4">
{{ errorMessage }}
<Button
class="m-auto"
label="A sad state of affairs"
@click="errorVisible = false"
/>
</div>
</Dialog>
<Tabs
lazy
:value="currentTab"
v-on:update:value="(value) => (currentTab = value)"
v-on:update:value="
(value) => {
currentTab = value;
}
"
class="h-screen"
>
<div class="fixed w-full flex z-100">
<TabList class="grow">
<TabList class="grow" :show-navigators="false">
<Tab :value="3"
><div class="pi pi-question-circle"></div
><div class="pi pi-users" v-tooltip="'Profiles'"></div
></Tab>
<Tab :disabled="isProfileDisabled" :value="0"
><div class="pi pi-box"></div
><div
class="pi pi-box"
v-tooltip="'Installed packages'"
></div
></Tab>
<Tab v-if="prf.current?.meta.game === 'chunithm'" :value="4"
><div class="pi pi-ticket"></div
><div class="pi pi-ticket" v-tooltip="'Patches'"></div
></Tab>
<Tab
v-if="pkg.networkStatus === 'online'"
:disabled="isProfileDisabled"
:value="1"
><div class="pi pi-download"></div
><div
class="pi pi-download"
v-tooltip="'Package store'"
></div
></Tab>
<Tab :disabled="isProfileDisabled" :value="2"
><div class="pi pi-cog"></div
><div class="pi pi-cog" v-tooltip="'Settings'"></div
></Tab>
<div class="grow"></div>
<div class="flex gap-4">
<div class="flex" v-if="currentTab !== 3">
<InputIcon class="self-center mr-2">
@ -93,6 +169,7 @@ onMounted(async () => {
</InputIcon>
<InputText
v-if="currentTab === 2"
style="min-width: 0; width: 25dvw"
class="self-center"
size="small"
placeholder="Search"
@ -100,6 +177,7 @@ onMounted(async () => {
/>
<InputText
v-else
style="min-width: 0; width: 25dvw"
class="self-center"
size="small"
placeholder="Search"
@ -114,12 +192,26 @@ onMounted(async () => {
:disabled="true"
/>
<Button
v-if="pkg.networkStatus === 'offline'"
v-if="
pkg.networkStatus === 'offline' &&
!client.offlineMode
"
class="shrink self-center"
icon="pi pi-sync"
size="small"
@click="pkg.fetch(false)"
/>
<Button
v-if="
pkg.networkStatus === 'online' &&
pkg.hasAvailableUpdates
"
icon="pi pi-download"
label="UPDATE ALL"
size="small"
class="mr-4 m-2.5"
@click="pkg.updateAll()"
></Button>
</div>
<div class="grow"></div>
<StartButton />
@ -136,9 +228,6 @@ onMounted(async () => {
<OptionList />
</TabPanel>
<TabPanel :value="3">
<strong>UNDER CONSTRUCTION</strong><br />Some features are
missing.<br />Existing features are expected to break
sometimes.
<ProfileList />
<br /><br /><br />
<footer>
@ -151,8 +240,13 @@ onMounted(async () => {
</footer>
</TabPanel>
<TabPanel :value="4">
CHUNITHM patches are not yet implemented.<br />Use
<a href=https://patcher.two-torial.xyz/ target="_blank" style="text-decoration: underline;">patcher.two-torial.xyz</a>
CHUNITHM patches are not implemented yet.<br />Use
<a
href="https://patcher.two-torial.xyz/"
target="_blank"
style="text-decoration: underline"
>patcher.two-torial.xyz</a
>
</TabPanel>
</TabPanels>
<div v-if="currentTab === 5 || currentTab === 3">
@ -184,4 +278,32 @@ body {
margin: 0;
padding: 0;
}
.main-scale-s {
zoom: 1;
}
.main-scale-m {
zoom: 1.25;
}
.main-scale-l {
zoom: 1.4;
}
.main-scale-xl {
zoom: 1.7;
}
.p-tablist {
background-color: #ffffff !important;
border-bottom: white 10px !important;
}
.p-tab {
border-bottom: none !important;
}
.p-tablist-active-bar {
display: none !important;
}
</style>

View File

@ -1,7 +1,9 @@
<script setup lang="ts">
import Button from 'primevue/button';
import InputText from 'primevue/inputtext';
import * as path from '@tauri-apps/api/path';
import { open } from '@tauri-apps/plugin-dialog';
import { usePrfStore } from '../stores';
const props = defineProps({
placeholder: String,
@ -12,10 +14,25 @@ const props = defineProps({
callback: Function,
});
const prf = usePrfStore();
const filePick = async () => {
const exePath = prf.current?.data.sgt.target;
let defaultPath: string | undefined;
if (
exePath !== undefined &&
exePath.length > 0 &&
props.value !== undefined &&
!(await path.isAbsolute(props.value))
) {
defaultPath = await path.join(exePath, '..');
defaultPath = await path.join(defaultPath, props.value);
defaultPath = await path.join(defaultPath, '..');
}
const res = await open({
multiple: false,
directory: props.directory,
defaultPath,
filters:
props.promptname && props.extension
? [
@ -28,7 +45,7 @@ const filePick = async () => {
});
if (res != null && props.callback !== undefined) {
props.callback(res);
/*path.relative(cfgs.current?.data.exe_dir ?? '', res) */
/*path.relative(prf.current?.data.sgt.target ?? '', res) */
}
};
</script>

View File

@ -1,33 +1,16 @@
<script setup lang="ts">
import Button from 'primevue/button';
import { invoke } from '../invoke';
import { usePkgStore } from '../stores';
import { Package } from '../types';
import { pkgKey } from '../util';
const pkgs = usePkgStore();
const props = defineProps({
pkg: Object as () => Package,
});
const install = async () => {
if (props.pkg === undefined) {
return;
}
try {
await invoke('install_package', {
key: pkgKey(props.pkg),
force: true,
});
} catch (err) {
console.error(err);
if (props.pkg !== undefined) {
props.pkg.js.busy = false;
}
}
//if (rv === 'Deferred') { /* download progress */ }
};
const remove = async () => {
if (props.pkg === undefined) {
return;
@ -41,7 +24,7 @@ const remove = async () => {
<template>
<Button
v-if="pkg?.loc"
v-if="pkg?.loc && !pkg?.js.busy"
rounded
icon="pi pi-trash"
severity="danger"
@ -63,6 +46,6 @@ const remove = async () => {
class="self-center ml-4"
style="width: 2rem; height: 2rem"
:loading="pkg?.js.busy"
v-on:click="install()"
v-on:click="async () => await pkgs.install(pkg)"
/>
</template>

View File

@ -0,0 +1,245 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import InputText from 'primevue/inputtext';
import { usePrfStore } from '../stores';
import { OngekiButtons } from '../types';
const prf = usePrfStore();
const hasClickedM1Once = ref(false);
const handleKey = (
button: string | undefined,
event: KeyboardEvent,
index?: number
) => {
event.preventDefault();
const keycode = toKeycode(event.code);
if (keycode !== null && button !== undefined) {
const data = prf.current!.data.keyboard!.data as any;
if (index !== undefined) {
data[button][index] = keycode;
} else {
data[button] = keycode;
}
}
};
const handleMouse = (
button: string | undefined,
event: MouseEvent,
index?: number
) => {
if (button === undefined || button == 'use_mouse') {
return;
}
if (event.button === 0) {
if (hasClickedM1Once.value === false) {
hasClickedM1Once.value = true;
return;
}
} else {
event.preventDefault();
}
let keycode;
switch (event.button) {
case 0:
keycode = 1;
break;
case 1:
keycode = 4;
break;
case 2:
keycode = 2;
break;
case 3:
keycode = 5;
break;
case 4:
keycode = 6;
break;
default:
break;
}
if (keycode !== undefined) {
const data = prf.current!.data.keyboard!.data as any;
if (index !== undefined) {
data[button][index] = keycode;
} else {
data[button] = keycode;
}
}
};
const getKey = (key: keyof OngekiButtons, index?: number) =>
computed(() => {
const data = prf.current!.data.keyboard?.data as any;
const keycode =
index === undefined
? (data[key] as number | undefined)
: (data[key]?.[index] as number | undefined);
return keycode && fromKeycode(keycode) ? fromKeycode(keycode) : '';
});
const KEY_MAP: { [key: number]: string } = {
1: 'M1',
2: 'M2',
4: 'M3',
5: 'M4',
6: 'M5',
8: 'Backspace',
9: 'Tab',
13: 'Enter',
19: 'Pause',
20: 'CapsLock',
27: 'Escape',
32: 'Space',
33: 'PageUp',
34: 'PageDown',
35: 'End',
36: 'Home',
37: 'ArrowLeft',
38: 'ArrowUp',
39: 'ArrowRight',
40: 'ArrowDown',
45: 'Insert',
46: 'Delete',
48: 'Digit0',
49: 'Digit1',
50: 'Digit2',
51: 'Digit3',
52: 'Digit4',
53: 'Digit5',
54: 'Digit6',
55: 'Digit7',
56: 'Digit8',
57: 'Digit9',
65: 'KeyA',
66: 'KeyB',
67: 'KeyC',
68: 'KeyD',
69: 'KeyE',
70: 'KeyF',
71: 'KeyG',
72: 'KeyH',
73: 'KeyI',
74: 'KeyJ',
75: 'KeyK',
76: 'KeyL',
77: 'KeyM',
78: 'KeyN',
79: 'KeyO',
80: 'KeyP',
81: 'KeyQ',
82: 'KeyR',
83: 'KeyS',
84: 'KeyT',
85: 'KeyU',
86: 'KeyV',
87: 'KeyW',
88: 'KeyX',
89: 'KeyY',
90: 'KeyZ',
91: 'MetaLeft',
92: 'MetaRight',
93: 'ContextMenu',
96: 'Numpad0',
97: 'Numpad1',
98: 'Numpad2',
99: 'Numpad3',
100: 'Numpad4',
101: 'Numpad5',
102: 'Numpad6',
103: 'Numpad7',
104: 'Numpad8',
105: 'Numpad9',
106: 'NumpadMultiply',
107: 'NumpadAdd',
109: 'NumpadSubtract',
110: 'NumpadDecimal',
111: 'NumpadDivide',
112: 'F1',
113: 'F2',
114: 'F3',
115: 'F4',
116: 'F5',
117: 'F6',
118: 'F7',
119: 'F8',
120: 'F9',
121: 'F10',
122: 'F11',
123: 'F12',
144: 'NumLock',
145: 'ScrollLock',
160: 'ShiftLeft',
161: 'ShiftRight',
162: 'ControlLeft',
163: 'ControlRight',
164: 'AltLeft',
165: 'AltRight',
186: 'Semicolon',
187: 'Equal',
188: 'Comma',
189: 'Minus',
190: 'Period',
191: 'Slash',
192: 'Backquote',
219: 'BracketLeft',
220: 'Backslash',
221: 'BracketRight',
222: 'Quote',
};
const fromKeycode = (keyCode: number): string | null => {
return KEY_MAP[keyCode] ?? null;
};
const toKeycode = (key: string): number | null => {
const res = Object.entries(KEY_MAP).find(([_, v]) => v === key)?.[0];
return res ? parseInt(res) : null;
};
defineProps({
small: Boolean,
verySmall: Boolean,
tall: Boolean,
tooltip: String,
button: String,
color: String,
index: Number,
});
</script>
<template>
<InputText
:style="{
width: small ? '3em' : '5em',
height: small ? '3em' : tall ? '10em' : '5em',
fontSize: small ? '0.9em' : '1em',
backgroundColor: color,
}"
unstyled
class="text-center buttoninputtext"
v-tooltip="tooltip"
@contextmenu.prevent="() => {}"
@keydown="(ev: KeyboardEvent) => handleKey(button, ev, index)"
@mousedown="
(ev: MouseEvent) =>
handleMouse(button as keyof OngekiButtons, ev, index)
"
@focusout="() => (hasClickedM1Once = false)"
:model-value="getKey(button as keyof OngekiButtons, index) as any"
/>
</template>
<style scoped lang="css">
.buttoninputtext {
border-radius: 6px;
border: 1px solid rgba(200, 200, 200, 0.3);
}
</style>

View File

@ -6,6 +6,7 @@ const general = useGeneralStore();
defineProps({
title: String,
collapsed: Boolean,
});
</script>
@ -14,6 +15,7 @@ defineProps({
:legend="title"
:toggleable="true"
v-show="general.cfgCategories.has(title ?? '')"
:collapsed="collapsed"
>
<div class="flex w-full flex-col gap-1">
<slot />

View File

@ -7,9 +7,11 @@ import OptionCategory from './OptionCategory.vue';
import OptionRow from './OptionRow.vue';
import AimeOptions from './options/Aime.vue';
import DisplayOptions from './options/Display.vue';
import KeyboardOptions from './options/Keyboard.vue';
import MiscOptions from './options/Misc.vue';
import NetworkOptions from './options/Network.vue';
import SegatoolsOptions from './options/Segatools.vue';
import StartlinerOptions from './options/Startliner.vue';
import { usePrfStore } from '../stores';
const prf = usePrfStore();
@ -128,6 +130,8 @@ prf.reload();
v-model="blacklistMaxModel"
/></OptionRow> -->
</OptionCategory>
<KeyboardOptions />
<StartlinerOptions />
</template>
<style>

View File

@ -8,6 +8,7 @@ const category = getCurrentInstance()?.parent?.parent?.parent?.parent; // yes in
const props = defineProps({
title: String,
tooltip: String,
dangerousTooltip: String,
});
const searched = computed(() => {
@ -32,6 +33,11 @@ const searched = computed(() => {
class="pi pi-question-circle ml-2"
v-tooltip="tooltip"
></span>
<span
v-if="dangerousTooltip"
class="pi pi-exclamation-circle ml-2 text-red-500"
v-tooltip="dangerousTooltip"
></span>
</div>
<slot />

View File

@ -7,6 +7,9 @@ const prf = usePrfStore();
</script>
<template>
<div v-if="prf.list.length === 0">
Welcome to STARTLINER! Start by creating a profile.
</div>
<div class="mt-4 flex flex-row flex-wrap align-middle gap-4">
<Button
label="O.N.G.E.K.I. profile"

View File

@ -74,6 +74,8 @@ const deleteProfile = async () => {
<div v-if="!isEditing">{{ p!.name }}</div>
<div v-else>
<InputText
unstyled
class="text-center"
:model-value="p!.name"
@vue:mounted="$event?.el?.focus()"
@keyup="renameProfile"
@ -127,3 +129,11 @@ const deleteProfile = async () => {
/>
</div>
</template>
<style lang="css">
.p-tablist-tab-list {
border: none !important;
border-color: transparent !important;
border-radius: 0 !important;
}
</style>

View File

@ -1,9 +1,7 @@
<script setup lang="ts">
import { Ref, computed, ref } from 'vue';
import Button from 'primevue/button';
import ConfirmDialog from 'primevue/confirmdialog';
import ContextMenu from 'primevue/contextmenu';
import ScrollPanel from 'primevue/scrollpanel';
import { useConfirm } from 'primevue/useconfirm';
import { listen } from '@tauri-apps/api/event';
import { getCurrentWindow } from '@tauri-apps/api/window';
@ -38,6 +36,8 @@ const startline = async (force: boolean, refresh: boolean) => {
confirmDialog.require({
message: message.join('\n'),
header: 'Start check failed',
acceptLabel: 'Run anyway',
rejectLabel: 'Cancel',
accept: () => {
startline(true, refresh);
},
@ -85,10 +85,6 @@ listen('launch-end', () => {
getCurrentWindow().setFocus();
});
const messageSplit = (message: any) => {
return message.message?.split('\n');
};
const menuItems = [
{
label: 'Refresh and start',
@ -111,45 +107,6 @@ const showContextMenu = (event: Event) => {
<template>
<ContextMenu ref="menu" :model="menuItems" />
<ConfirmDialog>
<template #container="{ message, acceptCallback, rejectCallback }">
<div
class="flex flex-col p-8 bg-surface-0 dark:bg-surface-900 rounded"
>
<span class="font-bold self-center text-2xl block mb-2 mt-2">{{
message.header
}}</span>
<ScrollPanel
v-if="messageSplit(message).length > 5"
style="width: 100%; height: 40vh"
>
<p v-for="m in messageSplit(message)">
{{ m }}
</p></ScrollPanel
>
<div v-else>
<p v-for="m in messageSplit(message)">
{{ m }}
</p>
</div>
<div class="flex self-center items-center gap-2 mt-6">
<Button
label="Run anyway"
@click="acceptCallback"
size="small"
class="w-32"
></Button>
<Button
label="Cancel"
outlined
size="small"
@click="rejectCallback"
class="w-32"
></Button>
</div>
</div>
</template>
</ConfirmDialog>
<Button
v-if="startStatus === 'ready'"
v-tooltip="disabledTooltip"

View File

@ -32,7 +32,7 @@ const install = async () => {
<Button
v-if="needsUpdate(pkg) && !pkg?.js.busy"
rounded
icon="pi pi-sync"
icon="pi pi-download"
severity="success"
aria-label="remove"
size="small"

View File

@ -3,6 +3,7 @@ import { computed, ref } from 'vue';
import InputText from 'primevue/inputtext';
import Select from 'primevue/select';
import ToggleSwitch from 'primevue/toggleswitch';
import { listen } from '@tauri-apps/api/event';
import * as path from '@tauri-apps/api/path';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
import OptionCategory from '../OptionCategory.vue';
@ -40,15 +41,22 @@ const aimeCodePaste = (ev: ClipboardEvent) => {
.join('') ?? '';
};
(async () => {
const load = async () => {
const aime_path = await path.join(await prf.configDir, 'aime.txt');
aimeCode.value = await readTextFile(aime_path).catch(() => '');
})();
};
listen('reload-aime-code', load);
load();
</script>
<template>
<OptionCategory title="Aime">
<OptionRow title="Aime emulation">
<OptionRow
title="Aime emulation"
tooltip="Aime plugins can be downloaded from the package store."
>
<Select
v-model="prf.current!.data.sgt.aime"
:options="[

View File

@ -157,5 +157,40 @@ loadDisplays();
v-model="prf.current!.data.display.borderless_fullscreen"
/>
</OptionRow>
<OptionRow
title="Skip switching primary display"
v-if="
capabilities.includes('display') &&
prf.current?.data.display.target !== 'default' &&
(prf.current!.data.display.dont_switch_primary ||
displayList.length > 2)
"
dangerous-tooltip="Only enable this option if switching the primary display causes issues. The monitors must have a matching refresh rate."
>
<ToggleSwitch
:disabled="extraDisplayOptionsDisabled"
v-model="prf.current!.data.display.dont_switch_primary"
/>
</OptionRow>
<OptionRow
title="Unity display index"
class="number-input"
v-if="
capabilities.includes('display') &&
prf.current?.data.display.target !== 'default' &&
prf.current!.data.display.dont_switch_primary
"
>
<InputNumber
class="shrink"
size="small"
:min="1"
:max="32"
:use-grouping="false"
v-model="prf.current!.data.display.monitor_index_override"
:disabled="extraDisplayOptionsDisabled"
:allow-empty="true"
/>
</OptionRow>
</OptionCategory>
</template>

View File

@ -0,0 +1,177 @@
<script setup lang="ts">
import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch';
import KeyboardKey from '../KeyboardKey.vue';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { usePrfStore } from '../../stores';
import { ChunithmButtons } from '@/types';
ToggleSwitch;
const prf = usePrfStore();
</script>
<template>
<OptionCategory title="Keyboard">
<OptionRow
title="Lever mode"
v-if="prf.current!.data.keyboard!.game === 'Ongeki'"
>
<SelectButton
v-model="prf.current!.data.keyboard!.data.use_mouse"
:options="[
{ title: 'XInput', value: false },
{ title: 'Mouse', value: true },
]"
:allow-empty="false"
option-label="title"
option-value="value"
/>
</OptionRow>
<OptionRow
title="Enable multiple IRs"
v-if="prf.current!.data.keyboard!.game === 'Chunithm'"
>
<ToggleSwitch v-model="prf.current!.data.keyboard!.data.split_ir" />
</OptionRow>
<div
:style="`position: relative; height: ${prf.current!.data.keyboard!.game === 'Ongeki' ? 400 : 250}px`"
>
<div
class="absolute left-1/6 top-1/10"
style="transform: translateX(-30%) translateY(-50%)"
>
<div class="flex flex-row gap-2 self-center w-full">
<KeyboardKey button="test" small tooltip="Test" />
<KeyboardKey button="svc" small tooltip="Service" />
<KeyboardKey button="coin" small tooltip="Coin" />
</div>
</div>
<div v-if="prf.current?.meta.game === 'ongeki'">
<div
class="absolute left-1/2 top-1/2"
style="transform: translateX(-540%) translateY(-200%)"
>
<KeyboardKey
button="lmenu"
small
color="rgba(255, 0, 0, 0.2)"
/>
</div>
<div
class="absolute right-1/2 top-1/2"
style="transform: translateX(540%) translateY(-200%)"
>
<KeyboardKey
button="rmenu"
small
color="rgba(255, 255, 0, 0.2)"
/>
</div>
<div
class="absolute left-1/2 top-1/2"
style="transform: translateX(-50%) translateY(-20%)"
>
<div class="flex flex-row gap-2 self-center w-full">
<KeyboardKey
button="lwad"
tall
color="rgba(180, 0, 255, 0.2)"
/>
<div style="width: 0.7em"></div>
<KeyboardKey button="l1" color="rgba(255, 0, 0, 0.2)" />
<KeyboardKey button="l2" color="rgba(0, 255, 0, 0.2)" />
<KeyboardKey button="l3" color="rgba(0, 0, 255, 0.2)" />
<div style="width: 0.7em"></div>
<KeyboardKey button="r1" color="rgba(255, 0, 0, 0.2)" />
<KeyboardKey button="r2" color="rgba(0, 255, 0, 0.2)" />
<KeyboardKey button="r3" color="rgba(0, 0, 255, 0.2)" />
<div style="width: 0.7em"></div>
<KeyboardKey
button="rwad"
tall
color="rgba(255, 0, 180, 0.2)"
/>
</div>
</div>
</div>
<div v-if="prf.current?.meta.game === 'chunithm'">
<div class="absolute left-1/2 top-1/5">
<div
class="flex flex-row flex-nowrap gap-2 self-center w-full"
>
<div
v-if="
(
prf.current!.data.keyboard!
.data as ChunithmButtons
).split_ir
"
v-for="idx in Array(6)
.fill(0)
.map((_, i) => i + 1)"
>
<KeyboardKey
button="ir"
:index="idx - 1"
:tooltip="`ir${idx}`"
small
color="rgba(0, 255, 0, 0.2)"
/>
</div>
<div v-else>
<KeyboardKey
button="ir"
:index="0"
:tooltip="`ir0`"
small
color="rgba(0, 255, 0, 0.2)"
/>
</div>
</div>
</div>
<div
class="absolute left-1/2 top-1/2"
style="transform: translateX(-50%) translateY(-5%)"
>
<div
class="flex flex-row flex-nowrap gap-2 self-center w-full"
>
<div
v-for="idx in Array(16)
.fill(0)
.map((_, i) => 16 - i)"
>
<KeyboardKey
button="cell"
:index="idx - 1"
:tooltip="`cell${idx}`"
small
color="rgba(255, 255, 0, 0.2)"
/>
</div>
</div>
<div style="height: 0.6em"></div>
<div
class="flex flex-row flex-nowrap gap-2 self-center w-full"
>
<div
v-for="idx in Array(16)
.fill(0)
.map((_, i) => 32 - i)"
>
<KeyboardKey
button="cell"
:index="idx - 1"
:tooltip="`cell${idx}`"
small
color="rgba(255, 255, 0, 0.2)"
/>
</div>
</div>
</div>
</div>
</div>
</OptionCategory>
</template>

View File

@ -13,7 +13,11 @@ const prf = usePrfStore();
<OptionRow title="OpenSSL bug workaround for Intel ≥10th gen">
<ToggleSwitch v-model="prf.current!.data.sgt.intel" />
</OptionRow>
<OptionRow title="More segatools options">
<OptionRow
title="More segatools options"
tooltip="Advanced options not covered by STARTLINER"
>
<!-- <Button icon="pi pi-refresh" size="small" /> -->
<FileEditor filename="segatools-base.ini" />
</OptionRow>
</OptionCategory>

View File

@ -1,15 +1,20 @@
<script setup lang="ts">
import { computed } from 'vue';
import Select from 'primevue/select';
import { useConfirm } from 'primevue/useconfirm';
import { emit } from '@tauri-apps/api/event';
import * as path from '@tauri-apps/api/path';
import FilePicker from '../FilePicker.vue';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { invoke } from '../../invoke';
import { usePkgStore, usePrfStore } from '../../stores';
import { Feature } from '../../types';
import { pkgKey } from '../../util';
const prf = usePrfStore();
const pkgs = usePkgStore();
const confirmDialog = useConfirm();
const names = computed(() => {
switch (prf.current?.meta.game) {
@ -31,18 +36,39 @@ const names = computed(() => {
throw new Error('Option tab without a profile');
}
});
const checkSegatoolsIni = async (target: string) => {
const iniPath = await path.join(target, '../segatools.ini');
if (await invoke('file_exists', { path: iniPath })) {
confirmDialog.require({
message: 'Would you like to load the existing configuration data?',
header: 'segatools.ini found',
accept: async () => {
await invoke('load_segatools_ini', { path: iniPath });
await prf.reload();
await emit('reload-aime-code');
},
});
}
};
</script>
<template>
<OptionCategory title="General">
<OptionRow :title="names.exe">
<OptionRow
:title="names.exe"
tooltip="STARTLINER expects unpacked executables put into otherwise clean data."
>
<FilePicker
:directory="false"
:promptname="names.exe"
extension="exe"
:value="prf.current!.data.sgt.target"
:callback="
(value: string) => (prf.current!.data.sgt.target = value)
(value: string) => (
(prf.current!.data.sgt.target = value),
checkSegatoolsIni(value)
)
"
></FilePicker>
</OptionRow>
@ -76,7 +102,10 @@ const names = computed(() => {
"
></FilePicker>
</OptionRow>
<OptionRow :title="names.hook">
<OptionRow
:title="names.hook"
tooltip="Hooks can be downloaded from the package store."
>
<Select
v-model="prf.current!.data.sgt.hook"
:options="
@ -94,7 +123,11 @@ const names = computed(() => {
option-value="value"
></Select>
</OptionRow>
<OptionRow :title="names.io" v-if="prf.current?.meta.game === 'ongeki'">
<OptionRow
:title="names.io"
v-if="prf.current?.meta.game === 'ongeki'"
tooltip="IO plugins can be downloaded from the package store."
>
<Select
v-model="prf.current!.data.sgt.io"
placeholder="segatools built-in"

View File

@ -0,0 +1,56 @@
<script setup lang="ts">
import { computed } from 'vue';
import SelectButton from 'primevue/selectbutton';
import ToggleSwitch from 'primevue/toggleswitch';
import OptionCategory from '../OptionCategory.vue';
import OptionRow from '../OptionRow.vue';
import { useClientStore } from '../../stores';
const client = useClientStore();
const offlineModel = computed({
get() {
return client.offlineMode;
},
async set(value: boolean) {
await client.setOfflineMode(value);
},
});
const updatesModel = computed({
get() {
return client.enableAutoupdates;
},
async set(value: boolean) {
await client.setAutoupdates(value);
},
});
</script>
<template>
<OptionCategory title="STARTLINER">
<OptionRow title="UI scaling">
<SelectButton
v-model="client.scaleModel"
:options="[
{ title: 'S', value: 's' },
{ title: 'M', value: 'm' },
{ title: 'L', value: 'l' },
{ title: 'XL', value: 'xl' },
]"
:allow-empty="false"
option-label="title"
option-value="value"
/>
</OptionRow>
<OptionRow
title="Offline mode"
tooltip="Disables the package store. Requires a restart."
>
<ToggleSwitch v-model="offlineModel" />
</OptionRow>
<OptionRow title="Enable automatic updates">
<ToggleSwitch v-model="updatesModel" />
</OptionRow>
</OptionCategory>
</template>

View File

@ -3,7 +3,7 @@ import {
InvokeOptions,
invoke as real_invoke,
} from '@tauri-apps/api/core';
import { message } from '@tauri-apps/plugin-dialog';
import { emit } from '@tauri-apps/api/event';
export const invoke = async <T>(
cmd: string,
@ -14,9 +14,9 @@ export const invoke = async <T>(
return await real_invoke(cmd, args, options);
} catch (e: unknown) {
if (typeof e === 'string') {
await message(`${cmd}: ${e}`, {
title: `Error`,
kind: 'error',
emit('invoke-error', {
message: e,
header: `${cmd} failed`,
});
} else {
console.error(`Unresolved error: ${e}`);

View File

@ -2,6 +2,8 @@ import { Ref, computed, ref, watchEffect } from 'vue';
import { defineStore } from 'pinia';
import { listen } from '@tauri-apps/api/event';
import * as path from '@tauri-apps/api/path';
import { PhysicalSize, getCurrentWindow } from '@tauri-apps/api/window';
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
import { invoke, invoke_nopopup } from './invoke';
import { Dirs, Feature, Game, Package, Profile, ProfileMeta } from './types';
import { changePrimaryColor, hasFeature, pkgKey } from './util';
@ -102,6 +104,10 @@ export const usePkgStore = defineStore('pkg', {
),
byFeature: (state) => (feature: Feature) =>
Object.values(state.pkg).filter((p) => hasFeature(p, feature)),
hasAvailableUpdates: (state) =>
Object.values(state.pkg).some(
(p) => p.loc && (p.rmt?.version ?? 0) > p.loc.version
),
},
actions: {
setupListeners() {
@ -170,6 +176,36 @@ export const usePkgStore = defineStore('pkg', {
}
await this.reloadAll();
},
async install(pkg: Package | undefined) {
if (pkg === undefined) {
return;
}
try {
await invoke('install_package', {
key: pkgKey(pkg),
force: true,
});
} catch (err) {
console.error(err);
if (pkg !== undefined) {
pkg.js.busy = false;
}
}
//if (rv === 'Deferred') { /* download progress */ }
},
async updateAll() {
const list = [];
for (const pkg of this.allLocal) {
if (pkg.rmt && pkg.rmt.version > pkg.loc!.version) {
list.push(this.install(pkg));
}
}
await Promise.all(list);
},
},
});
@ -295,3 +331,134 @@ export const usePrfStore = defineStore('prf', () => {
configDir,
};
});
export const useClientStore = defineStore('client', () => {
type ScaleType = 's' | 'm' | 'l' | 'xl';
const scaleFactor: Ref<ScaleType> = ref('s');
const timeout: Ref<NodeJS.Timeout | null> = ref(null);
const offlineMode = ref(false);
const enableAutoupdates = ref(true);
const scaleValue = (value: ScaleType) =>
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
const setScaleFactor = async (value: ScaleType) => {
scaleFactor.value = value;
const window = getCurrentWindow();
const w = Math.floor(scaleValue(value) * 900);
const h = Math.floor(scaleValue(value) * 480);
let size = await window.innerSize();
window.setMinSize(new PhysicalSize(w, h));
window.setSize(
new PhysicalSize(Math.max(w, size.width), Math.max(h, size.height))
);
};
const scaleModel = computed({
get() {
return scaleFactor;
},
async set(value: ScaleType) {
await setScaleFactor(value);
await save();
},
});
const load = async () => {
const generalStore = useGeneralStore();
try {
const input = JSON.parse(
await readTextFile(
await path.join(
generalStore.configDir,
'client-options.json'
)
)
);
if (input.windowSize) {
getCurrentWindow().setSize(
new PhysicalSize(input.windowSize.w, input.windowSize.h)
);
}
if (input.scaleFactor) {
await setScaleFactor(input.scaleFactor);
}
} catch (e) {
console.error(`Error reading client options: ${e}`);
}
offlineMode.value = await invoke('get_global_config', {
field: 'OfflineMode',
});
enableAutoupdates.value = await invoke('get_global_config', {
field: 'EnableAutoupdates',
});
};
const save = async () => {
const generalStore = useGeneralStore();
const w = getCurrentWindow();
const size = await w.innerSize();
await writeTextFile(
await path.join(generalStore.configDir, 'client-options.json'),
JSON.stringify({
scaleFactor: scaleFactor.value,
windowSize: {
w: Math.floor(size.width),
h: Math.floor(size.height),
},
})
);
};
const queueSave = async () => {
if (timeout.value !== null) {
clearTimeout(timeout.value);
}
timeout.value = setTimeout(async () => {
timeout.value = null;
await save();
}, 1000);
};
const setOfflineMode = async (value: boolean) => {
offlineMode.value = value;
await invoke('set_global_config', { field: 'OfflineMode', value });
};
const setAutoupdates = async (value: boolean) => {
enableAutoupdates.value = value;
await invoke('set_global_config', {
field: 'EnableAutoupdates',
value,
});
};
getCurrentWindow().onResized(async ({ payload }) => {
// For whatever reason this is 0 when minimized
if (payload.width > 0) {
await queueSave();
}
});
return {
scaleFactor,
offlineMode,
enableAutoupdates,
timeout,
scaleModel,
load,
save,
queueSave,
setOfflineMode,
setAutoupdates,
};
});

View File

@ -53,6 +53,7 @@ export interface ProfileData {
network: NetworkConfig;
bepinex: BepInExConfig;
mu3_ini: Mu3IniConfig | undefined;
keyboard: KeyboardConfig | undefined;
}
export interface SegatoolsConfig {
@ -78,6 +79,8 @@ export interface DisplayConfig {
rotation: number;
frequency: number;
borderless_fullscreen: boolean;
dont_switch_primary: boolean;
monitor_index_override: number | null;
}
export interface NetworkConfig {
@ -99,6 +102,42 @@ export interface Mu3IniConfig {
// blacklist?: [number, number];
}
export interface OngekiButtons {
use_mouse: boolean;
coin: number;
svc: number;
test: number;
lmenu: number;
rmenu: number;
l1: number;
l2: number;
l3: number;
r1: number;
r2: number;
r3: number;
lwad: number;
rwad: number;
}
export interface ChunithmButtons {
split_ir: boolean;
coin: number;
svc: number;
test: number;
cell: number[];
ir: number[];
}
export type KeyboardConfig =
| {
game: 'Ongeki';
data: OngekiButtons;
}
| {
game: 'Chunithm';
data: ChunithmButtons;
};
export interface Profile {
meta: ProfileMeta;
data: ProfileData;

View File

@ -55,3 +55,7 @@ export const hasFeature = (pkg: Package | undefined, feature: Feature) => {
pkg.loc.status.OK & feature
);
};
export const messageSplit = (message: any) => {
return message.message?.split('\n');
};