mirror of
https://github.com/SrIzan10/osu-radio.git
synced 2026-05-01 10:55:12 +00:00
Merge branch 'master' of https://github.com/Team-BTMC/osu-radio into feature/color-changes
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Run Eslint
|
||||
run: npm run lint:check
|
||||
run: npm run lint
|
||||
|
||||
format:
|
||||
timeout-minutes: 10
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
/** @type {import("prettier").Options} */
|
||||
export default {
|
||||
printWidth: 100,
|
||||
plugins: ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"],
|
||||
};
|
||||
9
.prettierrc.ts
Normal file
9
.prettierrc.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/** @type {import("prettier").Options} */
|
||||
export default {
|
||||
printWidth: 100,
|
||||
plugins: [
|
||||
"@trivago/prettier-plugin-sort-imports",
|
||||
"prettier-plugin-tailwindcss",
|
||||
"prettier-plugin-packagejson",
|
||||
],
|
||||
};
|
||||
21
eslint.config.js
Normal file
21
eslint.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import eslint from "@eslint/js";
|
||||
import solid from "eslint-plugin-solid";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
ignores: ["**/out", "**/build", "**/dist"],
|
||||
},
|
||||
{
|
||||
files: ["**/*"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/renderer/**/*.tsx"],
|
||||
...solid.configs["flat/recommended"],
|
||||
},
|
||||
);
|
||||
@@ -1,37 +0,0 @@
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
import js from "@eslint/js";
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all,
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ["**/out", "**/build", "**/dist"],
|
||||
},
|
||||
...compat.extends("plugin:@typescript-eslint/recommended"),
|
||||
{
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*"],
|
||||
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
},
|
||||
];
|
||||
398
package-lock.json
generated
398
package-lock.json
generated
@@ -12,7 +12,7 @@
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@types/graceful-fs": "^4.1.6",
|
||||
"@types/graceful-fs": "^4.1.9",
|
||||
"@xhayper/discord-rpc": "^1.2.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -32,25 +32,27 @@
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@electron/notarize": "^2.5.0",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@eslint/js": "^9.13.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@types/node": "^22.7.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
||||
"@typescript-eslint/parser": "^8.8.0",
|
||||
"@types/eslint__eslintrc": "^2.1.2",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/node": "^22.7.7",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"electron": "^32.1.2",
|
||||
"electron-builder": "^25.1.7",
|
||||
"electron-vite": "^2.3.0",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-solid": "^0.14.3",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-packagejson": "^2.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
"solid-js": "^1.7.6",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"typescript": "~5.5.0",
|
||||
"typescript": "~5.6.3",
|
||||
"typescript-eslint": "^8.10.0",
|
||||
"vite": "^5.4.8",
|
||||
"vite-plugin-lucide-preprocess": "^1.1.1",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
@@ -1361,9 +1363,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz",
|
||||
"integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz",
|
||||
"integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
@@ -1395,9 +1397,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz",
|
||||
"integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==",
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz",
|
||||
"integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1588,6 +1590,30 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz",
|
||||
"integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/node": {
|
||||
"version": "0.16.5",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz",
|
||||
"integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@humanfs/core": "^0.19.0",
|
||||
"@humanwhocodes/retry": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/module-importer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||
@@ -1603,9 +1629,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/retry": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz",
|
||||
"integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==",
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
|
||||
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
@@ -2780,6 +2806,37 @@
|
||||
"@types/ms": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint__eslintrc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint__eslintrc/-/eslint__eslintrc-2.1.2.tgz",
|
||||
"integrity": "sha512-qXvzPFY7Rz05xD8ZApXJ3S8xStQD2Ibzu3EFIF0UMNOAfLY5xUu3H61q0JrHo2OXD6rcFG75yUxNQbkKtFKBSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint__js": {
|
||||
"version": "8.42.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz",
|
||||
"integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
@@ -2854,9 +2911,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz",
|
||||
"integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==",
|
||||
"version": "22.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.7.tgz",
|
||||
"integrity": "sha512-SRxCrrg9CL/y54aiMCG3edPKdprgMVGDXjA3gB8UmmBW5TcXzRUYAh8EWzTnSJFAd1rgImPELza+A3bJ+qxz8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
@@ -2902,17 +2959,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz",
|
||||
"integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz",
|
||||
"integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.8.0",
|
||||
"@typescript-eslint/type-utils": "8.8.0",
|
||||
"@typescript-eslint/utils": "8.8.0",
|
||||
"@typescript-eslint/visitor-keys": "8.8.0",
|
||||
"@typescript-eslint/scope-manager": "8.10.0",
|
||||
"@typescript-eslint/type-utils": "8.10.0",
|
||||
"@typescript-eslint/utils": "8.10.0",
|
||||
"@typescript-eslint/visitor-keys": "8.10.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -2936,16 +2993,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz",
|
||||
"integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz",
|
||||
"integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.8.0",
|
||||
"@typescript-eslint/types": "8.8.0",
|
||||
"@typescript-eslint/typescript-estree": "8.8.0",
|
||||
"@typescript-eslint/visitor-keys": "8.8.0",
|
||||
"@typescript-eslint/scope-manager": "8.10.0",
|
||||
"@typescript-eslint/types": "8.10.0",
|
||||
"@typescript-eslint/typescript-estree": "8.10.0",
|
||||
"@typescript-eslint/visitor-keys": "8.10.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2965,14 +3022,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz",
|
||||
"integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz",
|
||||
"integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.8.0",
|
||||
"@typescript-eslint/visitor-keys": "8.8.0"
|
||||
"@typescript-eslint/types": "8.10.0",
|
||||
"@typescript-eslint/visitor-keys": "8.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2983,14 +3040,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz",
|
||||
"integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz",
|
||||
"integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.8.0",
|
||||
"@typescript-eslint/utils": "8.8.0",
|
||||
"@typescript-eslint/typescript-estree": "8.10.0",
|
||||
"@typescript-eslint/utils": "8.10.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
@@ -3008,9 +3065,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz",
|
||||
"integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz",
|
||||
"integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3022,14 +3079,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz",
|
||||
"integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz",
|
||||
"integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.8.0",
|
||||
"@typescript-eslint/visitor-keys": "8.8.0",
|
||||
"@typescript-eslint/types": "8.10.0",
|
||||
"@typescript-eslint/visitor-keys": "8.10.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -3077,16 +3134,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz",
|
||||
"integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz",
|
||||
"integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.8.0",
|
||||
"@typescript-eslint/types": "8.8.0",
|
||||
"@typescript-eslint/typescript-estree": "8.8.0"
|
||||
"@typescript-eslint/scope-manager": "8.10.0",
|
||||
"@typescript-eslint/types": "8.10.0",
|
||||
"@typescript-eslint/typescript-estree": "8.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3100,13 +3157,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz",
|
||||
"integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==",
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz",
|
||||
"integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.8.0",
|
||||
"@typescript-eslint/types": "8.10.0",
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4681,6 +4738,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-indent": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz",
|
||||
"integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.20"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
@@ -4690,6 +4757,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-newline": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-4.0.1.tgz",
|
||||
"integrity": "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
@@ -4725,6 +4805,19 @@
|
||||
"p-limit": "^3.1.0 "
|
||||
}
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-type": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.37.101",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.101.tgz",
|
||||
@@ -5195,22 +5288,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.11.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz",
|
||||
"integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==",
|
||||
"version": "9.13.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz",
|
||||
"integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.11.0",
|
||||
"@eslint/config-array": "^0.18.0",
|
||||
"@eslint/core": "^0.6.0",
|
||||
"@eslint/core": "^0.7.0",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "9.11.1",
|
||||
"@eslint/js": "9.13.0",
|
||||
"@eslint/plugin-kit": "^0.2.0",
|
||||
"@humanfs/node": "^0.16.5",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.3.0",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
"@humanwhocodes/retry": "^0.3.1",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"ajv": "^6.12.4",
|
||||
@@ -5218,9 +5311,9 @@
|
||||
"cross-spawn": "^7.0.2",
|
||||
"debug": "^4.3.2",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^8.0.2",
|
||||
"eslint-visitor-keys": "^4.0.0",
|
||||
"espree": "^10.1.0",
|
||||
"eslint-scope": "^8.1.0",
|
||||
"eslint-visitor-keys": "^4.1.0",
|
||||
"espree": "^10.2.0",
|
||||
"esquery": "^1.5.0",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@@ -5230,13 +5323,11 @@
|
||||
"ignore": "^5.2.0",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-glob": "^4.0.0",
|
||||
"is-path-inside": "^3.0.3",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"minimatch": "^3.1.2",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.9.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"text-table": "^0.2.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -5902,6 +5993,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stdin": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
|
||||
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
||||
@@ -5917,6 +6021,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/git-hooks-list": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-3.1.0.tgz",
|
||||
"integrity": "sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/fisker/git-hooks-list?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -6000,6 +6114,26 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/globby": {
|
||||
"version": "13.2.2",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
|
||||
"integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dir-glob": "^3.0.1",
|
||||
"fast-glob": "^3.3.0",
|
||||
"ignore": "^5.2.4",
|
||||
"merge2": "^1.4.1",
|
||||
"slash": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
@@ -6527,14 +6661,17 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-path-inside": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
|
||||
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
|
||||
"node_modules/is-plain-obj": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
|
||||
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
@@ -7871,6 +8008,16 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pe-library": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
|
||||
@@ -8160,6 +8307,25 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-packagejson": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.3.tgz",
|
||||
"integrity": "sha512-ATMEEXr+ywls1kgrZEWl4SBPEm0uDdyDAjyNzUC0/Z8WZTD3RqbJcQDR+Dau+wYkW9KHK6zqQIsFyfn+9aduWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sort-package-json": "2.10.1",
|
||||
"synckit": "0.9.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prettier": ">= 1.16.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"prettier": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-tailwindcss": {
|
||||
"version": "0.6.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz",
|
||||
@@ -8876,6 +9042,19 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
|
||||
"integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
|
||||
@@ -8984,6 +9163,33 @@
|
||||
"solid-js": "^1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-object-keys": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz",
|
||||
"integrity": "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sort-package-json": {
|
||||
"version": "2.10.1",
|
||||
"resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.10.1.tgz",
|
||||
"integrity": "sha512-d76wfhgUuGypKqY72Unm5LFnMpACbdxXsLPcL27pOsSrmVqH3PztFp1uq+Z22suk15h7vXmTesuh2aEjdCqb5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-indent": "^7.0.1",
|
||||
"detect-newline": "^4.0.0",
|
||||
"get-stdin": "^9.0.0",
|
||||
"git-hooks-list": "^3.0.0",
|
||||
"globby": "^13.1.2",
|
||||
"is-plain-obj": "^4.1.0",
|
||||
"semver": "^7.6.0",
|
||||
"sort-object-keys": "^1.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"sort-package-json": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -9281,9 +9487,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
|
||||
"integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==",
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
|
||||
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9612,9 +9818,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -9625,6 +9831,30 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.10.0.tgz",
|
||||
"integrity": "sha512-YIu230PeN7z9zpu/EtqCIuRVHPs4iSlqW6TEvjbyDAE3MZsSl2RXBo+5ag+lbABCG8sFM1WVKEXhlQ8Ml8A3Fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.10.0",
|
||||
"@typescript-eslint/parser": "8.10.0",
|
||||
"@typescript-eslint/utils": "8.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz",
|
||||
|
||||
46
package.json
46
package.json
@@ -2,32 +2,32 @@
|
||||
"name": "osu-radio",
|
||||
"version": "1.0.0",
|
||||
"description": "Application to play your osu! songs.",
|
||||
"main": "./out/main/index.js",
|
||||
"type": "module",
|
||||
"author": "@CaptSiro",
|
||||
"homepage": "https://github.com/Team-BTMC/osu-radio",
|
||||
"author": "@CaptSiro",
|
||||
"type": "module",
|
||||
"main": "./out/main/index.js",
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"lint": "eslint . --fix",
|
||||
"lint:check": "eslint .",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"start": "electron-vite preview",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"build:linux": "electron-vite build && electron-builder --linux --config",
|
||||
"build:mac": "electron-vite build && electron-builder --mac --config",
|
||||
"build:win": "npm run build && electron-builder --win --config",
|
||||
"dev": "electron-vite dev",
|
||||
"dev:watch": "electron-vite dev --watch",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:win": "npm run build && electron-builder --win --config",
|
||||
"build:mac": "electron-vite build && electron-builder --mac --config",
|
||||
"build:linux": "electron-vite build && electron-builder --linux --config"
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"start": "electron-vite preview",
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@types/graceful-fs": "^4.1.6",
|
||||
"@types/graceful-fs": "^4.1.9",
|
||||
"@xhayper/discord-rpc": "^1.2.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -47,25 +47,27 @@
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@electron/notarize": "^2.5.0",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@eslint/js": "^9.13.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@types/node": "^22.7.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.8.0",
|
||||
"@typescript-eslint/parser": "^8.8.0",
|
||||
"@types/eslint__eslintrc": "^2.1.2",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/node": "^22.7.7",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"electron": "^32.1.2",
|
||||
"electron-builder": "^25.1.7",
|
||||
"electron-vite": "^2.3.0",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-solid": "^0.14.3",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-packagejson": "^2.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
"solid-js": "^1.7.6",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"typescript": "~5.5.0",
|
||||
"typescript": "~5.6.3",
|
||||
"typescript-eslint": "^8.10.0",
|
||||
"vite": "^5.4.8",
|
||||
"vite-plugin-lucide-preprocess": "^1.1.1",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
|
||||
9
src/@types.d.ts
vendored
9
src/@types.d.ts
vendored
@@ -223,15 +223,6 @@ export type SongViewProps = {
|
||||
playlist?: string;
|
||||
};
|
||||
|
||||
export type NoticeType = {
|
||||
id?: string;
|
||||
class: "notice" | "warning" | "error";
|
||||
title: string;
|
||||
content: string;
|
||||
timeoutMS?: number;
|
||||
active?: boolean;
|
||||
};
|
||||
|
||||
export type InfiniteScrollerRequest = {
|
||||
index: number;
|
||||
init: number;
|
||||
|
||||
@@ -51,14 +51,14 @@ export class TemplateTokenizer {
|
||||
switch (this.char) {
|
||||
case "\0":
|
||||
return createToken(Tokens.EOF, this.char, this.position);
|
||||
case "\\":
|
||||
case "\\": {
|
||||
const next = this.peek();
|
||||
if (next === "{" || next === "}") {
|
||||
this.readChar();
|
||||
return createToken(Tokens.Text, this.char, this.position);
|
||||
}
|
||||
|
||||
return createToken(Tokens.Text, this.char, this.position);
|
||||
}
|
||||
case "{":
|
||||
return createToken(Tokens.LeftSquirly, this.char, this.position);
|
||||
case "}":
|
||||
|
||||
@@ -52,7 +52,7 @@ async function configureOsuDir(mainWindow: BrowserWindow) {
|
||||
let tables: Awaited<DirParseResult>;
|
||||
const settings = Storage.getTable("settings");
|
||||
|
||||
do {
|
||||
while (true) {
|
||||
await Router.dispatch(mainWindow, "changeScene", "dir-select");
|
||||
const dir = await dirSubmit();
|
||||
|
||||
@@ -91,7 +91,7 @@ async function configureOsuDir(mainWindow: BrowserWindow) {
|
||||
// All went smoothly. Save osu directory and continue with import procedure
|
||||
settings.write("osuSongsDir", dir);
|
||||
break;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
// Show finished state
|
||||
await Router.dispatch(mainWindow, "loadingScene::update", {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.notice-container-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: clamp(250px, 400px, 33vw);
|
||||
overflow: auto;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.notice-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px 16px 16px 32px;
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
.notice-wrapper {
|
||||
overflow: hidden;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.notice {
|
||||
background-color: rgba(var(--color-fg), var(--level-0));
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.notice.warning {
|
||||
background-color: oklch(70% 0.15 55 / var(--level-0));
|
||||
}
|
||||
|
||||
.notice.error {
|
||||
background-color: oklch(70% 0.15 20 / var(--level-0));
|
||||
}
|
||||
|
||||
.notice .content {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.notice .content .head {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.notice .timeout {
|
||||
height: 8px;
|
||||
width: 100%;
|
||||
background-color: rgba(var(--color-fg), var(--level-3));
|
||||
}
|
||||
|
||||
.notice.warning .timeout {
|
||||
background-color: oklch(70% 0.15 55 / var(--level-3));
|
||||
}
|
||||
|
||||
.notice.error .timeout {
|
||||
background-color: oklch(70% 0.15 20 / var(--level-3));
|
||||
}
|
||||
|
||||
.notice .timeout.pause {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
@keyframes drain-time {
|
||||
from {
|
||||
width: 100%;
|
||||
}
|
||||
to {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import "../assets/css/bar.css";
|
||||
import { clamp } from "../lib/tungsten/math";
|
||||
import { Component, createEffect, createSignal, onMount } from "solid-js";
|
||||
|
||||
type BarAlignment = "vertical" | "v" | "horizontal" | "h";
|
||||
|
||||
function isVertical(alignment?: BarAlignment): boolean {
|
||||
return alignment === "vertical" || alignment === "v";
|
||||
}
|
||||
|
||||
type BarProps = {
|
||||
alignment?: BarAlignment;
|
||||
fill: number;
|
||||
setFill?: (fill: number) => any;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const Bar: Component<BarProps> = (props) => {
|
||||
const [fill, setFill] = createSignal(props.fill);
|
||||
let bar: HTMLDivElement | undefined, handle: HTMLDivElement | undefined;
|
||||
|
||||
onMount(() => {
|
||||
createEffect(() => {
|
||||
const f = fill();
|
||||
bar?.style.setProperty("--fill-per", `${clamp(0, 1, f) * 100}%`);
|
||||
|
||||
if (props.setFill !== undefined) {
|
||||
props.setFill(clamp(0, 1, f));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const calculateFill = (evt: PointerEvent) => {
|
||||
if (props.disabled === true || !bar) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect: DOMRect = bar.getBoundingClientRect();
|
||||
|
||||
if (isVertical(props.alignment)) {
|
||||
setFill(clamp(0, 1, -(evt.clientY - rect.top) / rect.height + 1));
|
||||
return;
|
||||
}
|
||||
|
||||
setFill(clamp(0, 1, (evt.clientX - rect.left) / rect.width));
|
||||
};
|
||||
|
||||
const onDown = (evt: PointerEvent) => {
|
||||
if (props.disabled === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
handle?.setPointerCapture(evt.pointerId);
|
||||
|
||||
handle?.addEventListener("pointermove", calculateFill);
|
||||
handle?.addEventListener(
|
||||
"pointerup",
|
||||
() => handle.removeEventListener("pointermove", calculateFill),
|
||||
{ once: true },
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={bar}
|
||||
classList={{
|
||||
bar: true,
|
||||
vertical: props.alignment !== undefined,
|
||||
editable: props.setFill !== undefined,
|
||||
}}
|
||||
style={{
|
||||
"--fill-per": clamp(0, 1, props.fill) * 100 + "%",
|
||||
}}
|
||||
onPointerDown={calculateFill}
|
||||
data-disabled={props.disabled}
|
||||
>
|
||||
<div class="filling-container">
|
||||
<div class="filling"></div>
|
||||
</div>
|
||||
|
||||
<div ref={handle} class="handle" onPointerDown={onDown}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Bar;
|
||||
@@ -1,59 +0,0 @@
|
||||
import "../assets/css/gradient.css";
|
||||
import Impulse from "../lib/Impulse";
|
||||
import { Component, JSXElement, onCleanup, onMount } from "solid-js";
|
||||
|
||||
export type GradientColors = {
|
||||
top: string;
|
||||
bottom: string;
|
||||
};
|
||||
|
||||
type GradientProps = {
|
||||
classTop?: string;
|
||||
classBottom?: string;
|
||||
update?: Impulse;
|
||||
children: JSXElement;
|
||||
};
|
||||
|
||||
const Gradient: Component<GradientProps> = (props) => {
|
||||
let bottomLayer: HTMLDivElement | undefined;
|
||||
|
||||
const calculateBackground = () => {
|
||||
if (!bottomLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect: DOMRect = bottomLayer.getBoundingClientRect();
|
||||
|
||||
bottomLayer.style.setProperty("--left", `${Math.round(-rect.left)}px`);
|
||||
bottomLayer.style.setProperty("--top", `${Math.round(-rect.top)}px`);
|
||||
|
||||
bottomLayer.style.setProperty("--right", `${Math.round(rect.left + rect.width)}px`);
|
||||
bottomLayer.style.setProperty("--bottom", `${Math.round(rect.top + rect.height)}px`);
|
||||
|
||||
bottomLayer.style.setProperty("--width", `${Math.round(window.innerWidth * 1.25)}px`);
|
||||
bottomLayer.style.setProperty("--height", `${Math.round(window.innerHeight * 1.25)}px`);
|
||||
};
|
||||
|
||||
if (props.update !== undefined) {
|
||||
props.update.listen(calculateBackground);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
calculateBackground();
|
||||
window.addEventListener("resize", calculateBackground);
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("resize", calculateBackground);
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={bottomLayer} class={`bottom-layer ${props.classBottom ?? ""}`}>
|
||||
<div class={`gradient-layer`}>
|
||||
<div class={`top-layer ${props.classTop ?? ""}`}>{props.children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Gradient;
|
||||
@@ -1,49 +0,0 @@
|
||||
import "../assets/css/select.css";
|
||||
import { Component, For, onMount, Setter } from "solid-js";
|
||||
|
||||
export type SelectOption = {
|
||||
value: string;
|
||||
text: string;
|
||||
selected?: boolean;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
setValue: Setter<string>;
|
||||
options: SelectOption[];
|
||||
selected?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const Select: Component<SelectProps> = (props) => {
|
||||
let select: HTMLSelectElement | undefined;
|
||||
|
||||
onMount(() => {
|
||||
if (!select) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.setValue(select.value);
|
||||
});
|
||||
|
||||
return (
|
||||
<select
|
||||
class={"button-like select"}
|
||||
ref={select}
|
||||
onchange={() => select && props.setValue(select.value)}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
<For each={props.options}>
|
||||
{(option) => (
|
||||
<option
|
||||
value={option.value}
|
||||
selected={option.selected === true || option.value === props.selected}
|
||||
>
|
||||
{option.text}
|
||||
</option>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -1,85 +0,0 @@
|
||||
import { Component, JSX, onMount, Setter, Signal } from "solid-js";
|
||||
|
||||
type TextFieldProps = {
|
||||
value: Signal<string>;
|
||||
setInput?: Setter<HTMLElement | undefined>;
|
||||
children?: JSX.Element;
|
||||
};
|
||||
|
||||
const TextField: Component<TextFieldProps> = (props) => {
|
||||
const [value, setValue] = props.value;
|
||||
let input: HTMLInputElement | undefined;
|
||||
|
||||
onMount(() => {
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.setInput !== undefined) {
|
||||
props.setInput(input);
|
||||
}
|
||||
|
||||
input.textContent = value();
|
||||
});
|
||||
|
||||
const onInput = () => {
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
setValue(
|
||||
String(input.textContent).replaceAll(
|
||||
String.fromCharCode(160), // non-breaking space
|
||||
String.fromCharCode(32), // breaking space
|
||||
) ?? "",
|
||||
);
|
||||
};
|
||||
|
||||
const onPaste = (evt: ClipboardEvent) => {
|
||||
const selection = window.getSelection();
|
||||
if (selection === null || !evt.clipboardData) return;
|
||||
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
||||
selection.deleteFromDocument();
|
||||
selection.getRangeAt(0).insertNode(document.createTextNode(evt.clipboardData.getData("Text")));
|
||||
selection.collapseToEnd();
|
||||
|
||||
onInput();
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.textContent = "";
|
||||
onInput();
|
||||
input.focus();
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="button-like flex w-full items-center gap-2 overflow-hidden p-2 focus-within:outline-2 focus-within:transition-none hover:cursor-text">
|
||||
{props.children}
|
||||
<div
|
||||
class="flex-1 overflow-hidden whitespace-nowrap focus:outline-none"
|
||||
ref={input}
|
||||
onInput={onInput}
|
||||
onKeyDown={(evt) => evt.stopPropagation()}
|
||||
onPaste={onPaste}
|
||||
contenteditable={true}
|
||||
spellcheck={false}
|
||||
></div>
|
||||
<button
|
||||
class="mr-1 grid place-items-center bg-transparent p-0 hover:cursor-pointer hover:bg-transparent focus:outline-none"
|
||||
onClick={clear}
|
||||
title="Clear text input"
|
||||
>
|
||||
{/* TODO: Add clear icon */}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextField;
|
||||
@@ -1,109 +1,188 @@
|
||||
import { Optional } from "../../../../@types";
|
||||
import "../../assets/css/notice/notice.css";
|
||||
import Impulse from "../../lib/Impulse";
|
||||
import { none, orDefault, some } from "../../lib/rust-like-utils-client/Optional";
|
||||
import Gradient from "../Gradient";
|
||||
import { hideNotice } from "./NoticeContainer";
|
||||
import Button from "../button/Button";
|
||||
import { cva } from "class-variance-authority";
|
||||
import { XIcon } from "lucide-solid";
|
||||
import { Accessor, Component, createSignal } from "solid-js";
|
||||
import { Component, createEffect, createMemo, createSignal, JSX, onMount, Show } from "solid-js";
|
||||
import { DOMElement } from "solid-js/jsx-runtime";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
const bgStyle = cva([], {
|
||||
variants: {
|
||||
variant: {
|
||||
success: "bg-green after:bg-green",
|
||||
neutral: "bg-overlay after:bg-overlay",
|
||||
error: "bg-red after:bg-red",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "neutral",
|
||||
},
|
||||
});
|
||||
|
||||
const textStyle = cva([], {
|
||||
variants: {
|
||||
variant: {
|
||||
success: "text-green",
|
||||
neutral: "text-subtext",
|
||||
error: "text-red",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "neutral",
|
||||
},
|
||||
});
|
||||
|
||||
export type NoticeType = {
|
||||
id?: string;
|
||||
class: "notice" | "warning" | "error";
|
||||
title: string;
|
||||
content: string;
|
||||
timeoutMS?: number;
|
||||
active?: boolean;
|
||||
variant?: "neutral" | "success" | "error";
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon?: JSX.Element;
|
||||
};
|
||||
|
||||
type NoticeProps = {
|
||||
notice: NoticeType;
|
||||
updateGradient: Impulse;
|
||||
onMount: (notice: HTMLElement) => any;
|
||||
onRemove: (id: string) => void;
|
||||
isPaused: boolean;
|
||||
};
|
||||
|
||||
const Notice: Component<NoticeProps> = (props) => {
|
||||
const removeNotice = () => {
|
||||
const action = hideNotice(props.notice.id);
|
||||
const NOTICE_DURATION = 3_000; // 3 seconds
|
||||
|
||||
if (action.isError) {
|
||||
const Notice: Component<NoticeProps> = (props) => {
|
||||
const [isRemoving, setIsRemoving] = createSignal(false);
|
||||
let noticeRef: HTMLDivElement | undefined;
|
||||
let timeout: NodeJS.Timeout;
|
||||
let startTime: number;
|
||||
let pausedTime: number = 0;
|
||||
|
||||
const removeNotice = () => {
|
||||
setIsRemoving(true);
|
||||
};
|
||||
|
||||
const startRemoveTimeout = () => {
|
||||
startTime = Date.now();
|
||||
timeout = setTimeout(removeNotice, NOTICE_DURATION);
|
||||
};
|
||||
|
||||
const pauseRemoveTimeout = () => {
|
||||
pausedTime = Date.now() - startTime;
|
||||
};
|
||||
|
||||
const resumeRemoveTimeout = () => {
|
||||
startTime = Date.now() - pausedTime;
|
||||
timeout = setTimeout(removeNotice, NOTICE_DURATION - pausedTime);
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
startRemoveTimeout();
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
clearTimeout(timeout);
|
||||
if (props.isPaused) {
|
||||
pauseRemoveTimeout();
|
||||
} else {
|
||||
resumeRemoveTimeout();
|
||||
}
|
||||
});
|
||||
|
||||
const handleAnimationEnd = (
|
||||
event: AnimationEvent & {
|
||||
currentTarget: HTMLDivElement;
|
||||
target: DOMElement;
|
||||
},
|
||||
) => {
|
||||
// Validates if the animation finidhed on the current element
|
||||
if (event.target !== event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pauseDrain();
|
||||
} catch {}
|
||||
if (!isRemoving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onRemove(props.notice.id!);
|
||||
};
|
||||
|
||||
const [drain, setDrainTime, pauseDrain] = createDrainAnimation(
|
||||
props.notice.timeoutMS ?? 10_000,
|
||||
removeNotice,
|
||||
);
|
||||
|
||||
const onRef = (notice: HTMLElement) => {
|
||||
props.updateGradient.pulse();
|
||||
props.onMount(notice);
|
||||
};
|
||||
const removingClasses = createMemo(() => {
|
||||
if (isRemoving()) {
|
||||
return "animate-notice-slide-out";
|
||||
}
|
||||
return "my-2 min-h-20";
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
class={"notice-wrapper"}
|
||||
onPointerEnter={pauseDrain}
|
||||
onPointerLeave={() => setDrainTime(props.notice.timeoutMS ?? 10_000)}
|
||||
ref={noticeRef}
|
||||
onAnimationEnd={handleAnimationEnd}
|
||||
class={twMerge(
|
||||
// Background
|
||||
bgStyle({ variant: props.notice.variant }),
|
||||
// General
|
||||
"group relative w-96 transform overflow-hidden rounded-xl bg-thick-material p-4 shadow-2xl ring-2 ring-inset ring-stroke backdrop-blur-md duration-300 ease-in-out",
|
||||
// After
|
||||
`after:absolute after:inset-0 after:-z-20 after:size-20 after:rounded-full after:blur-2xl after:content-['']`,
|
||||
// Before
|
||||
`before:absolute before:inset-0.5 before:-z-10 before:rounded-[10px] before:bg-thick-material before:content-['']`,
|
||||
// Enter animation
|
||||
"animate-notice-slide-in",
|
||||
// Removing
|
||||
removingClasses(),
|
||||
)}
|
||||
data-id={props.notice.id}
|
||||
ref={onRef}
|
||||
>
|
||||
<Gradient update={props.updateGradient}>
|
||||
<div class={"notice " + (props.notice.class !== "notice" ? props.notice.class : "")}>
|
||||
<div class="content">
|
||||
<div class="head">
|
||||
<h3>{props.notice.title}</h3>
|
||||
<button onClick={removeNotice}>
|
||||
<XIcon size={20} />
|
||||
</button>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={removeNotice}
|
||||
class="absolute right-4 top-4 size-5 p-1 text-subtext opacity-0 transition-opacity duration-150 group-hover:opacity-100"
|
||||
>
|
||||
<XIcon size={16} />
|
||||
</Button>
|
||||
|
||||
<p>{props.notice.content}</p>
|
||||
<div class="mr-6 flex items-start">
|
||||
<Show when={props.notice.icon}>
|
||||
<div
|
||||
class={twMerge(
|
||||
"mr-3 mt-0.5 flex-shrink-0",
|
||||
textStyle({ variant: props.notice.variant }),
|
||||
)}
|
||||
>
|
||||
{props.notice.icon}
|
||||
</div>
|
||||
</Show>
|
||||
<div class="overflow-hidden">
|
||||
<Show when={props.notice.title}>
|
||||
<h3 class="mb-1 overflow-hidden text-ellipsis whitespace-nowrap text-base font-semibold">
|
||||
{props.notice.title}
|
||||
</h3>
|
||||
</Show>
|
||||
<Show when={props.notice.description}>
|
||||
<p class="line-clamp-2 text-ellipsis text-sm text-subtext">
|
||||
{props.notice.description}
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="timeout"
|
||||
classList={{
|
||||
pause: drain().isNone,
|
||||
}}
|
||||
class="absolute inset-0 m-px overflow-hidden pointer-events-none"
|
||||
style={{
|
||||
animation: orDefault(drain(), undefined),
|
||||
"border-radius": "13px",
|
||||
}}
|
||||
></div>
|
||||
>
|
||||
<div
|
||||
class={twMerge(
|
||||
"absolute bottom-px left-0 h-1 rounded-full",
|
||||
bgStyle({ variant: props.notice.variant }),
|
||||
)}
|
||||
style={{
|
||||
animation: `progress ${NOTICE_DURATION}ms linear`,
|
||||
"animation-play-state": props.isPaused ? "paused" : "running",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Gradient>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function createDrainAnimation(
|
||||
timeMS: number,
|
||||
onDrained: () => any,
|
||||
): [Accessor<Optional<string>>, (timeMS: number) => any, () => any] {
|
||||
const [get, set] = createSignal<Optional<string>>(some(drainTemplate(timeMS)));
|
||||
|
||||
let timeout = window.setTimeout(onDrained, timeMS);
|
||||
|
||||
return [
|
||||
get,
|
||||
(ms: number) => {
|
||||
set(some(drainTemplate(ms)));
|
||||
timeout = window.setTimeout(onDrained, ms);
|
||||
},
|
||||
() => {
|
||||
set(none());
|
||||
clearTimeout(timeout);
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function drainTemplate(timeMS: number): string {
|
||||
return `drain-time ${Math.round(timeMS)}ms linear forwards`;
|
||||
}
|
||||
|
||||
export default Notice;
|
||||
|
||||
35
src/renderer/src/components/notice/NoticeAnimations.ts
Normal file
35
src/renderer/src/components/notice/NoticeAnimations.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export const noticeAnimations = {
|
||||
keyframes: {
|
||||
"notice-slide-in": {
|
||||
from: { transform: "translateY(-100%)", filter: "blur(4px)", opacity: 0 },
|
||||
to: { transform: "translateX(0)", opacity: 1 },
|
||||
},
|
||||
"notice-progress": {
|
||||
from: { width: "100%" },
|
||||
to: { width: "0%" },
|
||||
},
|
||||
"notice-slide-out": {
|
||||
from: {
|
||||
minHeight: "5rem",
|
||||
marginBlock: "0.5rem",
|
||||
},
|
||||
to: {
|
||||
"padding-block": "0",
|
||||
"margin-block": "0",
|
||||
height: "0",
|
||||
"min-height": "0",
|
||||
"max-height": "0",
|
||||
transform: "scale(0.75)",
|
||||
"transform-origin": "top right",
|
||||
opacity: 0,
|
||||
filter: "blur(4px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"notice-slide-in": "notice-slide-in 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards",
|
||||
// TODO: Make this 3000ms configurable
|
||||
"notice-progress": "notice-progress 3000ms linear",
|
||||
"notice-slide-out": "notice-slide-out 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards",
|
||||
},
|
||||
};
|
||||
@@ -1,113 +1,62 @@
|
||||
import { Result } from "../../../../@types";
|
||||
import "../../assets/css/notice/notice-container.css";
|
||||
import Impulse from "../../lib/Impulse";
|
||||
import { fail, ok } from "../../lib/rust-like-utils-client/Result";
|
||||
import { TokenNamespace } from "../../lib/tungsten/token";
|
||||
import Notice, { NoticeType } from "./Notice";
|
||||
import { For, onMount } from "solid-js";
|
||||
import { For, createSignal } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
|
||||
type NoticeExtended = {
|
||||
notice: NoticeType;
|
||||
updateGradient: Impulse;
|
||||
visible: boolean;
|
||||
};
|
||||
|
||||
const [notices, setNotices] = createStore<NoticeExtended[]>([]);
|
||||
const [notices, setNotices] = createStore<NoticeType[]>([]);
|
||||
const namespace = new TokenNamespace();
|
||||
const [isPaused, setIsPaused] = createSignal(false);
|
||||
|
||||
export function addNotice(notice: NoticeType): void {
|
||||
if (notice.id === undefined) {
|
||||
notice.id = namespace.create();
|
||||
}
|
||||
|
||||
setNotices([
|
||||
...notices,
|
||||
{
|
||||
notice,
|
||||
updateGradient: new Impulse<void>(),
|
||||
visible: false,
|
||||
...notice,
|
||||
variant: notice.variant || "neutral",
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export function hideNotice(id: string | undefined): Result<void, string> {
|
||||
function hideNotice(id: string | undefined): Result<void, string> {
|
||||
if (id === undefined) {
|
||||
return fail("Passed undefined ID.");
|
||||
}
|
||||
|
||||
setNotices(
|
||||
(ex) => ex.notice.id === id,
|
||||
"notice",
|
||||
"active",
|
||||
() => false,
|
||||
);
|
||||
|
||||
setNotices((notices) => notices.filter((n) => n.id !== id));
|
||||
return ok(undefined);
|
||||
}
|
||||
|
||||
export { notices };
|
||||
export { notices, isPaused, setIsPaused };
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const el = entry.target;
|
||||
|
||||
if (!(el instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = el.dataset.id;
|
||||
|
||||
if (id === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
setNotices((ex) => ex.notice.id === id, "visible", entry.isIntersecting);
|
||||
}
|
||||
});
|
||||
|
||||
window.api.listen("notify", (n) => {
|
||||
if (n.id === undefined) {
|
||||
n.id = namespace.create();
|
||||
}
|
||||
|
||||
setNotices([
|
||||
...notices,
|
||||
{
|
||||
notice: n,
|
||||
updateGradient: new Impulse<void>(),
|
||||
visible: false,
|
||||
},
|
||||
]);
|
||||
window.api.listen("notify", (n: NoticeType) => {
|
||||
addNotice(n);
|
||||
});
|
||||
|
||||
const NoticeContainer = () => {
|
||||
let wrapper: HTMLDivElement | undefined;
|
||||
|
||||
onMount(() => {
|
||||
wrapper?.addEventListener("scroll", () => {
|
||||
for (let i = 0; i < notices.length; i++) {
|
||||
if (notices[i].visible) {
|
||||
notices[i].updateGradient.pulse();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
const handleRemove = (id: string) => {
|
||||
hideNotice(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={"notice-container-wrapper"} ref={wrapper}>
|
||||
<div class={"notice-container"}>
|
||||
<For each={notices.filter((n) => n.notice.active !== false)}>
|
||||
{(n) => (
|
||||
<Notice
|
||||
notice={n.notice}
|
||||
updateGradient={n.updateGradient}
|
||||
onMount={(e) => observer.observe(e)}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
class="fixed right-4 top-16 z-50 flex w-96 flex-col gap-0"
|
||||
onMouseEnter={() => setIsPaused(true)}
|
||||
onMouseLeave={() => setIsPaused(false)}
|
||||
>
|
||||
<style>{`
|
||||
@keyframes progress {
|
||||
from { width: 100%; }
|
||||
to { width: 0%; }
|
||||
}
|
||||
`}</style>
|
||||
<For each={notices}>
|
||||
{(n) => <Notice notice={n} onRemove={handleRemove} isPaused={isPaused()} />}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function NoScene() {
|
||||
return <div class="scene"></div>;
|
||||
return <div class="scene" />;
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import "../../assets/css/search/tag-item.css";
|
||||
import { XIcon } from "lucide-solid";
|
||||
import { Component } from "solid-js";
|
||||
|
||||
export type TagItemProps = {
|
||||
name: string;
|
||||
isSpecial: boolean;
|
||||
onRemove: (name: string) => any;
|
||||
onChange: (name: string) => any;
|
||||
};
|
||||
|
||||
const TagItem: Component<TagItemProps> = (props) => {
|
||||
let container;
|
||||
|
||||
const changeState = (evt: Event) => {
|
||||
evt.preventDefault();
|
||||
props.onChange(props.name);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={container}
|
||||
class={"tag"}
|
||||
classList={{ special: props.isSpecial === true }}
|
||||
onContextMenu={changeState}
|
||||
>
|
||||
<span>{props.name}</span>
|
||||
<button onClick={() => props.onRemove(props.name)}>
|
||||
<XIcon size={20} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagItem;
|
||||
@@ -1,160 +0,0 @@
|
||||
import "../../assets/css/search/tag-select.css";
|
||||
import Gradient from "../Gradient";
|
||||
import TextField from "../form/TextField";
|
||||
import TagItem from "./TagItem";
|
||||
import { TagIcon } from "lucide-solid";
|
||||
import { Component, createEffect, createSignal, For, Signal } from "solid-js";
|
||||
|
||||
export type Tag = {
|
||||
name: string;
|
||||
isSpecial?: boolean;
|
||||
};
|
||||
|
||||
type TagSelectProps = {
|
||||
/** Must have ```json
|
||||
* { equals: false }
|
||||
* ``` */
|
||||
tags: Signal<Tag[]>;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const TagSelect: Component<TagSelectProps> = (props) => {
|
||||
const [tags, setTags] = props.tags;
|
||||
const tagSignal = createSignal("");
|
||||
const [tagField, setTagField] = createSignal<HTMLElement | undefined>();
|
||||
let dialog: HTMLDialogElement | undefined;
|
||||
|
||||
const openDialog = () => {
|
||||
dialog?.showModal();
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
dialog?.close();
|
||||
const field = tagField();
|
||||
|
||||
if (field === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
field.textContent = "";
|
||||
};
|
||||
|
||||
const onKeyDown = (evt: KeyboardEvent) => {
|
||||
if (evt.key !== "Enter") {
|
||||
return;
|
||||
}
|
||||
|
||||
const input = tagField();
|
||||
|
||||
if (input === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
if (input.textContent === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newTags = input.textContent.trim().split(" ");
|
||||
const oldTags = tags();
|
||||
|
||||
for (let i = 0; i < newTags.length; i++) {
|
||||
if (newTags[i] === "") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const index = oldTags.findIndex((t) => t.name === newTags[i]);
|
||||
|
||||
if (index !== -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
oldTags.push({ name: newTags[i] });
|
||||
}
|
||||
|
||||
setTags(oldTags);
|
||||
|
||||
input.textContent = "";
|
||||
input.focus();
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const input = tagField();
|
||||
|
||||
if (input === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.addEventListener("keydown", onKeyDown);
|
||||
});
|
||||
|
||||
const onTagRemove = (name: string) => {
|
||||
const t = tags();
|
||||
const index = t.findIndex((x) => x.name === name);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
t.splice(index, 1);
|
||||
setTags(t);
|
||||
};
|
||||
|
||||
const onTagChange = (name: string) => {
|
||||
const t = tags();
|
||||
const index = t.findIndex((x) => x.name === name);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
t[index] = {
|
||||
name,
|
||||
isSpecial: !t[index].isSpecial,
|
||||
};
|
||||
|
||||
setTags(t);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={"tags"}>
|
||||
<button
|
||||
onClick={openDialog}
|
||||
disabled={props.disabled}
|
||||
title={"Add/Remove tags for searching"}
|
||||
>
|
||||
<TagIcon size={20} />
|
||||
</button>
|
||||
<dialog ref={dialog} class={"tag-select"}>
|
||||
<Gradient classTop={"tag-select-container"}>
|
||||
<TextField value={tagSignal} setInput={setTagField} />
|
||||
|
||||
<span class={"hint"}>
|
||||
Type the name of a tag into input above and press enter to add it. After it is added you
|
||||
can right-click it and change it to exclude songs with given tag.
|
||||
</span>
|
||||
|
||||
<div class={"tags-container"}>
|
||||
<For each={tags()}>
|
||||
{(tag: Tag) => (
|
||||
<TagItem
|
||||
name={tag.name}
|
||||
isSpecial={tag.isSpecial === true}
|
||||
onRemove={onTagRemove}
|
||||
onChange={onTagChange}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
<button onClick={closeDialog}>Close</button>
|
||||
</Gradient>
|
||||
</dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagSelect;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, Show } from "solid-js";
|
||||
import { Component, For, Show } from "solid-js";
|
||||
|
||||
type SettingDropdownProps = {
|
||||
label: string;
|
||||
@@ -27,9 +27,9 @@ const SettingDropdown: Component<SettingDropdownProps> = (props) => {
|
||||
disabled={props.disabled}
|
||||
onChange={changeOption}
|
||||
>
|
||||
{Array.from(props.options.keys()).map((option) => (
|
||||
<option value={option}>{option}</option>
|
||||
))}
|
||||
<For each={Array.from(props.options.keys())}>
|
||||
{(option) => <option value={option}>{option}</option>}
|
||||
</For>
|
||||
</select>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { cn } from "../../lib/css.utils";
|
||||
import Dropdown from "../dropdown/Dropdown";
|
||||
import { changeAudioDevice } from "@renderer/components/song/song.utils";
|
||||
import { GlobeIcon, LucideIcon, Volume2Icon } from "lucide-solid";
|
||||
import { Component, createEffect, createSignal, JSX, onMount } from "solid-js";
|
||||
import { Component, createEffect, createSignal, For, JSX, onMount } from "solid-js";
|
||||
|
||||
const Settings: Component = () => {
|
||||
return (
|
||||
@@ -22,14 +22,14 @@ type SettingsSectionProps = JSX.IntrinsicElements["div"] & {
|
||||
Icon: LucideIcon;
|
||||
};
|
||||
|
||||
const SettingsSection: Component<SettingsSectionProps> = ({ title, Icon, children, ...rest }) => {
|
||||
const SettingsSection: Component<SettingsSectionProps> = (props) => {
|
||||
return (
|
||||
<div class={cn("flex flex-col gap-6", rest.class)}>
|
||||
<div class={cn("flex flex-col gap-6", props.class)}>
|
||||
<div class="flex items-center gap-3">
|
||||
<Icon class="text-text opacity-70" size={16} />
|
||||
<h3 class="text-base text-text">{title}</h3>
|
||||
<props.Icon class="text-text opacity-70" size={16} />
|
||||
<h3 class="text-base text-text">{props.title}</h3>
|
||||
</div>
|
||||
{children}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -39,13 +39,13 @@ type SettingProps = JSX.IntrinsicElements["div"] & {
|
||||
name: string;
|
||||
};
|
||||
|
||||
const Setting: Component<SettingProps> = ({ label, name, children, ...rest }) => {
|
||||
const Setting: Component<SettingProps> = (props) => {
|
||||
return (
|
||||
<div class={cn("flex flex-col gap-2.5", rest.class)}>
|
||||
<label class="text-sm font-semibold text-text" for={name}>
|
||||
{label}
|
||||
<div class={cn("flex flex-col gap-2.5", props.class)}>
|
||||
<label class="text-sm font-semibold text-text" for={props.name}>
|
||||
{props.label}
|
||||
</label>
|
||||
{children}
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -85,9 +85,9 @@ const AudioDeviceSetting: Component = () => {
|
||||
</Dropdown.SelectTrigger>
|
||||
|
||||
<Dropdown.List value={selectedAudioDevice} onValueChange={handleValueChange}>
|
||||
{Array.from(audioDevices().keys()).map((audioDevice) => (
|
||||
<Dropdown.Item value={audioDevice}>{audioDevice}</Dropdown.Item>
|
||||
))}
|
||||
<For each={Array.from(audioDevices().keys())}>
|
||||
{(audioDevice) => <Dropdown.Item value={audioDevice}>{audioDevice}</Dropdown.Item>}
|
||||
</For>
|
||||
</Dropdown.List>
|
||||
</Dropdown>
|
||||
</Setting>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export default function SongHint(): HTMLElement {
|
||||
return (
|
||||
<div class="flex items-center rounded-md bg-black bg-opacity-80 p-2 shadow-lg">
|
||||
<div class="mr-3 h-12 w-12 rounded-md bg-gray-700"></div>
|
||||
<div class="mr-3 h-12 w-12 rounded-md bg-gray-700" />
|
||||
<div class="flex flex-col">
|
||||
<div class="mb-2 h-4 w-32 rounded bg-gray-700"></div>
|
||||
<div class="h-3 w-24 rounded bg-gray-600"></div>
|
||||
<div class="mb-2 h-4 w-32 rounded bg-gray-700" />
|
||||
<div class="h-3 w-24 rounded bg-gray-600" />
|
||||
</div>
|
||||
</div>
|
||||
) as HTMLElement;
|
||||
|
||||
@@ -18,40 +18,31 @@ type SongItemProps = {
|
||||
children?: any;
|
||||
};
|
||||
|
||||
const SongItem: Component<SongItemProps> = ({
|
||||
group,
|
||||
onSelect,
|
||||
song,
|
||||
draggable: isDraggable,
|
||||
onDrop,
|
||||
selectable,
|
||||
}) => {
|
||||
const SongItem: Component<SongItemProps> = (props) => {
|
||||
let item: HTMLDivElement | undefined;
|
||||
const [, setCoords] = createSignal<[number, number]>([0, 0], { equals: false });
|
||||
|
||||
const { extractColorFromImage } = useColorExtractor();
|
||||
const { primaryColor, secondaryColor, processImage } = extractColorFromImage(song);
|
||||
const { primaryColor, secondaryColor, processImage } = extractColorFromImage(props.song);
|
||||
|
||||
onMount(() => {
|
||||
if (!item) return;
|
||||
|
||||
// Initialize draggable functionality
|
||||
draggable(item, {
|
||||
onClick: ignoreClickInContextMenu(() => {
|
||||
onSelect(song.path);
|
||||
}),
|
||||
onDrop: onDrop ?? (() => {}),
|
||||
onClick: ignoreClickInContextMenu(() => props.onSelect(props.song.path)),
|
||||
onDrop: props.onDrop ?? (() => {}),
|
||||
createHint: SongHint,
|
||||
useOnlyAsOnClickBinder: !isDraggable || selectedSong().path === song.path,
|
||||
useOnlyAsOnClickBinder: !props.draggable || selectedSong().path === props.song.path,
|
||||
});
|
||||
|
||||
if (selectable === true) {
|
||||
item.dataset.path = song.path;
|
||||
if (props.selectable === true) {
|
||||
item.dataset.path = props.song.path;
|
||||
}
|
||||
});
|
||||
|
||||
const isSelected = createMemo(() => {
|
||||
return selectedSong().audio === song.audio;
|
||||
return selectedSong().audio === props.song.audio;
|
||||
});
|
||||
|
||||
const borderColor = createMemo(() => {
|
||||
@@ -90,13 +81,13 @@ const SongItem: Component<SongItemProps> = ({
|
||||
<div
|
||||
class="group relative isolate select-none rounded-lg"
|
||||
ref={item}
|
||||
data-url={song.bg}
|
||||
data-url={props.song.bg}
|
||||
onContextMenu={(evt) => setCoords([evt.clientX, evt.clientY])}
|
||||
>
|
||||
<SongImage
|
||||
class={`absolute inset-0 z-[-1] h-full w-full rounded-md bg-cover bg-center bg-no-repeat`}
|
||||
src={song.bg}
|
||||
group={group}
|
||||
src={props.song.bg}
|
||||
group={props.group}
|
||||
onImageLoaded={processImage}
|
||||
/>
|
||||
<div
|
||||
@@ -105,8 +96,8 @@ const SongItem: Component<SongItemProps> = ({
|
||||
background: backgrund(),
|
||||
}}
|
||||
>
|
||||
<h3 class="text-shadow text-[22px] font-extrabold leading-7">{song.title}</h3>
|
||||
<p class="text-base text-subtext">{song.artist}</p>
|
||||
<h3 class="text-shadow text-[22px] font-extrabold leading-7">{props.song.title}</h3>
|
||||
<p class="text-base text-subtext">{props.song.artist}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Optional, Order } from "../../../../../@types";
|
||||
import { Optional, Order, Tag } from "../../../../../@types";
|
||||
import { SearchQueryError } from "../../../../../main/lib/search-parser/@search-types";
|
||||
import { Tag } from "../../search/TagSelect";
|
||||
import { setSongsSearch } from "../song-list/song-list.utils";
|
||||
import SongListSearchOrderBy from "./SongListSearchOrderBy";
|
||||
import { SearchIcon } from "lucide-solid";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Button from "@renderer/components/button/Button";
|
||||
import Dropdown from "@renderer/components/dropdown/Dropdown";
|
||||
import { ArrowDownAzIcon, ArrowUpZaIcon } from "lucide-solid";
|
||||
import { Component, createMemo, createSignal, Match, Setter, Switch } from "solid-js";
|
||||
import { Component, createMemo, createSignal, For, Match, Setter, Switch } from "solid-js";
|
||||
import { OrderDirection, OrderOptions, Order } from "src/@types";
|
||||
|
||||
type OrderOption = {
|
||||
@@ -84,14 +84,16 @@ const SongListSearchOrderBy: Component<OrderSelectProps> = (props) => {
|
||||
}}
|
||||
value={option}
|
||||
>
|
||||
{orderOptions.map((option) => (
|
||||
<For each={orderOptions}>
|
||||
{(option) => (
|
||||
<Dropdown.Item
|
||||
class="px-4 py-2 transition-colors duration-200 hover:bg-accent/20"
|
||||
value={option.value}
|
||||
>
|
||||
{option.text}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
)}
|
||||
</For>
|
||||
</Dropdown.List>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -16,17 +16,18 @@ export type SongViewProps = {
|
||||
playlist?: string;
|
||||
};
|
||||
|
||||
const SongList: Component<SongViewProps> = (props) => {
|
||||
const tagsSignal = createSignal<Tag[]>([], { equals: false });
|
||||
const [tags] = tagsSignal;
|
||||
const DEFAULT_TAGS_VALUE: Tag[] = [];
|
||||
const DEFAULT_ORDER_VALUE: Order = { option: "title", direction: "asc" };
|
||||
|
||||
const [order, setOrder] = createSignal<Order>({ option: "title", direction: "asc" });
|
||||
const SongList: Component<SongViewProps> = (props) => {
|
||||
const [tags, setTags] = createSignal(DEFAULT_TAGS_VALUE, { equals: false });
|
||||
const [order, setOrder] = createSignal(DEFAULT_ORDER_VALUE);
|
||||
const [count, setCount] = createSignal(0);
|
||||
|
||||
const [payload, setPayload] = createSignal<SongsQueryPayload>({
|
||||
view: props,
|
||||
order: order(),
|
||||
tags: tags(),
|
||||
order: DEFAULT_ORDER_VALUE,
|
||||
tags: DEFAULT_TAGS_VALUE,
|
||||
});
|
||||
|
||||
const [searchError, setSearchError] = createSignal<Optional<SearchQueryError>>(none(), {
|
||||
@@ -75,7 +76,12 @@ const SongList: Component<SongViewProps> = (props) => {
|
||||
return (
|
||||
<div class="flex h-full flex-col">
|
||||
<div class="sticky top-0 z-10">
|
||||
<SongListSearch tags={tagsSignal} setOrder={setOrder} count={count} error={searchError} />
|
||||
<SongListSearch
|
||||
tags={[tags, setTags]}
|
||||
setOrder={setOrder}
|
||||
count={count}
|
||||
error={searchError}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-grow overflow-y-auto p-5 py-0">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { addNotice } from "../components/notice/NoticeContainer";
|
||||
import { Keyboard } from "../lib/Keyboard";
|
||||
import { mainActiveTab, TABS } from "@renderer/scenes/main-scene/main.utils";
|
||||
import { ShuffleIcon } from "lucide-solid";
|
||||
|
||||
Keyboard.register({
|
||||
key: "F2",
|
||||
@@ -12,9 +13,9 @@ Keyboard.register({
|
||||
}
|
||||
|
||||
addNotice({
|
||||
class: "notice",
|
||||
title: "Shuffled",
|
||||
content: "Current queue have been shuffled",
|
||||
icon: <ShuffleIcon size={20} />,
|
||||
description: "The current queue has been shuffled.",
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function NoScene() {
|
||||
return <div class="scene"></div>;
|
||||
return <div class="scene" />;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import SongList from "../../components/song/song-list/SongList";
|
||||
import { mainActiveTab, setMainActiveTab, Tab, TABS } from "./main.utils";
|
||||
import "./styles.css";
|
||||
import Button from "@renderer/components/button/Button";
|
||||
import NoticeContainer from "@renderer/components/notice/NoticeContainer";
|
||||
import Settings from "@renderer/components/settings/Settings";
|
||||
import SongImage from "@renderer/components/song/SongImage";
|
||||
import SongQueue from "@renderer/components/song/song-queue/SongQueue";
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
JSXElement,
|
||||
Match,
|
||||
onCleanup,
|
||||
onMount,
|
||||
Setter,
|
||||
Show,
|
||||
Switch,
|
||||
@@ -29,6 +31,7 @@ import {
|
||||
const MainScene: Component = () => {
|
||||
return (
|
||||
<div class="main-scene flex h-screen flex-col overflow-hidden">
|
||||
<NoticeContainer />
|
||||
<Nav />
|
||||
<main class="relative flex h-[calc(100vh-52px)]">
|
||||
<TabContent />
|
||||
@@ -53,7 +56,7 @@ const Nav: Component = () => {
|
||||
const [os, setOs] = createSignal<NodeJS.Platform>();
|
||||
const [maximized, setMaximized] = createSignal<boolean>(false);
|
||||
|
||||
createEffect(async () => {
|
||||
onMount(async () => {
|
||||
const fetchOS = async () => {
|
||||
return await window.api.request("os::platform");
|
||||
};
|
||||
@@ -64,10 +67,17 @@ const Nav: Component = () => {
|
||||
|
||||
setOs(await fetchOS());
|
||||
setMaximized(await fetchMaximized());
|
||||
|
||||
window.api.listen("window::maximizeChange", (maximized: boolean) => {
|
||||
setMaximized(maximized);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const resizeListener = (maximized: boolean) => {
|
||||
setMaximized(maximized);
|
||||
};
|
||||
|
||||
window.api.listen("window::maximizeChange", resizeListener);
|
||||
return () => {
|
||||
window.api.removeListener("window::maximizeChange", resizeListener);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -101,13 +111,13 @@ function WindowControls(props: { maximized: Accessor<boolean>; setMaximized: Set
|
||||
return (
|
||||
<div class="nav-window-controls">
|
||||
<button
|
||||
onclick={async () => window.api.request("window::minimize")}
|
||||
onClick={async () => window.api.request("window::minimize")}
|
||||
class="nav-window-control"
|
||||
>
|
||||
<MinusIcon size={20} />
|
||||
</button>
|
||||
<button
|
||||
onclick={async () => {
|
||||
onClick={async () => {
|
||||
window.api.request("window::maximize");
|
||||
props.setMaximized(!props.maximized());
|
||||
}}
|
||||
@@ -116,7 +126,7 @@ function WindowControls(props: { maximized: Accessor<boolean>; setMaximized: Set
|
||||
{props.maximized() ? <Minimize2Icon size={20} /> : <SquareIcon size={18} />}
|
||||
</button>
|
||||
<button
|
||||
onclick={async () => window.api.request("window::close")}
|
||||
onClick={async () => window.api.request("window::close")}
|
||||
class="nav-window-control close"
|
||||
>
|
||||
<XIcon size={20} />
|
||||
@@ -128,19 +138,19 @@ function WindowControls(props: { maximized: Accessor<boolean>; setMaximized: Set
|
||||
type NavItemProps = Pick<Tab, "value" | "Icon"> & {
|
||||
children: JSXElement;
|
||||
};
|
||||
const NavItem: Component<NavItemProps> = ({ children, value, Icon }) => {
|
||||
const NavItem: Component<NavItemProps> = (props) => {
|
||||
return (
|
||||
<button
|
||||
class={`nav-item flex items-center gap-4 rounded-sm px-4 py-1 hover:bg-surface ${mainActiveTab() === value ? "bg-surface" : ""}`}
|
||||
onclick={() => setMainActiveTab(value)}
|
||||
class={`nav-item flex items-center gap-4 rounded-sm px-4 py-1 hover:bg-surface ${mainActiveTab() === props.value ? "bg-surface" : ""}`}
|
||||
onClick={() => setMainActiveTab(props.value)}
|
||||
>
|
||||
<span class={`${mainActiveTab() === value ? "" : "opacity-70"}`}>
|
||||
<Icon size={20} />
|
||||
<span class={`${mainActiveTab() === props.value ? "" : "opacity-70"}`}>
|
||||
<props.Icon size={20} />
|
||||
</span>
|
||||
<span
|
||||
class={`text-base font-semibold ${mainActiveTab() === value ? "text-text" : "text-subtext"}`}
|
||||
class={`text-base font-semibold ${mainActiveTab() === props.value ? "text-text" : "text-subtext"}`}
|
||||
>
|
||||
{children}
|
||||
{props.children}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
import { noticeAnimations } from "./src/renderer/src/components/notice/NoticeAnimations";
|
||||
|
||||
export default {
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
keyframes: {
|
||||
...noticeAnimations.keyframes,
|
||||
},
|
||||
animation: {
|
||||
...noticeAnimations.animation,
|
||||
},
|
||||
colors: {
|
||||
transparent: "transparent",
|
||||
"thick-material": "rgba(var(--color-thick-material), 0.9)",
|
||||
|
||||
Reference in New Issue
Block a user