From c7194e93308cbadc9795e2baa6111d0de6fb17da Mon Sep 17 00:00:00 2001 From: FoolRunning Date: Wed, 1 Mar 2023 12:02:11 -0500 Subject: [PATCH 1/5] #31 Added docking panel support and the beginnings of the user interface layout. --- cspell.json | 9 + package-lock.json | 463 +++++++++++++++++- package.json | 1 + src/renderer/App.css | 8 +- src/renderer/App.tsx | 201 +++----- .../Components/docking/ParanextDockLayout.css | 57 +++ .../Components/docking/ParanextDockLayout.tsx | 130 +++++ .../Components/docking/ParanextPanel.css | 3 + .../Components/docking/ParanextPanel.tsx | 12 + .../Components/docking/ParanextTabTitle.css | 17 + .../Components/docking/ParanextTabTitle.tsx | 25 + .../Components/testing/HelloPanel.tsx | 21 + .../Components/testing/TestButtonsPanel.tsx | 142 ++++++ src/shared/data/WebviewTypes.ts | 51 ++ 14 files changed, 972 insertions(+), 168 deletions(-) create mode 100644 cspell.json create mode 100644 src/renderer/Components/docking/ParanextDockLayout.css create mode 100644 src/renderer/Components/docking/ParanextDockLayout.tsx create mode 100644 src/renderer/Components/docking/ParanextPanel.css create mode 100644 src/renderer/Components/docking/ParanextPanel.tsx create mode 100644 src/renderer/Components/docking/ParanextTabTitle.css create mode 100644 src/renderer/Components/docking/ParanextTabTitle.tsx create mode 100644 src/renderer/Components/testing/HelloPanel.tsx create mode 100644 src/renderer/Components/testing/TestButtonsPanel.tsx create mode 100644 src/shared/data/WebviewTypes.ts diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000000..7bb006d871 --- /dev/null +++ b/cspell.json @@ -0,0 +1,9 @@ +{ + "version": "0.2", + "ignorePaths": ["package.json"], + "dictionaryDefinitions": [], + "dictionaries": [], + "words": [], + "ignoreWords": [], + "import": [] +} diff --git a/package-lock.json b/package-lock.json index ceca18ad68..d44daac3bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "electron-updater": "^5.2.3", "electron-window-state": "^5.0.3", "memoize-one": "^6.0.0", + "rc-dock": "^3.2.17", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.4.0", @@ -1939,12 +1940,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", - "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", - "dev": true, + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" }, "engines": { "node": ">=6.9.0" @@ -5615,6 +5615,11 @@ "dev": true, "optional": true }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6138,6 +6143,11 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "node_modules/clean-css": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", @@ -7411,6 +7421,11 @@ "integrity": "sha512-R305kwb5CcMDIpSHUnLyIAp7SrSPBx6F0VfQFB3M75xVMHhXJJIdePYgbPPh1o57vCHNu5QztokWUPsLjWzFqw==", "dev": true }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==" + }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -12770,8 +12785,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -14963,6 +14977,220 @@ "node": ">=0.10.0" } }, + "node_modules/rc-align": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.15.tgz", + "integrity": "sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "dom-align": "^1.7.0", + "rc-util": "^5.26.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dock": { + "version": "3.2.17", + "resolved": "https://registry.npmjs.org/rc-dock/-/rc-dock-3.2.17.tgz", + "integrity": "sha512-GhgIVsQ/h3lnamcXz0gO9JKtJ54uhfaDC7Sa6EgchvVyRfljArqxL3nMdWNCo47Y7SEbtptmqPNS1FP7xo2Rog==", + "dependencies": { + "classnames": "^2.3.1", + "lodash": "^4.17.21", + "rc-dropdown": "^3.2.2", + "rc-menu": "^9.3.0", + "rc-new-window": "^0.1.13", + "rc-tabs": "^11.10.5" + }, + "peerDependencies": { + "react": ">=15.0.0", + "react-dom": ">=15.0.0" + } + }, + "node_modules/rc-dropdown": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.6.2.tgz", + "integrity": "sha512-Wsw7GkVbUXADEs8FPL0v8gd+3mWQiydPFXBlr2imMScQaf8hh79pG9KrBc1DwK+nqHmYOpQfK2gn6jG2AQw9Pw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-trigger": "^5.0.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-menu": { + "version": "9.8.2", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.8.2.tgz", + "integrity": "sha512-EahOJVjLuEnJsThoPN+mGnVm431RzVzDLZWHRS/YnXTQULa7OsgdJa/Y7qXxc3Z5sz8mgT6xYtgpmBXLxrZFaQ==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.8", + "rc-trigger": "^5.1.2", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.6.3.tgz", + "integrity": "sha512-xFLkes3/7VL/J+ah9jJruEW/Akbx5F6jVa2wG5o/ApGKQKSOd5FR3rseHLL9+xtJg4PmCwo6/1tqhDO/T+jFHA==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-new-window": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/rc-new-window/-/rc-new-window-0.1.13.tgz", + "integrity": "sha512-KqANLQVfgNcfs+R4ntpzV5ELyqXMlAUimdSfFHapk2VwsoZX3y+BK2RjFBFb7q865yqAshP87g0PbIPqblKVTg==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "bowser": "^2.11.0", + "classnames": "2.x", + "lodash": "^4.17.20" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.8.tgz", + "integrity": "sha512-QJ0UItckWPQ37ZL1dMEBAdY1dhfTXFL9k6oTTcyydVwoUNMnMqCGqnRNA98axSr/OeDKqR6DVFyi8eA5RQI/uQ==", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.19.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.3.1.tgz", + "integrity": "sha512-iFUdt3NNhflbY3mwySv5CA1TC06zdJ+pfo0oc27xpf4PIOvfZwZGtD9Kz41wGYqC4SLio93RVAirSSpYlV/uYg==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.27.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "11.16.1", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.16.1.tgz", + "integrity": "sha512-bR7Dap23YyfzZQwtKomhiFEFzZuE7WaKWo+ypNRSGB9PDKSc6tM12VP8LWYkvmmQHthgwP0WRN8nFbSJWuqLYw==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.0.0", + "rc-menu": "~9.6.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs/node_modules/rc-dropdown": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.0.1.tgz", + "integrity": "sha512-OdpXuOcme1rm45cR0Jzgfl1otzmU4vuBVb+etXM8vcaULGokAKVpKlw8p6xzspG7jGd/XxShvq+N3VNEfk/l5g==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.6", + "rc-trigger": "^5.3.1", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-tabs/node_modules/rc-menu": { + "version": "9.6.4", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.6.4.tgz", + "integrity": "sha512-6DiNAjxjVIPLZXHffXxxcyE15d4isRL7iQ1ru4MqYDH2Cqc5bW96wZOdMydFtGLyDdnmEQ9jVvdCE9yliGvzkw==", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.0", + "rc-trigger": "^5.1.2", + "rc-util": "^5.12.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-trigger": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.3.4.tgz", + "integrity": "sha512-mQv+vas0TwKcjAO2izNPkqR4j86OemLRmvL2nOzdP9OWNWA1ivoTt5hzFqYNW9zACwmTezRiN8bttrC7cZzYSw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.6", + "rc-align": "^4.0.0", + "rc-motion": "^2.0.0", + "rc-util": "^5.19.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.28.0.tgz", + "integrity": "sha512-KYDjhGodswVj29v0TRciKTqRPgumIFvFDndbCD227pitQ+0Cei196rxk+OXb/blu6V8zdTRK5RjCJn+WmHLvBA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -15150,10 +15378,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/regenerator-transform": { "version": "0.15.0", @@ -15289,6 +15516,11 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -15854,6 +16086,11 @@ "node": ">=8" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -19375,12 +19612,11 @@ } }, "@babel/runtime": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", - "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", - "dev": true, + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" } }, "@babel/runtime-corejs3": { @@ -22240,6 +22476,11 @@ "dev": true, "optional": true }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -22636,6 +22877,11 @@ "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "dev": true }, + "classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + }, "clean-css": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", @@ -23584,6 +23830,11 @@ "integrity": "sha512-R305kwb5CcMDIpSHUnLyIAp7SrSPBx6F0VfQFB3M75xVMHhXJJIdePYgbPPh1o57vCHNu5QztokWUPsLjWzFqw==", "dev": true }, + "dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==" + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -27582,8 +27833,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.debounce": { "version": "4.0.8", @@ -29165,6 +29415,166 @@ } } }, + "rc-align": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-4.0.15.tgz", + "integrity": "sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "dom-align": "^1.7.0", + "rc-util": "^5.26.0", + "resize-observer-polyfill": "^1.5.1" + } + }, + "rc-dock": { + "version": "3.2.17", + "resolved": "https://registry.npmjs.org/rc-dock/-/rc-dock-3.2.17.tgz", + "integrity": "sha512-GhgIVsQ/h3lnamcXz0gO9JKtJ54uhfaDC7Sa6EgchvVyRfljArqxL3nMdWNCo47Y7SEbtptmqPNS1FP7xo2Rog==", + "requires": { + "classnames": "^2.3.1", + "lodash": "^4.17.21", + "rc-dropdown": "^3.2.2", + "rc-menu": "^9.3.0", + "rc-new-window": "^0.1.13", + "rc-tabs": "^11.10.5" + } + }, + "rc-dropdown": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-3.6.2.tgz", + "integrity": "sha512-Wsw7GkVbUXADEs8FPL0v8gd+3mWQiydPFXBlr2imMScQaf8hh79pG9KrBc1DwK+nqHmYOpQfK2gn6jG2AQw9Pw==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-trigger": "^5.0.4", + "rc-util": "^5.17.0" + } + }, + "rc-menu": { + "version": "9.8.2", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.8.2.tgz", + "integrity": "sha512-EahOJVjLuEnJsThoPN+mGnVm431RzVzDLZWHRS/YnXTQULa7OsgdJa/Y7qXxc3Z5sz8mgT6xYtgpmBXLxrZFaQ==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.8", + "rc-trigger": "^5.1.2", + "rc-util": "^5.27.0" + } + }, + "rc-motion": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.6.3.tgz", + "integrity": "sha512-xFLkes3/7VL/J+ah9jJruEW/Akbx5F6jVa2wG5o/ApGKQKSOd5FR3rseHLL9+xtJg4PmCwo6/1tqhDO/T+jFHA==", + "requires": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.21.0" + } + }, + "rc-new-window": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/rc-new-window/-/rc-new-window-0.1.13.tgz", + "integrity": "sha512-KqANLQVfgNcfs+R4ntpzV5ELyqXMlAUimdSfFHapk2VwsoZX3y+BK2RjFBFb7q865yqAshP87g0PbIPqblKVTg==", + "requires": { + "@babel/runtime": "^7.10.1", + "bowser": "^2.11.0", + "classnames": "2.x", + "lodash": "^4.17.20" + } + }, + "rc-overflow": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.2.8.tgz", + "integrity": "sha512-QJ0UItckWPQ37ZL1dMEBAdY1dhfTXFL9k6oTTcyydVwoUNMnMqCGqnRNA98axSr/OeDKqR6DVFyi8eA5RQI/uQ==", + "requires": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.19.2" + } + }, + "rc-resize-observer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.3.1.tgz", + "integrity": "sha512-iFUdt3NNhflbY3mwySv5CA1TC06zdJ+pfo0oc27xpf4PIOvfZwZGtD9Kz41wGYqC4SLio93RVAirSSpYlV/uYg==", + "requires": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.27.0", + "resize-observer-polyfill": "^1.5.1" + } + }, + "rc-tabs": { + "version": "11.16.1", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-11.16.1.tgz", + "integrity": "sha512-bR7Dap23YyfzZQwtKomhiFEFzZuE7WaKWo+ypNRSGB9PDKSc6tM12VP8LWYkvmmQHthgwP0WRN8nFbSJWuqLYw==", + "requires": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.0.0", + "rc-menu": "~9.6.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.5.0" + }, + "dependencies": { + "rc-dropdown": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.0.1.tgz", + "integrity": "sha512-OdpXuOcme1rm45cR0Jzgfl1otzmU4vuBVb+etXM8vcaULGokAKVpKlw8p6xzspG7jGd/XxShvq+N3VNEfk/l5g==", + "requires": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.6", + "rc-trigger": "^5.3.1", + "rc-util": "^5.17.0" + } + }, + "rc-menu": { + "version": "9.6.4", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.6.4.tgz", + "integrity": "sha512-6DiNAjxjVIPLZXHffXxxcyE15d4isRL7iQ1ru4MqYDH2Cqc5bW96wZOdMydFtGLyDdnmEQ9jVvdCE9yliGvzkw==", + "requires": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.2.0", + "rc-trigger": "^5.1.2", + "rc-util": "^5.12.0", + "shallowequal": "^1.1.0" + } + } + } + }, + "rc-trigger": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-5.3.4.tgz", + "integrity": "sha512-mQv+vas0TwKcjAO2izNPkqR4j86OemLRmvL2nOzdP9OWNWA1ivoTt5hzFqYNW9zACwmTezRiN8bttrC7cZzYSw==", + "requires": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.6", + "rc-align": "^4.0.0", + "rc-motion": "^2.0.0", + "rc-util": "^5.19.2" + } + }, + "rc-util": { + "version": "5.28.0", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.28.0.tgz", + "integrity": "sha512-KYDjhGodswVj29v0TRciKTqRPgumIFvFDndbCD227pitQ+0Cei196rxk+OXb/blu6V8zdTRK5RjCJn+WmHLvBA==", + "requires": { + "@babel/runtime": "^7.18.3", + "react-is": "^16.12.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -29311,10 +29721,9 @@ } }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regenerator-transform": { "version": "0.15.0", @@ -29422,6 +29831,11 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -29846,6 +30260,11 @@ "kind-of": "^6.0.2" } }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index ead9753ce9..5d2899c136 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "electron-updater": "^5.2.3", "electron-window-state": "^5.0.3", "memoize-one": "^6.0.0", + "rc-dock": "^3.2.17", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.4.0", diff --git a/src/renderer/App.css b/src/renderer/App.css index 5fd7095dec..98d1ded9cd 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -6,17 +6,13 @@ body { position: relative; color: white; height: 100vh; - background: linear-gradient( - 200.96deg, - #b8d432 -29.09%, - #5f7333 51.77%, - #47314e 129.35% - ); + background: #8c8c8c; font-family: sans-serif; overflow-y: hidden; display: flex; justify-content: center; align-items: center; + margin: 0; } button { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index ef9113e579..90e46c6775 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,152 +1,73 @@ -import { useCallback, useState } from 'react'; import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; -import * as NetworkService from '@shared/services/NetworkService'; -import icon from '@assets/icon.png'; import './App.css'; -import papi from '@shared/services/papi'; -import { getErrorMessage } from '@shared/util/Util'; -import usePromise from '@renderer/hooks/usePromise'; - -const getVar: (envVar: string) => Promise = - NetworkService.createRequestFunction('electronAPI.env.getVar'); - -const testBase: () => Promise = NetworkService.createRequestFunction( - 'electronAPI.env.test', -); - -const test = async () => { - /* const start = performance.now(); */ - const result = await testBase(); - /* console.log(`Test took ${performance.now() - start} ms`); */ - return result; -}; - -const addOne = async (message: number) => - papi.commands.sendCommand<[number], number>('addOne', message); - -const echo = async (message: string) => - papi.commands.sendCommand<[string], string>('echo', message); - -const throwError = async (message: string) => - papi.commands.sendCommand<[string], string>('throwError', message); - -const executeMany = async (fn: () => Promise) => { - const numRequests = 10000; - const requests = new Array>(numRequests); - const requestTime = new Array(numRequests); - const start = performance.now(); - for (let i = 0; i < numRequests; i++) { - requestTime[i] = performance.now(); - requests[i] = fn() - .then((response) => { - requestTime[i] = performance.now() - requestTime[i]; - return response; - }) - .catch((err) => console.error(err)); - } - - try { - const responses = await Promise.all(requests); - const finish = performance.now(); - - const avgResponseTime = - requestTime.reduce((sum, time) => sum + time, 0) / numRequests; - const maxTime = requestTime.reduce((max, time) => Math.max(max, time), 0); - const minTime = requestTime.reduce( - (min, time) => Math.min(min, time), - Number.MAX_VALUE, - ); - console.log( - `Of ${numRequests} requests:\n\tAvg response time: ${avgResponseTime} ms\n\tMax response time: ${maxTime} ms\n\tMin response time: ${minTime}\n\tTotal time: ${ - finish - start - }\n\tResponse times:`, - requestTime, - ); - console.log(responses[responses.length - 1]); - } catch (e) { - console.error(e); - } -}; +import { DockMode, TabData } from 'rc-dock'; +import ParanextDockLayout from './Components/docking/ParanextDockLayout'; const Hello = () => { - const [promiseReturn, setPromiseReturn] = useState(''); + // This object is necessary for Typescript to not complain, but it isn't actually needed + // to be part of the tab saved data. + const tab: TabData = { + content: <> , + title: <> , + }; - const [NODE_ENV] = usePromise( - useCallback(() => getVar('NODE_ENV'), []), - 'retrieving', - ); - - const runPromise = useCallback( - async (asyncFn: () => Promise) => { - try { - const result = await asyncFn(); - console.log(result); - setPromiseReturn(JSON.stringify(result)); - return result; - } catch (e) { - console.error(e); - setPromiseReturn(`Error: ${getErrorMessage(e)}`); - return undefined; - } + // NOTE: This structure represents what might be saved in a saved layout and + // thus looks different than a normal rc-dock layout. This is also why it's not + // typed to rc-dock.LayoutData. + const defaultLayout = { + dockbox: { + mode: 'horizontal' as DockMode, + children: [ + { + mode: 'vertical' as DockMode, + size: 200, + children: [ + { + tabs: [ + { + ...tab, + type: 'tab', + data: '{"title":"Bla","content":"Random content!"}', + minWidth: 150, + minHeight: 150, + }, + ], + }, + { + tabs: [ + { + ...tab, + type: 'tab', + data: '{"title":"two","content":"Content for tab two"}', + }, + { + ...tab, + type: 'tab', + data: '{"title":"one","content":"Content for tab one"}', + }, + ], + }, + ], + }, + { + tabs: [ + { + ...tab, + type: 'hello', + }, + { + ...tab, + type: 'buttons', + }, + ], + }, + ], }, - [setPromiseReturn], - ); + }; return (
-
- icon -
-
-

