diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..4b6c597 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,53 @@ +name: Generate docs + +on: + push: + branches: + - main + +jobs: + release: + name: Generate Docs + runs-on: ubuntu-22.04 + steps: + - name: Setup | Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + path: lib + - name: Setup | Checkout wiki + uses: actions/checkout@v3 + with: + repository: SteamDeckHomebrew/wiki + path: wiki + ssh-key: ${{ secrets.SSH_DEPLOY_KEY }} + persist-credentials: true + - name: Setup | Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Setup | Dependencies + run: | + cd lib + npm ci + + - name: Build Docs + run: | + cd lib + npm run docs -- --out ../wiki/api-docs/decky-frontend-lib + + - name: Commit files + run: | + cd wiki + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add -A ./api-docs/decky-frontend-lib + git commit -m "Update decky-frontend-lib API docs" + + - name: Push changes + uses: ad-m/github-push-action@master + with: + ssh: true + directory: ./wiki + repository: SteamDeckHomebrew/wiki + branch: main \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2f8f83d..711fd5b 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ research/ # PNPM lockfile pnpm-lock.yaml + +docs/ \ No newline at end of file diff --git a/.releaserc.json b/.releaserc.json index 3db0142..cce7e56 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -6,7 +6,8 @@ { "preset": "angular", "releaseRules": [ - {"type": "chore", "scope": "classes", "release": "patch"} + {"type": "chore", "scope": "classes", "release": "patch"}, + {"type": "*", "scope": "docs", "release": false} ] } ], diff --git a/CHANGELOG.md b/CHANGELOG.md index 60bd9c5..3e13775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,122 @@ +# [3.6.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.5.6...v3.6.0) (2022-10-15) + + +### Features + +* **plugin:** add alwaysRender ([2fc2060](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/2fc2060a6c0d9414d1c36a1a022fdc6f2cd7f8bb)) + +## [3.5.6](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.5.5...v3.5.6) (2022-10-08) + + +### Bug Fixes + +* **Dialog:** remove not exported dialog button ([#37](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/37)) ([5a5218a](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/5a5218a7c43f6a90fc4de5f7a0cd524d1cd298d6)) + +## [3.5.5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.5.4...v3.5.5) (2022-10-08) + + +### Bug Fixes + +* **sidebarnavigation:** no dont ([0ce1b54](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/0ce1b5499df699f602aa83ab87ad8b246d133eac)) + +## [3.5.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.5.3...v3.5.4) (2022-10-08) + + +### Bug Fixes + +* **sidebarnavigation:** allow null pags ([d6b00b0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/d6b00b07337f7a9d38813eeec7c0a848d5c15f17)) + +## [3.5.3](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.5.2...v3.5.3) (2022-10-08) + + +### Bug Fixes + +* **tabs:** fix props and add example ([4024b76](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/4024b76918eea43e43a24c162a937877f18627f0)) + +## [3.5.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.5.1...v3.5.2) (2022-10-08) + + +### Bug Fixes + +* **Tabs:** make onShowTab required ([7161e75](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/7161e757e9c98d677510e03eb2606ce58152f3b1)) + +## [3.5.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.5.0...v3.5.1) (2022-10-08) + + +### Bug Fixes + +* **Tabs:** actually export it lmao ([0e0e0d2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/0e0e0d204adc8d888f05e98edb6c1a1a171d00bb)) + +# [3.5.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.4.0...v3.5.0) (2022-10-08) + + +### Features + +* **Tabs:** initial tabs component, props, docs ([abbd3cd](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/abbd3cddae24039cbc9b7d955924431e8fbacf94)) + +# [3.4.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.5...v3.4.0) (2022-10-06) + + +### Features + +* **hooks:** Added useParams hook ([#36](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/36)) ([e2920dd](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/e2920dd91e81d915a2319280d8473df71a4e4232)) + +## [3.3.5](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.4...v3.3.5) (2022-10-02) + + +### Bug Fixes + +* **docs:** set categorizeByGroup to true ([0f205e8](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/0f205e891694e2cee211b0c2db74a6dda2432507)) + +## [3.3.4](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.3...v3.3.4) (2022-10-02) + + +### Bug Fixes + +* **docs:** build each component as a seperate page ([fbd936d](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/fbd936dc1fe4c23c72f4ee27af95abc004382acd)) + +## [3.3.3](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.2...v3.3.3) (2022-10-02) + + +### Bug Fixes + +* **modal:** make children optional ([#34](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/34)) ([99ad754](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/99ad7543a9966b8ff3f4ec01e6f05c94e5242c93)) + +## [3.3.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.1...v3.3.2) (2022-10-02) + + +### Bug Fixes + +* **modal:** allow children ([40871af](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/40871af8539858f435c83123a56d4b31b63d627d)) + +## [3.3.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.3.0...v3.3.1) (2022-10-02) + + +### Bug Fixes + +* **SidebarNavigation:** add more props ([#29](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/29)) ([ed0be5e](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/ed0be5e87e964ed57cc99b40ff55fe35a2f518b2)) + +# [3.3.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.2.2...v3.3.0) (2022-10-02) + + +### Features + +* **Menu:** add nested menu groups + more props ([#30](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/30)) ([4233128](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/4233128c7ee8c6e5ab4ee74385c7b1b911d507a6)) + +## [3.2.2](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.2.1...v3.2.2) (2022-09-29) + + +### Bug Fixes + +* **modal:** extend props for modals ([#32](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/32)) ([1fbe55a](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/1fbe55aa544c9e84e2b3e2d6af9950db2fe7546c)) + +## [3.2.1](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.2.0...v3.2.1) (2022-09-24) + + +### Bug Fixes + +* **modal:** update showModal types ([#27](https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues/27)) ([6996e54](https://github.com/SteamDeckHomebrew/decky-frontend-lib/commit/6996e5424f33467ef5bb93f47614058c127cb3ee)) + # [3.2.0](https://github.com/SteamDeckHomebrew/decky-frontend-lib/compare/v3.1.4...v3.2.0) (2022-09-20) diff --git a/LICENSE b/LICENSE index b4821e3..8d8c7cf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ - GNU LESSER GENERAL PUBLIC LICENSE (DECKY-FRONTEND-LIB v1) + GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. @@ -457,24 +457,6 @@ DAMAGES. END OF TERMS AND CONDITIONS -EXCEPTION NOTICE - -1. The exception is that you may use, copy, link, modify and distribute -under your own terms, binary object code versions of works based on the -Library. - -2. If you copy code from files distributed under the terms of the GNU -General Public Licence or the GNU Library General Public Licence into a -copy of this library, as this licence permits, the exception does not apply -to the code that you add in this way. To avoid misleading anyone as to the -status of such modified files, you must delete this exception notice from -such code and/or adjust the licensing conditions notice accordingly. - -3. If you write modifications of your own for this library, it is your -choice whether to permit this exception to apply to your modifications. If -you do not wish that, you must delete the exception notice from such code -and/or adjust the licensing conditions notice accordingly. - How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest diff --git a/package-lock.json b/package-lock.json index 4559925..7b8243b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "decky-frontend-lib", - "version": "3.2.0", + "version": "3.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "decky-frontend-lib", - "version": "3.2.0", - "license": "GPL-2.0-or-later", + "version": "3.6.0", + "license": "LGPL-2.1", "dependencies": { "minimist": "^1.2.6" }, @@ -28,6 +28,10 @@ "semantic-release": "^19.0.3", "shx": "^0.3.4", "ts-jest": "^27.1.4", + "typedoc": "^0.23.15", + "typedoc-plugin-markdown": "^3.13.6", + "typedoc-plugin-mdn-links": "^2.0.0", + "typedoc-plugin-missing-exports": "^1.0.0", "typescript": "^4.6.3" } }, @@ -7335,6 +7339,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -7624,6 +7634,12 @@ "node": ">=10" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -7667,9 +7683,9 @@ } }, "node_modules/marked": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.16.tgz", - "integrity": "sha512-wahonIQ5Jnyatt2fn8KqF/nIqZM8mh3oRu2+l5EANGMhu6RFjiSG52QNE2eWzFMI94HqYSgN184NurgNG6CztA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", + "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==", "dev": true, "bin": { "marked": "bin/marked.js" @@ -11706,6 +11722,17 @@ "node": ">=4" } }, + "node_modules/shiki": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.11.1.tgz", + "integrity": "sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "^6.0.0" + } + }, "node_modules/shx": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", @@ -12401,6 +12428,78 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typedoc": { + "version": "0.23.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.15.tgz", + "integrity": "sha512-x9Zu+tTnwxb9YdVr+zvX7LYzyBl1nieOr6lrSHbHsA22/RJK2m4Y525WIg5Mj4jWCmfL47v6f4hUzY7EIuwS5w==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.0.19", + "minimatch": "^5.1.0", + "shiki": "^0.11.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 14.14" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "3.13.6", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.13.6.tgz", + "integrity": "sha512-ISSc9v3BK7HkokxSBuJPttXox4tJ6hP0N9wfSIk0fmLN67+eqtAxbk97gs2nDiuha+RTO5eW9gdeAb+RPP0mgg==", + "dev": true, + "dependencies": { + "handlebars": "^4.7.7" + }, + "peerDependencies": { + "typedoc": ">=0.23.0" + } + }, + "node_modules/typedoc-plugin-mdn-links": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-2.0.0.tgz", + "integrity": "sha512-IGLuelXPOenGdmklr5DHgPPf/MfZj7aEYCxCMtPN8C1D0lA7w0YLahd0jhDDcOMU7zL1EPcM5pPnhZHltDhqGQ==", + "dev": true, + "peerDependencies": { + "typedoc": "0.22.x || 0.23.x" + } + }, + "node_modules/typedoc-plugin-missing-exports": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-1.0.0.tgz", + "integrity": "sha512-7s6znXnuAj1eD9KYPyzVzR1lBF5nwAY8IKccP5sdoO9crG4lpd16RoFpLsh2PccJM+I2NASpr0+/NMka6ThwVA==", + "dev": true, + "peerDependencies": { + "typedoc": "0.22.x || 0.23.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/typescript": { "version": "4.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", @@ -12514,6 +12613,18 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vscode-oniguruma": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", + "integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz", + "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", + "dev": true + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -18354,6 +18465,12 @@ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -18581,6 +18698,12 @@ "yallist": "^4.0.0" } }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -18612,9 +18735,9 @@ "dev": true }, "marked": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.16.tgz", - "integrity": "sha512-wahonIQ5Jnyatt2fn8KqF/nIqZM8mh3oRu2+l5EANGMhu6RFjiSG52QNE2eWzFMI94HqYSgN184NurgNG6CztA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", + "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==", "dev": true }, "marked-terminal": { @@ -21487,6 +21610,17 @@ "rechoir": "^0.6.2" } }, + "shiki": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.11.1.tgz", + "integrity": "sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==", + "dev": true, + "requires": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "^6.0.0" + } + }, "shx": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz", @@ -22008,6 +22142,61 @@ "is-typedarray": "^1.0.0" } }, + "typedoc": { + "version": "0.23.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.15.tgz", + "integrity": "sha512-x9Zu+tTnwxb9YdVr+zvX7LYzyBl1nieOr6lrSHbHsA22/RJK2m4Y525WIg5Mj4jWCmfL47v6f4hUzY7EIuwS5w==", + "dev": true, + "requires": { + "lunr": "^2.3.9", + "marked": "^4.0.19", + "minimatch": "^5.1.0", + "shiki": "^0.11.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "typedoc-plugin-markdown": { + "version": "3.13.6", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.13.6.tgz", + "integrity": "sha512-ISSc9v3BK7HkokxSBuJPttXox4tJ6hP0N9wfSIk0fmLN67+eqtAxbk97gs2nDiuha+RTO5eW9gdeAb+RPP0mgg==", + "dev": true, + "requires": { + "handlebars": "^4.7.7" + } + }, + "typedoc-plugin-mdn-links": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-2.0.0.tgz", + "integrity": "sha512-IGLuelXPOenGdmklr5DHgPPf/MfZj7aEYCxCMtPN8C1D0lA7w0YLahd0jhDDcOMU7zL1EPcM5pPnhZHltDhqGQ==", + "dev": true, + "requires": {} + }, + "typedoc-plugin-missing-exports": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-1.0.0.tgz", + "integrity": "sha512-7s6znXnuAj1eD9KYPyzVzR1lBF5nwAY8IKccP5sdoO9crG4lpd16RoFpLsh2PccJM+I2NASpr0+/NMka6ThwVA==", + "dev": true, + "requires": {} + }, "typescript": { "version": "4.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", @@ -22098,6 +22287,18 @@ "spdx-expression-parse": "^3.0.0" } }, + "vscode-oniguruma": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz", + "integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==", + "dev": true + }, + "vscode-textmate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz", + "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", + "dev": true + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/package.json b/package.json index 71a11be..13a6f71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-frontend-lib", - "version": "3.2.0", + "version": "3.6.0", "description": "A library for building decky plugins", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -9,6 +9,7 @@ "scripts": { "build": "shx rm -rf dist && tsc -b", "dev": "tsc -b -w", + "docs": "typedoc --tsconfig ./tsconfig.json src/**/*", "prepack": "npm run build", "test": "echo 'No tests for now!'", "prepare": "husky install", @@ -30,7 +31,7 @@ "components" ], "author": "Jonas Dellinger ", - "license": "LGPL-2.1-with-decky-exceptions", + "license": "LGPL-2.1", "bugs": { "url": "https://github.com/SteamDeckHomebrew/decky-frontend-lib/issues" }, @@ -57,6 +58,10 @@ "semantic-release": "^19.0.3", "shx": "^0.3.4", "ts-jest": "^27.1.4", + "typedoc": "^0.23.15", + "typedoc-plugin-markdown": "^3.13.6", + "typedoc-plugin-mdn-links": "^2.0.0", + "typedoc-plugin-missing-exports": "^1.0.0", "typescript": "^4.6.3" }, "pnpm": { diff --git a/src/custom-hooks/index.ts b/src/custom-hooks/index.ts new file mode 100644 index 0000000..56ae7bf --- /dev/null +++ b/src/custom-hooks/index.ts @@ -0,0 +1 @@ +export * from './usequickaccessvisible'; diff --git a/src/custom-hooks/usequickaccessvisible.tsx b/src/custom-hooks/usequickaccessvisible.tsx new file mode 100644 index 0000000..e66de38 --- /dev/null +++ b/src/custom-hooks/usequickaccessvisible.tsx @@ -0,0 +1,67 @@ +import { useState, useEffect } from 'react'; + +declare global { + var FocusNavController: any; +} + +/** + * Returns state indicating the visibility of quick access menu. + * + * @remarks + * During development it is possible to open the quick access menu without giving it + * focus in some cases. In such cases, the quick access menu state is invisible. + * + * This seems to be impossible to replicate when running the deck normally. Even in + * the edge cases it always seems to have a focus. + * + * @returns `true` if quick access menu is visible (focused) and `false` otherwise. + * + * @example + * import { VFC, useEffect } from "react"; + * import { useQuickAccessVisible } from "decky-frontend-lib"; + * + * export const PluginPanelView: VFC<{}> = ({ }) => { + * const isVisible = useQuickAccessVisible(); + * + * useEffect(() => { + * if (!isVisible) { + * return; + * } + * + * const interval = setInterval(() => console.log("Hello world!"), 1000); + * return () => { + * clearInterval(interval); + * } + * }, [isVisible]) + * + * return ( + *
+ * {isVisible ? "VISIBLE" : "INVISIBLE"} + *
+ * ); + * }; + */ +export function useQuickAccessVisible(): boolean { + // Assuming that the component is rendered in QAM already, so true by default... + const [isVisible, setIsVisible] = useState(true); + + useEffect(() => { + const quickAccessWindow: Window | null = FocusNavController?.GetGamepadNavTreeByID("QuickAccess-NA")?.m_Root?.m_element?.ownerDocument.defaultView ?? null; + if (quickAccessWindow === null) { + console.error("Could not get window of QuickAccess menu!"); + return; + } + + const onBlur = () => setIsVisible(false); + const onFocus = () => setIsVisible(true); + + quickAccessWindow.addEventListener("blur", onBlur); + quickAccessWindow.addEventListener("focus", onFocus); + return () => { + quickAccessWindow.removeEventListener("blur", onBlur); + quickAccessWindow.removeEventListener("focus", onFocus); + }; + }, []); + + return isVisible; +} diff --git a/src/deck-components/Button.tsx b/src/deck-components/Button.tsx index 11cf75e..9fee9c6 100644 --- a/src/deck-components/Button.tsx +++ b/src/deck-components/Button.tsx @@ -1,22 +1,8 @@ -import { CSSProperties, FC, RefAttributes } from 'react'; +import { FC } from 'react'; +import { DialogButton, DialogButtonProps } from "./Dialog"; -import { CommonUIModule } from '../webpack'; -import { FooterLegendProps } from './FooterLegend'; - -export interface DialogButtonProps extends RefAttributes, FooterLegendProps { - label?: string; - style?: CSSProperties; - className?: string; - noFocusRing?: boolean; - description?: string; - layout?: 'below'; - onClick?(e: MouseEvent): void; - disabled?: boolean; - bottomSeparator?: boolean; +export interface ButtonProps extends DialogButtonProps { } -export const DialogButton = Object.values(CommonUIModule).find( - (mod: any) => - mod?.render?.toString()?.includes('Object.assign({type:"button"') && - mod?.render?.toString()?.includes('DialogButton'), -) as FC; \ No newline at end of file +// Button isn't exported, so call DialogButton to grab it +export const Button = (DialogButton as any)?.render({}).type as FC; diff --git a/src/deck-components/Dialog.tsx b/src/deck-components/Dialog.tsx new file mode 100644 index 0000000..dd88e5c --- /dev/null +++ b/src/deck-components/Dialog.tsx @@ -0,0 +1,56 @@ +import { CommonUIModule } from "../webpack"; +import { CSSProperties, FC, RefAttributes } from "react"; +import { FooterLegendProps } from './FooterLegend'; + +export interface DialogCommonProps extends RefAttributes { + style?: CSSProperties; + className?: string; +} + +export interface DialogButtonProps extends DialogCommonProps, FooterLegendProps { + noFocusRing?: boolean; + disabled?: boolean; + onClick?(e: MouseEvent): void; + onPointerDown?(e: PointerEvent): void; + onPointerUp?(e: PointerEvent): void; + onPointerCancel?(e: PointerEvent): void; + onMouseDown?(e: MouseEvent): void; + onMouseUp?(e: MouseEvent): void; + onTouchStart?(e: TouchEvent): void; + onTouchEnd?(e: TouchEvent): void; + onTouchCancel?(e: TouchEvent): void; + onSubmit?(e: SubmitEvent): void; +} + +const CommonDialogDivs = Object.values(CommonUIModule).filter((m: any) => typeof m === "object" && m?.render?.toString().includes('"div",Object.assign({},')); +const MappedDialogDivs = new Map(Object.values(CommonDialogDivs).map((m: any) => { + const renderedDiv = m.render({}); + // Take only the first class name segment as it identifies the element we want + return [renderedDiv.props.className.split(" ")[0], m] +})); + +export const DialogHeader = MappedDialogDivs.get("DialogHeader") as FC; +export const DialogSubHeader = MappedDialogDivs.get("DialogSubHeader") as FC; +export const DialogFooter = MappedDialogDivs.get("DialogFooter") as FC; +export const DialogLabel = MappedDialogDivs.get("DialogLabel") as FC; +export const DialogBodyText = MappedDialogDivs.get("DialogBodyText") as FC; +export const DialogBody = MappedDialogDivs.get("DialogBody") as FC; +export const DialogControlsSection = MappedDialogDivs.get("DialogControlsSection") as FC; +export const DialogControlsSectionHeader = MappedDialogDivs.get("DialogControlsSectionHeader") as FC; + +export const DialogButtonPrimary = Object.values(CommonUIModule).find( + (mod: any) => + mod?.render?.toString()?.includes('DialogButton') && + mod?.render?.toString()?.includes('Primary') +) as FC; + +export const DialogButtonSecondary = Object.values(CommonUIModule).find( + (mod: any) => + mod?.render?.toString()?.includes('Object.assign({type:"button"') && + mod?.render?.toString()?.includes('DialogButton') && + mod?.render?.toString()?.includes('Secondary') +) as FC; + +// This is the "main" button. The Primary can act as a submit button, +// therefore secondary is chosen (also for backwards comp. reasons) +export const DialogButton = DialogButtonSecondary; diff --git a/src/deck-components/Menu.tsx b/src/deck-components/Menu.tsx index 85c34fd..3f31692 100755 --- a/src/deck-components/Menu.tsx +++ b/src/deck-components/Menu.tsx @@ -15,6 +15,7 @@ export interface MenuProps { label: string; onCancel?(): void; cancelText?: string; + children?: ReactNode; } export const Menu: FC = findModuleChild((m) => { @@ -27,8 +28,26 @@ export const Menu: FC = findModuleChild((m) => { } }); +export interface MenuGroupProps { + label: string; + disabled?: boolean; + children?: ReactNode; +} + +export const MenuGroup: FC = findModuleChild((m) => { + if (typeof m !== 'object') return undefined; + + for (let prop in m) { + if (m[prop]?.prototype?.RenderSubMenu && m[prop]?.prototype?.ShowSubMenu) { + return m[prop]; + } + } +}); + export interface MenuItemProps { onSelected?(): void; + disabled?: boolean; + children?: ReactNode; } export const MenuItem: FC = findModuleChild((m) => { diff --git a/src/deck-components/Modal.tsx b/src/deck-components/Modal.tsx index ea0ff5f..12064c0 100755 --- a/src/deck-components/Modal.tsx +++ b/src/deck-components/Modal.tsx @@ -1,9 +1,33 @@ import { FC, ReactNode } from 'react'; - import { findModuleChild } from '../webpack'; -// TODO: there is another argument, figure out what it does -export const showModal: (children: ReactNode, parent?: EventTarget) => void = findModuleChild((m) => { +// All of the popout options + strTitle are related. Proper usage is not yet known... +export interface ShowModalProps { + browserContext?: unknown; // This is another Deck Object that is yet to be found + bForcePopOut?: boolean; + bHideActionIcons?: boolean; + bHideMainWindowForPopouts?: boolean; + bNeverPopOut?: boolean; + fnOnClose?: () => void; // Seems to be the same as "closeModal" callback, but only when the modal is a popout. Will no longer work after "Update" invocation! + popupHeight?: number; + popupWidth?: number; + promiseRenderComplete?: Promise; // Invoked once the render is complete. Currently, it seems to be used as image loading success/error callback... + strTitle?: string; +} + +export interface ShowModalResult { + // This method will not invoke any of the variations of "closeModal" callbacks! + Close: () => void; + + // This method will replace the modal element completely and will not update the callback chains, + // meaning that "closeModal" and etc. will not automatically close the modal anymore (also "fnOnClose" + // will not be even called upon close anymore)! You have to manually call the "Close" method when, for example, + // the "closeModal" is invoked in the newly updated modal: + // { console.log("ABOUT TO CLOSE"); showModalRes.Close(); }} /> + Update: (modal: ReactNode) => void; +} + +export const showModal: (modal: ReactNode, parent?: EventTarget, props?: ShowModalProps) => Promise = findModuleChild((m) => { if (typeof m !== 'object') return undefined; for (let prop in m) { if (typeof m[prop] === 'function' && m[prop].toString().includes('bHideMainWindowForPopouts:!0')) { @@ -13,6 +37,7 @@ export const showModal: (children: ReactNode, parent?: EventTarget) => void = fi }); export interface ModalRootProps { + children?: ReactNode; onCancel?(): void; closeModal?(): void; onOK?(): void; @@ -24,10 +49,18 @@ export interface ModalRootProps { bDisableBackgroundDismiss?: boolean; bHideCloseIcon?: boolean; bOKDisabled?: boolean; + bCancelDisabled?: boolean; } export interface ConfirmModalProps extends ModalRootProps { - onMiddleButton?(): void; + onMiddleButton?(): void; // setting this prop will enable the middle button + strTitle?: ReactNode; + strDescription?: ReactNode; + strOKButtonText?: ReactNode; + strCancelButtonText?: ReactNode; + strMiddleButtonText?: ReactNode; + bAlertDialog?: boolean; // This will open a modal with only OK button enabled + bMiddleDisabled?: boolean; } export const ConfirmModal = findModuleChild((m) => { @@ -46,4 +79,4 @@ export const ModalRoot = findModuleChild((m) => { return m[prop]; } } -}) as FC; \ No newline at end of file +}) as FC; diff --git a/src/deck-components/SidebarNavigation.tsx b/src/deck-components/SidebarNavigation.tsx index 3305b37..85228cc 100644 --- a/src/deck-components/SidebarNavigation.tsx +++ b/src/deck-components/SidebarNavigation.tsx @@ -2,17 +2,25 @@ import { ReactNode, VFC } from 'react'; import { Module, findModuleChild } from '../webpack'; -export interface SidebarNavigationPages { +export interface SidebarNavigationPage { title: string; - route: string; content: ReactNode; + icon?: ReactNode; + visible?: boolean; + hideTitle?: boolean; + identifier?: string; + route?: string; + link?: string; + padding?: "none" | "compact"; } export interface SidebarNavigationProps { title?: string; - pages: SidebarNavigationPages[]; + pages: SidebarNavigationPage[]; showTitle?: boolean; disableRouteReporting?: boolean; + page?: string; + onPageRequested?: (page: string) => void; } export const SidebarNavigation = findModuleChild((mod: Module) => { diff --git a/src/deck-components/Tabs.tsx b/src/deck-components/Tabs.tsx new file mode 100644 index 0000000..cc12f86 --- /dev/null +++ b/src/deck-components/Tabs.tsx @@ -0,0 +1,74 @@ +import { FC, ReactNode } from 'react'; +import { findModule } from '../webpack'; +import { FooterLegendProps } from './FooterLegend'; + +/** + * Individual tab objects for the Tabs component + * + * `id` ID of this tab, can be used with activeTab to auto-focus a given tab + * `title` Title shown in the header bar + * `renderTabAddon` Return a {@link ReactNode} to render it next to the tab title, i.e. the counts for each tab on the Media page + * `content` Content of the tab + * `footer` Sets up button handlers and labels + */ +export interface Tab { + id: string; + title: string; + renderTabAddon?: () => ReactNode; + content: ReactNode; + footer?: FooterLegendProps; +} + +/** + * Props for the {@link Tabs} + * + * `tabs` array of {@link Tab} + * `activeTab` tab currently active, needs to be one of the tabs {@link Tab.id}, must be set using a `useState` in the `onShowTab` handler + * `onShowTab` Called when the active tab should change, needs to set `activeTab`. See example. + * `autoFocusContents` Whether to automatically focus the tab contents or not. + * `footer` Sets up button handlers and labels + * + * @example + * const Component: FC = () => { + * const [currentTab, setCurrentTab] = useState("Tab1"); + * + * return ( + * { + * setCurrentTabRoute(tabID); + * }} + * tabs={[ + * { + * title: "Tab 1", + * content: , + * id: "Tab1", + * }, + * { + * title: "Tab 2", + * content: , + * id: "Tab2", + * }, + * ]} + * /> + * ); + * }; + */ +export interface TabsProps { + tabs: Tab[]; + activeTab: string; + onShowTab: (tab: string) => void; + autoFocusContents?: boolean; +} + +/** + * Tabs component as used in the library and media tabs. See {@link TabsProps} + */ +export const Tabs = Object.values(findModule((m) => { + if (typeof m !== 'object') return false; + for (let prop in m) { + if (m[prop]?.Unbleed) return true; + } + return false; +})).find((x: any) => x?.type?.toString()?.includes("((function(){")) as FC; \ No newline at end of file diff --git a/src/deck-components/index.ts b/src/deck-components/index.ts index 94cfadb..a8c9904 100755 --- a/src/deck-components/index.ts +++ b/src/deck-components/index.ts @@ -1,6 +1,7 @@ export * from './Button'; export * from './ButtonItem'; export * from './Carousel'; +export * from './Dialog'; export * from './Dropdown'; export * from './Field'; export * from './Focusable'; @@ -16,6 +17,7 @@ export * from './SliderField'; export * from './Spinner'; export * from './static-classes'; export * from './SteamSpinner'; +export * from './Tabs'; export * from './TextField'; export * from './Toggle'; export * from './ToggleField'; diff --git a/src/deck-hooks/index.ts b/src/deck-hooks/index.ts new file mode 100644 index 0000000..1009bae --- /dev/null +++ b/src/deck-hooks/index.ts @@ -0,0 +1 @@ +export * from './useParams' diff --git a/src/deck-hooks/useParams.ts b/src/deck-hooks/useParams.ts new file mode 100644 index 0000000..39f7fe3 --- /dev/null +++ b/src/deck-hooks/useParams.ts @@ -0,0 +1,15 @@ +import { ReactRouter } from "../webpack"; + +/** + * Get the current params from ReactRouter + * + * @returns an object with the current ReactRouter params + * + * @example + * import { useParams } from "decky-frontend-lib"; + * + * const { appid } = useParams<{ appid: string }>() + */ +export const useParams = Object.values(ReactRouter).find((val) => + /return (\w)\?\1\.params:{}/.test(`${val}`) +) as () => T \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ad884b4..0d36125 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ // export * from './deck-libs'; export * from './custom-components'; +export * from './custom-hooks'; export * from './deck-components'; +export * from './deck-hooks' export * from './plugin'; export * from './webpack'; export * from './utils'; diff --git a/src/plugin.tsx b/src/plugin.tsx index b07d378..de09949 100644 --- a/src/plugin.tsx +++ b/src/plugin.tsx @@ -6,6 +6,7 @@ export interface Plugin { icon: JSX.Element; content?: JSX.Element; onDismount?(): void; + alwaysRender?: boolean; } interface ServerResponseSuccess { @@ -37,8 +38,8 @@ export interface ToastData { icon?: ReactNode; className?: string; contentClassName?: string; - duration?: number - critical?: boolean + duration?: number; + critical?: boolean; } export interface Toaster { diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..057e6c2 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,5 @@ +{ + "githubPages": false, + "categorizeByGroup": false, + "excludeExternals": true +} \ No newline at end of file