diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..084fd1f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 +spaces_around_operators = true + +[*.rs] +indent_size = 4 +indent_style = space +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 +spaces_around_operators = true diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..6b6575f --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,34 @@ +module.exports = { + root: true, + env: { + node: true, + browser: true, + commonjs: true, + es6: true, + amd: true + }, + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.eslint.json'] + }, + plugins: ['@typescript-eslint'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking' + ], + overrides: [ + { + files: ['**/*.tsx', '**/*.ts', '**/*.js'], + rules: { + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/ban-ts-comment': 'off' + } + } + ] +}; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ce65f23 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,18 @@ +*.min.js +/node_modules +/dist +# OS +.DS_Store +.idea +.editorconfig +.npmrc +package-lock.json +# Ignored suffix +*.log +*.md +*.svg +*.png +*ignore +## Built-files +.cache +dist diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..0e72bbe --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,18 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "trailingComma": "none", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "avoid", + "requirePragma": false, + "insertPragma": false, + "proseWrap": "preserve", + "htmlWhitespaceSensitivity": "css", + "endOfLine": "lf" +} diff --git a/package.json b/package.json index fae7397..6e575c0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "tauri": "tauri" + "tauri": "tauri", + "tauri-dev": "tauri dev" }, "dependencies": { "@emotion/css": "^11.10.6", @@ -21,17 +22,26 @@ "@mantine/nprogress": "^6.0.0", "@tabler/icons-react": "^2.8.0", "@tauri-apps/api": "^1.2.0", + "@tanstack/react-virtual": "3.0.0-beta.35", + "framer-motion": "^8.1.1", + "immer": "^9.0.17", + "ramda": "^0.28.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.8.2" + "react-router-dom": "^6.8.2", + "react-use": "^17.4.0", + "use-immer": "^0.8.1", + "zustand": "^4.2.0" }, "devDependencies": { + "@babel/core": "^7.0.0", "@tauri-apps/cli": "^1.2.2", "@types/node": "^18.7.10", + "@types/ramda": "^0.28.20", "@types/react": "^18.0.15", "@types/react-dom": "^18.0.6", "@vitejs/plugin-react": "^3.0.0", "typescript": "^4.6.4", "vite": "^4.0.0" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f25325..fafde50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,6 +1,7 @@ lockfileVersion: 5.4 specifiers: + '@babel/core': ^7.0.0 '@emotion/css': ^11.10.6 '@emotion/react': ^11.10.6 '@emotion/styled': ^11.10.6 @@ -11,17 +12,25 @@ specifiers: '@mantine/notifications': ^6.0.0 '@mantine/nprogress': ^6.0.0 '@tabler/icons-react': ^2.8.0 + '@tanstack/react-virtual': 3.0.0-beta.35 '@tauri-apps/api': ^1.2.0 '@tauri-apps/cli': ^1.2.2 '@types/node': ^18.7.10 + '@types/ramda': ^0.28.20 '@types/react': ^18.0.15 '@types/react-dom': ^18.0.6 '@vitejs/plugin-react': ^3.0.0 + framer-motion: ^8.1.1 + immer: ^9.0.17 + ramda: ^0.28.0 react: ^18.2.0 react-dom: ^18.2.0 react-router-dom: ^6.8.2 + react-use: ^17.4.0 typescript: ^4.6.4 + use-immer: ^0.8.1 vite: ^4.0.0 + zustand: ^4.2.0 dependencies: '@emotion/css': 11.10.6 @@ -34,14 +43,23 @@ dependencies: '@mantine/notifications': 6.0.0_uziugpv5zwkk3pqsn64qbjkxrm '@mantine/nprogress': 6.0.0_uziugpv5zwkk3pqsn64qbjkxrm '@tabler/icons-react': 2.8.0_react@18.2.0 + '@tanstack/react-virtual': 3.0.0-beta.35_react@18.2.0 '@tauri-apps/api': 1.2.0 + framer-motion: 8.5.5_biqbaboplfbrettd7655fr4n2y + immer: 9.0.19 + ramda: 0.28.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-router-dom: 6.8.2_biqbaboplfbrettd7655fr4n2y + react-use: 17.4.0_biqbaboplfbrettd7655fr4n2y + use-immer: 0.8.1_immer@9.0.19+react@18.2.0 + zustand: 4.3.6_immer@9.0.19+react@18.2.0 devDependencies: + '@babel/core': 7.21.0 '@tauri-apps/cli': 1.2.3 '@types/node': 18.14.6 + '@types/ramda': 0.28.23 '@types/react': 18.0.28 '@types/react-dom': 18.0.11 '@vitejs/plugin-react': 3.1.0_vite@4.1.4 @@ -319,12 +337,25 @@ packages: resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} dev: false + /@emotion/is-prop-valid/0.8.8: + resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} + requiresBuild: true + dependencies: + '@emotion/memoize': 0.7.4 + dev: false + optional: true + /@emotion/is-prop-valid/1.2.0: resolution: {integrity: sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==} dependencies: '@emotion/memoize': 0.8.0 dev: false + /@emotion/memoize/0.7.4: + resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} + dev: false + optional: true + /@emotion/memoize/0.8.0: resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} dev: false @@ -782,6 +813,53 @@ packages: react: 18.2.0 dev: false + /@motionone/animation/10.15.1: + resolution: {integrity: sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==} + dependencies: + '@motionone/easing': 10.15.1 + '@motionone/types': 10.15.1 + '@motionone/utils': 10.15.1 + tslib: 2.5.0 + dev: false + + /@motionone/dom/10.15.5: + resolution: {integrity: sha512-Xc5avlgyh3xukU9tydh9+8mB8+2zAq+WlLsC3eEIp7Ax7DnXgY7Bj/iv0a4X2R9z9ZFZiaXK3BO0xMYHKbAAdA==} + dependencies: + '@motionone/animation': 10.15.1 + '@motionone/generators': 10.15.1 + '@motionone/types': 10.15.1 + '@motionone/utils': 10.15.1 + hey-listen: 1.0.8 + tslib: 2.5.0 + dev: false + + /@motionone/easing/10.15.1: + resolution: {integrity: sha512-6hIHBSV+ZVehf9dcKZLT7p5PEKHGhDwky2k8RKkmOvUoYP3S+dXsKupyZpqx5apjd9f+php4vXk4LuS+ADsrWw==} + dependencies: + '@motionone/utils': 10.15.1 + tslib: 2.5.0 + dev: false + + /@motionone/generators/10.15.1: + resolution: {integrity: sha512-67HLsvHJbw6cIbLA/o+gsm7h+6D4Sn7AUrB/GPxvujse1cGZ38F5H7DzoH7PhX+sjvtDnt2IhFYF2Zp1QTMKWQ==} + dependencies: + '@motionone/types': 10.15.1 + '@motionone/utils': 10.15.1 + tslib: 2.5.0 + dev: false + + /@motionone/types/10.15.1: + resolution: {integrity: sha512-iIUd/EgUsRZGrvW0jqdst8st7zKTzS9EsKkP+6c6n4MPZoQHwiHuVtTQLD6Kp0bsBLhNzKIBlHXponn/SDT4hA==} + dev: false + + /@motionone/utils/10.15.1: + resolution: {integrity: sha512-p0YncgU+iklvYr/Dq4NobTRdAPv9PveRDUXabPEeOjBLSO/1FNB2phNTZxOxpi1/GZwYpAoECEa0Wam+nsmhSw==} + dependencies: + '@motionone/types': 10.15.1 + hey-listen: 1.0.8 + tslib: 2.5.0 + dev: false + /@radix-ui/number/1.0.0: resolution: {integrity: sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==} dependencies: @@ -913,6 +991,19 @@ packages: resolution: {integrity: sha512-8diABuB3J+NEUtdwIXJF0bJSE5VpnnyzWeiZGFq/XlcTYJNEF+36ijiPNUGpsV/QXY6syesJKmmPkUUwD+LxdA==} dev: false + /@tanstack/react-virtual/3.0.0-beta.35_react@18.2.0: + resolution: {integrity: sha512-x4dicQTGao3p7wA1bGJyKsbtabO6UJKNUJdMWbxzrdc9CvpFl+ASOBtQHReDRjjAy7Z5/4loTLle8RtZu+2c2A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@tanstack/virtual-core': 3.0.0-beta.35 + react: 18.2.0 + dev: false + + /@tanstack/virtual-core/3.0.0-beta.35: + resolution: {integrity: sha512-p+dNBkN70nz3RzsfJImpmg1eoEHoX3X49lOk1N4I6jtUttKzZ/0U2+LqDmErNa5q/ksWD45zegOU4MnHwKRp2w==} + dev: false + /@tauri-apps/api/1.2.0: resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==} engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} @@ -1015,6 +1106,10 @@ packages: '@tauri-apps/cli-win32-x64-msvc': 1.2.3 dev: true + /@types/js-cookie/2.2.7: + resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + dev: false + /@types/node/18.14.6: resolution: {integrity: sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==} dev: true @@ -1026,6 +1121,12 @@ packages: /@types/prop-types/15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + /@types/ramda/0.28.23: + resolution: {integrity: sha512-9TYWiwkew+mCMsL7jZ+kkzy6QXn8PL5/SKmBPmjgUlTpkokZWTBr+OhiIUDztpAEbslWyt24NNfEmZUBFmnXig==} + dependencies: + ts-toolbelt: 6.15.5 + dev: true + /@types/react-dom/18.0.11: resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} dependencies: @@ -1058,6 +1159,10 @@ packages: - supports-color dev: true + /@xobotyi/scrollbar-width/1.9.5: + resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} + dev: false + /ansi-styles/3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -1124,6 +1229,12 @@ packages: /convert-source-map/1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + /copy-to-clipboard/3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + dependencies: + toggle-selection: 1.0.6 + dev: false + /cosmiconfig/7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -1135,6 +1246,20 @@ packages: yaml: 1.10.2 dev: false + /css-in-js-utils/3.1.0: + resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + dependencies: + hyphenate-style-name: 1.0.4 + dev: false + + /css-tree/1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + dev: false + /csstype/3.0.9: resolution: {integrity: sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==} dev: false @@ -1175,6 +1300,12 @@ packages: is-arrayish: 0.2.1 dev: false + /error-stack-parser/2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + dependencies: + stackframe: 1.3.4 + dev: false + /esbuild/0.16.17: resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} engines: {node: '>=12'} @@ -1223,10 +1354,37 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: false + /fast-loops/1.1.3: + resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==} + dev: false + + /fast-shallow-equal/1.0.0: + resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} + dev: false + + /fastest-stable-stringify/2.0.2: + resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} + dev: false + /find-root/1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} dev: false + /framer-motion/8.5.5_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-5IDx5bxkjWHWUF3CVJoSyUVOtrbAxtzYBBowRE2uYI/6VYhkEBD+rbTHEGuUmbGHRj6YqqSfoG7Aa1cLyWCrBA==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@motionone/dom': 10.15.5 + hey-listen: 1.0.8 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + tslib: 2.5.0 + optionalDependencies: + '@emotion/is-prop-valid': 0.8.8 + dev: false + /fsevents/2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1263,12 +1421,24 @@ packages: dependencies: function-bind: 1.1.1 + /hey-listen/1.0.8: + resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==} + dev: false + /hoist-non-react-statics/3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} dependencies: react-is: 16.13.1 dev: false + /hyphenate-style-name/1.0.4: + resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==} + dev: false + + /immer/9.0.19: + resolution: {integrity: sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==} + dev: false + /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1277,6 +1447,13 @@ packages: resolve-from: 4.0.0 dev: false + /inline-style-prefixer/6.0.4: + resolution: {integrity: sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg==} + dependencies: + css-in-js-utils: 3.1.0 + fast-loops: 1.1.3 + dev: false + /invariant/2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: @@ -1292,6 +1469,10 @@ packages: dependencies: has: 1.0.3 + /js-cookie/2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + dev: false + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1340,10 +1521,32 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /mdn-data/2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + dev: false + /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true + /nano-css/5.3.5_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg==} + peerDependencies: + react: '*' + react-dom: '*' + dependencies: + css-tree: 1.1.3 + csstype: 3.1.1 + fastest-stable-stringify: 2.0.2 + inline-style-prefixer: 6.0.4 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + rtl-css-js: 1.16.1 + sourcemap-codec: 1.4.8 + stacktrace-js: 2.0.2 + stylis: 4.1.3 + dev: false + /nanoid/3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1405,6 +1608,10 @@ packages: react-is: 16.13.1 dev: false + /ramda/0.28.0: + resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==} + dev: false + /react-dom/18.2.0_react@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -1527,6 +1734,40 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false + /react-universal-interface/0.6.2_react@18.2.0+tslib@2.5.0: + resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==} + peerDependencies: + react: '*' + tslib: '*' + dependencies: + react: 18.2.0 + tslib: 2.5.0 + dev: false + + /react-use/17.4.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@types/js-cookie': 2.2.7 + '@xobotyi/scrollbar-width': 1.9.5 + copy-to-clipboard: 3.3.3 + fast-deep-equal: 3.1.3 + fast-shallow-equal: 1.0.0 + js-cookie: 2.2.1 + nano-css: 5.3.5_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-universal-interface: 0.6.2_react@18.2.0+tslib@2.5.0 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + set-harmonic-interval: 1.0.1 + throttle-debounce: 3.0.1 + ts-easing: 0.2.0 + tslib: 2.5.0 + dev: false + /react/18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -1538,6 +1779,10 @@ packages: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: false + /resize-observer-polyfill/1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + dev: false + /resolve-from/4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1559,27 +1804,83 @@ packages: fsevents: 2.3.2 dev: true + /rtl-css-js/1.16.1: + resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} + dependencies: + '@babel/runtime': 7.21.0 + dev: false + /scheduler/0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: loose-envify: 1.4.0 dev: false + /screenfull/5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + dev: false + /semver/6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true dev: true + /set-harmonic-interval/1.0.1: + resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} + engines: {node: '>=6.9'} + dev: false + /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} dev: true + /source-map/0.5.6: + resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} + engines: {node: '>=0.10.0'} + dev: false + /source-map/0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} dev: false + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false + + /sourcemap-codec/1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + dev: false + + /stack-generator/2.0.10: + resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + dependencies: + stackframe: 1.3.4 + dev: false + + /stackframe/1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + dev: false + + /stacktrace-gps/3.1.2: + resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} + dependencies: + source-map: 0.5.6 + stackframe: 1.3.4 + dev: false + + /stacktrace-js/2.0.2: + resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + dependencies: + error-stack-parser: 2.1.4 + stack-generator: 2.0.10 + stacktrace-gps: 3.1.2 + dev: false + /stylis/4.1.3: resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==} dev: false @@ -1598,10 +1899,27 @@ packages: resolution: {integrity: sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==} dev: false + /throttle-debounce/3.0.1: + resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} + engines: {node: '>=10'} + dev: false + /to-fast-properties/2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} + /toggle-selection/1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + dev: false + + /ts-easing/0.2.0: + resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} + dev: false + + /ts-toolbelt/6.15.5: + resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} + dev: true + /tslib/2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} dev: false @@ -1646,6 +1964,16 @@ packages: react: 18.2.0 dev: false + /use-immer/0.8.1_immer@9.0.19+react@18.2.0: + resolution: {integrity: sha512-OfTFf1pL+ICjjcLPn9+ZnaJO/Yg4MBzYZtACEe2mZ/W2A5col28PNUnwowOAaBuOogACOK/37TU17KgsIhUpOw==} + peerDependencies: + immer: '>=2.0.0' + react: ^16.8.0 || ^17.0.1 || ^18.0.0 + dependencies: + immer: 9.0.19 + react: 18.2.0 + dev: false + /use-isomorphic-layout-effect/1.1.2_pmekkgnqduwlme35zpnqhenc34: resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} peerDependencies: @@ -1689,6 +2017,14 @@ packages: tslib: 2.5.0 dev: false + /use-sync-external-store/1.2.0_react@18.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /vite/4.1.4_@types+node@18.14.6: resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -1731,3 +2067,20 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} dev: false + + /zustand/4.3.6_immer@9.0.19+react@18.2.0: + resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==} + engines: {node: '>=12.7.0'} + peerDependencies: + immer: '>=9.0' + react: '>=16.8' + peerDependenciesMeta: + immer: + optional: true + react: + optional: true + dependencies: + immer: 9.0.19 + react: 18.2.0 + use-sync-external-store: 1.2.0_react@18.2.0 + dev: false diff --git a/src/App.css b/src/App.css deleted file mode 100644 index a89ebd1..0000000 --- a/src/App.css +++ /dev/null @@ -1,7 +0,0 @@ -.logo.vite:hover { - filter: drop-shadow(0 0 2em #747bff); -} - -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafb); -} diff --git a/src/MainLayout.tsx b/src/MainLayout.tsx new file mode 100644 index 0000000..934d7a6 --- /dev/null +++ b/src/MainLayout.tsx @@ -0,0 +1,15 @@ +import { Box, Group } from "@mantine/core"; +import { FC } from "react"; +import { Outlet } from "react-router-dom"; +import { NavMenu } from "./NavMenu"; + +export const MainLayout: FC = () => { + return ( + + + + + + + ); +}; diff --git a/src/NavMenu.tsx b/src/NavMenu.tsx new file mode 100644 index 0000000..7a771d9 --- /dev/null +++ b/src/NavMenu.tsx @@ -0,0 +1,32 @@ +import { Stack, useMantineTheme } from "@mantine/core"; +import { ifElse, path, propEq } from "ramda"; +import { FC, useMemo } from "react"; + +const bgSelectFn = ifElse( + propEq("colorScheme", "dark"), + path(["colors", "cbg", 2]), + path(["colors", "cbg", 7]) +); + +export const NavMenu: FC = () => { + const theme = useMantineTheme(); + const normalColor = useMemo(() => path(["violet", 7])(theme.colors), [theme.colors]); + const activatedColor = useMemo(() => path(["violet", 3])(theme.colors), [theme.colors]); + const disabledColor = useMemo(() => path(["gray", 7])(theme.colors), [theme.colors]); + const navMenuBg = useMemo(() => bgSelectFn(theme), [theme, theme]); + + return ( + ({ + flexGrow: 1, + backgroundColor: navMenuBg, + })} + align="center" + px={16} + py={16} + > + ); +}; diff --git a/src/main.tsx b/src/main.tsx index fa1b97e..0d36a42 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,30 @@ -import React from "react"; +import { NotificationsProvider } from "@mantine/notifications"; +import React, { FC, useState } from "react"; import ReactDOM from "react-dom/client"; -import App from "./App"; -import "./styles.css"; +import { RouterProvider } from "react-router-dom"; +import { appRouter } from "./router"; +import { useAppTheme } from "./theme"; + +const AppMain: FC = () => { + const preferredColorScheme = useColorScheme(); + const [colorScheme, setColorScheme] = useState(preferredColorScheme); + const theme = useAppTheme(); + + return ( + + + + + + + + + + ); +}; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + ); diff --git a/src/router.tsx b/src/router.tsx new file mode 100644 index 0000000..5e0c0f7 --- /dev/null +++ b/src/router.tsx @@ -0,0 +1,9 @@ +import { createBrowserRouter } from "react-router-dom"; +import { MainLayout } from "./MainLayout"; + +export const appRouter = createBrowserRouter([ + { + path: "/", + element: , + }, +]); diff --git a/src/styles.css b/src/styles.css deleted file mode 100644 index f7de85b..0000000 --- a/src/styles.css +++ /dev/null @@ -1,109 +0,0 @@ -:root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color: #0f0f0f; - background-color: #f6f6f6; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -.container { - margin: 0; - padding-top: 10vh; - display: flex; - flex-direction: column; - justify-content: center; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: 0.75s; -} - -.logo.tauri:hover { - filter: drop-shadow(0 0 2em #24c8db); -} - -.row { - display: flex; - justify-content: center; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} - -a:hover { - color: #535bf2; -} - -h1 { - text-align: center; -} - -input, -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - color: #0f0f0f; - background-color: #ffffff; - transition: border-color 0.25s; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); -} - -button { - cursor: pointer; -} - -button:hover { - border-color: #396cd8; -} -button:active { - border-color: #396cd8; - background-color: #e8e8e8; -} - -input, -button { - outline: none; -} - -#greet-input { - margin-right: 5px; -} - -@media (prefers-color-scheme: dark) { - :root { - color: #f6f6f6; - background-color: #2f2f2f; - } - - a:hover { - color: #24c8db; - } - - input, - button { - color: #ffffff; - background-color: #0f0f0f98; - } - button:active { - background-color: #0f0f0f69; - } -} diff --git a/src/theme.ts b/src/theme.ts new file mode 100644 index 0000000..8d32bad --- /dev/null +++ b/src/theme.ts @@ -0,0 +1,61 @@ +import { MantineTheme } from '@mantine/core'; +import { useColorScheme } from '@mantine/hooks'; +import { ifElse, path, propEq } from 'ramda'; + +const bgColorSelectFn = ifElse( + propEq('colorScheme', 'light'), + path(['colors', 'cbg', 8]), + path(['colors', 'cbg', 0]) +); +const fgColorSelectFn = ifElse( + propEq('colorScheme', 'light'), + path(['colors', 'cfg', 0]), + path(['colors', 'cfg', 8]) +); + +export function useAppTheme(): Partial { + const colorScheme = useColorScheme(); + + return { + colorScheme, + focusRing: 'never', + defaultRadius: 'xs', + colors: { + cfg: [ + '#0f0f0f', + '#151515', + '#262626', + '#414141', + '#626262', + '#878787', + '#acacac', + '#cecece', + '#e8e8e8', + '#f9f9f9' + ], + cbg: [ + '#1a202c', + '#1e2533', + '#2a3446', + '#3c4a65', + '#53678b', + '#7689ad', + '#a0adc6', + '#c6cedd', + '#e5e8ef', + '#f8f9fb' + ] + }, + primaryColor: 'violet', + globalStyles: theme => ({ + 'html, body': { + height: '100vh', + color: fgColorSelectFn(theme), + backgroundColor: bgColorSelectFn(theme) + }, + '#root': { + height: '100%' + } + }) + }; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..23ead78 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,216 @@ +/** + * 不需要任何参数即可以生成指定类型对象的工厂函数类型。 + */ +export type Factory = () => T; + +/** + * 接受一个参数来生成一个指定类型的新对象的工厂函数类型。 + */ +export type MapFactory = (obj: P) => T; + +/** + * 可能接受一个参数也可以不使用任何参数来生成指定类型新对象的工厂函数类型。 + */ +export type MaybeParamFactory = (obj?: P) => T; + +/** + * 可能接受一组任意类型的参数集合来生成指定类型新对象的工厂函数类型。 + */ +export type ParamFactory = (...args: P[]) => T; + +/** + * 接受一个对象(默认是字符串)作为参数的回调函数,可以是同步函数也可以是异步函数。 + */ +export type ObjectCallback

= (obj: P) => T | Promise; + +/** + * 接受一个对象(默认是字符串)作为参数的回调函数,仅可以是同步函数。 + */ +export type SyncObjectCallback

= (obj: P) => T; + +/** + * 接受一个对象(默认是字符串)作为参数的回调函数,仅可以是异步函数。 + */ +export type AsyncObjectCallback

= (obj: P) => Promise; + +/** + * 接受一个对象(默认是字符串)或者零个对象作为参数的回调函数,可以是同步函数也可以是异步函数。 + */ +export type MaybeObjectCallback

= ObjectCallback

; + +/** + * 接受一个对象(默认是字符串)或者零个对象作为参数的回调函数,仅可以是同步函数。 + */ +export type SyncMaybeObjectCallback

= SyncObjectCallback

; + +/** + * 接受一个对象(默认是字符串)或者零个对象作为参数的回调函数,仅可以是异步函数。 + */ +export type AsyncMaybeObjectCallback

= AsyncObjectCallback

; + +/** + * 接受一个对象数组(默认是字符串数组)作为参数的回调函数,可以是同步函数也可以是异步函数。 + */ +export type ObjectsCallback

= ObjectCallback; + +/** + * 接受一个对象数组(默认是字符串数组)作为参数的回调函数,仅可以是同步函数。 + */ +export type SyncObjectsCallback

= SyncObjectCallback; + +/** + * 接受一个对象数组(默认是字符串数组)作为参数的回调函数,仅可以是异步函数。 + */ +export type AsyncObjectsCallback

= AsyncObjectCallback; + +/** + * 接收一个对象数组(默认是字符串数组)或者零个对象作为参数的回调函数,可以是同步函数也可以是异步函数。 + */ +export type MaybeObjectsCallback

= ObjectCallback; + +/** + * 接收一个对象数组(默认是字符串数组)或者零个对象作为参数的回调函数,仅可以是同步函数。 + */ +export type SyncMaybeObjectsCallback

= SyncObjectCallback; + +/** + * 接收一个对象数组(默认是字符串数组)或者零个对象作为参数的回调函数,仅可以是异步函数。 + */ +export type AsyncMaybeObjectsCallback

= AsyncObjectCallback< + P[] | undefined, + T +>; + +/** + * 可以接受任意数量任意参数类型的回调函数。 + */ +export type ExtendParamCallback

= (...args: P[]) => T | Promise; + +/** + * 可以接受任意数量任意参数类型的仅支持同步的回调函数。 + */ +export type SyncExtendParamCallback

= (...args: P[]) => T; + +/** + * 可以接受任意数量任意参数类型的仅支持异步的回调函数。 + */ +export type AsyncExtendParamCallback

= (...args: P[]) => Promise; + +/** + * 不接受任何参数内容的回调函数,可以是同步函数也可以是异步函数。 + */ +export type Callback = () => T | Promise; + +/** + * 不接受任何参数内容的回调函数,仅可以是同步函数。 + */ +export type SyncCallback = () => T; + +/** + * 不接受任何参数内容的回调函数,仅可以是异步函数。 + */ +export type AsyncCallback = () => Promise; + +/** + * 用于在Store中定义状态操作Action的无参同步Action类型。 + */ +export type SyncAction = SyncCallback; + +/** + * 用于在Store中定义状态操作Action的无参可异步Action类型。 + */ +export type AsyncAction = Callback; + +/** + * 用于在Store中定义状态操作Action的单一参数同步Action类型。 + */ +export type SyncParamAction = SyncObjectCallback; + +/** + * 用于在Store中定义状态操作Action的单一参数异步Action类型。 + */ +export type AsyncParamAction = AsyncObjectCallback; + +/** + * 用于在Store中定义状态操作Action的不定参同步Action类型。 + */ +export type SyncMaybeAction = SyncMaybeObjectCallback; + +/** + * 用于在Store中定义状态操作Action的不定参可异步Action类型。 + */ +export type AsyncMaybeAction = AsyncMaybeObjectCallback; + +/** + * 用于在Store中定义状态操作Action的展开参同步Action类型。 + */ +export type SyncExtendParamAction = SyncExtendParamCallback; + +/** + * 用于在Store中定义状态操作Action的展开参可异步Action类型。 + */ +export type AsyncExtendParamAction = AsyncExtendParamCallback; + +/** + * 用于定义可以接受重置事件的Ref组件。 + */ +export interface Resetable { + /** + * 组件可以接受的重置方法。 + */ + reset: Callback; +} + +/** + * 用于定义可执行打开动作的Ref组件。 + */ +export interface Openable { + /** + * 组件可以接受的打开方法。 + */ + open: Callback; +} + +/** + * 用于定义可执行关闭动作的Ref组件。 + */ +export interface Closeable { + /** + * 组件可以接受的关闭方法。 + */ + close: Callback; +} + +/** + * 用于定义可以执行特定动作的Ref组件。 + * 组件所执行的动作可以接受任意数量的参数,但不会返回任何结果。 + */ +export interface Actionable { + /** + * 组件可以接受的执行特定动作的方法。 + */ + action?: ExtendParamCallback; +} + +/** + * 用于记录可以发生修改的脏数据。 + */ +export interface DirtyableValue { + /** + * 记录数据当前的值。 + */ + value: T; + /** + * 记录数据在修改前的全部历史值。 + */ + lastValues: T[]; + /** + * 记录数据是否发生了更改。 + */ + dirty: boolean; +} + +/** + * 转换一般的数据记录类型成为可记录修改的脏数据类型。 + */ +export type Dirtyable = { [P in keyof T]: DirtyableValue }; diff --git a/src/utils/store_creator.ts b/src/utils/store_creator.ts new file mode 100644 index 0000000..321345f --- /dev/null +++ b/src/utils/store_creator.ts @@ -0,0 +1,56 @@ +//@ts-nocheck +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { create, State, StateCreator, StoreApi, UseBoundStore } from 'zustand'; +import { immer } from 'zustand/middleware/immer'; + +interface EnhancedStoreType { + use: { + [key in keyof StoreType]: () => StoreType[key]; + }; + reset: () => void; +} + +type CreateStoreHookOptions = { + debug?: boolean; +}; + +/** + * 自动为一个Store Hook创建快速访问其中状态和Action的选择器。 + */ +function createSelectors( + store: UseBoundStore>, + debug?: boolean +): UseBoundStore> & EnhancedStoreType { + const initialState = store.getState(); + (store as unknown).use = {}; + + Object.keys(store.getState()).forEach(key => { + const selector = (state: StoreType) => state[key as keyof StoreType]; + (store as unknown).use[key] = () => store(selector); + }); + (store as unknown).reset = () => store.setState(initialState, true); + + if (debug ?? false) { + store.subscribe((current, previous) => { + console.log('[状态调试]Action应用前: ', previous); + console.log('[状态调试]Action应用后: ', current); + }); + } + + return store as UseBoundStore & EnhancedStoreType; +} + +/** + * 自动嵌套使用Devtools和Immer中间件的Zustand创建Store Hook的函数。 + * 同时将会自动应用创建快速访问状态和Action的选择器。 + */ +export const createStoreHook = < + T extends State, + Mps extends [StoreMutatorIdentifier, unknown][] = [], + Mcs extends [StoreMutatorIdentifier, unknown][] = [] +>( + initializer: StateCreator, + options?: CreateStoreHookOptions +): UseBoundStore> & EnhancedStoreType => + createSelectors(create()(immer(initializer)), options?.debug ?? false); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..0b89edf --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "./src", + ".eslintrc.js", + ], + "exclude": [ + "node_modules", + "dist" + ] +}