Paranext

-
-
- - - - -
-
-
NODE_ENV: {NODE_ENV}
-
{promiseReturn}
-
+
); }; diff --git a/src/renderer/Components/docking/ParanextDockLayout.css b/src/renderer/Components/docking/ParanextDockLayout.css new file mode 100644 index 0000000000..4c2092ee22 --- /dev/null +++ b/src/renderer/Components/docking/ParanextDockLayout.css @@ -0,0 +1,57 @@ +.dock-panel.dock-style-paranext { + border: 0; +} + +.dock-panel.dock-style-paranext .dock-tab { + margin-right: 0; + border: 1px solid #5c5c5c; + background: #bfbfbf; + color: #191919; +} + +.dock-panel.dock-style-paranext .dock-tab.dock-tab-active { + background: #a6c9ff; + color: #191919; +} + +.dock-panel.dock-style-paranext .dock-bar { + background: #8c8c8c; + border-bottom: 1px solid #5c5c5c; + padding: 0; +} + +.dock-panel.dock-style-paranext .dock-tab-hit-area { + /* cover the border area */ + left: -1px; + right: -1px; +} + +/* tab buttons take all the space , and only apply it when it's not in float panel */ +.dock-layout > :not(.dock-fbox) .dock-panel.dock-style-paranext .dock-nav-list { + flex-grow: 1; +} + +.dock-layout > :not(.dock-fbox) .dock-panel.dock-style-paranext .dock-tab { + flex: 1 0 auto; +} + +/* global dock layout styles */ +.dock-layout > .dock-drop-indicator { + border: solid 1px #a6c9ff; + /* box-shadow: inset 0 0 10px #a6c9ff; */ + background: #a6c9ff; +} + +.dock-nav-more { + padding: 0; +} + +.dock-nav-operations { + display: none; +} + +.dock-content-holder { + border-bottom: 1px solid #5c5c5c; + border-left: 1px solid #5c5c5c; + border-right: 1px solid #5c5c5c; +} diff --git a/src/renderer/Components/docking/ParanextDockLayout.tsx b/src/renderer/Components/docking/ParanextDockLayout.tsx new file mode 100644 index 0000000000..143f1768b1 --- /dev/null +++ b/src/renderer/Components/docking/ParanextDockLayout.tsx @@ -0,0 +1,130 @@ +import 'rc-dock/dist/rc-dock.css'; +import './ParanextDockLayout.css'; +import { newGuid } from '@shared/util/Util'; +import { SavedTabInfo, TabCreator } from '@shared/data/WebviewTypes'; +import DockLayout, { LayoutData, TabBase } from 'rc-dock'; +import ParanextPanel from './ParanextPanel'; +import ParanextTabTitle from './ParanextTabTitle'; +// TODO: Remove these testing panels when we can create extensions for them +import HelloPanel from '../testing/HelloPanel'; +import TestButtonsPanel from '../testing/TestButtonsPanel'; + +// NOTE: 'card' is a built-in style. We can likely remove it when we +// create a full theme for Paranext. +const TAB_GROUPS = 'card paranext'; + +// TODO: Move the following methods to Webview. They should eventually come from extensions. +const createHello = () => { + return { + type: 'hello', + title: 'Hello', + content: , + minWidth: 230, + minHeight: 230, + }; +}; + +const createButtons = () => { + return { + type: 'buttons', + title: 'Test Buttons', + content: , + }; +}; + +const createTab = (tabInfo: SavedTabInfo) => { + const data = tabInfo.data ? JSON.parse(tabInfo.data) : {}; + + const title = data.title ? data.title : 'Unknown'; + const content = data.content ? data.content : 'Unknown'; + return { + type: 'tab', + title: `Tab ${title}`, + content, + }; +}; + +// TODO: Build this mapping from extensions so extensions can create their own panels +const tabTypeCreationMap = new Map([ + ['hello', createHello], + ['buttons', createButtons], + ['tab', createTab], +]); + +const ParanextDockLayout = ({ + startingLayout, +}: { + startingLayout: LayoutData; +}) => { + const groups = { + TAB_STYLE: { + // the css class for this would be dock-panel-custom + // this is a custom panel style defined in panel-style.html + floatable: true, + newWindow: true, + maximizable: true, + animated: false, + moreIcon: undefined, + panelExtra: () => <> , // Get rid of buttons on tab panel (we only want buttons on each tab) + }, + }; + + const createErrorTab = (errorMessage: string) => { + return { + id: `error${newGuid()}`, + title: , + content: ( + +
+ Content could not be loaded. Please make sure you have the correct + extension loaded. +
+
Message: {errorMessage}
+
+ ), + closable: true, + group: TAB_GROUPS, + minWidth: 150, + minHeight: 150, + }; + }; + + const loadTab = (savedTabInfo: TabBase) => { + let { id } = savedTabInfo; + const tabInfo = savedTabInfo as SavedTabInfo; + if (!tabInfo.type) return createErrorTab('Tab is missing a defined type'); + + const tabCreator = tabTypeCreationMap.get(tabInfo.type); + if (!tabCreator) + return createErrorTab(`No handler for the tab type '${tabInfo.type}'`); + + // Call the creation method to let the extension method create the tab + const newTabData = tabCreator(tabInfo); + + if (!id) id = newGuid(); + + // Translate the data from the extension to be in the form needed by rc-dock + return { + id, + title: , + content: {newTabData.content}, + cached: newTabData.cached, + minWidth: newTabData.minWidth, + minHeight: newTabData.minHeight, + group: TAB_GROUPS, + closable: true, + }; + }; + + return ( + + ); +}; + +export default ParanextDockLayout; diff --git a/src/renderer/Components/docking/ParanextPanel.css b/src/renderer/Components/docking/ParanextPanel.css new file mode 100644 index 0000000000..d568be287d --- /dev/null +++ b/src/renderer/Components/docking/ParanextPanel.css @@ -0,0 +1,3 @@ +.paranextPanel { + margin: 0.5em; +} diff --git a/src/renderer/Components/docking/ParanextPanel.tsx b/src/renderer/Components/docking/ParanextPanel.tsx new file mode 100644 index 0000000000..9cad7ac47e --- /dev/null +++ b/src/renderer/Components/docking/ParanextPanel.tsx @@ -0,0 +1,12 @@ +import { ReactNode } from 'react'; +import './ParanextPanel.css'; + +/** + * Used for possible styling on every panel in Paranext + * @param children The children of the panel (usually supplied from an extension) + */ +const ParanextPanel = ({ children }: { children: ReactNode }) => { + return
{children}
; +}; + +export default ParanextPanel; diff --git a/src/renderer/Components/docking/ParanextTabTitle.css b/src/renderer/Components/docking/ParanextTabTitle.css new file mode 100644 index 0000000000..9a9885df8e --- /dev/null +++ b/src/renderer/Components/docking/ParanextTabTitle.css @@ -0,0 +1,17 @@ +.title { + align-content: start; + display: flex; +} + +.tabMenuButton { + margin: 0; + margin-right: 5px; + padding: 0; + border: 0; + background: transparent; + background-image: url(../../../../assets/icon.png); + background-size: 100%; + width: 16px; + height: 16px; + vertical-align: middle; +} diff --git a/src/renderer/Components/docking/ParanextTabTitle.tsx b/src/renderer/Components/docking/ParanextTabTitle.tsx new file mode 100644 index 0000000000..cb1c9abee7 --- /dev/null +++ b/src/renderer/Components/docking/ParanextTabTitle.tsx @@ -0,0 +1,25 @@ +import './ParanextTabTitle.css'; + +/** + * Custom tab title for all tabs in Paranext + * @param text The text to show on the tab title + */ +const ParanextTabTitle = ({ text }: { text: string }) => { + const toggleDropdown = () => { + console.log('Pretend a menu was shown!'); + }; + + return ( +
+
+ ); +}; + +export default ParanextTabTitle; diff --git a/src/renderer/Components/testing/HelloPanel.tsx b/src/renderer/Components/testing/HelloPanel.tsx new file mode 100644 index 0000000000..a1200a8d22 --- /dev/null +++ b/src/renderer/Components/testing/HelloPanel.tsx @@ -0,0 +1,21 @@ +import icon from '@assets/icon.png'; + +const HelloPanel = () => { + return ( +
+
+ icon +
+
+

Paranext

+
+
+ ); +}; + +export default HelloPanel; diff --git a/src/renderer/Components/testing/TestButtonsPanel.tsx b/src/renderer/Components/testing/TestButtonsPanel.tsx new file mode 100644 index 0000000000..b92162856d --- /dev/null +++ b/src/renderer/Components/testing/TestButtonsPanel.tsx @@ -0,0 +1,142 @@ +import { useCallback, useState } from 'react'; +import usePromise from '@renderer/hooks/usePromise'; +import papi from '@shared/services/papi'; +import * as NetworkService from '@shared/services/NetworkService'; +import { getErrorMessage } from '@shared/util/Util'; +import ParanextPanel from '../docking/ParanextPanel'; + +const getVar: (envVar: string) => Promise = + NetworkService.createRequestFunction('electronAPI.env.getVar'); + +const testBase: () => Promise = NetworkService.createRequestFunction( + 'electronAPI.env.test', +); + +const test = async () => { + /* const start = performance.now(); */ + const result = await testBase(); + /* console.log(`Test took ${performance.now() - start} ms`); */ + return result; +}; + +const addOne = async (message: number) => + papi.commands.sendCommand<[number], number>('addOne', message); + +const echo = async (message: string) => + papi.commands.sendCommand<[string], string>('echo', message); + +const throwError = async (message: string) => + papi.commands.sendCommand<[string], string>('throwError', message); + +const executeMany = async (fn: () => Promise) => { + const numRequests = 10000; + const requests = new Array>(numRequests); + const requestTime = new Array(numRequests); + const start = performance.now(); + for (let i = 0; i < numRequests; i++) { + requestTime[i] = performance.now(); + requests[i] = fn() + .then((response) => { + requestTime[i] = performance.now() - requestTime[i]; + return response; + }) + .catch((err) => console.error(err)); + } + + try { + const responses = await Promise.all(requests); + const finish = performance.now(); + + const avgResponseTime = + requestTime.reduce((sum, time) => sum + time, 0) / numRequests; + const maxTime = requestTime.reduce((max, time) => Math.max(max, time), 0); + const minTime = requestTime.reduce( + (min, time) => Math.min(min, time), + Number.MAX_VALUE, + ); + console.log( + `Of ${numRequests} requests:\n\tAvg response time: ${avgResponseTime} ms\n\tMax response time: ${maxTime} ms\n\tMin response time: ${minTime}\n\tTotal time: ${ + finish - start + }\n\tResponse times:`, + requestTime, + ); + console.log(responses[responses.length - 1]); + } catch (e) { + console.error(e); + } +}; + +const TestButtonsPanel = () => { + const [promiseReturn, setPromiseReturn] = useState(''); + + const [NODE_ENV] = usePromise( + useCallback(() => getVar('NODE_ENV'), []), + 'retrieving', + ); + + const runPromise = useCallback( + async (asyncFn: () => Promise) => { + try { + const result = await asyncFn(); + console.log(result); + setPromiseReturn(JSON.stringify(result)); + return result; + } catch (e) { + console.error(e); + setPromiseReturn(`Error: ${getErrorMessage(e)}`); + return undefined; + } + }, + [setPromiseReturn], + ); + + return ( + + + + + +
NODE_ENV: {NODE_ENV}
+
{promiseReturn}
+
+ ); +}; + +export default TestButtonsPanel; diff --git a/src/shared/data/WebviewTypes.ts b/src/shared/data/WebviewTypes.ts new file mode 100644 index 0000000000..3c000e43c4 --- /dev/null +++ b/src/shared/data/WebviewTypes.ts @@ -0,0 +1,51 @@ +/** + * Information used to recreate a tab + */ +export type SavedTabInfo = { + /** + * The underlying tab type. Used to determine which extension owns it. + */ + type: string; + /** + * Data needed to recreate the tab during load + */ + data?: string; +}; + +/** + * Information needed to create a tab inside of Paranext + */ +export type TabInfo = { + /** + * The underlying tab type. Used to determine which extension owns it. + */ + type: string; + /** + * Text to show on the title bar of the tab + */ + title: string; + /** + * Content to show inside the tab + */ + content: React.ReactNode; + /** + * (optional) Whether the tab can be closed by the user (default is true) + */ + closable?: boolean; + /** + * (optional) Minimum width that the tab can become + */ + minWidth?: number; + /** + * (optional) Minimum height that the tab can become + */ + minHeight?: number; + /** + * - when value is true: content will always reuse the react component thus allows the component to keep its internal state + * - when value is false: content will be destroyed when it's not visible + * - when value is undefined: content is rendered normally as react component + */ + cached?: boolean; +}; + +export type TabCreator = (tabData: SavedTabInfo) => TabInfo; From 56f321d590857355c29d81f415785d8484bf6a7e Mon Sep 17 00:00:00 2001 From: FoolRunning Date: Wed, 1 Mar 2023 12:09:16 -0500 Subject: [PATCH 2/5] Fixed merge (hopefully) --- .../Components/docking/ParanextTabTitle.tsx | 3 ++- .../Components/testing/TestButtonsPanel.tsx | 23 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/renderer/Components/docking/ParanextTabTitle.tsx b/src/renderer/Components/docking/ParanextTabTitle.tsx index cb1c9abee7..4d4407fa1c 100644 --- a/src/renderer/Components/docking/ParanextTabTitle.tsx +++ b/src/renderer/Components/docking/ParanextTabTitle.tsx @@ -1,4 +1,5 @@ import './ParanextTabTitle.css'; +import logger from '@shared/util/logger'; /** * Custom tab title for all tabs in Paranext @@ -6,7 +7,7 @@ import './ParanextTabTitle.css'; */ const ParanextTabTitle = ({ text }: { text: string }) => { const toggleDropdown = () => { - console.log('Pretend a menu was shown!'); + logger.log('Pretend a menu was shown!'); }; return ( diff --git a/src/renderer/Components/testing/TestButtonsPanel.tsx b/src/renderer/Components/testing/TestButtonsPanel.tsx index b92162856d..a8821df444 100644 --- a/src/renderer/Components/testing/TestButtonsPanel.tsx +++ b/src/renderer/Components/testing/TestButtonsPanel.tsx @@ -3,6 +3,7 @@ import usePromise from '@renderer/hooks/usePromise'; import papi from '@shared/services/papi'; import * as NetworkService from '@shared/services/NetworkService'; import { getErrorMessage } from '@shared/util/Util'; +import logger from '@shared/util/logger'; import ParanextPanel from '../docking/ParanextPanel'; const getVar: (envVar: string) => Promise = @@ -15,7 +16,7 @@ const testBase: () => Promise = NetworkService.createRequestFunction( const test = async () => { /* const start = performance.now(); */ const result = await testBase(); - /* console.log(`Test took ${performance.now() - start} ms`); */ + /* logger.log(`Test took ${performance.now() - start} ms`); */ return result; }; @@ -40,7 +41,7 @@ const executeMany = async (fn: () => Promise) => { requestTime[i] = performance.now() - requestTime[i]; return response; }) - .catch((err) => console.error(err)); + .catch((err) => logger.error(err)); } try { @@ -54,15 +55,15 @@ const executeMany = async (fn: () => Promise) => { (min, time) => Math.min(min, time), Number.MAX_VALUE, ); - console.log( + logger.log( `Of ${numRequests} requests:\n\tAvg response time: ${avgResponseTime} ms\n\tMax response time: ${maxTime} ms\n\tMin response time: ${minTime}\n\tTotal time: ${ finish - start }\n\tResponse times:`, requestTime, ); - console.log(responses[responses.length - 1]); + logger.log(responses[responses.length - 1]); } catch (e) { - console.error(e); + logger.error(e); } }; @@ -78,11 +79,11 @@ const TestButtonsPanel = () => { async (asyncFn: () => Promise) => { try { const result = await asyncFn(); - console.log(result); + logger.log(result); setPromiseReturn(JSON.stringify(result)); return result; } catch (e) { - console.error(e); + logger.error(e); setPromiseReturn(`Error: ${getErrorMessage(e)}`); return undefined; } @@ -91,13 +92,13 @@ const TestButtonsPanel = () => { ); return ( - + <>
NODE_ENV: {NODE_ENV}
{promiseReturn}
-
+ ); }; From b137730b34233da9ec071d4ed772e46d6a950497 Mon Sep 17 00:00:00 2001 From: FoolRunning Date: Wed, 1 Mar 2023 13:04:19 -0500 Subject: [PATCH 3/5] Fixed lint failure. --- src/renderer/Components/testing/TestButtonsPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/Components/testing/TestButtonsPanel.tsx b/src/renderer/Components/testing/TestButtonsPanel.tsx index a8821df444..e058a24ea5 100644 --- a/src/renderer/Components/testing/TestButtonsPanel.tsx +++ b/src/renderer/Components/testing/TestButtonsPanel.tsx @@ -4,7 +4,6 @@ import papi from '@shared/services/papi'; import * as NetworkService from '@shared/services/NetworkService'; import { getErrorMessage } from '@shared/util/Util'; import logger from '@shared/util/logger'; -import ParanextPanel from '../docking/ParanextPanel'; const getVar: (envVar: string) => Promise = NetworkService.createRequestFunction('electronAPI.env.getVar'); From cdae7c77c741f89943dbc38e4721e9a769a95180 Mon Sep 17 00:00:00 2001 From: FoolRunning Date: Wed, 1 Mar 2023 14:43:22 -0500 Subject: [PATCH 4/5] Fixed some display issues. Cleaned up some code. --- cspell.json | 9 ++++++-- .../Components/docking/ParanextDockLayout.css | 15 +++++++++---- .../Components/docking/ParanextDockLayout.tsx | 21 +++++++++---------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/cspell.json b/cspell.json index 7bb006d871..6fd78b0715 100644 --- a/cspell.json +++ b/cspell.json @@ -1,9 +1,14 @@ { "version": "0.2", - "ignorePaths": ["package.json"], + "ignorePaths": [ + "package.json" + ], "dictionaryDefinitions": [], "dictionaries": [], - "words": [], + "words": [ + "dockbox", + "maximizable" + ], "ignoreWords": [], "import": [] } diff --git a/src/renderer/Components/docking/ParanextDockLayout.css b/src/renderer/Components/docking/ParanextDockLayout.css index 4c2092ee22..f0dabdfd69 100644 --- a/src/renderer/Components/docking/ParanextDockLayout.css +++ b/src/renderer/Components/docking/ParanextDockLayout.css @@ -46,12 +46,19 @@ padding: 0; } -.dock-nav-operations { - display: none; -} - .dock-content-holder { border-bottom: 1px solid #5c5c5c; border-left: 1px solid #5c5c5c; border-right: 1px solid #5c5c5c; } + +/* Hide tab group overflow button space (button is hidden, but the space is still taken up). + TODO: Make this appear only when needed. */ +.dock-nav-operations { + display: none; +} + +/* Hide tab group buttons (maximize). Not sure why this is needed even when the group maximizable is set to false. */ +.dock-extra-content { + display: none; +} diff --git a/src/renderer/Components/docking/ParanextDockLayout.tsx b/src/renderer/Components/docking/ParanextDockLayout.tsx index 143f1768b1..1a64f6d662 100644 --- a/src/renderer/Components/docking/ParanextDockLayout.tsx +++ b/src/renderer/Components/docking/ParanextDockLayout.tsx @@ -2,7 +2,7 @@ import 'rc-dock/dist/rc-dock.css'; import './ParanextDockLayout.css'; import { newGuid } from '@shared/util/Util'; import { SavedTabInfo, TabCreator } from '@shared/data/WebviewTypes'; -import DockLayout, { LayoutData, TabBase } from 'rc-dock'; +import DockLayout, { LayoutData, TabBase, TabData, TabGroup } from 'rc-dock'; import ParanextPanel from './ParanextPanel'; import ParanextTabTitle from './ParanextTabTitle'; // TODO: Remove these testing panels when we can create extensions for them @@ -56,16 +56,15 @@ const ParanextDockLayout = ({ }: { startingLayout: LayoutData; }) => { - const groups = { + const groups: { + [key: string]: TabGroup; + } = { TAB_STYLE: { - // the css class for this would be dock-panel-custom - // this is a custom panel style defined in panel-style.html - floatable: true, - newWindow: true, - maximizable: true, - animated: false, - moreIcon: undefined, - panelExtra: () => <> , // Get rid of buttons on tab panel (we only want buttons on each tab) + maximizable: false, // Don't allow groups of tabs to be maximized + floatable: true, // Allow tabs to be floated + newWindow: true, // Allow floating windows to show in a native window + animated: false, // Don't animate tab transitions + panelExtra: undefined, // Get rid of buttons on tab group panel (we only want buttons on each tab) }, }; @@ -89,7 +88,7 @@ const ParanextDockLayout = ({ }; }; - const loadTab = (savedTabInfo: TabBase) => { + const loadTab = (savedTabInfo: TabBase): TabData => { let { id } = savedTabInfo; const tabInfo = savedTabInfo as SavedTabInfo; if (!tabInfo.type) return createErrorTab('Tab is missing a defined type'); From 881e4dad61693a5acf408c94f94565503ea38c95 Mon Sep 17 00:00:00 2001 From: FoolRunning Date: Mon, 6 Mar 2023 15:27:34 -0500 Subject: [PATCH 5/5] Changes for code review. Also reorganized some files and fixed some small problems I found. --- .vscode/settings.json | 14 +- cspell.json | 14 -- src/renderer/App.css | 19 --- src/renderer/App.tsx | 66 +-------- .../Components/docking/ParanextDockLayout.tsx | 129 ------------------ src/renderer/components/docking/ErrorTab.tsx | 28 ++++ .../docking/ParanextDockLayout.css | 20 +-- .../components/docking/ParanextDockLayout.tsx | 91 ++++++++++++ .../docking/ParanextPanel.css | 0 .../docking/ParanextPanel.tsx | 0 .../docking/ParanextTabTitle.css | 0 .../docking/ParanextTabTitle.tsx | 0 .../{Components => }/testing/HelloPanel.tsx | 13 +- src/renderer/testing/TestButtonsPanel.css | 18 +++ .../testing/TestButtonsPanel.tsx | 106 ++++++++------ src/renderer/testing/TestPanel.tsx | 26 ++++ src/renderer/testing/testLayout.tsx | 64 +++++++++ src/shared/data/WebviewTypes.ts | 19 +-- 18 files changed, 328 insertions(+), 299 deletions(-) delete mode 100644 cspell.json delete mode 100644 src/renderer/Components/docking/ParanextDockLayout.tsx create mode 100644 src/renderer/components/docking/ErrorTab.tsx rename src/renderer/{Components => components}/docking/ParanextDockLayout.css (59%) create mode 100644 src/renderer/components/docking/ParanextDockLayout.tsx rename src/renderer/{Components => components}/docking/ParanextPanel.css (100%) rename src/renderer/{Components => components}/docking/ParanextPanel.tsx (100%) rename src/renderer/{Components => components}/docking/ParanextTabTitle.css (100%) rename src/renderer/{Components => components}/docking/ParanextTabTitle.tsx (100%) rename src/renderer/{Components => }/testing/HelloPanel.tsx (61%) create mode 100644 src/renderer/testing/TestButtonsPanel.css rename src/renderer/{Components => }/testing/TestButtonsPanel.tsx (63%) create mode 100644 src/renderer/testing/TestPanel.tsx create mode 100644 src/renderer/testing/testLayout.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 43265ff411..22e9dec975 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,6 +28,16 @@ "*.{css,sass,scss}.d.ts": true }, + "cSpell.ignorePaths": [ + "package-lock.json", + "node_modules", + "vscode-extension", + ".git/objects", + ".vscode", + ".vscode-insiders", + "package.json" + ], + "cSpell.words": [ "asyncs", "endregion", @@ -37,6 +47,8 @@ "unsub", "unsubs", "unsubscriber", - "unsubscribers" + "unsubscribers", + "dockbox", + "maximizable" ], } diff --git a/cspell.json b/cspell.json deleted file mode 100644 index 6fd78b0715..0000000000 --- a/cspell.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "0.2", - "ignorePaths": [ - "package.json" - ], - "dictionaryDefinitions": [], - "dictionaries": [], - "words": [ - "dockbox", - "maximizable" - ], - "ignoreWords": [], - "import": [] -} diff --git a/src/renderer/App.css b/src/renderer/App.css index 98d1ded9cd..533213a572 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -15,25 +15,6 @@ body { margin: 0; } -button { - background-color: white; - padding: 10px 20px; - border-radius: 10px; - border: none; - appearance: none; - font-size: 1.3rem; - box-shadow: 0px 8px 28px -6px rgba(24, 39, 75, 0.12), - 0px 18px 88px -4px rgba(24, 39, 75, 0.14); - transition: all ease-in 0.1s; - cursor: pointer; - opacity: 0.9; -} - -button:hover { - transform: scale(1.05); - opacity: 1; -} - li { list-style: none; } diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 90e46c6775..408d62db5b 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,73 +1,11 @@ import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; import './App.css'; -import { DockMode, TabData } from 'rc-dock'; -import ParanextDockLayout from './Components/docking/ParanextDockLayout'; +import ParanextDockLayout from './components/docking/ParanextDockLayout'; const Hello = () => { - // This object is necessary for Typescript to not complain, but it isn't actually needed - // to be part of the tab saved data. - const tab: TabData = { - content: <> , - title: <> , - }; - - // NOTE: This structure represents what might be saved in a saved layout and - // thus looks different than a normal rc-dock layout. This is also why it's not - // typed to rc-dock.LayoutData. - const defaultLayout = { - dockbox: { - mode: 'horizontal' as DockMode, - children: [ - { - mode: 'vertical' as DockMode, - size: 200, - children: [ - { - tabs: [ - { - ...tab, - type: 'tab', - data: '{"title":"Bla","content":"Random content!"}', - minWidth: 150, - minHeight: 150, - }, - ], - }, - { - tabs: [ - { - ...tab, - type: 'tab', - data: '{"title":"two","content":"Content for tab two"}', - }, - { - ...tab, - type: 'tab', - data: '{"title":"one","content":"Content for tab one"}', - }, - ], - }, - ], - }, - { - tabs: [ - { - ...tab, - type: 'hello', - }, - { - ...tab, - type: 'buttons', - }, - ], - }, - ], - }, - }; - return (
- +
); }; diff --git a/src/renderer/Components/docking/ParanextDockLayout.tsx b/src/renderer/Components/docking/ParanextDockLayout.tsx deleted file mode 100644 index 1a64f6d662..0000000000 --- a/src/renderer/Components/docking/ParanextDockLayout.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import 'rc-dock/dist/rc-dock.css'; -import './ParanextDockLayout.css'; -import { newGuid } from '@shared/util/Util'; -import { SavedTabInfo, TabCreator } from '@shared/data/WebviewTypes'; -import DockLayout, { LayoutData, TabBase, TabData, TabGroup } from 'rc-dock'; -import ParanextPanel from './ParanextPanel'; -import ParanextTabTitle from './ParanextTabTitle'; -// TODO: Remove these testing panels when we can create extensions for them -import HelloPanel from '../testing/HelloPanel'; -import TestButtonsPanel from '../testing/TestButtonsPanel'; - -// NOTE: 'card' is a built-in style. We can likely remove it when we -// create a full theme for Paranext. -const TAB_GROUPS = 'card paranext'; - -// TODO: Move the following methods to Webview. They should eventually come from extensions. -const createHello = () => { - return { - type: 'hello', - title: 'Hello', - content: , - minWidth: 230, - minHeight: 230, - }; -}; - -const createButtons = () => { - return { - type: 'buttons', - title: 'Test Buttons', - content: , - }; -}; - -const createTab = (tabInfo: SavedTabInfo) => { - const data = tabInfo.data ? JSON.parse(tabInfo.data) : {}; - - const title = data.title ? data.title : 'Unknown'; - const content = data.content ? data.content : 'Unknown'; - return { - type: 'tab', - title: `Tab ${title}`, - content, - }; -}; - -// TODO: Build this mapping from extensions so extensions can create their own panels -const tabTypeCreationMap = new Map([ - ['hello', createHello], - ['buttons', createButtons], - ['tab', createTab], -]); - -const ParanextDockLayout = ({ - startingLayout, -}: { - startingLayout: LayoutData; -}) => { - const groups: { - [key: string]: TabGroup; - } = { - TAB_STYLE: { - maximizable: false, // Don't allow groups of tabs to be maximized - floatable: true, // Allow tabs to be floated - newWindow: true, // Allow floating windows to show in a native window - animated: false, // Don't animate tab transitions - panelExtra: undefined, // Get rid of buttons on tab group panel (we only want buttons on each tab) - }, - }; - - const createErrorTab = (errorMessage: string) => { - return { - id: `error${newGuid()}`, - title: , - content: ( - -
- Content could not be loaded. Please make sure you have the correct - extension loaded. -
-
Message: {errorMessage}
-
- ), - closable: true, - group: TAB_GROUPS, - minWidth: 150, - minHeight: 150, - }; - }; - - const loadTab = (savedTabInfo: TabBase): TabData => { - let { id } = savedTabInfo; - const tabInfo = savedTabInfo as SavedTabInfo; - if (!tabInfo.type) return createErrorTab('Tab is missing a defined type'); - - const tabCreator = tabTypeCreationMap.get(tabInfo.type); - if (!tabCreator) - return createErrorTab(`No handler for the tab type '${tabInfo.type}'`); - - // Call the creation method to let the extension method create the tab - const newTabData = tabCreator(tabInfo); - - if (!id) id = newGuid(); - - // Translate the data from the extension to be in the form needed by rc-dock - return { - id, - title: , - content: {newTabData.content}, - cached: newTabData.cached, - minWidth: newTabData.minWidth, - minHeight: newTabData.minHeight, - group: TAB_GROUPS, - closable: true, - }; - }; - - return ( - - ); -}; - -export default ParanextDockLayout; diff --git a/src/renderer/components/docking/ErrorTab.tsx b/src/renderer/components/docking/ErrorTab.tsx new file mode 100644 index 0000000000..c37983d957 --- /dev/null +++ b/src/renderer/components/docking/ErrorTab.tsx @@ -0,0 +1,28 @@ +import { TabInfo } from '@shared/data/WebviewTypes'; + +const ErrorTab = ({ errorMessage }: { errorMessage: string }) => { + return ( + <> +
+ Content could not be loaded. Please make sure you have the correct + extension loaded. +
+
Message: {errorMessage}
+ + ); +}; + +/** + * Creates a new error message tab with the specified error message + */ +const createErrorTab = (errorMessage: string): TabInfo => { + return { + type: 'error', + title: 'Error', + content: , + minWidth: 150, + minHeight: 150, + }; +}; + +export default createErrorTab; diff --git a/src/renderer/Components/docking/ParanextDockLayout.css b/src/renderer/components/docking/ParanextDockLayout.css similarity index 59% rename from src/renderer/Components/docking/ParanextDockLayout.css rename to src/renderer/components/docking/ParanextDockLayout.css index f0dabdfd69..6b36fab699 100644 --- a/src/renderer/Components/docking/ParanextDockLayout.css +++ b/src/renderer/components/docking/ParanextDockLayout.css @@ -26,12 +26,12 @@ right: -1px; } -/* tab buttons take all the space , and only apply it when it's not in float panel */ -.dock-layout > :not(.dock-fbox) .dock-panel.dock-style-paranext .dock-nav-list { +/* tabs take all the space */ +.dock-layout .dock-panel.dock-style-paranext .dock-nav-list { flex-grow: 1; } -.dock-layout > :not(.dock-fbox) .dock-panel.dock-style-paranext .dock-tab { +.dock-layout .dock-panel.dock-style-paranext .dock-tab { flex: 1 0 auto; } @@ -42,23 +42,13 @@ background: #a6c9ff; } -.dock-nav-more { - padding: 0; -} - .dock-content-holder { border-bottom: 1px solid #5c5c5c; border-left: 1px solid #5c5c5c; border-right: 1px solid #5c5c5c; } -/* Hide tab group overflow button space (button is hidden, but the space is still taken up). - TODO: Make this appear only when needed. */ -.dock-nav-operations { - display: none; -} - -/* Hide tab group buttons (maximize). Not sure why this is needed even when the group maximizable is set to false. */ -.dock-extra-content { +/* Hide tab group overflow button when the button is not needed */ +.dock-nav-operations-hidden { display: none; } diff --git a/src/renderer/components/docking/ParanextDockLayout.tsx b/src/renderer/components/docking/ParanextDockLayout.tsx new file mode 100644 index 0000000000..3480d6b518 --- /dev/null +++ b/src/renderer/components/docking/ParanextDockLayout.tsx @@ -0,0 +1,91 @@ +import 'rc-dock/dist/rc-dock.css'; +import './ParanextDockLayout.css'; +import { newGuid } from '@shared/util/Util'; +import { SavedTabInfo, TabCreator, TabInfo } from '@shared/data/WebviewTypes'; +import DockLayout, { LayoutData, TabBase, TabData, TabGroup } from 'rc-dock'; +import testLayout from '@renderer/testing/testLayout'; +import createHelloPanel from '@renderer/testing/HelloPanel'; +import createButtonsPanel from '@renderer/testing/TestButtonsPanel'; +import createTabPanel from '@renderer/testing/TestPanel'; +import createErrorTab from './ErrorTab'; +import ParanextPanel from './ParanextPanel'; +import ParanextTabTitle from './ParanextTabTitle'; + +// NOTE: 'card' is a built-in style. We can likely remove it when we +// create a full theme for Paranext. +const TAB_GROUPS = 'card paranext'; + +// TODO: Build this mapping from extensions so extensions can create their own panels +const tabTypeCreationMap = new Map([ + ['hello', createHelloPanel], + ['buttons', createButtonsPanel], + ['tab', createTabPanel], +]); + +const getTabDataFromSavedInfo = (tabInfo: SavedTabInfo): TabInfo => { + if (!tabInfo.type) + return createErrorTab(`No handler for the tab type '${tabInfo.type}'`); + + const tabCreator = tabTypeCreationMap.get(tabInfo.type); + if (!tabCreator) + return createErrorTab(`No handler for the tab type '${tabInfo.type}'`); + + // Call the creation method to let the extension method create the tab + try { + return tabCreator(tabInfo); + } catch (e) { + // If the tab couldn't be created, replace it with an error tab + if (e instanceof Error) return createErrorTab(e.message); + return createErrorTab(String(e)); + } +}; + +/** + * Creates tab data from the specified saved tab information by calling back to the + * extension that registered the creation of the tab type + * @param savedTabInfo Data that is to be used to create the new tab (comes from rc-dock, typically from disk) + */ +const loadTab = (savedTabInfo: TabBase): TabData => { + let { id } = savedTabInfo; + if (!id) id = newGuid(); + + const tabInfo = savedTabInfo as SavedTabInfo; + const newTabData = getTabDataFromSavedInfo(tabInfo); + + // Translate the data from the extension to be in the form needed by rc-dock + return { + id, + title: , + content: {newTabData.content}, + minWidth: newTabData.minWidth, + minHeight: newTabData.minHeight, + group: TAB_GROUPS, + closable: true, + }; +}; + +const groups: { + [key: string]: TabGroup; +} = { + [TAB_GROUPS]: { + maximizable: false, // Don't allow groups of tabs to be maximized + floatable: true, // Allow tabs to be floated + animated: false, // Don't animate tab transitions + // TODO: Currently allowing newWindow crashes since electron doesn't seem to have window.open defined? + // newWindow: true, // Allow floating windows to show in a native window + }, +}; + +const ParanextDockLayout = () => { + return ( + + ); +}; + +export default ParanextDockLayout; diff --git a/src/renderer/Components/docking/ParanextPanel.css b/src/renderer/components/docking/ParanextPanel.css similarity index 100% rename from src/renderer/Components/docking/ParanextPanel.css rename to src/renderer/components/docking/ParanextPanel.css diff --git a/src/renderer/Components/docking/ParanextPanel.tsx b/src/renderer/components/docking/ParanextPanel.tsx similarity index 100% rename from src/renderer/Components/docking/ParanextPanel.tsx rename to src/renderer/components/docking/ParanextPanel.tsx diff --git a/src/renderer/Components/docking/ParanextTabTitle.css b/src/renderer/components/docking/ParanextTabTitle.css similarity index 100% rename from src/renderer/Components/docking/ParanextTabTitle.css rename to src/renderer/components/docking/ParanextTabTitle.css diff --git a/src/renderer/Components/docking/ParanextTabTitle.tsx b/src/renderer/components/docking/ParanextTabTitle.tsx similarity index 100% rename from src/renderer/Components/docking/ParanextTabTitle.tsx rename to src/renderer/components/docking/ParanextTabTitle.tsx diff --git a/src/renderer/Components/testing/HelloPanel.tsx b/src/renderer/testing/HelloPanel.tsx similarity index 61% rename from src/renderer/Components/testing/HelloPanel.tsx rename to src/renderer/testing/HelloPanel.tsx index a1200a8d22..812b162e18 100644 --- a/src/renderer/Components/testing/HelloPanel.tsx +++ b/src/renderer/testing/HelloPanel.tsx @@ -1,4 +1,5 @@ import icon from '@assets/icon.png'; +import { TabInfo } from '@shared/data/WebviewTypes'; const HelloPanel = () => { return ( @@ -18,4 +19,14 @@ const HelloPanel = () => { ); }; -export default HelloPanel; +const createHelloPanel = (): TabInfo => { + return { + type: 'hello', + title: 'Hello', + content: , + minWidth: 230, + minHeight: 230, + }; +}; + +export default createHelloPanel; diff --git a/src/renderer/testing/TestButtonsPanel.css b/src/renderer/testing/TestButtonsPanel.css new file mode 100644 index 0000000000..3354a0dcb8 --- /dev/null +++ b/src/renderer/testing/TestButtonsPanel.css @@ -0,0 +1,18 @@ +.testButton { + background-color: white; + padding: 10px 20px; + border-radius: 10px; + border: none; + appearance: none; + font-size: 1.3rem; + box-shadow: 0px 8px 28px -6px rgba(24, 39, 75, 0.12), + 0px 18px 88px -4px rgba(24, 39, 75, 0.14); + transition: all ease-in 0.1s; + cursor: pointer; + opacity: 0.9; +} + +.testButton:hover { + transform: scale(1.05); + opacity: 1; +} diff --git a/src/renderer/Components/testing/TestButtonsPanel.tsx b/src/renderer/testing/TestButtonsPanel.tsx similarity index 63% rename from src/renderer/Components/testing/TestButtonsPanel.tsx rename to src/renderer/testing/TestButtonsPanel.tsx index e058a24ea5..6a8a2f6365 100644 --- a/src/renderer/Components/testing/TestButtonsPanel.tsx +++ b/src/renderer/testing/TestButtonsPanel.tsx @@ -1,9 +1,11 @@ +import './TestButtonsPanel.css'; import { useCallback, useState } from 'react'; import usePromise from '@renderer/hooks/usePromise'; import papi from '@shared/services/papi'; import * as NetworkService from '@shared/services/NetworkService'; import { getErrorMessage } from '@shared/util/Util'; import logger from '@shared/util/logger'; +import { TabInfo } from '@shared/data/WebviewTypes'; const getVar: (envVar: string) => Promise = NetworkService.createRequestFunction('electronAPI.env.getVar'); @@ -92,51 +94,67 @@ const TestButtonsPanel = () => { return ( <> - - - - -
NODE_ENV: {NODE_ENV}
-
{promiseReturn}
+
+ + + + +
+
+
NODE_ENV: {NODE_ENV}
+
{promiseReturn}
+
); }; -export default TestButtonsPanel; +const createButtonsPanel = (): TabInfo => { + return { + type: 'buttons', + title: 'Test Buttons', + content: , + }; +}; + +export default createButtonsPanel; diff --git a/src/renderer/testing/TestPanel.tsx b/src/renderer/testing/TestPanel.tsx new file mode 100644 index 0000000000..69fda0eeef --- /dev/null +++ b/src/renderer/testing/TestPanel.tsx @@ -0,0 +1,26 @@ +import { SavedTabInfo, TabInfo } from '@shared/data/WebviewTypes'; + +export type TabData = { + title: string; + content: string; +}; + +const TestPanel = ({ content }: { content: string }) => { + return
{content}
; +}; + +const createTabPanel = (tabInfo: SavedTabInfo): TabInfo => { + if (!tabInfo.data) throw Error('Tab creation data is missing'); + + // We need to make sure that the data is of the correct type + const data = tabInfo.data as TabData; + const title = data.title ? data.title : 'Unknown'; + const content = data.content ? data.content : 'Unknown'; + return { + type: 'tab', + title: `Tab ${title}`, + content: , + }; +}; + +export default createTabPanel; diff --git a/src/renderer/testing/testLayout.tsx b/src/renderer/testing/testLayout.tsx new file mode 100644 index 0000000000..e0741db311 --- /dev/null +++ b/src/renderer/testing/testLayout.tsx @@ -0,0 +1,64 @@ +import { SavedTabInfo } from '@shared/data/WebviewTypes'; +import { LayoutBase, TabBase } from 'rc-dock'; + +const createTab = (tabInfo: SavedTabInfo): TabBase => { + return tabInfo as TabBase; +}; + +// NOTE: This structure represents what might be saved in a saved layout and +// thus looks different than a normal rc-dock layout. +const testLayout: LayoutBase = { + dockbox: { + mode: 'horizontal', + children: [ + { + mode: 'vertical', + size: 200, + children: [ + { + tabs: [ + createTab({ + type: 'tab', + data: { title: 'Bla', content: 'Random content!' }, + }), + ], + }, + { + tabs: [ + createTab({ + type: 'tab', + data: { title: 'two', content: 'Content for tab two' }, + }), + createTab({ + type: 'tab', + data: { title: 'one', content: 'Content for tab one' }, + }), + ], + }, + ], + }, + { + tabs: [createTab({ type: 'hello' }), createTab({ type: 'buttons' })], + }, + ], + }, + floatbox: { + mode: 'float', + children: [ + { + tabs: [ + createTab({ + type: 'tab', + data: { title: 'Floating', content: 'Floating content!' }, + }), + ], + x: 60, + y: 60, + w: 320, + h: 300, + }, + ], + }, +}; + +export default testLayout; diff --git a/src/shared/data/WebviewTypes.ts b/src/shared/data/WebviewTypes.ts index 3c000e43c4..a698e26dd0 100644 --- a/src/shared/data/WebviewTypes.ts +++ b/src/shared/data/WebviewTypes.ts @@ -1,3 +1,5 @@ +import { ReactNode } from 'react'; + /** * Information used to recreate a tab */ @@ -9,7 +11,7 @@ export type SavedTabInfo = { /** * Data needed to recreate the tab during load */ - data?: string; + data?: unknown; }; /** @@ -27,11 +29,7 @@ export type TabInfo = { /** * Content to show inside the tab */ - content: React.ReactNode; - /** - * (optional) Whether the tab can be closed by the user (default is true) - */ - closable?: boolean; + content: ReactNode; /** * (optional) Minimum width that the tab can become */ @@ -40,12 +38,9 @@ export type TabInfo = { * (optional) Minimum height that the tab can become */ minHeight?: number; - /** - * - when value is true: content will always reuse the react component thus allows the component to keep its internal state - * - when value is false: content will be destroyed when it's not visible - * - when value is undefined: content is rendered normally as react component - */ - cached?: boolean; }; +/** + * For now all tab creators must do their own data type verification + */ export type TabCreator = (tabData: SavedTabInfo) => TabInfo;