diff options
| author | AAGaming <aa@mail.catvibers.me> | 2022-09-09 16:25:52 -0400 |
|---|---|---|
| committer | AAGaming <aa@mail.catvibers.me> | 2022-09-09 16:25:52 -0400 |
| commit | b5b041fdee3cdb3576cd4d1c77580f57da0b6435 (patch) | |
| tree | 2b63ccb6410868fe4f0c6f3882614d0f7a180be4 /frontend | |
| parent | 9d980618a78b41bc3262c5185df67ccf6076a296 (diff) | |
| download | decky-loader-b5b041fdee3cdb3576cd4d1c77580f57da0b6435.tar.gz decky-loader-b5b041fdee3cdb3576cd4d1c77580f57da0b6435.zip | |
add file picker, add library file picker patch, bump lib, logger tweaks
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/package.json | 5 | ||||
| -rw-r--r-- | frontend/pnpm-lock.yaml | 269 | ||||
| -rw-r--r-- | frontend/rollup.config.js | 2 | ||||
| -rw-r--r-- | frontend/src/components/WithSuspense.tsx | 32 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/iconCustomizations.ts | 170 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/index.tsx | 159 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/patches/README.md | 1 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/patches/index.ts | 10 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/patches/library.ts | 32 | ||||
| -rw-r--r-- | frontend/src/logger.ts | 6 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 83 | ||||
| -rw-r--r-- | frontend/src/store.tsx | 6 |
12 files changed, 729 insertions, 46 deletions
diff --git a/frontend/package.json b/frontend/package.json index 5ce04122..0b8e774d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "@rollup/plugin-replace": "^4.0.0", "@rollup/plugin-typescript": "^8.3.3", "@types/react": "16.14.0", + "@types/react-file-icon": "^1.0.1", "@types/react-router": "5.1.18", "@types/webpack": "^5.28.0", "husky": "^8.0.1", @@ -27,6 +28,7 @@ "react": "16.14.0", "react-dom": "16.14.0", "rollup": "^2.76.0", + "rollup-plugin-delete": "^2.0.0", "rollup-plugin-external-globals": "^0.6.1", "rollup-plugin-polyfill-node": "^0.10.2", "tslib": "^2.4.0", @@ -39,7 +41,8 @@ } }, "dependencies": { - "decky-frontend-lib": "^2.0.0", + "decky-frontend-lib": "^3.0.0", + "react-file-icon": "^1.2.0", "react-icons": "^4.4.0", "react-markdown": "^8.0.3", "remark-gfm": "^3.0.1" diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index a66dd98e..365573ef 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -7,9 +7,10 @@ specifiers: '@rollup/plugin-replace': ^4.0.0 '@rollup/plugin-typescript': ^8.3.3 '@types/react': 16.14.0 + '@types/react-file-icon': ^1.0.1 '@types/react-router': 5.1.18 '@types/webpack': ^5.28.0 - decky-frontend-lib: ^2.0.0 + decky-frontend-lib: ^3.0.0 husky: ^8.0.1 import-sort-style-module: ^6.0.0 inquirer: ^8.2.4 @@ -17,17 +18,20 @@ specifiers: prettier-plugin-import-sort: ^0.0.7 react: 16.14.0 react-dom: 16.14.0 + react-file-icon: ^1.2.0 react-icons: ^4.4.0 react-markdown: ^8.0.3 remark-gfm: ^3.0.1 rollup: ^2.76.0 + rollup-plugin-delete: ^2.0.0 rollup-plugin-external-globals: ^0.6.1 rollup-plugin-polyfill-node: ^0.10.2 tslib: ^2.4.0 typescript: ^4.7.4 dependencies: - decky-frontend-lib: 2.0.0 + decky-frontend-lib: 3.0.0 + react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty react-icons: 4.4.0_react@16.14.0 react-markdown: 8.0.3_vshvapmxg47tngu7tvrsqpq55u remark-gfm: 3.0.1 @@ -39,6 +43,7 @@ devDependencies: '@rollup/plugin-replace': 4.0.0_rollup@2.76.0 '@rollup/plugin-typescript': 8.3.3_mrkdcqv53wzt2ybukxlrvz47fu '@types/react': 16.14.0 + '@types/react-file-icon': 1.0.1 '@types/react-router': 5.1.18 '@types/webpack': 5.28.0 husky: 8.0.1 @@ -49,6 +54,7 @@ devDependencies: react: 16.14.0 react-dom: 16.14.0_react@16.14.0 rollup: 2.76.0 + rollup-plugin-delete: 2.0.0 rollup-plugin-external-globals: 0.6.1_rollup@2.76.0 rollup-plugin-polyfill-node: 0.10.2_rollup@2.76.0 tslib: 2.4.0 @@ -296,6 +302,27 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.13.0 + dev: true + /@rollup/plugin-commonjs/21.1.0_rollup@2.76.0: resolution: {integrity: sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA==} engines: {node: '>= 8.0.0'} @@ -427,6 +454,13 @@ packages: resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true + /@types/glob/7.2.0: + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 18.0.4 + dev: true + /@types/hast/2.3.4: resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} dependencies: @@ -451,6 +485,10 @@ packages: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} dev: false + /@types/minimatch/5.1.2: + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + dev: true + /@types/ms/0.7.31: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: false @@ -462,6 +500,12 @@ packages: /@types/prop-types/15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + /@types/react-file-icon/1.0.1: + resolution: {integrity: sha512-QTdYCkYXzh/PfKEIwcPxRdaPQkii5R4Ke7fcO+KB++IDPbYAG1jj+ulEcTA7pRf0gZ5jAvjWcTXBJJRtfYHjlw==} + dependencies: + '@types/react': 16.14.0 + dev: true + /@types/react-router/5.1.18: resolution: {integrity: sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==} dependencies: @@ -626,6 +670,14 @@ packages: hasBin: true dev: true + /aggregate-error/3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + /ajv-keywords/3.5.2_ajv@6.12.6: resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -675,6 +727,11 @@ packages: sprintf-js: 1.0.3 dev: true + /array-union/2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + /bail/2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} dev: false @@ -702,6 +759,13 @@ packages: concat-map: 0.0.1 dev: true + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + /browserslist/4.21.2: resolution: {integrity: sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -786,6 +850,11 @@ packages: engines: {node: '>=6.0'} dev: true + /clean-stack/2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + /cli-cursor/3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -875,8 +944,8 @@ packages: dependencies: ms: 2.1.2 - /decky-frontend-lib/2.0.0: - resolution: {integrity: sha512-H7+JpKHlClECVpo+MCEwej7R9wDWk9M2uMSyTvuhTfLZe3RThsxWCiqY640Cjh/zIW2A7GyVRd4SjLtn6Isdeg==} + /decky-frontend-lib/3.0.0: + resolution: {integrity: sha512-ZqJ9ii7QoYWHFfkU8hV82IHi3+McZDmE4wS22duXpgRI8r5BBMiZItw6tYkc24ZtsJIVso83FFt7adcEBqBwJA==} dependencies: minimist: 1.2.6 dev: false @@ -898,6 +967,20 @@ packages: clone: 1.0.4 dev: true + /del/5.1.0: + resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==} + engines: {node: '>=8'} + dependencies: + globby: 10.0.2 + graceful-fs: 4.2.10 + is-glob: 4.0.3 + is-path-cwd: 2.2.0 + is-path-inside: 3.0.3 + p-map: 3.0.0 + rimraf: 3.0.2 + slash: 3.0.0 + dev: true + /dequal/2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -913,6 +996,13 @@ packages: engines: {node: '>=0.3.1'} dev: false + /dir-glob/3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + /electron-to-chromium/1.4.189: resolution: {integrity: sha512-dQ6Zn4ll2NofGtxPXaDfY2laIa6NyCQdqXYHdwH90GJQW0LpJJib0ZU/ERtbb0XkBEmUD2eJtagbOie3pdMiPg==} dev: true @@ -1015,10 +1105,27 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true + /fast-glob/3.2.11: + resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + /fast-json-stable-stringify/2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true + /fastq/1.13.0: + resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} + dependencies: + reusify: 1.0.4 + dev: true + /figures/3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -1026,6 +1133,13 @@ packages: escape-string-regexp: 1.0.5 dev: true + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + /find-line-column/0.5.2: resolution: {integrity: sha512-eNhNkDt5RbxY4X++JwyDURP62FYhV1bh9LF4dfOiwpVCTk5vvfEANhnui5ypUEELGR02QZSrWFtaTgd4ulW5tw==} dev: true @@ -1055,6 +1169,13 @@ packages: engines: {node: '>=6.9.0'} dev: true + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + /glob-to-regexp/0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} dev: true @@ -1075,6 +1196,20 @@ packages: engines: {node: '>=4'} dev: true + /globby/10.0.2: + resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==} + engines: {node: '>=8'} + dependencies: + '@types/glob': 7.2.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.2.11 + glob: 7.2.3 + ignore: 5.2.0 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + /graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true @@ -1117,6 +1252,11 @@ packages: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true + /ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} + dev: true + /import-fresh/2.0.0: resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} engines: {node: '>=4'} @@ -1174,6 +1314,11 @@ packages: resolve: 1.22.1 dev: true + /indent-string/4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + /inflight/1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -1237,11 +1382,23 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + /is-fullwidth-code-point/3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} dev: true + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + /is-interactive/1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -1251,6 +1408,21 @@ packages: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} dev: true + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-path-cwd/2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} + dev: true + + /is-path-inside/3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + /is-plain-obj/4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -1321,6 +1493,10 @@ packages: engines: {node: '>=6.11.5'} dev: true + /lodash.uniqueid/4.0.1: + resolution: {integrity: sha512-GQQWaIeGlL6DIIr06kj1j6sSmBxyNMwI8kaX9aKpHR/XsMTiaXDVPNPAkiboOTK9OJpTJF/dXT3xYoFQnj386Q==} + dev: false + /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true @@ -1483,6 +1659,11 @@ packages: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + /micromark-core-commonmark/1.0.6: resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==} dependencies: @@ -1732,6 +1913,14 @@ packages: - supports-color dev: false + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -1816,6 +2005,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /p-map/3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} + dependencies: + aggregate-error: 3.1.0 + dev: true + /parse-json/4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -1833,6 +2029,11 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -1878,6 +2079,10 @@ packages: engines: {node: '>=6'} dev: true + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + /randombytes/2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -1894,7 +2099,19 @@ packages: prop-types: 15.8.1 react: 16.14.0 scheduler: 0.19.1 - dev: true + + /react-file-icon/1.2.0_wcqkhtmu7mswc6yz4uyexck3ty: + resolution: {integrity: sha512-BI8CTyZu/k8AmhjGJiGYOqgjfp2si2Lt5PUNF6kfF31c7BFYJeerpfHnZBfpPjrb2M/DAdW1qNub17Rt+xuefQ==} + peerDependencies: + react: ^18.0.0 || ^17.0.0 || ^16.2.0 + react-dom: ^18.0.0 || ^17.0.0 || ^16.2.0 + dependencies: + lodash.uniqueid: 4.0.1 + prop-types: 15.8.1 + react: 16.14.0 + react-dom: 16.14.0_react@16.14.0 + tinycolor2: 1.4.2 + dev: false /react-icons/4.4.0_react@16.14.0: resolution: {integrity: sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==} @@ -2012,6 +2229,25 @@ packages: signal-exit: 3.0.7 dev: true + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup-plugin-delete/2.0.0: + resolution: {integrity: sha512-/VpLMtDy+8wwRlDANuYmDa9ss/knGsAgrDhM+tEwB1npHwNu4DYNmDfUL55csse/GHs9Q+SMT/rw9uiaZ3pnzA==} + engines: {node: '>=10'} + dependencies: + del: 5.1.0 + dev: true + /rollup-plugin-external-globals/0.6.1_rollup@2.76.0: resolution: {integrity: sha512-mlp3KNa5sE4Sp9UUR2rjBrxjG79OyZAh/QC18RHIjM+iYkbBwNXSo8DHRMZWtzJTrH8GxQ+SJvCTN3i14uMXIA==} peerDependencies: @@ -2046,6 +2282,12 @@ packages: engines: {node: '>=0.12.0'} dev: true + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + /rxjs/7.5.6: resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==} dependencies: @@ -2076,7 +2318,6 @@ packages: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - dev: true /schema-utils/3.1.1: resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==} @@ -2102,6 +2343,11 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true + /slash/3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + /source-map-support/0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: @@ -2224,6 +2470,10 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true + /tinycolor2/1.4.2: + resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} + dev: false + /tmp/0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -2236,6 +2486,13 @@ packages: engines: {node: '>=4'} dev: true + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + /trim-lines/3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} dev: false diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index f472b816..c4bcd0a2 100644 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -2,6 +2,7 @@ import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import { nodeResolve } from '@rollup/plugin-node-resolve'; import externalGlobals from "rollup-plugin-external-globals"; +import del from 'rollup-plugin-delete' import replace from '@rollup/plugin-replace'; import typescript from '@rollup/plugin-typescript'; import { defineConfig } from 'rollup'; @@ -9,6 +10,7 @@ import { defineConfig } from 'rollup'; export default defineConfig({ input: 'src/index.tsx', plugins: [ + del({ targets: "../backend/static/*", force: true }), commonjs(), nodeResolve(), externalGlobals({ diff --git a/frontend/src/components/WithSuspense.tsx b/frontend/src/components/WithSuspense.tsx new file mode 100644 index 00000000..7460aa3d --- /dev/null +++ b/frontend/src/components/WithSuspense.tsx @@ -0,0 +1,32 @@ +import { SteamSpinner } from 'decky-frontend-lib'; +import { FunctionComponent, ReactElement, ReactNode, Suspense } from 'react'; + +interface WithSuspenseProps { + children: ReactNode; +} + +// Nice little wrapper around Suspense so we don't have to duplicate the styles and code for the loading spinner +const WithSuspense: FunctionComponent<WithSuspenseProps> = (props) => { + const propsCopy = { ...props }; + delete propsCopy.children; + (props.children as ReactElement)?.props && Object.assign((props.children as ReactElement).props, propsCopy); // There is probably a better way to do this but valve does it this way so ¯\_(ツ)_/¯ + return ( + <Suspense + fallback={ + <div + style={{ + marginTop: '40px', + height: 'calc( 100% - 40px )', + overflowY: 'scroll', + }} + > + <SteamSpinner /> + </div> + } + > + {props.children} + </Suspense> + ); +}; + +export default WithSuspense; diff --git a/frontend/src/components/modals/filepicker/iconCustomizations.ts b/frontend/src/components/modals/filepicker/iconCustomizations.ts new file mode 100644 index 00000000..e09c9e67 --- /dev/null +++ b/frontend/src/components/modals/filepicker/iconCustomizations.ts @@ -0,0 +1,170 @@ +// https://codesandbox.io/s/react-file-icon-colored-tmwut?file=/src/App.js +import { FileIconProps } from 'react-file-icon'; + +type T_FileExtList = string[]; + +const styleDef: [FileIconProps, T_FileExtList][] = []; + +// video //////////////////////////////////// +const videoStyle = { + color: '#f00f0f', +}; +const videoExtList = [ + 'avi', + '3g2', + '3gp', + 'aep', + 'asf', + 'flv', + 'm4v', + 'mkv', + 'mov', + 'mp4', + 'mpeg', + 'mpg', + 'ogv', + 'pr', + 'swfw', + 'webm', + 'wmv', + 'swf', + 'rm', +]; + +styleDef.push([videoStyle, videoExtList]); + +// image //////////////////////////////////// +const imageStyle = { + color: '#d18f00', +}; + +const imageExtList = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tif', 'tiff']; + +styleDef.push([imageStyle, imageExtList]); + +// zip //////////////////////////////////// +const zipStyle = { + color: '#f7b500', + labelTextColor: '#000', + // glyphColor: "#de9400" +}; + +const zipExtList = ['zip', 'zipx', '7zip', 'tar', 'sitx', 'gz', 'rar']; + +styleDef.push([zipStyle, zipExtList]); + +// audio //////////////////////////////////// +const audioStyle = { + color: '#f00f0f', +}; + +const audioExtList = ['aac', 'aif', 'aiff', 'flac', 'm4a', 'mid', 'mp3', 'ogg', 'wav']; + +styleDef.push([audioStyle, audioExtList]); + +// text //////////////////////////////////// +const textStyle = { + color: '#ffffff', + glyphColor: '#787878', +}; + +const textExtList = ['cue', 'odt', 'md', 'rtf', 'txt', 'tex', 'wpd', 'wps', 'xlr', 'fodt']; + +styleDef.push([textStyle, textExtList]); + +// system //////////////////////////////////// +const systemStyle = { + color: '#111', +}; + +const systemExtList = ['exe', 'ini', 'dll', 'plist', 'sys']; + +styleDef.push([systemStyle, systemExtList]); + +// srcCode //////////////////////////////////// +const srcCodeStyle = { + glyphColor: '#787878', + color: '#ffffff', +}; + +const srcCodeExtList = [ + 'asp', + 'aspx', + 'c', + 'cpp', + 'cs', + 'css', + 'scss', + 'py', + 'json', + 'htm', + 'html', + 'java', + 'yml', + 'php', + 'js', + 'ts', + 'rb', + 'jsx', + 'tsx', +]; + +styleDef.push([srcCodeStyle, srcCodeExtList]); + +// vector //////////////////////////////////// +const vectorStyle = { + color: '#ffe600', +}; + +const vectorExtList = ['dwg', 'dxf', 'ps', 'svg', 'eps']; + +styleDef.push([vectorStyle, vectorExtList]); + +// font //////////////////////////////////// +const fontStyle = { + color: '#555', +}; + +const fontExtList = ['fnt', 'ttf', 'otf', 'fon', 'eot', 'woff']; + +styleDef.push([fontStyle, fontExtList]); + +// objectModel //////////////////////////////////// +const objectModelStyle = { + color: '#bf6a02', + glyphColor: '#bf6a02', +}; + +const objectModelExtList = ['3dm', '3ds', 'max', 'obj', 'pkg']; + +styleDef.push([objectModelStyle, objectModelExtList]); + +// sheet //////////////////////////////////// +const sheetStyle = { + color: '#2a6e00', +}; + +const sheetExtList = ['csv', 'fods', 'ods', 'xlr']; + +styleDef.push([sheetStyle, sheetExtList]); + +// const defaultStyle: Record<string, FileIconProps> = { +// pdf: { +// glyphColor: "white", +// color: "#D93831" +// } +// }; + +////////////////////////////////////////////////// + +function createStyleObj(extList: T_FileExtList, styleObj: Partial<FileIconProps>) { + return Object.fromEntries( + extList.map((ext) => { + return [ext, { ...styleObj, glyphColor: 'white' }]; + }), + ); +} + +export const styleDefObj = styleDef.reduce((acc, [fileStyle, fileExtList]) => { + return { ...acc, ...createStyleObj(fileExtList, fileStyle) }; +}); diff --git a/frontend/src/components/modals/filepicker/index.tsx b/frontend/src/components/modals/filepicker/index.tsx new file mode 100644 index 00000000..0847bd14 --- /dev/null +++ b/frontend/src/components/modals/filepicker/index.tsx @@ -0,0 +1,159 @@ +import { DialogButton, Focusable, SteamSpinner, TextField } from 'decky-frontend-lib'; +import { useEffect } from 'react'; +import { FunctionComponent, useState } from 'react'; +import { FileIcon, defaultStyles } from 'react-file-icon'; +import { FaArrowUp, FaFolder } from 'react-icons/fa'; + +import Logger from '../../../logger'; +import { styleDefObj } from './iconCustomizations'; + +const logger = new Logger('FilePicker'); + +export interface FilePickerProps { + startPath: string; + includeFiles?: boolean; + regex?: RegExp; + onSubmit: (val: { path: string; realpath: string }) => void; + closeModal?: () => void; +} + +interface File { + isdir: boolean; + name: string; + realpath: string; +} + +interface FileListing { + realpath: string; + files: File[]; +} + +function getList( + path: string, + includeFiles: boolean = true, +): Promise<{ result: FileListing | string; success: boolean }> { + return window.DeckyPluginLoader.callServerMethod('filepicker_ls', { path, include_files: includeFiles }); +} + +const iconStyles = { + paddingRight: '10px', + width: '1em', +}; + +const FilePicker: FunctionComponent<FilePickerProps> = ({ + startPath, + includeFiles = true, + regex, + onSubmit, + closeModal, +}) => { + if (startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path + const [path, setPath] = useState<string>(startPath); + const [listing, setListing] = useState<FileListing>({ files: [], realpath: path }); + const [error, setError] = useState<string | null>(null); + const [loading, setLoading] = useState<boolean>(true); + + useEffect(() => { + (async () => { + if (error) setError(null); + setLoading(true); + const listing = await getList(path, includeFiles); + if (!listing.success) { + setListing({ files: [], realpath: path }); + setLoading(false); + setError(listing.result as string); + logger.error(listing.result); + return; + } + setLoading(false); + setListing(listing.result as FileListing); + logger.log('reloaded', path, listing); + })(); + }, [path]); + + return ( + <div className="deckyFilePicker"> + <Focusable style={{ display: 'flex', flexDirection: 'row', paddingBottom: '10px' }}> + <DialogButton + style={{ + minWidth: 'unset', + width: '40px', + flexGrow: '0', + borderRadius: 'unset', + margin: '0', + padding: '10px', + }} + onClick={() => { + const newPathArr = path.split('/'); + newPathArr.pop(); + const newPath = newPathArr.join('/'); + setPath(newPath); + }} + > + <FaArrowUp /> + </DialogButton> + <div style={{ flexGrow: '1', width: '100%' }}> + <TextField + value={path} + onChange={(e) => { + e.target.value && setPath(e.target.value); + }} + style={{ height: '100%' }} + /> + </div> + </Focusable> + <Focusable style={{ display: 'flex', flexDirection: 'column', height: '60vh', overflow: 'scroll' }}> + {loading && <SteamSpinner style={{ height: '100%' }} />} + {!loading && + listing.files + .filter((file) => (includeFiles || file.isdir) && (!regex || regex.test(file.name))) + .map((file) => { + let extension = file.realpath.split('.').pop() as string; + return ( + <DialogButton + style={{ borderRadius: 'unset', margin: '0', padding: '10px' }} + onClick={() => { + const fullPath = `${path}/${file.name}`; + if (file.isdir) setPath(fullPath); + else { + onSubmit({ path: fullPath, realpath: file.realpath }); + closeModal?.(); + } + }} + > + <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'flex-start' }}> + {file.isdir ? ( + <FaFolder style={iconStyles} /> + ) : ( + <div style={iconStyles}> + {file.realpath.includes('.') ? ( + <FileIcon {...defaultStyles[extension]} {...styleDefObj[extension]} extension={''} /> + ) : ( + <FileIcon /> + )} + </div> + )} + {file.name} + </div> + </DialogButton> + ); + })} + {error} + </Focusable> + {!loading && !error && !includeFiles && ( + <DialogButton + className="Primary" + style={{ marginTop: '10px', alignSelf: 'flex-end' }} + onClick={() => { + onSubmit({ path, realpath: listing.realpath }); + closeModal?.(); + }} + > + Use this folder + </DialogButton> + )} + </div> + ); +}; + +export default FilePicker; diff --git a/frontend/src/components/modals/filepicker/patches/README.md b/frontend/src/components/modals/filepicker/patches/README.md new file mode 100644 index 00000000..154914c5 --- /dev/null +++ b/frontend/src/components/modals/filepicker/patches/README.md @@ -0,0 +1 @@ +This directory contains patches that replace Valve's broken file picker with ours. diff --git a/frontend/src/components/modals/filepicker/patches/index.ts b/frontend/src/components/modals/filepicker/patches/index.ts new file mode 100644 index 00000000..310bfbf8 --- /dev/null +++ b/frontend/src/components/modals/filepicker/patches/index.ts @@ -0,0 +1,10 @@ +import library from './library'; +let patches: Function[] = []; + +export function deinitFilepickerPatches() { + patches.forEach((unpatch) => unpatch()); +} + +export async function initFilepickerPatches() { + patches.push(await library()); +} diff --git a/frontend/src/components/modals/filepicker/patches/library.ts b/frontend/src/components/modals/filepicker/patches/library.ts new file mode 100644 index 00000000..8792900d --- /dev/null +++ b/frontend/src/components/modals/filepicker/patches/library.ts @@ -0,0 +1,32 @@ +import { replacePatch, sleep } from 'decky-frontend-lib'; + +declare global { + interface Window { + SteamClient: any; + appDetailsStore: any; + } +} + +export default async function libraryPatch() { + await sleep(10000); // If you patch anything on SteamClient within the first few seconds of the client having loaded it will get redefined for some reason, so wait 10s + const patch = replacePatch(window.SteamClient.Apps, 'PromptToChangeShortcut', async ([appid]: number[]) => { + try { + const details = window.appDetailsStore.GetAppDetails(appid); + console.log(details); + // strShortcutStartDir + const file = await window.DeckyPluginLoader.openFilePicker(details.strShortcutStartDir.replaceAll('"', '')); + console.log('user selected', file); + window.SteamClient.Apps.SetShortcutExe(appid, JSON.stringify(file.path)); + const pathArr = file.path.split('/'); + pathArr.pop(); + const folder = pathArr.join('/'); + window.SteamClient.Apps.SetShortcutStartDir(appid, JSON.stringify(folder)); + } catch (e) { + console.error(e); + } + }); + + return () => { + patch.unpatch(); + }; +} diff --git a/frontend/src/logger.ts b/frontend/src/logger.ts index 22036362..143bef16 100644 --- a/frontend/src/logger.ts +++ b/frontend/src/logger.ts @@ -19,7 +19,7 @@ export const debug = (name: string, ...args: any[]) => { }; export const error = (name: string, ...args: any[]) => { - console.log( + console.error( `%c Decky %c ${name} %c`, 'background: #16a085; color: black;', 'background: #FF0000;', @@ -40,6 +40,10 @@ class Logger { debug(...args: any[]) { debug(this.name, ...args); } + + error(...args: any[]) { + error(this.name, ...args); + } } export default Logger; diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index 4d3415c8..493e5935 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -1,13 +1,15 @@ -import { ModalRoot, QuickAccessTab, Router, SteamSpinner, showModal, sleep, staticClasses } from 'decky-frontend-lib'; -import { Suspense, lazy } from 'react'; +import { ConfirmModal, ModalRoot, QuickAccessTab, Router, showModal, sleep, staticClasses } from 'decky-frontend-lib'; +import { lazy } from 'react'; import { FaPlug } from 'react-icons/fa'; import { DeckyState, DeckyStateContextProvider, useDeckyState } from './components/DeckyState'; import LegacyPlugin from './components/LegacyPlugin'; +import { deinitFilepickerPatches, initFilepickerPatches } from './components/modals/filepicker/patches'; import PluginInstallModal from './components/modals/PluginInstallModal'; import NotificationBadge from './components/NotificationBadge'; import PluginView from './components/PluginView'; import TitleView from './components/TitleView'; +import WithSuspense from './components/WithSuspense'; import Logger from './logger'; import { Plugin } from './plugin'; import RouterHook from './router-hook'; @@ -16,6 +18,11 @@ import TabsHook from './tabs-hook'; import Toaster from './toaster'; import { VerInfo, callUpdaterMethod } from './updater'; +const StorePage = lazy(() => import('./components/store/Store')); +const SettingsPage = lazy(() => import('./components/settings')); + +const FilePicker = lazy(() => import('./components/modals/filepicker')); + declare global { interface Window {} } @@ -58,47 +65,22 @@ class PluginLoader extends Logger { ), }); - const StorePage = lazy(() => import('./components/store/Store')); - const SettingsPage = lazy(() => import('./components/settings')); - this.routerHook.addRoute('/decky/store', () => ( - <Suspense - fallback={ - <div - style={{ - marginTop: '40px', - height: 'calc( 100% - 40px )', - overflowY: 'scroll', - }} - > - <SteamSpinner /> - </div> - } - > + <WithSuspense> <StorePage /> - </Suspense> + </WithSuspense> )); this.routerHook.addRoute('/decky/settings', () => { return ( <DeckyStateContextProvider deckyState={this.deckyState}> - <Suspense - fallback={ - <div - style={{ - marginTop: '40px', - height: 'calc( 100% - 40px )', - overflowY: 'scroll', - }} - > - <SteamSpinner /> - </div> - } - > + <WithSuspense> <SettingsPage /> - </Suspense> + </WithSuspense> </DeckyStateContextProvider> ); }); + + initFilepickerPatches(); } public async notifyUpdates() { @@ -147,7 +129,7 @@ class PluginLoader extends Logger { public uninstallPlugin(name: string) { showModal( - <ModalRoot + <ConfirmModal onOK={async () => { await this.callServerMethod('uninstall_plugin', { name }); }} @@ -158,7 +140,7 @@ class PluginLoader extends Logger { <div className={staticClasses.Title} style={{ flexDirection: 'column' }}> Uninstall {name}? </div> - </ModalRoot>, + </ConfirmModal>, ); } @@ -176,6 +158,7 @@ class PluginLoader extends Logger { public deinit() { this.routerHook.removeRoute('/decky/store'); this.routerHook.removeRoute('/decky/settings'); + deinitFilepickerPatches(); } public unloadPlugin(name: string) { @@ -257,11 +240,41 @@ class PluginLoader extends Logger { return response.json(); } + openFilePicker( + startPath: string, + includeFiles?: boolean, + regex?: RegExp, + ): Promise<{ path: string; realpath: string }> { + return new Promise((resolve, reject) => { + const Content = ({ closeModal }: { closeModal?: () => void }) => ( + // Purposely outside of the FilePicker component as lazy-loaded ModalRoots don't focus correctly + <ModalRoot + onCancel={() => { + reject('User canceled'); + closeModal?.(); + }} + > + <WithSuspense> + <FilePicker + startPath={startPath} + includeFiles={includeFiles} + regex={regex} + onSubmit={resolve} + closeModal={closeModal} + /> + </WithSuspense> + </ModalRoot> + ); + showModal(<Content />); + }); + } + createPluginAPI(pluginName: string) { return { routerHook: this.routerHook, toaster: this.toaster, callServerMethod: this.callServerMethod, + openFilePicker: this.openFilePicker, async callPluginMethod(methodName: string, args = {}) { const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, { method: 'POST', diff --git a/frontend/src/store.tsx b/frontend/src/store.tsx index 12c8972d..bdaae6f2 100644 --- a/frontend/src/store.tsx +++ b/frontend/src/store.tsx @@ -1,4 +1,4 @@ -import { ModalRoot, showModal, staticClasses } from 'decky-frontend-lib'; +import { ConfirmModal, showModal, staticClasses } from 'decky-frontend-lib'; import { Plugin } from './plugin'; @@ -51,7 +51,7 @@ export async function installFromURL(url: string) { export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVer: string) { showModal( - <ModalRoot + <ConfirmModal onOK={() => { window.DeckyPluginLoader.callServerMethod('install_plugin', { name: plugin.artifact, @@ -70,7 +70,7 @@ export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVe You are currently installing a <b>legacy</b> plugin. Legacy plugins are no longer supported and may have issues. Legacy plugins do not support gamepad input. To interact with a legacy plugin, you will need to use the touchscreen. - </ModalRoot>, + </ConfirmModal>, ); } |
