diff --git a/DeskThingServer/package-lock.json b/DeskThingServer/package-lock.json
index 9cb6be01..45b03187 100644
--- a/DeskThingServer/package-lock.json
+++ b/DeskThingServer/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "deskthing",
- "version": "0.9.0",
+ "version": "0.9.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "deskthing",
- "version": "0.9.0",
+ "version": "0.9.3",
"hasInstallScript": true,
"dependencies": {
"@electron-toolkit/preload": "^3.0.0",
@@ -22,6 +22,7 @@
"react-qr-code": "^2.0.15",
"react-rewards": "^2.0.4",
"react-router-dom": "^6.26.2",
+ "react-select": "^5.8.3",
"uuid": "^10.0.0",
"ws": "^8.17.1",
"zustand": "^5.0.0-rc.2"
@@ -81,7 +82,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/highlight": "^7.24.7",
@@ -146,7 +146,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz",
"integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7",
@@ -189,7 +188,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7"
@@ -202,7 +200,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.24.7",
@@ -216,7 +213,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7"
@@ -229,7 +225,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.24.7",
@@ -287,7 +282,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7"
@@ -300,7 +294,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz",
"integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -310,7 +303,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -344,7 +336,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.7",
@@ -360,7 +351,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz",
"integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==",
- "dev": true,
"license": "MIT",
"bin": {
"parser": "bin/babel-parser.js"
@@ -417,11 +407,21 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
+ "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz",
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.7",
@@ -436,7 +436,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz",
"integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.7",
@@ -458,7 +457,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz",
"integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.24.7",
@@ -833,6 +831,133 @@
"node": ">= 10.0.0"
}
},
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz",
+ "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.2.0",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.13.1",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz",
+ "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.0",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.13.3",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz",
+ "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.12.0",
+ "@emotion/cache": "^11.13.0",
+ "@emotion/serialize": "^1.3.1",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+ "@emotion/utils": "^1.4.0",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
+ "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.1",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
+ "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
+ "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA=="
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -1337,6 +1462,28 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.6.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
+ "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.8"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.6.12",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz",
+ "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==",
+ "dependencies": {
+ "@floating-ui/core": "^1.6.0",
+ "@floating-ui/utils": "^0.2.8"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
+ "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig=="
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -1506,7 +1653,6 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
@@ -1521,7 +1667,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1531,7 +1676,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1541,14 +1685,12 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -2171,6 +2313,11 @@
"undici-types": "~5.26.4"
}
},
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
+ },
"node_modules/@types/plist": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz",
@@ -2187,14 +2334,12 @@
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.3",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
"integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -2211,6 +2356,14 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.11",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
+ "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
@@ -2595,7 +2748,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
@@ -3135,6 +3287,59 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/babel-plugin-macros/node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/babel-plugin-macros/node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/babel-plugin-macros/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3600,7 +3805,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -3652,7 +3856,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
@@ -3806,7 +4009,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
@@ -3816,7 +4018,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
@@ -4113,7 +4314,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/data-view-buffer": {
@@ -4487,6 +4687,15 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/dotenv": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz",
@@ -5038,7 +5247,6 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
@@ -5275,7 +5483,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.8.0"
@@ -5955,6 +6162,11 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -6339,7 +6551,6 @@
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -6448,7 +6659,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -6556,6 +6766,14 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -6732,7 +6950,6 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
@@ -6818,7 +7035,6 @@
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
- "dev": true,
"license": "MIT"
},
"node_modules/is-async-function": {
@@ -6910,7 +7126,6 @@
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz",
"integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@@ -7426,7 +7641,6 @@
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
- "dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
@@ -7445,7 +7659,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "dev": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
@@ -7610,7 +7823,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
"license": "MIT"
},
"node_modules/linkify-it": {
@@ -7857,6 +8069,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
+ },
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
@@ -8454,7 +8671,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
@@ -8467,7 +8683,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0",
@@ -8524,7 +8739,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
"license": "MIT"
},
"node_modules/path-scurry": {
@@ -8561,7 +8775,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -8578,7 +8791,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
- "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@@ -9170,6 +9382,41 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-select": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.3.tgz",
+ "integrity": "sha512-lVswnIq8/iTj1db7XCG74M/3fbGB6ZaluCzvwPGT5ZOjCdL/k0CLWhEK0vCBLuU5bHTEf6Gj8jtSvi+3v+tO1w==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.0",
+ "@emotion/cache": "^11.4.0",
+ "@emotion/react": "^11.8.1",
+ "@floating-ui/dom": "^1.0.1",
+ "@types/react-transition-group": "^4.4.0",
+ "memoize-one": "^6.0.0",
+ "prop-types": "^15.6.0",
+ "react-transition-group": "^4.3.0",
+ "use-isomorphic-layout-effect": "^1.1.2"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -9274,6 +9521,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ },
"node_modules/regex": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/regex/-/regex-4.3.3.tgz",
@@ -9339,7 +9591,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -10124,6 +10375,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
+ },
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
@@ -10221,7 +10477,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
@@ -10234,7 +10489,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -10468,7 +10722,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -10885,6 +11138,19 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
+ "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/utf8-byte-length": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
diff --git a/DeskThingServer/package.json b/DeskThingServer/package.json
index e34ea32a..6c47ffbe 100644
--- a/DeskThingServer/package.json
+++ b/DeskThingServer/package.json
@@ -1,6 +1,6 @@
{
"name": "deskthing",
- "version": "0.9.2",
+ "version": "0.9.3",
"description": "A DeskThing server UI to interface with the DeskThing car thing app",
"main": "./out/main/index.js",
"author": "Riprod",
@@ -35,6 +35,7 @@
"react-qr-code": "^2.0.15",
"react-rewards": "^2.0.4",
"react-router-dom": "^6.26.2",
+ "react-select": "^5.8.3",
"uuid": "^10.0.0",
"ws": "^8.17.1",
"zustand": "^5.0.0-rc.2"
diff --git a/DeskThingServer/src/main/handlers/adbHandler.ts b/DeskThingServer/src/main/handlers/adbHandler.ts
index 3fecc4a9..50f957d5 100644
--- a/DeskThingServer/src/main/handlers/adbHandler.ts
+++ b/DeskThingServer/src/main/handlers/adbHandler.ts
@@ -1,9 +1,9 @@
import path from 'path'
import { execFile } from 'child_process'
import getPlatform from '../utils/get-platform'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import loggingStore from '../stores/loggingStore'
import settingsStore from '../stores/settingsStore'
-import { LoggingData } from '@shared/types'
+import { ReplyFn, MESSAGE_TYPES } from '@shared/types'
const isDevelopment = process.env.NODE_ENV === 'development'
const execPath = isDevelopment
@@ -25,45 +25,33 @@ const splitArgs = (str: string): string[] => {
return matches
}
-export const handleAdbCommands = async (
- command: string,
- send?: (channel: string, ...args: LoggingData[]) => void
-): Promise => {
+export const handleAdbCommands = async (command: string, replyFn?: ReplyFn): Promise => {
const settings = await settingsStore.getSettings()
const useGlobalADB = settings.globalADB === true
- dataListener.asyncEmit(
- MESSAGE_TYPES.LOGGING,
- useGlobalADB ? 'Using Global ADB' : 'Using Local ADB'
- )
+ loggingStore.log(MESSAGE_TYPES.LOGGING, useGlobalADB ? 'Using Global ADB' : 'Using Local ADB')
return new Promise((resolve, reject) => {
execFile(
useGlobalADB ? 'adb' : adbPath,
splitArgs(command),
{ cwd: execPath },
(error, stdout, stderr) => {
- console.log(error, stdout, stderr)
if (error) {
- if (send) {
- send('logging', {
+ replyFn &&
+ replyFn('logging', {
status: false,
data: 'Error Encountered!',
- final: true,
+ final: false,
error: stderr
})
- }
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- `ADB Error: ${stderr}, ${command}, ${adbPath}`
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, `ADB Error: ${stderr}, ${command}, ${adbPath}`)
reject(`ADB Error: ${stderr}, ${command}, ${adbPath}`)
} else {
- if (send) {
- send('logging', {
+ replyFn &&
+ replyFn('logging', {
status: true,
data: 'ADB Success!',
- final: true
+ final: false
})
- }
resolve(stdout)
}
}
diff --git a/DeskThingServer/src/main/handlers/appHandler.ts b/DeskThingServer/src/main/handlers/appHandler.ts
index 6df63eca..704871df 100644
--- a/DeskThingServer/src/main/handlers/appHandler.ts
+++ b/DeskThingServer/src/main/handlers/appHandler.ts
@@ -1,10 +1,16 @@
import path from 'path'
-import { AppIPCData, ReplyFn } from '@shared/types/ipcTypes'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import {
+ App,
+ AppDataInterface,
+ AppReturnData,
+ AppIPCData,
+ ReplyFn,
+ MESSAGE_TYPES
+} from '@shared/types'
+import loggingStore from '../stores/loggingStore'
import { getData, setData } from './dataHandler'
import { dialog, BrowserWindow } from 'electron'
import { sendMessageToApp, AppHandler } from '../services/apps'
-import { App, AppDataInterface, AppReturnData } from '@shared/types'
const appStore = AppHandler.getInstance()
export const appHandler: Record<
@@ -109,7 +115,7 @@ export const appHandler: Record<
return { path: filePath, name: path.basename(filePath) }
},
'dev-add-app': async (data, replyFn) => {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
'Developer App Not implemented Yet ',
data.payload.appPath
@@ -118,7 +124,6 @@ export const appHandler: Record<
replyFn('logging', { status: true, data: 'Finished', final: true })
},
'send-to-app': async (data, replyFn) => {
- console.log('sending data to app: ', data.payload.app, data)
await sendMessageToApp(data.payload.app, data.payload)
replyFn('logging', { status: true, data: 'Finished', final: true })
},
@@ -130,16 +135,14 @@ export const appHandler: Record<
const getApps = (replyFn: ReplyFn): App[] => {
replyFn('logging', { status: true, data: 'Getting data', final: false })
- console.log('Getting app data')
const data = appStore.getAllBase()
replyFn('logging', { status: true, data: 'Finished', final: true })
replyFn('app-data', { status: true, data: data, final: true })
return data
}
-const setAppData = async (replyFn, id, data: AppDataInterface): Promise => {
- console.log('Saving app data: ', data)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'SERVER: Saving ' + id + "'s data " + data)
+const setAppData = async (replyFn: ReplyFn, id, data: AppDataInterface): Promise => {
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'SERVER: Saving ' + id + "'s data " + data)
await setData(id, data)
replyFn('logging', { status: true, data: 'Finished', final: true })
}
@@ -150,7 +153,7 @@ const getAppData = async (replyFn, payload): Promise =>
replyFn('logging', { status: true, data: 'Finished', final: true })
return data
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'SERVER: Error saving manifest' + error)
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'SERVER: Error saving manifest' + error)
console.error('Error setting client manifest:', error)
replyFn('logging', { status: false, data: 'Unfinished', error: error, final: true })
return null
diff --git a/DeskThingServer/src/main/handlers/authHandler.ts b/DeskThingServer/src/main/handlers/authHandler.ts
index ba64065c..bbd87dfc 100644
--- a/DeskThingServer/src/main/handlers/authHandler.ts
+++ b/DeskThingServer/src/main/handlers/authHandler.ts
@@ -3,8 +3,8 @@ import { sendMessageToApp } from '../services/apps' // Assuming you have an app
import http from 'http'
import url from 'url'
import settingsStore from '../stores/settingsStore'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
-import { Settings } from '@shared/types'
+import loggingStore from '../stores/loggingStore'
+import { Settings, MESSAGE_TYPES } from '@shared/types'
const successView = 'Success You can now close this window.
'
@@ -14,7 +14,7 @@ let callBackPort: number
function handleCallback(req: http.IncomingMessage, res: http.ServerResponse): void {
const parsedUrl = url.parse(req.url || '', true)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`AUTH: Received callback request for ${parsedUrl.pathname}`
)
@@ -29,7 +29,6 @@ function handleCallback(req: http.IncomingMessage, res: http.ServerResponse): vo
const appName = urlParts[1] // The app name should be the third part after '/callback/'
const config = getAppData() // Assuming getConfig() returns an object with active apps
- console.log('AUTH DATA: ', config)
if (!config.apps || !config.apps.some((app) => app.name === appName && app.enabled)) {
res.writeHead(404, { 'Content-Type': 'text/html' })
res.end(`App Not Found App '${appName}' not found or not active.
`)
@@ -37,7 +36,6 @@ function handleCallback(req: http.IncomingMessage, res: http.ServerResponse): vo
}
const code = parsedUrl.query.code as string
- console.log('AUTH CODE: ', code)
sendMessageToApp(appName, { type: 'callback-data', payload: code })
res.writeHead(200, { 'Content-Type': 'text/html' })
@@ -47,11 +45,10 @@ function handleCallback(req: http.IncomingMessage, res: http.ServerResponse): vo
const startServer = async (): Promise => {
if (server) {
await server.close(() => {
- console.log('CALLBACK: Previous server closed.')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'CALLBACK: Shutting down the server...')
})
}
- console.log('CALLBACK: Starting server...')
server = http.createServer((req, res) => {
const parsedUrl = new URL(`http://${req.headers.host}${req.url}`)
const pathname = parsedUrl.pathname
@@ -63,9 +60,8 @@ const startServer = async (): Promise => {
}
})
- console.log('CALLBACK: Listening...')
server.listen(callBackPort, () => {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.MESSAGE,
`CALLBACK: running at http://localhost:${callBackPort}/`
)
@@ -82,16 +78,18 @@ const initializeServer = async (): Promise => {
}
}
-dataListener.on(MESSAGE_TYPES.SETTINGS, (newSettings) => {
+settingsStore.addListener((newSettings) => {
try {
if (newSettings.callbackPort != callBackPort) {
callBackPort = newSettings.callbackPort
startServer()
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'CALLBACK: Not starting - port is not changed')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'CALLBACK: Not starting - port is not changed')
}
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'CALLBACK: Error updating with settings', error)
+ if (error instanceof Error) {
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'CALLBACK: Error updating with settings' + error)
+ }
}
})
diff --git a/DeskThingServer/src/main/handlers/clientHandler.ts b/DeskThingServer/src/main/handlers/clientHandler.ts
index 6e86da2d..8d527b4a 100644
--- a/DeskThingServer/src/main/handlers/clientHandler.ts
+++ b/DeskThingServer/src/main/handlers/clientHandler.ts
@@ -1,5 +1,5 @@
-import { ClientIPCData, ReplyFn } from '@shared/types/ipcTypes'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import { ClientIPCData, ClientManifest, SocketData, ReplyFn, MESSAGE_TYPES } from '@shared/types'
+import loggingStore from '../stores/loggingStore'
import { handleAdbCommands } from './adbHandler'
import {
configureDevice,
@@ -9,7 +9,6 @@ import {
HandleWebappZipFromUrl,
SetupProxy
} from './deviceHandler'
-import { ClientManifest, SocketData } from '@shared/types'
import { sendMessageToClient, sendMessageToClients } from '../services/client/clientCom'
export const clientHandler: Record<
@@ -28,7 +27,11 @@ export const clientHandler: Record<
return `Pinging ${data.payload}...`
} catch (error) {
console.error('Error pinging client:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, error)
+ if (error instanceof Error) {
+ loggingStore.log(MESSAGE_TYPES.ERROR, error.message)
+ } else {
+ loggingStore.log(MESSAGE_TYPES.ERROR, String(error))
+ }
return 'Error pinging' + data.payload
}
},
@@ -45,27 +48,23 @@ export const clientHandler: Record<
return handleUrl(data, replyFn)
},
adb: async (data, replyFn) => {
- console.log('Running ADB command:', data.payload)
replyFn('logging', { status: true, data: 'Working', final: false })
- const reply = async (status, data, final, error): Promise => {
- replyFn('logging', {
- status: status,
- data: data,
- final: final,
- error: error
- })
- }
-
- return await handleAdbCommands(data.payload, reply)
+ const response = await handleAdbCommands(data.payload, replyFn)
+ replyFn('logging', { status: true, data: response, final: true })
+ return response
},
configure: async (data, replyFn) => {
replyFn('logging', { status: true, data: 'Configuring Device', final: false })
- return await configureDevice(data.payload, replyFn)
+ const response = await configureDevice(data.payload, replyFn)
+ replyFn('logging', { status: true, data: 'Device Configured!', final: true })
+ return response
},
'client-manifest': async (data, replyFn) => {
if (data.request === 'get') {
- return await getClientManifest(false, replyFn)
+ const response = await getClientManifest(replyFn)
+ replyFn('logging', { status: true, data: response, final: true })
+ return response
} else if (data.request === 'set') {
return await handleClientManifestUpdate(data.payload, replyFn)
}
@@ -73,7 +72,7 @@ export const clientHandler: Record<
},
'push-staged': async (data, replyFn) => {
try {
- console.log('Pushing staged webapp...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Pushing staged app...')
HandlePushWebApp(data.payload, replyFn)
} catch (error) {
replyFn('logging', {
@@ -82,14 +81,22 @@ export const clientHandler: Record<
error: 'Failed to push staged app!',
final: true
})
- console.error('Error extracting zip file:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, error)
+ if (error instanceof Error) {
+ loggingStore.log(MESSAGE_TYPES.ERROR, error.message)
+ } else {
+ loggingStore.log(MESSAGE_TYPES.ERROR, String(error))
+ }
}
},
'push-proxy-script': async (data, replyFn) => {
try {
- console.log('Pushing proxy script...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Pushing proxy script...')
SetupProxy(replyFn, data.payload)
+ replyFn('logging', {
+ status: true,
+ data: 'Proxy script pushed!',
+ final: true
+ })
} catch (error) {
replyFn('logging', {
status: false,
@@ -98,7 +105,11 @@ export const clientHandler: Record<
final: true
})
console.error('Error pushing proxy script:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, error)
+ if (error instanceof Error) {
+ loggingStore.log(MESSAGE_TYPES.ERROR, error.message)
+ } else {
+ loggingStore.log(MESSAGE_TYPES.ERROR, String(error))
+ }
}
},
'run-device-command': async (data, replyFn) => {
@@ -110,13 +121,12 @@ export const clientHandler: Record<
request: data.payload.request,
payload: !payload.includes('{') ? data.payload.payload : JSON.parse(data.payload.payload)
}
- console.log('Sending data', data)
replyFn('logging', { status: true, data: 'Finished', final: true })
return await sendMessageToClients(message)
}
}
-const handleUrl = (data, replyFn: ReplyFn): void => {
+const handleUrl = async (data, replyFn: ReplyFn): Promise => {
try {
replyFn('logging', {
status: true,
@@ -124,15 +134,24 @@ const handleUrl = (data, replyFn: ReplyFn): void => {
final: false
})
- const reply = async (channel: string, data): Promise => {
- replyFn(channel, data)
- }
-
- HandleWebappZipFromUrl(reply, data.payload)
+ await HandleWebappZipFromUrl(replyFn, data.payload)
+ replyFn('logging', { status: true, data: 'Successfully downloaded client', final: true })
} catch (error) {
console.error('Error extracting zip file:', error)
if (error instanceof Error) {
- replyFn('zip-extracted', { status: false, error: error.message, data: null, final: true })
+ replyFn('logging', {
+ status: false,
+ error: error.message,
+ data: 'Error handling URL',
+ final: true
+ })
+ } else {
+ replyFn('logging', {
+ status: false,
+ error: 'Unable to download CLIENT',
+ data: 'Error handling URL',
+ final: true
+ })
}
}
}
diff --git a/DeskThingServer/src/main/handlers/configHandler.ts b/DeskThingServer/src/main/handlers/configHandler.ts
index c575f504..bbc54805 100644
--- a/DeskThingServer/src/main/handlers/configHandler.ts
+++ b/DeskThingServer/src/main/handlers/configHandler.ts
@@ -1,12 +1,11 @@
import { sendIpcData } from '..'
-import { AppData, App, Manifest, ButtonMapping } from '@shared/types'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import { AppData, App, MESSAGE_TYPES, Manifest, ButtonMapping } from '@shared/types'
+import loggingStore from '../stores/loggingStore'
import { readFromFile, writeToFile } from '../utils/fileHandler'
const defaultData: AppData = {
apps: [],
config: {
- audiosources: ['local'],
testData: 'thisisastring'
}
}
@@ -18,7 +17,6 @@ const readData = (): AppData => {
const data = readFromFile(dataFilePath)
if (!data) {
// File does not exist, create it with default data
- console.log('File does not exist, creating it with default data')
writeToFile(defaultData, dataFilePath)
return defaultData
}
@@ -35,11 +33,11 @@ const writeData = (data: AppData): void => {
try {
const result = writeToFile(data, 'apps.json')
if (!result) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'Error writing data')
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'Error writing data')
}
sendIpcData('app-data', data) // Send data to the web UI
} catch (err) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'Error writing data' + err)
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'Error writing data' + err)
console.error('Error writing data:', err)
}
}
@@ -74,11 +72,6 @@ const addAppManifest = (manifest: Manifest, appName: string): void => {
// Find existing app by name
const existingAppIndex = data.apps.findIndex((app: App) => app.name === appName)
- if (manifest.isAudioSource) {
- addConfig('audiosources', appName, data)
- console.log(appName, 'added to audiosources')
- }
-
if (existingAppIndex !== -1) {
// Update existing app
data.apps[existingAppIndex].manifest = manifest
@@ -91,9 +84,7 @@ const addAppManifest = (manifest: Manifest, appName: string): void => {
const addConfig = (configName: string, config: string | Array, data = readData()): void => {
if (!data.config) {
- const val = {
- audiosources: ['local']
- }
+ const val = {}
val[configName] = config
data.config = val
} else if (Array.isArray(data.config[configName])) {
@@ -112,12 +103,11 @@ const addConfig = (configName: string, config: string | Array, data = re
} else {
data.config[configName] = config
}
- console.log('THIS IS THE FIRST TIME THIS IS BEING EMITTED - TRY AND TRACK IT')
- dataListener.asyncEmit(MESSAGE_TYPES.CONFIG, {
- app: 'server',
- type: 'config',
- payload: data.config
- })
+ // loggingStore.log(MESSAGE_TYPES.CONFIG, {
+ // app: 'server',
+ // type: 'config',
+ // payload: data.config
+ // })
writeData(data)
}
const getConfig = (
@@ -126,9 +116,7 @@ const getConfig = (
const data = readData()
if (!data.config) {
- const val = {
- audiosources: ['local']
- }
+ const val = {}
data.config = val
writeData(data)
}
@@ -160,24 +148,19 @@ const getAppByIndex = (index: number): App | undefined => {
}
const purgeAppConfig = async (appName: string): Promise => {
- console.log('SERVER: Deleting App From Config...', appName)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `Purging app: ${appName}`)
const data = readData()
// Filter out the app to be purged
const filteredApps = data.apps.filter((app: App) => app.name !== appName)
data.apps = filteredApps
- if (Array.isArray(data.config.audiosources)) {
- const updatedAudiosources = data.config.audiosources.filter((source) => source !== appName)
- data.config.audiosources = updatedAudiosources
- }
-
writeData(data)
- dataListener.asyncEmit(MESSAGE_TYPES.CONFIG, {
- app: 'server',
- type: 'config',
- payload: data.config
- })
+ // loggingStore.log(MESSAGE_TYPES.CONFIG, {
+ // app: 'server',
+ // type: 'config',
+ // payload: data.config
+ // })
}
export {
diff --git a/DeskThingServer/src/main/handlers/dataHandler.ts b/DeskThingServer/src/main/handlers/dataHandler.ts
index ff8fec17..b11a2945 100644
--- a/DeskThingServer/src/main/handlers/dataHandler.ts
+++ b/DeskThingServer/src/main/handlers/dataHandler.ts
@@ -62,7 +62,6 @@ const getData = (app: string): AppDataInterface => {
}
const purgeAppData = async (appName: string): Promise => {
- console.log('SERVER: Deleting app data...')
const data = readData()
delete data[appName]
writeData(data)
diff --git a/DeskThingServer/src/main/handlers/deviceHandler.ts b/DeskThingServer/src/main/handlers/deviceHandler.ts
index 88e800f5..5442ddfb 100644
--- a/DeskThingServer/src/main/handlers/deviceHandler.ts
+++ b/DeskThingServer/src/main/handlers/deviceHandler.ts
@@ -1,11 +1,12 @@
import { sendIpcData } from '..'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import loggingStore from '../stores/loggingStore'
import { join } from 'path'
import * as fs from 'fs'
import { app, net } from 'electron'
import { handleAdbCommands } from './adbHandler'
-import { Client, ClientManifest, ReplyData } from '@shared/types'
+import { Client, ClientManifest, MESSAGE_TYPES, ReplyData, ReplyFn } from '@shared/types'
import settingsStore from '../stores/settingsStore'
+import { getLatestRelease } from './githubHandler'
export const HandleDeviceData = async (data: string): Promise => {
try {
@@ -16,14 +17,11 @@ export const HandleDeviceData = async (data: string): Promise => {
sendIpcData('version-status', deviceData)
break
default:
- console.log('Unhandled response', deviceData)
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'HandleDeviceData Unable to find device version')
break
}
} catch (Exception) {
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- 'HandleDeviceData encountered the error ' + Exception
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'HandleDeviceData encountered the error ' + Exception)
}
}
@@ -33,7 +31,6 @@ export const getDeviceManifestVersion = async (deviceId: string): Promise void
-): Promise => {
+export const configureDevice = async (deviceId: string, reply?: ReplyFn): Promise => {
const settings = await settingsStore.getSettings()
- if (settings && settings.devicePort) {
- console.error('Settings not found')
- reply && reply('logging', { status: true, data: 'Opening Port...', final: false })
- try {
- const response = await handleAdbCommands(
- `-s ${deviceId} reverse tcp:${settings.devicePort} tcp:${settings.devicePort}`
- )
- reply && reply('logging', { status: true, data: response || 'Port Opened', final: false })
- } catch (error) {
- reply && reply('logging', { status: false, data: 'Unable to open port!', final: false })
+ // Opens the socket port
+ try {
+ if (settings && settings.devicePort) {
+ console.error('Settings not found')
+ reply && reply('logging', { status: true, data: 'Opening Port...', final: false })
+ try {
+ const response = await handleAdbCommands(
+ `-s ${deviceId} reverse tcp:${settings.devicePort} tcp:${settings.devicePort}`,
+ reply
+ )
+ reply && reply('logging', { status: true, data: response || 'Port Opened', final: false })
+ } catch (error) {
+ reply && reply('logging', { status: false, data: 'Unable to open port!', final: false })
+ }
+ } else {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Unable to open port!',
+ error: 'Device Port not found in settings',
+ final: false
+ })
+ }
+ } catch (error) {
+ console.error('Error opening device port:', error)
+ if (error instanceof Error) {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Unable to open port!',
+ error: error.message,
+ final: false
+ })
}
- } else {
- reply &&
- reply('logging', {
- status: false,
- data: 'Unable to open port!',
- error: 'Device Port not found in settings',
- final: false
- })
}
- const deviceManifestVersion = await getDeviceManifestVersion(deviceId)
- const clientManifest = await getClientManifest(true, reply)
- if (clientManifest && deviceManifestVersion !== clientManifest.version) {
- try {
- await HandlePushWebApp(deviceId, reply)
- } catch (error) {
+ // Check if the client is already installed. Install it if it is missing
+ try {
+ const clientExists = await checkForClient(reply)
+
+ if (!clientExists) {
+ // Download it from github
+ const repos = settings.clientRepos
+ reply && reply('logging', { status: true, data: 'Fetching Latest Client...', final: false })
+ const latestReleases = await Promise.all(
+ repos.map(async (repo) => {
+ return await getLatestRelease(repo)
+ })
+ )
+
+ // Sort releases by date and get the latest one
+ const clientAsset = latestReleases
+ .flatMap((release) =>
+ release.assets.map((asset) => ({ ...asset, created_at: release.created_at }))
+ )
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+ .find((asset) => asset.name.includes('-client'))
+
+ // Download it
+ if (clientAsset) {
+ reply && reply('logging', { status: true, data: 'Downloading Client...', final: false })
+ await HandleWebappZipFromUrl(reply, clientAsset.browser_download_url)
+
+ await new Promise((resolve) => {
+ setTimeout(async () => {
+ await checkForClient(reply)
+ resolve(null)
+ }, 5000)
+ })
+ } else {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'No client asset found in latest releases',
+ final: false
+ })
+ }
+ }
+ } catch (error) {
+ if (error instanceof Error) {
reply &&
reply('logging', {
status: false,
- data: 'Unable to push webapp!',
- error: typeof error == 'string' ? error : 'Unknown Error',
+ data: 'Unable to check for client!',
+ error: error.message,
+ final: false
+ })
+ } else {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Unable to check for client!',
+ error: 'Unknown error',
final: false
})
}
- } else {
+ console.error('Error checking for client:', error)
+ }
+
+ // Push the webapp to the device
+ try {
reply &&
- reply('logging', {
- status: true,
- data: 'Device has the same webapp version!',
- final: false
- })
+ reply('logging', { status: true, data: 'Fetching Device Manifest Version...', final: false })
+ const deviceManifestVersion = await getDeviceManifestVersion(deviceId)
+
+ reply &&
+ reply('logging', { status: true, data: 'Fetching Client Manifest Version...', final: false })
+ const clientManifest = await getClientManifest(reply)
+ if (
+ clientManifest &&
+ deviceManifestVersion &&
+ deviceManifestVersion !== clientManifest.version
+ ) {
+ try {
+ reply && reply('logging', { status: true, data: 'Pushing client...', final: false })
+ // Give a 30 second timeout to flash the webapp
+ await Promise.race([
+ HandlePushWebApp(deviceId, reply),
+ new Promise((_, reject) =>
+ setTimeout(() => reject('Timeout: Operation took longer than 30 seconds'), 30000)
+ )
+ ])
+ } catch (error) {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Unable to push webapp!',
+ error: typeof error == 'string' ? error : 'Unknown Error',
+ final: false
+ })
+ }
+ } else {
+ reply &&
+ reply('logging', {
+ status: true,
+ data: 'Device has the same webapp version or doesnt exist!',
+ final: false
+ })
+ }
+ } catch (error) {
+ if (error instanceof Error) {
+ reply &&
+ reply('logging', {
+ status: false,
+ error: error.message,
+ data: 'Error pushing webapp!',
+ final: false
+ })
+ } else {
+ reply &&
+ reply('logging', {
+ status: false,
+ error: 'Unknown Error',
+ data: 'Error pushing webapp!',
+ final: false
+ })
+ }
+ console.error('Error pushing webapp', error)
}
- reply && reply('logging', { status: true, data: 'Restarting Chromium', final: false })
try {
- await handleAdbCommands(`-s ${deviceId} shell supervisorctl restart chromium`)
+ reply && reply('logging', { status: true, data: 'Restarting Chromium', final: false })
+ await handleAdbCommands(`-s ${deviceId} shell supervisorctl restart chromium`, reply)
} catch (error) {
- reply &&
- reply('logging', {
- status: false,
- data: 'Unable to restart chromium!',
- error: typeof error == 'string' ? error : 'Unknown Error',
- final: false
- })
+ if (error instanceof Error) {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Unable to restart chromium!',
+ error: error.message,
+ final: false
+ })
+ } else {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Unable to restart chromium!',
+ error: 'Unknown Error',
+ final: false
+ })
+ }
+ console.error('Error restarting chromium', error)
}
- reply && reply('logging', { status: true, data: 'Device Configured!', final: true })
}
export const HandlePushWebApp = async (
@@ -117,15 +237,32 @@ export const HandlePushWebApp = async (
try {
const userDataPath = app.getPath('userData')
const extractDir = join(userDataPath, 'webapp')
+
+ const clientExists = await checkForClient(reply)
+ if (!clientExists) {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Unable to push webapp!',
+ error: 'Client not found!',
+ final: false
+ })
+ loggingStore.log(
+ MESSAGE_TYPES.ERROR,
+ '[HandlePushWebApp] Client Not Found! Ensure it is downloaded'
+ )
+ return
+ }
+
let response
- console.log('Remounting...')
reply && reply('logging', { status: true, data: 'Remounting...', final: false })
response = await handleAdbCommands(`-s ${deviceId} shell mount -o remount,rw /`)
- reply && reply('logging', { status: true, data: response || 'Moving...', final: false })
+ reply && reply('logging', { status: true, data: response || 'Writing to tmp...', final: false })
response = await handleAdbCommands(
`-s ${deviceId} shell mv /usr/share/qt-superbird-app/webapp /tmp/webapp-orig`
)
- reply && reply('logging', { status: true, data: response || 'Moving...', final: false })
+ reply &&
+ reply('logging', { status: true, data: response || 'Moving from tmp...', final: false })
response = await handleAdbCommands(
`-s ${deviceId} shell mv /tmp/webapp-orig /usr/share/qt-superbird-app/`
)
@@ -147,7 +284,7 @@ export const HandlePushWebApp = async (
reply('logging', {
status: false,
data: 'There has been an error updating the client manifests ID.',
- final: true,
+ final: false,
error: `${error}`
})
}
@@ -175,29 +312,36 @@ export const HandlePushWebApp = async (
reply('logging', {
status: false,
data: 'There has been an error cleaning the client manifests ID.',
- final: true,
+ final: false,
error: `${error}`
})
}
- reply && reply('logging', { status: true, data: await response, final: true })
+ reply && reply('logging', { status: true, data: await response, final: false })
} catch (Exception) {
- reply &&
- reply('logging', {
- status: false,
- data: 'There has been an error',
- final: true,
- error: `${Exception}`
- })
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- 'HandlePushWebApp encountered the error ' + Exception
- )
+ if (Exception instanceof Error) {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Error while trying to push webapp!',
+ final: false,
+ error: Exception.message
+ })
+ } else {
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Error while trying to push webapp!',
+ final: false,
+ error: `${Exception}`
+ })
+ }
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'HandlePushWebApp encountered the error ' + Exception)
}
}
export const HandleWebappZipFromUrl = async (
- reply: (channel: string, data: ReplyData) => void,
+ reply: ReplyFn | undefined,
zipFileUrl: string
): Promise => {
const userDataPath = app.getPath('userData')
@@ -210,7 +354,7 @@ export const HandleWebappZipFromUrl = async (
const AdmZip = await import('adm-zip')
- reply('logging', { status: true, data: 'Downloading...', final: false })
+ reply && reply('logging', { status: true, data: 'Downloading...', final: false })
const request = net.request(zipFileUrl)
@@ -236,66 +380,68 @@ export const HandleWebappZipFromUrl = async (
// Optionally remove the temporary zip file
fs.unlinkSync(tempZipPath)
-
- console.log(`Successfully extracted ${zipFileUrl} to ${extractDir}`)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`Successfully extracted ${zipFileUrl} to ${extractDir}`
)
// Notify success to the frontend
- reply('logging', { status: true, data: 'Success!', final: true })
+ reply && reply('logging', { status: true, data: 'Success!', final: false })
} catch (error) {
console.error('Error extracting zip file:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error extracting zip file: ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `Error extracting zip file: ${error}`)
// Notify failure to the frontend
- reply('logging', {
- status: false,
- data: 'Failed to extract!',
- final: true,
- error: error instanceof Error ? error.message : String(error)
- })
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Failed to extract!',
+ final: false,
+ error: error instanceof Error ? error.message : String(error)
+ })
}
})
response.on('error', (error) => {
console.error('Error downloading zip file:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error downloading zip file: ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `Error downloading zip file: ${error}`)
// Notify failure to the frontend
- reply('logging', {
- status: false,
- data: 'ERR Downloading file!',
- final: true,
- error: error.message
- })
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'ERR Downloading file!',
+ final: false,
+ error: error.message
+ })
})
} else {
const errorMessage = `Failed to download zip file: ${response.statusCode}`
console.error(errorMessage)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, errorMessage)
+ loggingStore.log(MESSAGE_TYPES.ERROR, errorMessage)
// Notify failure to the frontend
- reply('logging', {
- status: false,
- data: 'Failed to download zip file!',
- final: true,
- error: errorMessage
- })
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Failed to download zip file!',
+ final: false,
+ error: errorMessage
+ })
}
})
request.on('error', (error) => {
console.error('Error sending request:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error sending request: ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `Error sending request: ${error}`)
// Notify failure to the frontend
- reply('logging', {
- status: false,
- data: 'Failed to download zip file!',
- final: true,
- error: error.message
- })
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Failed to download zip file!',
+ final: false,
+ error: error.message
+ })
})
request.end()
@@ -315,7 +461,7 @@ export const handleClientManifestUpdate = async (
await fs.promises.mkdir(extractDir, { recursive: true })
// Read the existing manifest
- const existingManifest = await getClientManifest(true, reply)
+ const existingManifest = await getClientManifest(reply)
// Merge the existing manifest with the partial updates
const updatedManifest: Partial = {
@@ -328,40 +474,56 @@ export const handleClientManifestUpdate = async (
// Write the updated manifest to the file
await fs.promises.writeFile(manifestPath, manifestContent, 'utf8')
- console.log('Manifest file updated successfully')
- reply && reply('logging', { status: true, data: 'Manifest Updated!', final: true })
- dataListener.asyncEmit(
- MESSAGE_TYPES.LOGGING,
- 'DEVICE HANDLER: Manifest file updated successfully'
- )
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Manifest file updated successfully')
+ reply && reply('logging', { status: true, data: 'Manifest Updated!', final: false })
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'DEVICE HANDLER: Manifest file updated successfully')
} catch (error) {
console.error('Error updating manifest file:', error)
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- 'DEVICE HANDLER: Error updating manifest file: ' + error
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'DEVICE HANDLER: Error updating manifest file: ' + error)
+ }
+}
+
+export const checkForClient = async (
+ reply?: (channel: string, data: ReplyData) => void
+): Promise => {
+ reply && reply('logging', { status: true, data: 'Checking for client...', final: false })
+ const userDataPath = app.getPath('userData')
+ const extractDir = join(userDataPath, 'webapp')
+ const manifestPath = join(extractDir, 'manifest.js')
+
+ const manifestExists = fs.existsSync(manifestPath)
+ if (!manifestExists) {
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Manifest file not found')
+ reply &&
+ reply('logging', {
+ status: false,
+ data: 'Manifest file not found',
+ final: false
+ })
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'DEVICE HANDLER: Manifest file not found')
}
+ return manifestExists
}
export const getClientManifest = async (
- utility: boolean = false,
reply?: (channel: string, data: ReplyData) => void
): Promise => {
- console.log('Getting manifest...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Getting manifest...')
const userDataPath = app.getPath('userData')
const manifestPath = join(userDataPath, 'webapp', 'manifest.js')
- console.log('manifestPath: ', manifestPath)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'manifestPath: ' + manifestPath)
if (!fs.existsSync(manifestPath)) {
- console.log('Manifest file not found')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Manifest file not found')
reply &&
reply('logging', {
status: false,
+ error: 'Unable to find the client manifest!',
data: 'Manifest file not found',
- final: true
+ final: false
})
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
- 'DEVICE HANDLER: Client Manifest file not found! (Is the client downloaded?)'
+ 'DEVICE HANDLER: Client is not detected or downloaded! Please download the client! (downloads -> client)'
)
return null
}
@@ -384,14 +546,13 @@ export const getClientManifest = async (
reply('logging', {
status: true,
data: 'Manifest loaded!',
- final: !utility
+ final: false
})
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'DEVICE HANDLER: Manifest file read successfully')
- console.log(manifest)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'DEVICE HANDLER: Manifest file read successfully')
return manifest
} catch (error) {
console.error('Error reading or parsing manifest file:', error)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
'DEVICE HANDLER: Error reading or parsing manifest file: ' + error
)
@@ -399,7 +560,7 @@ export const getClientManifest = async (
reply('logging', {
status: false,
data: 'Unable to read Server Manifest file',
- final: true,
+ final: false,
error: 'Unable to read manifest file' + error
})
return null
@@ -442,7 +603,7 @@ export const SetupProxy = async (
final: false
})
- console.log('Remounting...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Remounting...')
reply('logging', { status: true, data: 'Remounting...', final: false })
response = await handleAdbCommands(`-s ${deviceId} shell mount -o remount,rw /`)
@@ -463,7 +624,7 @@ export const SetupProxy = async (
status: false,
data: 'Error ensuring supervisor include: ' + error,
error: 'Error ensuring supervisor include: ' + error,
- final: true
+ final: false
})
}
@@ -502,14 +663,14 @@ user=root`
reply('logging', {
status: true,
data: response || 'Supervisor updated configuration.',
- final: true
+ final: false
})
// Start the Supervisor program
response = await handleAdbCommands(`-s ${deviceId} shell supervisorctl start setupProxy`)
reply('logging', {
status: true,
data: response || 'Started setup-proxy program.',
- final: true
+ final: false
})
fs.unlinkSync(tempProxyConfPath)
@@ -517,10 +678,11 @@ user=root`
reply('logging', {
status: false,
data: 'There has been an error setting up the proxy.',
- final: true,
+ final: false,
error: `${Exception}`
})
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'SetupProxy encountered the error ' + Exception)
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'SetupProxy encountered the error ' + Exception)
+ throw new Error('SetupProxy encountered the error ' + Exception)
}
}
@@ -624,19 +786,16 @@ export const AppendToSupervisor = async (
reply('logging', {
status: true,
data: 'Supervisor configuration updated and applied.',
- final: true
+ final: false
})
} catch (Exception) {
reply('logging', {
status: false,
data: `Error appending to Supervisor: ${Exception}`,
- final: true,
+ final: false,
error: `${Exception}`
})
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- 'AppendToSupervisor encountered the error ' + Exception
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'AppendToSupervisor encountered the error ' + Exception)
}
}
@@ -712,23 +871,23 @@ files = /etc/supervisor.d/*.conf\n`
reply('logging', {
status: true,
data: response || 'Supervisor updated configuration.',
- final: true
+ final: false
})
} else {
reply('logging', {
status: true,
data: '[include] section already present. No need to update.',
- final: true
+ final: false
})
}
} catch (Exception) {
reply('logging', {
status: false,
data: `Error ensuring Supervisor [include] section: ${Exception}`,
- final: true,
+ final: false,
error: `${Exception}`
})
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
'EnsureSupervisorInclude encountered the error ' + Exception
)
diff --git a/DeskThingServer/src/main/handlers/firewallHandler.ts b/DeskThingServer/src/main/handlers/firewallHandler.ts
index 7924a3e0..e97159ea 100644
--- a/DeskThingServer/src/main/handlers/firewallHandler.ts
+++ b/DeskThingServer/src/main/handlers/firewallHandler.ts
@@ -1,10 +1,10 @@
import { exec } from 'child_process'
import os from 'os'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import loggingStore from '../stores/loggingStore'
import fs from 'fs'
import { join } from 'path'
import { app } from 'electron'
-import { ReplyFn } from '@shared/types'
+import { ReplyFn, MESSAGE_TYPES } from '@shared/types'
// Function to execute shell commands
function runCommand(command: string): Promise {
@@ -45,7 +45,7 @@ async function checkFirewallRuleExists(port: number): Promise {
const result = await runCommand(checkCommand)
return result.trim() === 'true'
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `FIREWALL: Unsupported OS!`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `FIREWALL: Unsupported OS!`)
console.error('Unsupported OS')
return false
}
@@ -65,15 +65,18 @@ async function setupFirewall(port: number, reply?: ReplyFn): Promise {
reply && reply('logging', { status: true, data: 'Checking if rules exist', final: false })
const ruleExists = await checkFirewallRuleExists(port)
if (ruleExists) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`FIREWALL: Firewall rule for port ${port} verified successfully`
)
- console.log(`Firewall rule for port ${port} verified successfully`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `Firewall rule for port ${port} verified successfully`
+ )
reply &&
reply('logging', { status: true, data: 'Verified that the rule exists!', final: false })
} else {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`FIREWALL: Failed to verify firewall rule for port ${port}!`
)
@@ -112,11 +115,11 @@ async function setupFirewall(port: number, reply?: ReplyFn): Promise {
try {
await runCommand(`powershell -ExecutionPolicy Bypass -File "${tempScriptPath}"`)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`FIREWALL: Firewall rules set up successfully on Windows`
)
- console.log('Firewall rules set up successfully on Windows')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Firewall rules set up successfully on Windows')
reply &&
reply('logging', { status: true, data: 'Firewall ran without error', final: false })
@@ -140,11 +143,11 @@ async function setupFirewall(port: number, reply?: ReplyFn): Promise {
`
await runCommand(`echo "${script}" | bash`)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`FIREWALL: Firewall rules set up successfully on Linux`
)
- console.log('Firewall rules set up successfully on Linux')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Firewall rules set up successfully on Linux')
} else if (platform === 'darwin') {
reply &&
reply('logging', {
@@ -165,16 +168,16 @@ async function setupFirewall(port: number, reply?: ReplyFn): Promise {
`
await runCommand(`echo "${script}" | bash`)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`FIREWALL: Firewall rules set up successfully on macOS`
)
- console.log('Firewall rules set up successfully on macOS')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Firewall rules set up successfully on macOS')
} else {
console.error('Unsupported OS')
}
} catch (error) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`FIREWALL: Error encountered trying to setup firewall for ${port}! Run administrator and try again`
)
diff --git a/DeskThingServer/src/main/handlers/githubHandler.ts b/DeskThingServer/src/main/handlers/githubHandler.ts
index fb729e23..36792a0d 100644
--- a/DeskThingServer/src/main/handlers/githubHandler.ts
+++ b/DeskThingServer/src/main/handlers/githubHandler.ts
@@ -1,5 +1,31 @@
import { GithubRelease } from '@shared/types'
+export async function getLatestRelease(repoUrl: string): Promise {
+ try {
+ // Extract the owner and repo from the URL
+ const repoMatch = repoUrl.match(/github\.com\/([^/]+)\/([^/]+)/)
+ if (!repoMatch) {
+ throw new Error('Invalid GitHub repository URL')
+ }
+
+ const owner = repoMatch[1]
+ const repo = repoMatch[2]
+
+ const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases/latest`
+ const response = await fetch(apiUrl)
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`)
+ }
+
+ const release = await response.json()
+ return release
+ } catch (error) {
+ console.error('Error fetching latest release:', error)
+ throw error
+ }
+}
+
export async function getReleases(repoUrl: string): Promise {
try {
// Extract the owner and repo from the URL
diff --git a/DeskThingServer/src/main/handlers/musicHandler.ts b/DeskThingServer/src/main/handlers/musicHandler.ts
index 0d9cc27f..2b4c6cd9 100644
--- a/DeskThingServer/src/main/handlers/musicHandler.ts
+++ b/DeskThingServer/src/main/handlers/musicHandler.ts
@@ -1,8 +1,9 @@
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import loggingStore from '../stores/loggingStore'
import settingsStore from '../stores/settingsStore'
-import { Settings, SocketData } from '@shared/types'
+import { Settings, SocketData, MESSAGE_TYPES } from '@shared/types'
import { sendMessageToApp } from '../services/apps'
import { getAppByName } from './configHandler'
+import appState from '../services/apps/appState'
export class MusicHandler {
private static instance: MusicHandler
@@ -22,10 +23,13 @@ export class MusicHandler {
private async initializeRefreshInterval(): Promise {
const settings = await settingsStore.getSettings() // Get from your settings store
+ this.currentApp = settings.playbackLocation || 'none'
+
this.updateRefreshInterval(settings.refreshInterval)
- dataListener.on(MESSAGE_TYPES.SETTINGS, this.handleSettingsUpdate)
+ settingsStore.addListener(this.handleSettingsUpdate.bind(this))
setTimeout(() => {
+ loggingStore.log(MESSAGE_TYPES.DEBUG, '[MusicHandler]: Initialized')
this.refreshMusicData()
}, 5000) // Delay to ensure settings are loaded
}
@@ -33,12 +37,12 @@ export class MusicHandler {
private handleSettingsUpdate = (settings: Settings): void => {
this.updateRefreshInterval(settings.refreshInterval)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
- `[MusicHandler]: Received settings update - checking for changes | Playback location: ${settings.playbackLocation}`
+ `[MusicHandler]: Received settings update - checking for changes | Playback location: ${this.currentApp} -> ${settings.playbackLocation}`
)
if (settings.playbackLocation) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`[MusicHandler]: Setting restarting to use ${settings.playbackLocation}`
)
@@ -53,7 +57,13 @@ export class MusicHandler {
}
if (refreshRate < 0) {
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `[MusicHandler]: Cancelling Refresh Interval!`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `[MusicHandler]: Cancelling Refresh Interval!`)
+ return
+ } else if (refreshRate < 5) {
+ loggingStore.log(
+ MESSAGE_TYPES.WARNING,
+ `[MusicHandler]: Refresh Interval is ${refreshRate}! Performance may be impacted`
+ )
return
}
@@ -62,66 +72,134 @@ export class MusicHandler {
}, refreshRate)
}
- private async refreshMusicData(): Promise {
- if (!this.currentApp || this.currentApp.length == 0) {
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- `[MusicHandler]: No playback location set! Go to settings -> Music to set the playback location!`
+ private async findCurrentPlaybackSource(): Promise {
+ const Apps = appState.getAllBase()
+
+ const audioSource = Apps.find((app) => app.manifest?.isAudioSource)
+
+ if (audioSource) {
+ loggingStore.log(
+ MESSAGE_TYPES.WARNING,
+ `[MusicHandler]: Found ${audioSource.name} as an audio source automatically. Applying.`
)
- return
+ return audioSource.name
+ } else {
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `[MusicHandler]: Unable to automatically set an audio source. No app found!`
+ )
+ return null
+ }
+ }
+
+ private async getPlaybackSource(): Promise {
+ if (this.currentApp == 'disabled') {
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `[MusicHandler]: Music is disabled! Cancelling refresh`
+ )
+ const settings = await settingsStore.getSettings()
+ if (settings.refreshInterval > 0) {
+ settingsStore.updateSetting('refreshInterval', -1)
+ }
+ return null
+ }
+
+ if (this.currentApp == 'none') {
+ const app = await this.findCurrentPlaybackSource()
+ if (app) {
+ this.currentApp = app
+ settingsStore.updateSetting('playbackLocation', app)
+ return app
+ } else {
+ loggingStore.log(
+ MESSAGE_TYPES.ERROR,
+ `[MusicHandler]: No Audiosource Found! Go to Downloads -> Apps and download an audio source! (Spotify, MediaWin, GMP, etc)`
+ )
+ return null
+ }
+ }
+
+ if (!this.currentApp || this.currentApp.length == 0) {
+ // Attempt to get audiosource from settings
+ const currentApp = (await settingsStore.getSettings()).playbackLocation
+ if (!currentApp || currentApp.length == 0) {
+ loggingStore.log(
+ MESSAGE_TYPES.ERROR,
+ `[MusicHandler]: No playback location set! Go to settings -> Music to set the playback location!`
+ )
+ return null
+ } else {
+ loggingStore.log(
+ MESSAGE_TYPES.WARNING,
+ `[MusicHandler]: Playback location was not set! Setting to ${currentApp}`
+ )
+ this.currentApp = currentApp
+ }
}
const app = await getAppByName(this.currentApp)
if (!app || app.running == false) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
- `[MusicHandler]: App ${this.currentApp} not found or not running!!`
+ `[MusicHandler]: App ${this.currentApp} is not found or not running!`
)
+ return null
+ }
+
+ return this.currentApp
+ }
+
+ private async refreshMusicData(): Promise {
+ const currentApp = await this.getPlaybackSource()
+
+ if (!currentApp) {
+ return
}
try {
- await sendMessageToApp(this.currentApp, { type: 'get', request: 'refresh', payload: '' })
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `[MusicHandler]: Refreshing Music Data!`)
+ await sendMessageToApp(currentApp, { type: 'get', request: 'refresh', payload: '' })
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `[MusicHandler]: Refreshing Music Data!`)
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `[MusicHandler]: Music refresh failed: ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `[MusicHandler]: Music refresh failed: ${error}`)
}
}
- public async handleClientRequest(request: SocketData): Promise {
- if (!this.currentApp) {
- const settings = await settingsStore.getSettings()
- if (settings.playbackLocation) {
- this.currentApp = settings.playbackLocation
- } else {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `[MusicHandler]: No playback location set!`)
- return
- }
- }
-
- if (this.currentApp == 'none') {
- dataListener.asyncEmit(
+ public async setAudioSource(source: string): Promise {
+ if (source.length == 0) {
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
- `[MusicHandler]: Playback location is 'none' ! Go to settings -> Music to set the playback location!`
+ `[MusicHandler]: Unable to update playback location. No playback location passed!`
)
return
}
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `[MusicHandler]: Setting Playback Location to ${source}`
+ )
+ this.currentApp = source
+ }
+
+ public async handleClientRequest(request: SocketData): Promise {
+ const currentApp = await this.getPlaybackSource()
+
+ if (!currentApp) {
+ return
+ }
if (request.app != 'music' && request.app != 'utility') return
if (request.app == 'utility') {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`[MusicHandler]: Legacy Name called! Support for this will be dropped in future updates. Migrate your app to use 'music' instead!`
)
}
- dataListener.asyncEmit(
- MESSAGE_TYPES.LOGGING,
- `[MusicHandler]: ${request.type} ${request.request}`
- )
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `[MusicHandler]: ${request.type} ${request.request}`)
- sendMessageToApp(this.currentApp, {
+ sendMessageToApp(currentApp, {
type: request.type,
request: request.request,
payload: request.payload
diff --git a/DeskThingServer/src/main/handlers/utilityHandler.ts b/DeskThingServer/src/main/handlers/utilityHandler.ts
index 53b64fac..2efb75a2 100644
--- a/DeskThingServer/src/main/handlers/utilityHandler.ts
+++ b/DeskThingServer/src/main/handlers/utilityHandler.ts
@@ -1,13 +1,20 @@
-import { ButtonMapping, Client, GithubRelease, Settings } from '@shared/types'
-import { ReplyFn, UtilityIPCData } from '@shared/types/ipcTypes'
+import {
+ ReplyFn,
+ UtilityIPCData,
+ ButtonMapping,
+ Client,
+ MESSAGE_TYPES,
+ GithubRelease,
+ Log,
+ Settings
+} from '@shared/types'
import ConnectionStore from '../stores/connectionsStore'
import settingsStore from '../stores/settingsStore'
import { getReleases } from './githubHandler'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import loggingStore from '../stores/loggingStore'
import path from 'path'
import { shell, app, dialog } from 'electron'
import keyMapStore from '../stores/keyMapStore'
-import logger from '../utils/logger'
import { setupFirewall } from './firewallHandler'
import { disconnectClient } from '../services/client/clientCom'
import { restartServer } from '../services/client/websocket'
@@ -18,11 +25,19 @@ export const utilityHandler: Record<
data: UtilityIPCData,
replyFn: ReplyFn
) => Promise<
- void | string | Client[] | boolean | string[] | Settings | GithubRelease[] | ButtonMapping
+ | void
+ | string
+ | Client[]
+ | boolean
+ | string[]
+ | Settings
+ | GithubRelease[]
+ | ButtonMapping
+ | Log[]
>
> = {
ping: async () => {
- console.log('Pinged! pong')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Pinged! pong')
return 'pong'
},
zip: async (data): Promise => {
@@ -63,14 +78,18 @@ export const utilityHandler: Record<
try {
return await getReleases(data.payload)
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, error)
+ if (error instanceof Error) {
+ loggingStore.log(MESSAGE_TYPES.ERROR, error.message)
+ } else {
+ loggingStore.log(MESSAGE_TYPES.ERROR, String(error))
+ }
return []
}
},
logs: async (data) => {
switch (data.request) {
case 'get':
- return await logger.getLogs()
+ return await loggingStore.getLogs()
default:
return
}
@@ -132,14 +151,13 @@ const refreshFirewall = async (replyFn: ReplyFn): Promise => {
replyFn('logging', { status: true, data: 'Refreshing Firewall', final: false })
const payload = (await settingsStore.getSettings()) as Settings
if (payload) {
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, '[firewall] Setting up firewall')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, '[firewall] Setting up firewall')
try {
await setupFirewall(payload.devicePort, replyFn)
} catch (firewallError) {
- console.log(firewallError)
if (!(firewallError instanceof Error)) return
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `FIREWALL: ${firewallError.message}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `FIREWALL: ${firewallError.message}`)
replyFn('logging', {
status: false,
data: 'Error in firewall',
@@ -149,7 +167,7 @@ const refreshFirewall = async (replyFn: ReplyFn): Promise => {
return
}
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, '[firewall] No settings found!')
+ loggingStore.log(MESSAGE_TYPES.ERROR, '[firewall] No settings found!')
replyFn('logging', {
status: false,
data: 'Error in firewall',
@@ -158,7 +176,7 @@ const refreshFirewall = async (replyFn: ReplyFn): Promise => {
})
}
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'SERVER: [firewall] Error saving manifest' + error)
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'SERVER: [firewall] Error saving manifest' + error)
console.error('[Firewall] Error setting client manifest:', error)
if (error instanceof Error) {
replyFn('logging', { status: false, data: 'Unfinished', error: error.message, final: true })
diff --git a/DeskThingServer/src/main/index.ts b/DeskThingServer/src/main/index.ts
index d5c65046..08dfa4c6 100644
--- a/DeskThingServer/src/main/index.ts
+++ b/DeskThingServer/src/main/index.ts
@@ -1,4 +1,4 @@
-import { AppIPCData, AuthScopes, Client, UtilityIPCData } from '@shared/types'
+import { AppIPCData, AuthScopes, Client, UtilityIPCData, MESSAGE_TYPES } from '@shared/types'
import { app, shell, BrowserWindow, ipcMain, Tray, Menu, nativeImage } from 'electron'
import { join, resolve } from 'path'
import icon from '../../resources/icon.png?asset'
@@ -213,17 +213,16 @@ async function initializeDoc(): Promise {
}
async function setupIpcHandlers(): Promise {
- const dataListener = (await import('./utils/events')).default
- const { MESSAGE_TYPES } = await import('./utils/events')
+ const loggingStore = (await import('./stores/loggingStore')).default
const { appHandler } = await import('./handlers/appHandler')
const { clientHandler } = await import('./handlers/clientHandler')
const { utilityHandler } = await import('./handlers/utilityHandler')
- const { ResponseLogger } = await import('./utils/events')
+ const { ResponseLogger } = await import('./stores/loggingStore')
const defaultHandler = async (data: AppIPCData): Promise => {
console.error(`No handler implemented for type: ${data.type} ${data}`)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `No handler implemented for type: ${data.type}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `No handler implemented for type: ${data.type}`)
}
ipcMain.handle('APPS', async (event, data: AppIPCData) => {
@@ -238,7 +237,7 @@ async function setupIpcHandlers(): Promise {
}
} catch (error) {
console.error('Error in IPC handler:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error in IPC handler: ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `Error in IPC handler: ${error}`)
}
})
@@ -254,12 +253,11 @@ async function setupIpcHandlers(): Promise {
}
} catch (error) {
console.error('Error in IPC handler:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error in IPC handler: ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `Error in IPC handler: ${error}`)
}
})
ipcMain.handle('UTILITY', async (event, data: UtilityIPCData) => {
- console.log('Received IPC data:', data)
const handler = utilityHandler[data.type] || defaultHandler
const replyFn = ResponseLogger(event.sender.send.bind(event.sender))
@@ -272,19 +270,13 @@ async function setupIpcHandlers(): Promise {
}
} catch (error) {
console.error('Error in IPC handler:', error)
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error in IPC handler: ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `Error in IPC handler: ${error}`)
}
})
- dataListener.on(MESSAGE_TYPES.ERROR, (errorData) => {
- sendIpcData('error', errorData)
- })
- dataListener.on(MESSAGE_TYPES.LOGGING, (errorData) => {
+ loggingStore.addListener((errorData) => {
sendIpcData('log', errorData)
})
- dataListener.on(MESSAGE_TYPES.MESSAGE, (errorData) => {
- sendIpcData('message', errorData)
- })
ConnectionStore.on((clients: Client[]) => {
sendIpcData('connections', { status: true, data: clients.length, final: true })
sendIpcData('clients', { status: true, data: clients, final: true })
@@ -292,7 +284,7 @@ async function setupIpcHandlers(): Promise {
ConnectionStore.onDevice((devices: string[]) => {
sendIpcData('adbdevices', { status: true, data: devices, final: true })
})
- dataListener.on(MESSAGE_TYPES.SETTINGS, (newSettings) => {
+ settingsStore.addListener((newSettings) => {
sendIpcData('settings-updated', newSettings)
})
}
@@ -367,8 +359,6 @@ function handleUrl(url: string | undefined): void {
if (url && url.startsWith('deskthing://')) {
const path = url.replace('deskthing://', '')
- console.log('Handling URL:', url, path)
-
if (mainWindow) {
mainWindow.webContents.send('handle-protocol-url', path)
}
diff --git a/DeskThingServer/src/main/services/apps/appCommunication.ts b/DeskThingServer/src/main/services/apps/appCommunication.ts
index c643b348..fea47498 100644
--- a/DeskThingServer/src/main/services/apps/appCommunication.ts
+++ b/DeskThingServer/src/main/services/apps/appCommunication.ts
@@ -1,6 +1,6 @@
import { openAuthWindow, sendIpcAuthMessage } from '../..'
-import { AuthScopes, IncomingData, Key, Action, ToClientType } from '@shared/types'
-import dataListener, { MESSAGE_TYPES } from '../../utils/events'
+import { AuthScopes, MESSAGE_TYPES, IncomingData, Key, Action, ToClientType } from '@shared/types'
+import loggingStore from '../../stores/loggingStore'
import { ipcMain } from 'electron'
/**
@@ -18,7 +18,7 @@ export async function handleDataFromApp(app: string, appData: IncomingData): Pro
switch (appData.type) {
case 'message':
- dataListener.asyncEmit(MESSAGE_TYPES.MESSAGE, appData.payload)
+ loggingStore.log(MESSAGE_TYPES.MESSAGE, appData.payload, app.toUpperCase())
break
case 'get':
switch (appData.request) {
@@ -67,7 +67,7 @@ export async function handleDataFromApp(app: string, appData: IncomingData): Pro
if (appData.payload && appData.request) {
sendMessageToApp(appData.request, appData.payload)
} else {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`${app.toUpperCase()}: App data malformed`,
appData.payload
@@ -75,10 +75,10 @@ export async function handleDataFromApp(app: string, appData: IncomingData): Pro
}
break
case 'error':
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `${app.toUpperCase()}: ${appData.payload}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `${appData.payload}`, app.toUpperCase())
break
case 'log':
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `${app.toUpperCase()}: ${appData.payload}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `${appData.payload}`, app.toUpperCase())
break
case 'button':
if (appData.request == 'add') {
@@ -90,16 +90,16 @@ export async function handleDataFromApp(app: string, appData: IncomingData): Pro
source: app,
version: appData.payload.version || '0.0.0',
enabled: true,
- flavors: appData.payload.flavors || []
+ Modes: appData.payload.Modes || []
}
keyMapStore.addKey(Key)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`${app.toUpperCase()}: Added Button Successfully`
)
}
} catch (Error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `${app.toUpperCase()}: ${Error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `${app.toUpperCase()}: ${Error}`)
}
} else if (appData.request == 'remove') {
keyMapStore.removeKey(appData.payload.id)
@@ -122,13 +122,13 @@ export async function handleDataFromApp(app: string, appData: IncomingData): Pro
source: app
}
keyMapStore.addAction(Action)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`${app.toUpperCase()}: Added Action Successfully`
)
}
} catch (Error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `${app.toUpperCase()}: ${Error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `${app.toUpperCase()}: ${Error}`)
}
break
case 'remove':
@@ -172,7 +172,7 @@ export async function requestUserInput(appName: string, scope: AuthScopes): Prom
export async function sendMessageToApp(appName: string, data: IncomingData): Promise {
const { AppHandler } = await import('./appState')
const appHandler = AppHandler.getInstance()
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`[sendMessageToApp] Sending message to ${appName} with ${data.type}`
)
@@ -181,7 +181,7 @@ export async function sendMessageToApp(appName: string, data: IncomingData): Pro
if (app && typeof app.func.toClient === 'function') {
;(app.func.toClient as ToClientType)(data)
} else {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`SERVER: App ${appName} not found or does not have toClient function. (is it running?)`
)
diff --git a/DeskThingServer/src/main/services/apps/appInstaller.ts b/DeskThingServer/src/main/services/apps/appInstaller.ts
index 8f8ab25c..c10fe039 100644
--- a/DeskThingServer/src/main/services/apps/appInstaller.ts
+++ b/DeskThingServer/src/main/services/apps/appInstaller.ts
@@ -1,13 +1,14 @@
import { join, resolve } from 'path'
import { app } from 'electron'
-import dataListener, { MESSAGE_TYPES } from '../../utils/events'
+import loggingStore from '../../stores/loggingStore'
import {
IncomingData,
ToClientType,
Response,
Manifest,
DeskThing,
- AppReturnData
+ AppReturnData,
+ MESSAGE_TYPES
} from '@shared/types'
import { getAppFilePath, getManifest } from './appUtils'
import { mkdirSync, existsSync, rmSync, promises } from 'node:fs'
@@ -29,11 +30,14 @@ import { handleDataFromApp } from './appCommunication'
export async function handleZip(zipFilePath: string, reply?): Promise {
const { getManifest } = await import('./appUtils')
try {
- console.log(`[handleZip] Extracting ${zipFilePath}...`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `[handleZip] Extracting ${zipFilePath}...`)
const appPath = join(app.getPath('userData'), 'apps') // Extract to user data folder
// Create the extraction directory if it doesn't exist
if (!existsSync(appPath)) {
- console.log(`[handleZip] Creating extraction directory at ${appPath}...`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `[handleZip] Creating extraction directory at ${appPath}...`
+ )
mkdirSync(appPath, { recursive: true })
}
@@ -42,7 +46,10 @@ export async function handleZip(zipFilePath: string, reply?): Promise((resolve, reject) => {
try {
- console.log(`[handleZip] Extracting app...`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `[handleZip] Extracting app...`)
const zip = new AdmZip.default(zipFilePath)
zip.getEntries().forEach((entry) => {
if (entry.isDirectory) {
- console.log(`[handleZip] Skipping directory ${entry.entryName}`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `[handleZip] Skipping directory ${entry.entryName}`
+ )
} else {
- console.log(`[handleZip] Extracting file ${entry.entryName}`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `[handleZip] Extracting file ${entry.entryName}`
+ )
zip.extractEntryTo(entry, tempDir, true, true)
}
})
zip.extractAllTo(tempDir, true)
- console.log(`[handleZip] App extracted to ${tempDir}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `[handleZip] App extracted to ${tempDir}`)
resolve()
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `SERVER: Error extracting ${zipFilePath}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `SERVER: Error extracting ${zipFilePath}`)
reply && reply('logging', { status: false, data: 'Extraction failed!', final: true })
reject(error)
}
@@ -80,12 +93,9 @@ export async function handleZip(zipFilePath: string, reply?): Promise {
if (app && typeof app.func.start === 'function') {
app.func.start()
} else {
- console.log(`App ${appName} not found.`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `App ${appName} not found.`)
}
const DeskThing = await getDeskThing(appName)
@@ -261,7 +271,6 @@ export async function run(appName: string): Promise {
const manifestResponse: Response = await DeskThing.getManifest()
- // Check if all required apps are running (I know this can be better...)
const manifest = await handleManifest(appName, manifestResponse)
if (!manifest) {
@@ -271,17 +280,17 @@ export async function run(appName: string): Promise {
appState.appendManifest(manifest, appName)
}
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `Configuring ${appName}!`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `Configuring ${appName}!`)
await setupFunctions(appName, DeskThing)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `Running ${appName}!`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `Running ${appName}!`)
const result = await start(appName)
if (!result) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `App ${appName} failed to start!`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `App ${appName} failed to start!`)
}
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error running app ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `Error running app ${error}`)
console.error('Error running app:', error)
}
}
@@ -297,17 +306,14 @@ export const start = async (appName: string): Promise => {
const appInstance = appState.get(appName)
if (!appInstance || !appInstance.func.start || appInstance.func.start === undefined) {
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- `App ${appName} not found. or not started correctly`
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, `App ${appName} not found. or not started correctly`)
return false
}
// Check if all required apps are running
const manifest = appInstance.manifest
if (!manifest) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `App ${appName} not found.`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `App ${appName} not found.`)
return false
}
@@ -315,7 +321,7 @@ export const start = async (appName: string): Promise => {
const requiredApps = manifest.requires || []
for (const requiredApp of requiredApps) {
if (!appState.getOrder().includes(requiredApp) && requiredApp.length > 2) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`Unable to run ${appName}! This app requires '${requiredApp}' to be enabled and running.`
)
@@ -328,20 +334,20 @@ export const start = async (appName: string): Promise => {
try {
const startResponse: Response = await appInstance.func.start()
if (startResponse.status == 200) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.MESSAGE,
`App ${appName} started successfully with response ${startResponse.data.message}`
)
return true
} else {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`App ${appName} failed to start with response ${startResponse.data.message}`
)
return false
}
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `Error starting app ${error}`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `Error starting app ${error}`)
console.error('Error starting app:', error)
}
return false
@@ -361,7 +367,7 @@ const setupFunctions = async (appName: string, DeskThing: DeskThing): Promise => {
return DeskThing.start({
toServer: (data) => handleDataFromApp(appName, data),
- SysEvents: (event: string, listener: (...args: string[]) => void) => {
- dataListener.on(event, listener) // Add event listener
- return () => dataListener.removeListener(event, listener)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ SysEvents: (_event: string, _listener: (...args: string[]) => void) => {
+ return () => {
+ /* do something with this to let apps listen for server events like apps being added or settings being changed */
+ }
}
})
}
@@ -387,7 +395,7 @@ const setupFunctions = async (appName: string, DeskThing: DeskThing): Promise => {
} else if (existsSync(appEntryPointCjs)) {
appEntryPoint = appEntryPointCjs
} else {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`Entry point for app ${appName} not found. (Does it have an index.js file?)`
)
diff --git a/DeskThingServer/src/main/services/apps/appManager.ts b/DeskThingServer/src/main/services/apps/appManager.ts
index 5df36a6a..8e1a3ca2 100644
--- a/DeskThingServer/src/main/services/apps/appManager.ts
+++ b/DeskThingServer/src/main/services/apps/appManager.ts
@@ -1,6 +1,6 @@
import { rmSync, readdirSync, statSync, existsSync } from 'node:fs'
-import dataListener, { MESSAGE_TYPES } from '../../utils/events'
-
+import loggingStore from '../../stores/loggingStore'
+import { MESSAGE_TYPES } from '@shared/types'
export async function clearCache(appName: string): Promise {
try {
const { join } = await import('path')
@@ -21,24 +21,41 @@ export async function clearCache(appName: string): Promise {
const resolvedPath = require.resolve(itemPath)
if (require.cache[resolvedPath]) {
delete require.cache[resolvedPath]
- dataListener.asyncEmit(
- MESSAGE_TYPES.LOGGING,
- `SERVER: Removed ${resolvedPath} from cache`
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `SERVER: Removed ${resolvedPath} from cache`)
+ } else {
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `SERVER: ${resolvedPath} not in cache!`)
+ }
+ } catch (error) {
+ if (error instanceof Error) {
+ loggingStore.log(
+ MESSAGE_TYPES.ERROR,
+ `SERVER: Error clearing cache for ${itemPath}:`,
+ error.message
)
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `SERVER: ${resolvedPath} not in cache!`)
+ loggingStore.log(
+ MESSAGE_TYPES.ERROR,
+ `SERVER: Error clearing cache for ${itemPath}:`,
+ String(error)
+ )
}
- } catch (e) {
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `SERVER: clearCache Error`, e)
}
}
})
} catch (error) {
- dataListener.asyncEmit(
- MESSAGE_TYPES.LOGGING,
- `SERVER: Error clearing cache for directory ${appName}:`,
- error
- )
+ if (error instanceof Error) {
+ loggingStore.log(
+ MESSAGE_TYPES.ERROR,
+ `SERVER: Error clearing cache for directory ${appName}:`,
+ error.message
+ )
+ } else {
+ loggingStore.log(
+ MESSAGE_TYPES.ERROR,
+ `SERVER: Error clearing cache for directory ${appName}:`,
+ String(error)
+ )
+ }
}
}
@@ -49,7 +66,7 @@ export async function clearCache(appName: string): Promise {
*/
export async function purgeApp(appName: string): Promise {
try {
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `SERVER: Purging App ${appName}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `SERVER: Purging App ${appName}`)
const { purgeAppData } = await import('../../handlers/dataHandler')
const { purgeAppConfig } = await import('../../handlers/configHandler')
@@ -74,9 +91,9 @@ export async function purgeApp(appName: string): Promise {
// Remove the file from filesystem
if (existsSync(dir)) {
await rmSync(dir, { recursive: true, force: true })
- console.log(`Purged all data for app ${appName}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `Purged all data for app ${appName}`)
}
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `SERVER: Purged App ${appName}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `SERVER: Purged App ${appName}`)
} catch (error) {
console.error(`Error purging app data for ${appName}`, error)
}
diff --git a/DeskThingServer/src/main/services/apps/appRunner.ts b/DeskThingServer/src/main/services/apps/appRunner.ts
index 9d279db2..627ce125 100644
--- a/DeskThingServer/src/main/services/apps/appRunner.ts
+++ b/DeskThingServer/src/main/services/apps/appRunner.ts
@@ -1,4 +1,5 @@
-import dataListener, { MESSAGE_TYPES } from '../../utils/events'
+import loggingStore from '../../stores/loggingStore'
+import { MESSAGE_TYPES } from '@shared/types'
/**
* Loads and runs all enabled apps from appData.json
@@ -12,12 +13,12 @@ export async function loadAndRunEnabledApps(): Promise {
try {
const appInstances = appHandler.getAll()
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'SERVER: Loaded apps config. Running apps...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'SERVER: Loaded apps config. Running apps...')
const enabledApps = appInstances.filter((appConfig) => appConfig.enabled === true)
await Promise.all(
enabledApps.map(async (appConfig) => {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`SERVER: Automatically running app ${appConfig.name}`
)
@@ -29,15 +30,12 @@ export async function loadAndRunEnabledApps(): Promise {
await Promise.all(
failedApps.map(async (failedApp) => {
- dataListener.asyncEmit(
- MESSAGE_TYPES.LOGGING,
- `SERVER: Attempting to run ${failedApp.name} again`
- )
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `SERVER: Attempting to run ${failedApp.name} again`)
await appHandler.run(failedApp.name)
})
)
} catch (error) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`SERVER: Error loading and running enabled apps: ${error}`
)
diff --git a/DeskThingServer/src/main/services/apps/appState.ts b/DeskThingServer/src/main/services/apps/appState.ts
index e494ded1..2eeae632 100644
--- a/DeskThingServer/src/main/services/apps/appState.ts
+++ b/DeskThingServer/src/main/services/apps/appState.ts
@@ -1,5 +1,6 @@
-import { App, AppInstance, Manifest, AppReturnData } from '@shared/types'
+import { App, AppInstance, Manifest, AppReturnData, MESSAGE_TYPES } from '@shared/types'
import { sendConfigData, sendSettingsData } from '../client/clientCom'
+import loggingStore from '../../stores/loggingStore'
/**
* TODO: Sync with the file
@@ -32,9 +33,11 @@ export class AppHandler {
* Loads the apps from file
*/
async loadApps(): Promise {
- console.log('[appState] [loadApps]: Loading apps...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, '[appState] [loadApps]: Loading apps...')
const { getAppData } = await import('../../handlers/configHandler')
+
const data = await getAppData()
+
data.apps.forEach((app) => {
if (this.apps[app.name]) {
// Update existing app instance with stored data
diff --git a/DeskThingServer/src/main/services/apps/appUtils.ts b/DeskThingServer/src/main/services/apps/appUtils.ts
index 3999ffae..272249d5 100644
--- a/DeskThingServer/src/main/services/apps/appUtils.ts
+++ b/DeskThingServer/src/main/services/apps/appUtils.ts
@@ -1,7 +1,7 @@
-import { Manifest } from '@shared/types'
+import { Manifest, MESSAGE_TYPES } from '@shared/types'
import { join } from 'path'
import { existsSync, promises } from 'node:fs'
-import dataListener, { MESSAGE_TYPES } from '../../utils/events'
+import loggingStore from '../../stores/loggingStore'
import { app } from 'electron'
let devAppPath: string
@@ -16,7 +16,7 @@ let devAppPath: string
*/
export async function getManifest(fileLocation: string): Promise {
try {
- console.log('[getManifest] Getting manifest for app')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, '[getManifest] Getting manifest for app')
const manifestPath = join(fileLocation, 'manifest.json')
if (!existsSync(manifestPath)) {
throw new Error('manifest.json not found after extraction')
@@ -39,7 +39,7 @@ export async function getManifest(fileLocation: string): Promise => {
- console.log(`Sending message to clients: ${JSON.stringify(data)}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `Sending message to clients: ${JSON.stringify(data)}`)
if (server) {
server.clients.forEach((client) => {
if (client.readyState === 1) {
@@ -15,7 +15,7 @@ export const sendMessageToClients = async (data: SocketData): Promise => {
}
})
} else {
- dataListener.emit(MESSAGE_TYPES.LOGGING, 'WSOCKET: No server running - setting one up')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: No server running - setting one up')
}
}
@@ -25,10 +25,13 @@ export const disconnectClient = (connectionId: string): void => {
if (client && server) {
client.socket.terminate()
Clients.splice(Clients.indexOf(client), 1)
- console.log(`Forcibly disconnected client: ${connectionId}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `Forcibly disconnected client: ${connectionId}`)
connectionsStore.removeClient(connectionId)
} else {
- console.log(`Client not found or server not running: ${connectionId}`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `Client not found or server not running: ${connectionId}`
+ )
}
}
@@ -54,9 +57,11 @@ export const sendConfigData = async (clientId?: string): Promise => {
try {
const appData = await appState.getAllBase()
- sendMessageToClient(clientId, { app: 'client', type: 'config', payload: appData })
+ const filteredAppData = appData.filter((app) => app.manifest?.isWebApp !== false)
- console.log('WSOCKET: Preferences sent!')
+ sendMessageToClient(clientId, { app: 'client', type: 'config', payload: filteredAppData })
+
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: Preferences sent!')
} catch (error) {
console.error('WSOCKET: Error getting config data:', error)
sendError(clientId, 'WSOCKET: Error getting config data')
@@ -87,7 +92,7 @@ export const sendSettingsData = async (clientId?: string): Promise => {
}
sendMessageToClient(clientId, { app: 'client', type: 'settings', payload: settings })
- console.log('WSOCKET: Preferences sent!')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: Preferences sent!')
} catch (error) {
console.error('WSOCKET: Error getting config data:', error)
sendError(clientId, 'WSOCKET: Error getting config data')
@@ -100,8 +105,8 @@ export const sendMappings = async (clientId?: string): Promise => {
sendMessageToClient(clientId, { app: 'client', type: 'button_mappings', payload: mappings })
- console.log('WSOCKET: Button mappings sent!')
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Client has been sent button maps!`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: Button mappings sent!')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Client has been sent button maps!`)
} catch (error) {
console.error('WSOCKET: Error getting button mappings:', error)
sendError(clientId, 'WSOCKET: Error getting button mappings')
diff --git a/DeskThingServer/src/main/services/client/clientUtils.ts b/DeskThingServer/src/main/services/client/clientUtils.ts
index 67a4cf18..f84c6936 100644
--- a/DeskThingServer/src/main/services/client/clientUtils.ts
+++ b/DeskThingServer/src/main/services/client/clientUtils.ts
@@ -30,7 +30,6 @@ export const sendTime = async (): Promise => {
const formattedMinutes = minutes < 10 ? '0' + minutes : minutes
const time = `${formattedHours}:${formattedMinutes} ${ampm}`
sendMessageToClients({ app: 'client', type: 'time', payload: time })
- console.log(time)
}
const initializeTimer = async (): Promise => {
diff --git a/DeskThingServer/src/main/services/client/expressServer.ts b/DeskThingServer/src/main/services/client/expressServer.ts
index 9e3da617..4b4101b5 100644
--- a/DeskThingServer/src/main/services/client/expressServer.ts
+++ b/DeskThingServer/src/main/services/client/expressServer.ts
@@ -1,4 +1,5 @@
-import dataListener, { MESSAGE_TYPES } from '../../utils/events'
+import loggingStore from '../../stores/loggingStore'
+import { MESSAGE_TYPES } from '@shared/types'
import { app as electronApp } from 'electron'
import { join } from 'path'
import { getAppFilePath } from '../apps'
@@ -24,47 +25,57 @@ export const setupExpressServer = async (expressApp: express.Application): Promi
): void => {
const userDataPath = electronApp.getPath('userData')
const webAppDir = join(userDataPath, 'webapp')
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Serving ${appName} from ${webAppDir}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Serving ${appName} from ${webAppDir}`)
const clientIp = req.hostname
- console.log(`WEBSOCKET: Serving ${appName} from ${webAppDir} to ${clientIp}`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `WEBSOCKET: Serving ${appName} from ${webAppDir} to ${clientIp}`
+ )
+ try {
+ if (req.path.endsWith('manifest.js')) {
+ const manifestPath = join(webAppDir, 'manifest.js')
+ if (fs.existsSync(manifestPath)) {
+ let manifestContent = fs.readFileSync(manifestPath, 'utf8')
- if (req.path.endsWith('manifest.js')) {
- const manifestPath = join(webAppDir, 'manifest.js')
- if (fs.existsSync(manifestPath)) {
- let manifestContent = fs.readFileSync(manifestPath, 'utf8')
+ manifestContent = manifestContent.replace(
+ /"ip":\s*".*?"/,
+ `"ip": "${clientIp === '127.0.0.1' ? 'localhost' : clientIp}"`
+ )
- manifestContent = manifestContent.replace(
- /"ip":\s*".*?"/,
- `"ip": "${clientIp === '127.0.0.1' ? 'localhost' : clientIp}"`
- )
-
- res.type('application/javascript').send(manifestContent)
+ res.type('application/javascript').send(manifestContent)
+ } else {
+ res.status(404).send('Manifest not found')
+ }
} else {
- res.status(404).send('Manifest not found')
+ if (fs.existsSync(webAppDir)) {
+ express.static(webAppDir)(req, res, next)
+ } else {
+ res.status(404).send('App not found')
+ }
}
- } else {
- if (fs.existsSync(webAppDir)) {
- express.static(webAppDir)(req, res, next)
+ } catch (error) {
+ if (error instanceof Error) {
+ console.error('WEBSOCKET: Error serving app:', error.message)
} else {
- res.status(404).send('App not found')
+ console.error('WEBSOCKET: Error serving app:', error)
}
}
}
- expressApp.use('/', (req, res, next) => {
+ expressApp.use(['/', '/client/'], (req, res, next) => {
handleClientConnection('client', req, res, next)
})
// Serve web apps dynamically based on the URL
- expressApp.use('/:appName', (req, res, next) => {
+ expressApp.use(['/app/:appName', '/:appName'], (req, res, next) => {
const appName = req.params.appName
if (appName === 'client' || appName == null) {
handleClientConnection(appName, req, res, next)
} else {
const appPath = getAppFilePath(appName)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Serving ${appName} from ${appPath}`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Serving ${appName} from ${appPath}`)
if (fs.existsSync(appPath)) {
express.static(appPath)(req, res, next)
@@ -73,4 +84,62 @@ export const setupExpressServer = async (expressApp: express.Application): Promi
}
}
})
+
+ // Serve icons dynamically based on the URL
+ expressApp.use('/icon/:appName/:iconName', (req, res, next) => {
+ const iconName = req.params.iconName
+ const appName = req.params.appName
+ if (iconName != null) {
+ const appPath = getAppFilePath(appName)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Serving ${appName} from ${appPath}`)
+
+ if (fs.existsSync(join(appPath, 'icons', iconName))) {
+ express.static(join(appPath, 'icons'))(req, res, next)
+ } else {
+ res.status(404).send('Icon not found')
+ }
+ }
+ })
+
+ // Serve icons dynamically based on the URL
+ expressApp.use('/image/:appName/:imageName', (req, res, next) => {
+ const imageName = req.params.imageName
+ const appName = req.params.appName
+ if (imageName != null) {
+ const appPath = getAppFilePath(appName)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Serving ${appName} from ${appPath}`)
+
+ if (fs.existsSync(join(appPath, 'images', imageName))) {
+ express.static(join(appPath, 'icons'))(req, res, next)
+ } else {
+ res.status(404).send('Icon not found')
+ }
+ }
+ })
+
+ // Proxy external resources
+ expressApp.use('/fetch/:url(*)', async (req, res) => {
+ try {
+ const url = decodeURIComponent(req.params.url)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Fetching external resource from ${url}`)
+
+ const response = await fetch(url)
+ const contentType = response.headers.get('content-type')
+
+ if (contentType) {
+ res.setHeader('Content-Type', contentType)
+ }
+ if (response.body) {
+ response.body.pipeTo(res)
+ }
+ } catch (error) {
+ if (error instanceof Error) {
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `WEBSOCKET: Error fetching external resource: ${error.message}`
+ )
+ }
+ res.status(500).send('Error fetching resource')
+ }
+ })
}
diff --git a/DeskThingServer/src/main/services/client/websocket.ts b/DeskThingServer/src/main/services/client/websocket.ts
index d5b1cca6..592ad86a 100644
--- a/DeskThingServer/src/main/services/client/websocket.ts
+++ b/DeskThingServer/src/main/services/client/websocket.ts
@@ -1,7 +1,14 @@
import WebSocket, { WebSocketServer } from 'ws'
import { createServer, Server as HttpServer, IncomingMessage } from 'http'
-import dataListener, { MESSAGE_TYPES } from '../../utils/events'
-import { AppDataInterface, Client, ClientManifest, Settings, SocketData } from '@shared/types'
+import loggingStore from '../../stores/loggingStore'
+import {
+ AppDataInterface,
+ MESSAGE_TYPES,
+ Client,
+ ClientManifest,
+ Settings,
+ SocketData
+} from '@shared/types'
import { addData } from '../../handlers/dataHandler'
import { HandleDeviceData } from '../../handlers/deviceHandler'
import settingsStore from '../../stores/settingsStore'
@@ -36,8 +43,8 @@ const THROTTLE_DELAY = 100 // milliseconds
export const restartServer = async (): Promise => {
try {
if (server) {
- console.log('WSOCKET: Shutting down the WebSocket server...')
- dataListener.emit(MESSAGE_TYPES.LOGGING, 'WSOCKET: Shutting down the WebSocket server...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: Shutting down the WebSocket server...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: Shutting down the WebSocket server...')
ConnectionStore.removeAllClients()
server.clients.forEach((client) => {
@@ -47,25 +54,28 @@ export const restartServer = async (): Promise => {
server.close((err) => {
if (err) {
console.error('WSOCKET: Error shutting down WebSocket server:', err)
- dataListener.emit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
'WSOCKET: Error shutting down WebSocket server:' + err
)
} else {
- console.log('WSOCKET: WebSocket server shut down successfully.')
- dataListener.emit(
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ 'WSOCKET: WebSocket server shut down successfully.'
+ )
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
'WSOCKET: WebSocket server shut down successfully.'
)
}
if (httpServer && httpServer.listening) {
- console.log('WSOCKET: Stopping HTTP server...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: Stopping HTTP server...')
httpServer.close((err) => {
if (err) {
console.error('WSOCKET: Error stopping HTTP server:', err)
} else {
- console.log('WSOCKET: HTTP server stopped successfully.')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: HTTP server stopped successfully.')
setupServer()
}
})
@@ -74,14 +84,17 @@ export const restartServer = async (): Promise => {
}
})
} else {
- console.log('WSOCKET: No WebSocket server running - setting one up')
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ 'WSOCKET: No WebSocket server running - setting one up'
+ )
if (httpServer && httpServer.listening) {
- console.log('WSOCKET: Stopping HTTP server...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: Stopping HTTP server...')
httpServer.close((err) => {
if (err) {
console.error('WSOCKET: Error stopping HTTP server:', err)
} else {
- console.log('WSOCKET: HTTP server stopped successfully.')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: HTTP server stopped successfully.')
setupServer()
}
})
@@ -95,7 +108,7 @@ export const restartServer = async (): Promise => {
}
export const setupServer = async (): Promise => {
- dataListener.asyncEmit(MESSAGE_TYPES.MESSAGE, 'WSOCKET: Attempting to setup the server')
+ loggingStore.log(MESSAGE_TYPES.MESSAGE, 'WSOCKET: Attempting to setup the server')
if (!currentPort || !currentAddress) {
const settings = await settingsStore.getSettings()
@@ -112,11 +125,10 @@ export const setupServer = async (): Promise => {
server = new WebSocketServer({ server: httpServer })
- console.log('WSOCKET: WebSocket server is running.')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: WebSocket server is running.')
httpServer.listen(currentPort, currentAddress, () => {
- console.log(`CALLBACK: Server listening on ${currentAddress}:${currentPort}`)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`WEBSOCKET: Server is listening on ${currentAddress}:${currentPort}`
)
@@ -130,7 +142,10 @@ export const setupServer = async (): Promise => {
const clientIp = socket._socket.remoteAddress
// Setup the initial client data
- console.log(`WSOCKET: Client connected! Looking for client with IP ${clientIp}`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `WSOCKET: Client connected! Looking for client with IP ${clientIp}`
+ )
// Local client that is the true source of truth for the device details
const client: Client = {
@@ -145,13 +160,17 @@ export const setupServer = async (): Promise => {
Clients.push({ client, socket })
- console.log(
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
`WSOCKET: Client with id: ${client.connectionId} connected!\nWSOCKET: Sending preferences...`
)
ConnectionStore.addClient(client)
- console.log('WSOCKET: Client connected!\nWSOCKET: Sending preferences...')
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Sending client preferences...`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ 'WSOCKET: Client connected!\nWSOCKET: Sending preferences...'
+ )
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `WEBSOCKET: Sending client preferences...`)
sendConfigData(client.connectionId)
sendSettingsData(client.connectionId)
@@ -160,8 +179,7 @@ export const setupServer = async (): Promise => {
socket.on('message', async (message) => {
const messageData = JSON.parse(message) as SocketData
- console.log(`WSOCKET: ${client.connectionId} sent message `, messageData)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`WEBSOCKET: Client ${client.connectionId} has sent ${message}`
)
@@ -207,7 +225,7 @@ export const setupServer = async (): Promise => {
})
socket.on('close', () => {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`WSOCKET: Client ${client.connectionId} has disconnected!`
)
@@ -223,7 +241,10 @@ export const setupServer = async (): Promise => {
const handleServerMessage = (socket, client: Client, messageData: SocketData): void => {
try {
if (messageData.app === 'server') {
- console.log(`WSOCKET: Server message received! ${messageData.type}`)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ `WSOCKET: Server message received! ${messageData.type}`
+ )
try {
switch (messageData.type) {
case 'preferences':
@@ -241,7 +262,7 @@ const handleServerMessage = (socket, client: Client, messageData: SocketData): v
)
break
case 'pong':
- console.log('Received pong from ', client.connectionId)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Received pong from ', client.connectionId)
sendIpcData(`pong-${client.connectionId}`, messageData.payload)
break
case 'set':
@@ -266,7 +287,7 @@ const handleServerMessage = (socket, client: Client, messageData: SocketData): v
sendTime()
break
case 'message':
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.MESSAGE,
`${client.connectionId}: ${messageData.payload}`
)
@@ -279,7 +300,10 @@ const handleServerMessage = (socket, client: Client, messageData: SocketData): v
const manifest = messageData.payload as ClientManifest
if (!manifest) return
- console.log('WSOCKET: Received manifest from client', manifest)
+ loggingStore.log(
+ MESSAGE_TYPES.LOGGING,
+ 'WSOCKET: Received manifest from client' + JSON.stringify(manifest)
+ )
// Update the client to the info received from the client
@@ -313,10 +337,10 @@ const handleServerMessage = (socket, client: Client, messageData: SocketData): v
}
}
-dataListener.on(MESSAGE_TYPES.SETTINGS, (newSettings: Settings) => {
+settingsStore.addListener((newSettings: Settings) => {
if (currentPort !== newSettings.devicePort || currentAddress !== newSettings.address) {
restartServer()
} else {
- dataListener.emit(MESSAGE_TYPES.LOGGING, 'WSOCKET: No settings changed!')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'WSOCKET: No settings changed!')
}
})
diff --git a/DeskThingServer/src/main/static/defaultMapping.ts b/DeskThingServer/src/main/static/defaultMapping.ts
index c7a44633..46e2244c 100644
--- a/DeskThingServer/src/main/static/defaultMapping.ts
+++ b/DeskThingServer/src/main/static/defaultMapping.ts
@@ -1,4 +1,4 @@
-import { Key, EventFlavor, Action, MappingStructure, ButtonMapping } from '@shared/types'
+import { Key, EventMode, Action, MappingStructure, ButtonMapping } from '@shared/types'
const keys: Key[] = [
{
@@ -7,7 +7,7 @@ const keys: Key[] = [
description: 'First dynamic action button on the miniplayer',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'DynamicAction2',
@@ -15,7 +15,7 @@ const keys: Key[] = [
description: 'Second dynamic action button on the miniplayer',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'DynamicAction3',
@@ -23,7 +23,7 @@ const keys: Key[] = [
description: 'Third dynamic action button on the miniplayer',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'DynamicAction4',
@@ -31,7 +31,7 @@ const keys: Key[] = [
description: 'Fourth dynamic action button on the miniplayer',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Action5',
@@ -39,7 +39,7 @@ const keys: Key[] = [
description: 'Fifth action button, always visible on the miniplayer',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Action6',
@@ -47,7 +47,7 @@ const keys: Key[] = [
description: 'Sixth action button, always visible on the miniplayer',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Action7',
@@ -55,7 +55,7 @@ const keys: Key[] = [
description: 'Seventh action button, always visible on the miniplayer',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Digit1',
@@ -63,7 +63,7 @@ const keys: Key[] = [
description: 'Physical Button Digit1',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp]
+ Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp]
},
{
id: 'Digit2',
@@ -71,7 +71,7 @@ const keys: Key[] = [
description: 'Physical Button Digit2',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp]
+ Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp]
},
{
id: 'Digit3',
@@ -79,7 +79,7 @@ const keys: Key[] = [
description: 'Physical Button Digit3',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp]
+ Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp]
},
{
id: 'Digit4',
@@ -87,7 +87,7 @@ const keys: Key[] = [
description: 'Physical Button Digit4',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp]
+ Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp]
},
{
id: 'KeyM',
@@ -95,7 +95,7 @@ const keys: Key[] = [
description: 'Physical Button M',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp]
+ Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp]
},
{
id: 'Scroll',
@@ -103,12 +103,7 @@ const keys: Key[] = [
description: 'Physical Button Scroll',
version: '0.9.0',
enabled: true,
- flavors: [
- EventFlavor.ScrollUp,
- EventFlavor.ScrollDown,
- EventFlavor.ScrollLeft,
- EventFlavor.ScrollRight
- ]
+ Modes: [EventMode.ScrollUp, EventMode.ScrollDown, EventMode.ScrollLeft, EventMode.ScrollRight]
},
{
id: 'Enter',
@@ -116,7 +111,7 @@ const keys: Key[] = [
description: 'Physical Button Enter',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp]
+ Modes: [EventMode.KeyDown, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp]
},
{
id: 'Escape',
@@ -124,7 +119,7 @@ const keys: Key[] = [
description: 'Physical Button Escape',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.PressShort, EventFlavor.PressLong, EventFlavor.KeyDown, EventFlavor.KeyUp]
+ Modes: [EventMode.PressShort, EventMode.PressLong, EventMode.KeyDown, EventMode.KeyUp]
},
{
id: 'Swipe',
@@ -132,12 +127,7 @@ const keys: Key[] = [
description: 'Touchpad Swipe Button',
version: '0.9.0',
enabled: true,
- flavors: [
- EventFlavor.ScrollUp,
- EventFlavor.ScrollDown,
- EventFlavor.ScrollLeft,
- EventFlavor.ScrollRight
- ]
+ Modes: [EventMode.ScrollUp, EventMode.ScrollDown, EventMode.ScrollLeft, EventMode.ScrollRight]
},
{
id: 'Pad1',
@@ -145,7 +135,7 @@ const keys: Key[] = [
description: 'Touch Pad 1 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Pad2',
@@ -153,7 +143,7 @@ const keys: Key[] = [
description: 'Touch Pad 2 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Pad3',
@@ -161,7 +151,7 @@ const keys: Key[] = [
description: 'Touch Pad 3 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Pad4',
@@ -169,7 +159,7 @@ const keys: Key[] = [
description: 'Touch Pad 4 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Pad5',
@@ -177,7 +167,7 @@ const keys: Key[] = [
description: 'Touch Pad 5 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Pad6',
@@ -185,7 +175,7 @@ const keys: Key[] = [
description: 'Touch Pad 6 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Pad7',
@@ -193,7 +183,7 @@ const keys: Key[] = [
description: 'Touch Pad 7 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Pad8',
@@ -201,7 +191,7 @@ const keys: Key[] = [
description: 'Touch Pad 8 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
},
{
id: 'Pad9',
@@ -209,7 +199,7 @@ const keys: Key[] = [
description: 'Touch Pad 9 on the fullscreen miniplayer view',
version: '0.9.0',
enabled: true,
- flavors: [EventFlavor.KeyDown]
+ Modes: [EventMode.KeyDown]
}
]
@@ -364,7 +354,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
mapping: {
Pad1: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Volume Up',
id: 'volUp',
value: '15',
@@ -376,7 +366,7 @@ const defaults: ButtonMapping = {
}
},
Pad2: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Open Previous',
id: 'swipeL',
description: 'Opens the app at the previous index',
@@ -386,7 +376,7 @@ const defaults: ButtonMapping = {
}
},
Pad3: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Open Next',
id: 'swipeR',
description: 'Opens the app at the next index',
@@ -396,7 +386,7 @@ const defaults: ButtonMapping = {
}
},
Pad4: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Volume Down',
id: 'volDown',
value: '15',
@@ -408,7 +398,7 @@ const defaults: ButtonMapping = {
}
},
Pad5: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Toggle AppsList',
id: 'appsList',
value: 'hide',
@@ -421,7 +411,7 @@ const defaults: ButtonMapping = {
}
},
Pad6: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Toggle AppsList',
id: 'appsList',
value: 'show',
@@ -434,7 +424,7 @@ const defaults: ButtonMapping = {
}
},
Pad7: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Repeat',
id: 'repeat',
description: 'Toggles repeat',
@@ -444,7 +434,7 @@ const defaults: ButtonMapping = {
}
},
Pad8: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'PlayPause',
id: 'play',
icon: 'play',
@@ -455,7 +445,7 @@ const defaults: ButtonMapping = {
}
},
Pad9: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Fullscreen',
id: 'fullscreen',
description: 'Toggles Fullscreen on most devices',
@@ -465,7 +455,7 @@ const defaults: ButtonMapping = {
}
},
DynamicAction1: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Shuffle',
id: 'shuffle',
value: 'toggle',
@@ -478,7 +468,7 @@ const defaults: ButtonMapping = {
}
},
DynamicAction2: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Repeat',
id: 'repeat',
description: 'Repeats the song',
@@ -488,7 +478,7 @@ const defaults: ButtonMapping = {
}
},
DynamicAction3: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Rewind',
id: 'rewind',
value: 'stop',
@@ -502,7 +492,7 @@ const defaults: ButtonMapping = {
}
},
DynamicAction4: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Hidden Button',
id: 'hidden',
description: 'Hides the button. Has no action',
@@ -512,7 +502,7 @@ const defaults: ButtonMapping = {
}
},
Action5: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Hidden Button',
id: 'hidden',
description: 'Hides the button. Has no action',
@@ -522,7 +512,7 @@ const defaults: ButtonMapping = {
}
},
Action6: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'PlayPause',
id: 'play',
icon: 'play',
@@ -533,7 +523,7 @@ const defaults: ButtonMapping = {
}
},
Action7: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'Skip',
id: 'skip',
description: 'Skips the song',
@@ -543,7 +533,7 @@ const defaults: ButtonMapping = {
}
},
Digit1: {
- [EventFlavor.PressShort]: {
+ [EventMode.PressShort]: {
name: 'Open Preference App',
id: 'pref',
value: '0',
@@ -553,7 +543,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.PressLong]: {
+ [EventMode.PressLong]: {
name: 'Swap Apps',
id: 'swap',
value: '0',
@@ -565,7 +555,7 @@ const defaults: ButtonMapping = {
}
},
Digit2: {
- [EventFlavor.PressShort]: {
+ [EventMode.PressShort]: {
name: 'Open Preference App',
id: 'pref',
value: '1',
@@ -575,7 +565,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.PressLong]: {
+ [EventMode.PressLong]: {
name: 'Swap Apps',
id: 'swap',
value: '1',
@@ -587,7 +577,7 @@ const defaults: ButtonMapping = {
}
},
Digit3: {
- [EventFlavor.PressShort]: {
+ [EventMode.PressShort]: {
name: 'Open Preference App',
id: 'pref',
value: '2',
@@ -597,7 +587,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.PressLong]: {
+ [EventMode.PressLong]: {
name: 'Swap Apps',
id: 'swap',
value: '2',
@@ -609,7 +599,7 @@ const defaults: ButtonMapping = {
}
},
Digit4: {
- [EventFlavor.PressShort]: {
+ [EventMode.PressShort]: {
name: 'Open Preference App',
id: 'pref',
value: '3',
@@ -619,7 +609,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.PressLong]: {
+ [EventMode.PressLong]: {
name: 'Swap Apps',
id: 'swap',
value: '3',
@@ -631,7 +621,7 @@ const defaults: ButtonMapping = {
}
},
KeyM: {
- [EventFlavor.PressShort]: {
+ [EventMode.PressShort]: {
name: 'Open App',
id: 'open',
value: 'dashboard',
@@ -641,7 +631,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.PressLong]: {
+ [EventMode.PressLong]: {
name: 'Open App',
id: 'open',
value: 'utility',
@@ -653,7 +643,7 @@ const defaults: ButtonMapping = {
}
},
Scroll: {
- [EventFlavor.ScrollRight]: {
+ [EventMode.ScrollRight]: {
name: 'Volume Up',
id: 'volUp',
value: '15',
@@ -663,7 +653,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.ScrollUp]: {
+ [EventMode.ScrollUp]: {
name: 'Volume Up',
id: 'volUp',
value: '15',
@@ -673,7 +663,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.ScrollLeft]: {
+ [EventMode.ScrollLeft]: {
name: 'Volume Down',
id: 'volDown',
value: '15',
@@ -683,7 +673,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.ScrollDown]: {
+ [EventMode.ScrollDown]: {
name: 'Volume Down',
id: 'volDown',
value: '15',
@@ -695,7 +685,7 @@ const defaults: ButtonMapping = {
}
},
Enter: {
- [EventFlavor.KeyDown]: {
+ [EventMode.KeyDown]: {
name: 'PlayPause',
id: 'play',
icon: 'play',
@@ -704,7 +694,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.PressLong]: {
+ [EventMode.PressLong]: {
name: 'Skip',
id: 'skip',
description: 'Skips the song',
@@ -714,7 +704,7 @@ const defaults: ButtonMapping = {
}
},
Escape: {
- [EventFlavor.PressShort]: {
+ [EventMode.PressShort]: {
name: 'Toggle AppsList',
id: 'appsList',
value: 'show',
@@ -725,7 +715,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.PressLong]: {
+ [EventMode.PressLong]: {
name: 'Toggle AppsList',
id: 'appsList',
value: 'hide',
@@ -738,7 +728,7 @@ const defaults: ButtonMapping = {
}
},
Swipe: {
- [EventFlavor.SwipeUp]: {
+ [EventMode.SwipeUp]: {
name: 'Toggle AppsList',
id: 'appsList',
value: 'hide',
@@ -748,7 +738,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.SwipeDown]: {
+ [EventMode.SwipeDown]: {
name: 'Toggle AppsList',
id: 'appsList',
value: 'show',
@@ -758,7 +748,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.SwipeLeft]: {
+ [EventMode.SwipeLeft]: {
name: 'Open Previous',
id: 'swipeL',
description: 'Opens the app at the previous index',
@@ -766,7 +756,7 @@ const defaults: ButtonMapping = {
version: '0.9.0',
enabled: true
},
- [EventFlavor.SwipeRight]: {
+ [EventMode.SwipeRight]: {
name: 'Open Next',
id: 'swipeR',
description: 'Opens the app at the next index',
diff --git a/DeskThingServer/src/main/stores/connectionsStore.ts b/DeskThingServer/src/main/stores/connectionsStore.ts
index d69aab86..3fcfc193 100644
--- a/DeskThingServer/src/main/stores/connectionsStore.ts
+++ b/DeskThingServer/src/main/stores/connectionsStore.ts
@@ -1,6 +1,6 @@
-import { Client } from '@shared/types'
+import { Client, MESSAGE_TYPES } from '@shared/types'
import { handleAdbCommands } from '../handlers/adbHandler'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import loggingStore from '../stores/loggingStore'
import settingsStore from './settingsStore'
type ClientListener = (client: Client[]) => void
@@ -20,7 +20,7 @@ class ConnectionStore {
this.autoDetectADB = settings.autoDetectADB
})
- dataListener.on(MESSAGE_TYPES.SETTINGS, (newSettings) => {
+ settingsStore.addListener((newSettings) => {
try {
if (newSettings.autoDetectADB !== undefined) {
this.autoDetectADB = newSettings.autoDetectADB
@@ -31,14 +31,18 @@ class ConnectionStore {
if (newSettings.autoDetectADB) {
this.checkAutoDetectADB()
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, '[ADB]: Auto-Detect is Enabled')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, '[ADB]: Auto-Detect is Enabled')
} else {
- console.log('Auto-detect ADB disabled')
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, '[ADB]: Auto-Detect is Disabled')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Auto-detect ADB disabled')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, '[ADB]: Auto-Detect is Disabled')
}
}
} catch (error) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, 'ADB: Error updating with settings', error)
+ if (error instanceof Error) {
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'ADB: Error updating with settings', error.message)
+ } else {
+ loggingStore.log(MESSAGE_TYPES.ERROR, 'ADB: Error updating with settings', String(error))
+ }
}
})
@@ -70,7 +74,7 @@ class ConnectionStore {
}
pingClient(connectionId: string): boolean {
- console.log('Pinging client:', connectionId)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Pinging client:', connectionId)
const clientIndex = this.clients.findIndex((c) => c.connectionId === connectionId)
console.error('PINGING CLIENTS NOT IMPLEMENTED YET')
if (clientIndex !== -1) {
@@ -81,41 +85,38 @@ class ConnectionStore {
}
getClients(): Client[] {
- console.log('Getting clients:', this.clients)
return this.clients
}
getDevices(): string[] {
- console.log('Getting devices:', this.devices)
return this.devices
}
addClient(client: Client): void {
- console.log('Adding client:', client)
this.clients.push(client)
this.notifyListeners()
}
updateClient(connectionId: string, updates: Partial): void {
- console.log('Updating client:', connectionId, updates)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Updating client:' + connectionId + updates)
const clientIndex = this.clients.findIndex((c) => c.connectionId === connectionId)
if (clientIndex !== -1) {
this.clients[clientIndex] = { ...this.clients[clientIndex], ...updates }
this.notifyListeners()
} else {
- console.log('Client not found:', connectionId)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Client not found:', connectionId)
}
}
removeClient(connectionId: string): void {
- console.log('Removing client:', connectionId)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Removing client:' + connectionId)
this.clients = this.clients.filter((c) => c.connectionId !== connectionId)
this.notifyListeners()
}
removeAllClients(): void {
- console.log('Removing all clients')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Removing all clients')
this.clients = []
this.notifyListeners()
}
@@ -142,7 +143,7 @@ class ConnectionStore {
const newDevices = parseADBDevices(result) || []
this.devices = newDevices
this.notifyDeviceListeners()
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'ADB Device found!')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'ADB Device found!')
return newDevices
})
.catch((error) => {
@@ -158,7 +159,7 @@ class ConnectionStore {
const checkAndAutoDetect = async (): Promise => {
if (this.autoDetectADB === true) {
- console.log('Auto-detecting ADB devices...')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'Auto-detecting ADB devices...')
await this.getAdbDevices()
this.clearTimeout = await setTimeout(checkAndAutoDetect, 7000)
}
diff --git a/DeskThingServer/src/main/stores/keyMapStore.ts b/DeskThingServer/src/main/stores/keyMapStore.ts
index 8caa4a3d..b3ccb417 100644
--- a/DeskThingServer/src/main/stores/keyMapStore.ts
+++ b/DeskThingServer/src/main/stores/keyMapStore.ts
@@ -1,6 +1,13 @@
import { defaultData } from '../static/defaultMapping'
-import { Action, ButtonMapping, EventFlavor, Key, MappingStructure } from '@shared/types'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import {
+ Action,
+ ButtonMapping,
+ MESSAGE_TYPES,
+ EventMode,
+ Key,
+ MappingStructure
+} from '@shared/types'
+import loggingStore from '../stores/loggingStore'
import {
readFromFile,
readFromGlobalFile,
@@ -50,7 +57,7 @@ export class MappingState {
private loadMappings(): MappingStructure {
const data = readFromFile('mappings.json') as MappingStructure
if (!data || data?.version !== defaultData.version) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Mappings file is corrupt or does not exist, using default`
)
@@ -59,7 +66,7 @@ export class MappingState {
}
const parsedData = data as MappingStructure
if (!this.isValidFileStructure(parsedData)) {
- dataListener.emit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Mappings file is corrupt, resetting to default`
)
@@ -73,7 +80,7 @@ export class MappingState {
if (this.isValidFileStructure(mapping)) {
writeToFile(mapping, 'mappings.json')
} else {
- dataListener.emit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: New Mappings file is corrupt, resetting to default`
)
@@ -114,12 +121,12 @@ export class MappingState {
*/
isValidButtonMapping = (mapping: ButtonMapping): boolean => {
try {
- for (const [key, flavors] of Object.entries(mapping.mapping)) {
+ for (const [key, Modes] of Object.entries(mapping.mapping)) {
if (typeof key !== 'string') return false
- if (typeof flavors !== 'object') return false
+ if (typeof Modes !== 'object') return false
- for (const [flavor, action] of Object.entries(flavors)) {
- if (!Object.values(EventFlavor).includes(Number(flavor))) {
+ for (const [Mode, action] of Object.entries(Modes)) {
+ if (!Object.values(EventMode).includes(Number(Mode))) {
return false
}
if (
@@ -174,8 +181,8 @@ export class MappingState {
typeof key.source === 'string' &&
typeof key.version === 'string' &&
typeof key.enabled === 'boolean' &&
- Array.isArray(key.flavors) &&
- key.flavors.every((flavor) => Object.values(EventFlavor).includes(flavor))
+ Array.isArray(key.Modes) &&
+ key.Modes.every((Mode) => Object.values(EventMode).includes(Mode))
)
}
@@ -189,18 +196,13 @@ export class MappingState {
* adds a new button mapping to the mapping structure. If the key already exists, it will update the mapping.
* @param DynamicAction2 - the button to add
* @param key - The key to map the button to
- * @param flavor - default is 'onPress'
+ * @param Mode - default is 'onPress'
* @param profile - default is 'default'
*/
- addButton = (
- action: Action,
- key: string,
- flavor: EventFlavor,
- profile: string = 'default'
- ): void => {
+ addButton = (action: Action, key: string, Mode: EventMode, profile: string = 'default'): void => {
const mappings = this.mappings
if (!mappings[profile]) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Profile ${profile} does not exist! Create a new profile with the name ${profile} and try again`
)
@@ -211,7 +213,7 @@ export class MappingState {
}
// Ensure that the structure of the button is valid
if (!this.isValidAction(action)) {
- dataListener.emit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Action ${action.id} is invalid, cannot add to mapping`
)
@@ -219,7 +221,7 @@ export class MappingState {
}
// Adding the button to the mapping
- mappings[profile][key][flavor] = action
+ mappings[profile][key][Mode] = action
// Save the mappings to file
this.mappings = mappings
@@ -228,13 +230,13 @@ export class MappingState {
/**
* Removes a button mapping from the mapping structure.
* @param key - The key to remove the button from
- * @param flavor - The flavor of the button to remove. Default removes all flavors
+ * @param Mode - The Mode of the button to remove. Default removes all Modes
* @param profile - default is 'default'
*/
- removeButton = (key: string, flavor: EventFlavor | null, profile: string = 'default'): void => {
+ removeButton = (key: string, Mode: EventMode | null, profile: string = 'default'): void => {
const mappings = this.mappings
if (!mappings[profile]) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Profile ${profile} does not exist! Create a new profile with the name ${profile} and try again`
)
@@ -242,36 +244,36 @@ export class MappingState {
}
// Ensuring the key exists in the mapping
if (!mappings[profile][key]) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Key ${key} does not exist in profile ${profile}!`
)
return
}
- if (flavor === null) {
+ if (Mode === null) {
// Remove the entire key
delete mappings[profile][key]
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Key ${key} removed from profile ${profile}`
)
} else {
- // Ensure that the flavor exists in the mapping
- if (!mappings[profile][key][flavor]) {
- dataListener.asyncEmit(
+ // Ensure that the Mode exists in the mapping
+ if (!mappings[profile][key][Mode]) {
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
- `MAPHANDLER: Flavor ${flavor} does not exist in key ${key} in profile ${profile}!`
+ `MAPHANDLER: Mode ${Mode} does not exist in key ${key} in profile ${profile}!`
)
} else {
// Removing the button from the mapping
- delete mappings[profile][key][flavor]
+ delete mappings[profile][key][Mode]
}
}
// Save the mappings to file
this.mappings = mappings
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Button ${key} removed from profile ${profile}`
)
@@ -281,7 +283,7 @@ export class MappingState {
const mappings = this.mappings
// Validate key structure
if (!this.isValidKey(key)) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `MAPHANDLER: Invalid key structure`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Invalid key structure`)
return
}
// Check if the key already exists
@@ -289,11 +291,11 @@ export class MappingState {
if (existingKeyIndex !== -1) {
// Replace the existing key
mappings.keys[existingKeyIndex] = key
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Key ${key.id} updated`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Key ${key.id} updated`)
} else {
// Add the new key
mappings.keys.push(key)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Key ${key.id} added`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Key ${key.id} added`)
}
// Save the mappings
this.mappings = mappings
@@ -306,9 +308,9 @@ export class MappingState {
if (keyIndex !== -1) {
// Remove the key
mappings.keys.splice(keyIndex, 1)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Key ${keyId} removed`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Key ${keyId} removed`)
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `MAPHANDLER: Key ${keyId} not found`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Key ${keyId} not found`)
}
// Save the mappings
this.mappings = mappings
@@ -323,7 +325,7 @@ export class MappingState {
const mappings = this.mappings
// Validate action structure
if (!this.isValidAction(action)) {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `MAPHANDLER: Invalid action structure`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Invalid action structure`)
return
}
// Check if the action already exists
@@ -331,11 +333,11 @@ export class MappingState {
if (existingActionIndex !== -1) {
// Replace the existing action
mappings.actions[existingActionIndex] = action
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Action ${action.id} updated`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Action ${action.id} updated`)
} else {
// Add the new action
mappings.actions.push(action)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Action ${action.id} added`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Action ${action.id} added`)
}
// Save the mappings
this.mappings = mappings
@@ -348,9 +350,9 @@ export class MappingState {
if (actionIndex !== -1) {
// Remove the action
mappings.actions.splice(actionIndex, 1)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Action ${actionId} removed`)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Action ${actionId} removed`)
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `MAPHANDLER: Action ${actionId} not found`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Action ${actionId} not found`)
}
// Save the mappings
this.mappings = mappings
@@ -385,7 +387,7 @@ export class MappingState {
// Remove keys with the specified source
mappings.keys = mappings.keys.filter((key) => key.source !== sourceId)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Actions for source ${sourceId} disabled in all profiles, global actions, and keys`
)
@@ -427,7 +429,7 @@ export class MappingState {
}
})
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Actions for source ${sourceId} disabled in all profiles, global actions, and keys`
)
@@ -443,20 +445,17 @@ export class MappingState {
if (actionIndex !== -1) {
// Update the icon
mappings.actions[actionIndex].icon = icon
- dataListener.asyncEmit(
- MESSAGE_TYPES.LOGGING,
- `MAPHANDLER: Icon for action ${actionId} updated`
- )
+ loggingStore.log(MESSAGE_TYPES.LOGGING, `MAPHANDLER: Icon for action ${actionId} updated`)
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `MAPHANDLER: Action ${actionId} not found`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Action ${actionId} not found`)
}
// Update the icon for all actions inside the current profile
const currentMap = mappings.selected_profile
if (currentMap) {
const currentMapActions = mappings.profiles[currentMap].mapping
- Object.values(currentMapActions).forEach((buttonFlavors) => {
- Object.values(buttonFlavors).forEach((action) => {
+ Object.values(currentMapActions).forEach((buttonModes) => {
+ Object.values(buttonModes).forEach((action) => {
if (action && action.id === actionId) {
action.icon = icon
}
@@ -476,7 +475,7 @@ export class MappingState {
// Update the icon
return mappings.actions[actionIndex]
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.ERROR, `MAPHANDLER: Action ${actionId} not found`)
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Action ${actionId} not found`)
return null
}
}
@@ -492,7 +491,7 @@ export class MappingState {
if (this.mappings.profiles[profile]) {
this.mappings.selected_profile = profile
} else {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Profile ${profile} does not exist! Create a new profile with the name ${profile} and try again`
)
@@ -513,16 +512,13 @@ export class MappingState {
// Check if the profile name already exists
if (mappings.profiles[profileName]) {
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- `MAPHANDLER: Profile "${profileName}" already exists!`
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Profile "${profileName}" already exists!`)
return
}
// Ensure the base profile exists
if (!mappings.profiles[baseProfile]) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Base profile "${baseProfile}" does not exist!`
)
@@ -549,7 +545,7 @@ export class MappingState {
// Save the updated mappings
this.mappings = mappings
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Profile "${profileName}" added successfully.`
)
@@ -564,19 +560,13 @@ export class MappingState {
// Prevent removal of the default profile
if (profileName === 'default') {
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- `MAPHANDLER: The "default" profile cannot be removed.`
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: The "default" profile cannot be removed.`)
return
}
// Check if the profile exists
if (!mappings.profiles[profileName]) {
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- `MAPHANDLER: Profile "${profileName}" does not exist!`
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Profile "${profileName}" does not exist!`)
return
}
@@ -586,7 +576,7 @@ export class MappingState {
// If the removed profile was the selected profile, revert to default
if (mappings.selected_profile === profileName) {
mappings.selected_profile = 'default'
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Selected profile was removed. Reverted to "default" profile.`
)
@@ -595,7 +585,7 @@ export class MappingState {
// Save the updated mappings
this.mappings = mappings
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Profile "${profileName}" removed successfully.`
)
@@ -610,7 +600,7 @@ export class MappingState {
const mappings = this.mappings
if (!mappings.profiles[profile]) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Profile ${profile} does not exist! Cannot export.`
)
@@ -620,7 +610,7 @@ export class MappingState {
const profileData = mappings.profiles[profile]
writeToGlobalFile(profileData, filePath)
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Profile ${profile} exported to ${filePath}`
)
@@ -638,7 +628,7 @@ export class MappingState {
const profileData = readFromGlobalFile(filePath)
if (!profileData) {
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.ERROR,
`MAPHANDLER: Failed to load profile data from ${filePath}`
)
@@ -646,10 +636,7 @@ export class MappingState {
}
if (!this.isValidButtonMapping(profileData)) {
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- `MAPHANDLER: Invalid profile data in file ${filePath}`
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Invalid profile data in file ${filePath}`)
return
}
@@ -657,7 +644,7 @@ export class MappingState {
mappings.profiles[profileName] = profileData
this.mappings = mappings
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Profile ${profileName} imported from ${filePath}`
)
@@ -667,16 +654,13 @@ export class MappingState {
const mappings = this.mappings
const profile = mappings.profiles[profileName]
if (!profile) {
- dataListener.asyncEmit(
- MESSAGE_TYPES.ERROR,
- `MAPHANDLER: Profile ${profileName} does not exist!`
- )
+ loggingStore.log(MESSAGE_TYPES.ERROR, `MAPHANDLER: Profile ${profileName} does not exist!`)
return
}
// Update the profile with the provided data
deepMerge(profile, updatedProfile)
this.mappings = mappings
- dataListener.asyncEmit(
+ loggingStore.log(
MESSAGE_TYPES.LOGGING,
`MAPHANDLER: Profile ${profileName} updated successfully.`
)
diff --git a/DeskThingServer/src/main/stores/loggingStore.ts b/DeskThingServer/src/main/stores/loggingStore.ts
new file mode 100644
index 00000000..70e7f320
--- /dev/null
+++ b/DeskThingServer/src/main/stores/loggingStore.ts
@@ -0,0 +1,133 @@
+import fs from 'fs'
+import { join } from 'path'
+import { app } from 'electron'
+import { MESSAGE_TYPES, Log, LOGGING_LEVEL, Settings, ReplyData, ReplyFn } from '@shared/types'
+import settingsStore from './settingsStore'
+
+// LoggingStore configuration
+const logFile = join(app.getPath('userData'), 'application.log.json')
+const readableLogFile = join(app.getPath('userData'), 'readable.log')
+
+// Ensure log directory exists
+const logDir = app.getPath('userData')
+if (!fs.existsSync(logDir)) {
+ fs.mkdirSync(logDir, { recursive: true })
+}
+
+class LoggingStore {
+ private static instance: LoggingStore
+ private listeners: ((data: Log) => void)[] = []
+ private logs: Log[] = []
+ private logLevel: LOGGING_LEVEL = LOGGING_LEVEL.PRODUCTION
+
+ private constructor() {
+ fs.writeFileSync(logFile, '[]')
+ fs.writeFileSync(readableLogFile, '')
+
+ settingsStore.addListener(this.settingsStoreListener.bind(this))
+ }
+
+ private settingsStoreListener(settings: Settings): void {
+ this.logLevel = settings.logLevel
+ }
+
+ // Singleton instance
+ public static getInstance(): LoggingStore {
+ if (!LoggingStore.instance) {
+ LoggingStore.instance = new LoggingStore()
+ }
+ return LoggingStore.instance
+ }
+
+ // Log a message
+ async log(level: MESSAGE_TYPES, message: string, source: string = 'server'): Promise {
+ if (
+ level === MESSAGE_TYPES.LOGGING &&
+ source === 'server' &&
+ this.logLevel != LOGGING_LEVEL.SYSTEM
+ ) {
+ return
+ }
+
+ if (level === MESSAGE_TYPES.LOGGING && this.logLevel == LOGGING_LEVEL.PRODUCTION) {
+ return
+ }
+
+ const timestamp = new Date().toISOString()
+ const trace = new Error().stack || ''
+
+ const logData: Log = {
+ source: source,
+ type: level,
+ log: message,
+ trace: trace,
+ date: timestamp
+ }
+
+ this.logs.push(logData)
+ this.notifyListeners(logData)
+
+ const readableTimestamp = new Date(timestamp).toLocaleString()
+ const readableMessage = `[${readableTimestamp}] [${source}] ${level.toUpperCase()}: ${message}\n`
+
+ console.log(readableMessage)
+
+ // Write to log file as JSON array
+ return new Promise((resolve, reject) => {
+ fs.writeFile(logFile, JSON.stringify(this.logs, null, 2), (err) => {
+ if (err) {
+ console.error('Failed to write to log file:', err)
+ reject(err)
+ }
+ resolve()
+ })
+
+ fs.appendFile(readableLogFile, readableMessage, (err) => {
+ if (err) {
+ console.error('Failed to write to log file:', err)
+ reject(err)
+ }
+ resolve()
+ })
+ })
+ }
+
+ notifyListeners(data: Log): void {
+ this.listeners.forEach((listener) => listener(data))
+ }
+
+ addListener(callback: (data: Log) => void): void {
+ this.listeners.push(callback)
+ }
+
+ public async getLogs(): Promise {
+ return new Promise((resolve, reject) => {
+ if (!fs.existsSync(logFile)) {
+ resolve([])
+ return
+ }
+
+ fs.readFile(logFile, 'utf8', (err, data) => {
+ if (err) {
+ return reject(err)
+ }
+ try {
+ const logs = data ? JSON.parse(data) : []
+ resolve(logs)
+ } catch (error) {
+ reject(error)
+ }
+ })
+ })
+ }
+}
+
+export const ResponseLogger = (replyFn: ReplyFn): ReplyFn => {
+ return async (channel: string, reply: ReplyData): Promise => {
+ LoggingStore.getInstance().log(MESSAGE_TYPES.LOGGING, `[${channel}]: ${JSON.stringify(reply)}`)
+
+ replyFn(channel, reply)
+ }
+}
+
+export default LoggingStore.getInstance()
diff --git a/DeskThingServer/src/main/stores/settingsStore.ts b/DeskThingServer/src/main/stores/settingsStore.ts
index 2035c0ff..327b8848 100644
--- a/DeskThingServer/src/main/stores/settingsStore.ts
+++ b/DeskThingServer/src/main/stores/settingsStore.ts
@@ -1,15 +1,18 @@
import { readFromFile, writeToFile } from '../utils/fileHandler'
-import dataListener, { MESSAGE_TYPES } from '../utils/events'
+import loggingStore from './loggingStore'
import os from 'os'
-import { Settings } from '@shared/types'
+import { LOGGING_LEVEL, Settings, MESSAGE_TYPES } from '@shared/types'
const settingsVersion = '0.9.2'
const version_code = 9.2
+type SettingsStoreListener = (settings: Settings) => void
+
class SettingsStore {
private settings: Settings
private settingsFilePath: string = 'settings.json'
private static instance: SettingsStore
+ private listeners: SettingsStoreListener[] = []
constructor() {
this.settings = this.getDefaultSettings()
@@ -18,7 +21,7 @@ class SettingsStore {
if (settings) {
this.settings = settings as Settings
this.settings.localIp = getLocalIpAddress()
- dataListener.asyncEmit(MESSAGE_TYPES.SETTINGS, this.settings)
+ this.notifyListeners()
}
})
.catch((err) => {
@@ -32,6 +35,16 @@ class SettingsStore {
return SettingsStore.instance
}
+ public addListener(listener: SettingsStoreListener): void {
+ this.listeners.push(listener)
+ }
+
+ private async notifyListeners(): Promise {
+ this.listeners.forEach((listener) => {
+ listener(this.settings)
+ })
+ }
+
public async getSettings(): Promise {
if (this.settings) {
return this.settings
@@ -41,19 +54,23 @@ class SettingsStore {
}
}
+ /**
+ * Updates a specific setting and saves it to file
+ * @param key - The key of the setting to update
+ * @param value - The new value for the setting
+ */
public updateSetting(key: string, value: boolean | undefined | string | number | string[]): void {
if (key === 'autoStart' && typeof value === 'boolean') {
this.updateAutoLaunch(value)
}
this.settings[key] = value
- dataListener.asyncEmit(MESSAGE_TYPES.SETTINGS, this.settings)
this.saveSettings()
}
public async loadSettings(): Promise {
try {
const data = await readFromFile(this.settingsFilePath)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'SETTINGS: Loaded settings!')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'SETTINGS: Loaded settings!')
if (!data || !data.version_code || data.version_code < version_code) {
// File does not exist, create it with default settings
@@ -66,6 +83,8 @@ class SettingsStore {
await this.updateAutoLaunch(data.autoStart)
}
+ this.notifyListeners()
+
return data
} catch (err) {
console.error('Error loading settings:', err)
@@ -89,21 +108,31 @@ class SettingsStore {
}
}
+ /**
+ * Saves the current settings to file. Emits an update if settings are passed
+ * @param settings - Overrides the current settings with the passed settings if passed
+ */
public async saveSettings(settings?: Settings): Promise {
try {
if (settings) {
this.settings = settings as Settings
await writeToFile(this.settings, this.settingsFilePath)
- dataListener.asyncEmit(MESSAGE_TYPES.SETTINGS, this.settings)
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'SETTINGS: Updated settings!')
+ console.log('SETTINGS: Updated settings!', this.settings)
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'SETTINGS: Updated settings!')
} else {
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, 'SETTINGS: Invalid setting format!')
+ loggingStore.log(MESSAGE_TYPES.LOGGING, 'SETTINGS: Invalid setting format!')
}
+
+ this.notifyListeners()
} catch (err) {
console.error('Error saving settings:', err)
}
}
+ /**
+ *
+ * @returns Returns the default settings for the application
+ */
private getDefaultSettings(): Settings {
return {
version: settingsVersion,
@@ -111,6 +140,7 @@ class SettingsStore {
callbackPort: 8888,
devicePort: 8891,
address: '0.0.0.0',
+ LogLevel: LOGGING_LEVEL.PRODUCTION,
autoStart: false,
autoConfig: false,
minimizeApp: true,
diff --git a/DeskThingServer/src/main/utils/events.ts b/DeskThingServer/src/main/utils/events.ts
deleted file mode 100644
index 1e1666b0..00000000
--- a/DeskThingServer/src/main/utils/events.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import EventEmitter from 'events'
-import Logger from './logger'
-import { ReplyData, ReplyFn, SocketData } from '@shared/types'
-
-/**
- * The MESSAGE_TYPES object defines a set of constants that represent the different types of messages that can be sent or received in the application.
- */
-export const MESSAGE_TYPES = {
- ERROR: 'error',
- LOGGING: 'log',
- MESSAGE: 'message',
- CONFIG: 'config',
- SETTINGS: 'settings',
- MAPPINGS: 'mapping'
-}
-
-/**
- * Events is a class that extends the EventEmitter class from the 'events' module.
- */
-class Events extends EventEmitter {
- constructor() {
- super()
- }
-
- /**
- * Emits an event with associated data to all connected clients.
- *
- * @param event - The name of the event to emit. Should be one of the MESSAGE_TYPES defined in this file.
- * @param data - The data to be sent along with the event. Can be of any type, but typically an object containing relevant information.
- * @returns void
- *
- * Usage:
- * 1. Import the Events instance from this file.
- * 2. Call the method with the appropriate event type and data.
- *
- * @example
- *
- * import { events } from './events'
- *
- * events.emit(MESSAGE_TYPES.MESSAGE, { content: 'Hello, world!' })
- */
- async asyncEmit(event: string, ...data: (string | SocketData | unknown)[]): Promise {
- console.log(`[${event}] `, data)
- return new Promise((resolve) => {
- setImmediate(() => {
- // Ensure that two arguments are only emitted at once
- if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'string') {
- this.emit(event, data.join(' '))
- } else {
- this.emit(event, data[0])
- }
-
- if (event === MESSAGE_TYPES.ERROR) {
- Logger.error(`[${event}]: ${JSON.stringify(data)}`)
- } else if (event === MESSAGE_TYPES.LOGGING) {
- Logger.info(`[${event}]: ${JSON.stringify(data)}`)
- } else {
- Logger.debug(`[${event}]: ${JSON.stringify(data)}`)
- }
- resolve()
- })
- })
- }
-}
-
-const dataListener = new Events()
-
-/**
- * Handles and standardizes the way to reply to ipc handlers
- * @param replyFn
- * @returns
- */
-export const ResponseLogger = (replyFn: ReplyFn): ReplyFn => {
- return async (channel: string, reply: ReplyData): Promise => {
- dataListener.asyncEmit(MESSAGE_TYPES.LOGGING, `[CHANNEL][${channel}]: ${JSON.stringify(reply)}`)
-
- replyFn(channel, reply)
- }
-}
-
-export default dataListener
diff --git a/DeskThingServer/src/main/utils/logger.ts b/DeskThingServer/src/main/utils/logger.ts
deleted file mode 100644
index b517135c..00000000
--- a/DeskThingServer/src/main/utils/logger.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import fs from 'fs'
-import { join } from 'path'
-import { app } from 'electron'
-
-// Define log levels
-enum LogLevel {
- INFO = 'info',
- DEBUG = 'debug',
- ERROR = 'error'
-}
-
-// Logger configuration
-const logFile = join(app.getPath('userData'), 'application.log')
-
-// Ensure log directory exists
-const logDir = app.getPath('userData')
-if (!fs.existsSync(logDir)) {
- fs.mkdirSync(logDir, { recursive: true })
-}
-
-class Logger {
- private static instance: Logger
-
- private constructor() {
- fs.writeFileSync(logFile, '')
- }
-
- // Singleton instance
- public static getInstance(): Logger {
- if (!Logger.instance) {
- Logger.instance = new Logger()
- }
- return Logger.instance
- }
-
- // Log a message
- private async log(level: LogLevel, message: string): Promise {
- const timestamp = new Date().toLocaleTimeString()
- const logMessage = `[${timestamp}]: ${level.toUpperCase()} | ${message}`
-
- // Append to log file
- fs.appendFile(logFile, logMessage + '\n', (err) => {
- if (err) {
- console.error('Failed to write to log file:', err)
- }
- })
- }
-
- // Info level logging
- public async info(message: string): Promise {
- this.log(LogLevel.INFO, message)
- }
-
- // Debug level logging
- public async debug(message: string): Promise {
- this.log(LogLevel.DEBUG, message)
- }
-
- // Error level logging
- public async error(message: string): Promise {
- this.log(LogLevel.ERROR, message)
- }
-
- public async getLogs(): Promise {
- console.log('LOGGER: Getting logs')
- return new Promise((resolve, reject) => {
- fs.readFile(logFile, 'utf8', (err, data) => {
- if (err) {
- return reject(err)
- }
- const logs = data.trim().split('\n').filter(Boolean)
- resolve(logs)
- })
- })
- }
-}
-
-export default Logger.getInstance()
diff --git a/DeskThingServer/src/preload/index.d.ts b/DeskThingServer/src/preload/index.d.ts
index f3e5bb1d..e6cf083d 100644
--- a/DeskThingServer/src/preload/index.d.ts
+++ b/DeskThingServer/src/preload/index.d.ts
@@ -1,5 +1,5 @@
import { ElectronAPI } from '@electron-toolkit/preload'
-import { AppDataInterface, AppReturnData, Client, ClientManifest } from '@shared/types'
+import { AppDataInterface, AppReturnData, Client, ClientManifest, Log } from '@shared/types'
type AppData = { [key: string]: string }
@@ -38,7 +38,7 @@ declare global {
saveSettings: (settings: Settings) => Promise
getSettings: () => Promise
fetchGithub: (url: string) => Promise
- getLogs: () => Promise
+ getLogs: () => Promise
getMappings: () => Promise
addProfile: (profile: string, baseProfile?: string) => Promise
deleteProfile: (profile: string) => Promise
diff --git a/DeskThingServer/src/preload/index.ts b/DeskThingServer/src/preload/index.ts
index 5a38ef4c..7195ade0 100644
--- a/DeskThingServer/src/preload/index.ts
+++ b/DeskThingServer/src/preload/index.ts
@@ -11,6 +11,7 @@ import {
IncomingData,
IPC_HANDLERS,
IPCData,
+ Log,
Settings,
SocketData
} from '@shared/types'
@@ -209,7 +210,7 @@ const api = {
})
},
- getLogs: (): Promise => {
+ getLogs: (): Promise => {
return sendCommand('UTILITY', {
type: 'logs',
request: 'get',
diff --git a/DeskThingServer/src/renderer/src/assets/animations/Landing Animation-v2.json b/DeskThingServer/src/renderer/src/assets/animations/Landing Animation-v2.json
index b6be43c4..07ef8fc7 100644
--- a/DeskThingServer/src/renderer/src/assets/animations/Landing Animation-v2.json
+++ b/DeskThingServer/src/renderer/src/assets/animations/Landing Animation-v2.json
@@ -1 +1 @@
-{"v":"5.9.0","fr":30,"ip":0,"op":60,"w":1230,"h":390,"nm":"LandingAnimation_Centered","ddd":0,"assets":[],"fonts":{"list":[{"fName":"Geist-Bold","fFamily":"Geist","fStyle":"Bold","ascent":70.9991455078125},{"fName":"GeistMono-Medium","fFamily":"Geist Mono","fStyle":"Medium","ascent":70.9991455078125}]},"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[679.5,146.941,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[310.393,146.941,0]}],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"NULL","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":0,"s":[0]},{"t":45,"s":[-360]}],"ix":10},"p":{"a":0,"k":[-65.299,89.341,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"InsideMask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,50.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-51.694,0],[0,51.694],[51.694,0],[0,-51.694]],"o":[[51.694,0],[0,-51.694],[-51.694,0],[0,51.694]],"v":[[0,93.6],[93.6,0],[0,-93.6],[-93.6,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.117647059262,0.843137264252,0.376470595598,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":0,"s":[0]},{"t":35,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 2","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Inside","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[49.025,49.837,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[242,242,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-23.57],[-23.79,0],[0,23.57],[23.79,0]],"o":[[0,23.57],[23.79,0],[0,-23.57],[-23.79,0]],"v":[[-43.15,0],[0,42.75],[43.15,0],[0,-42.75]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,20.12],[-20.34,0],[0,-20.12],[20.34,0]],"o":[[0,-20.12],[20.34,0],[0,20.12],[-20.34,0]],"v":[[-36.89,0],[0,-36.49],[36.89,0],[0,36.49]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.133333340287,0.772549033165,0.368627458811,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"OutsideMask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,49.998,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-41.36,-133.505],[-31.87,-173.275],[33.27,-173.275],[41.13,-133.005],[65.5,-123.555],[99.81,-145.935],[145.97,-98.215],[123.8,-63.315],[133.25,-40.025],[174.08,-32.005],[174.08,33.405],[133.25,41.925],[123.58,65.195],[146.42,100.095],[99.63,145.815],[64.71,123.385],[41.23,132.975],[32.23,173.285],[-33.15,173.285],[-41.6,132.195],[-65.92,123.285],[-100.17,145.545],[-146.41,99.695],[-124.24,64.555],[-133.95,41.725],[-173.84,32.745],[-174.08,-32.045],[-133.95,-40.025],[-124.1,-64.105],[-146.73,-98.215],[-99.5,-145.935],[-64.2,-124.055]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":25,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":0,"s":[0]},{"t":35,"s":[100]}],"ix":2},"o":{"a":0,"k":4,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 2","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Outside","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[49.025,49.817,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[242,242,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.33,0],[0,0],[0,0],[-0.68,3.26],[0,0],[0,0],[0,0],[-2.36,2.36],[0,0],[1.83,2.79],[0,0],[0,0],[0,0],[0,3.33],[0,0],[3.26,0.68],[0,0],[0,0],[0,0],[2.36,2.36],[0,0],[2.79,-1.83],[0,0],[0,0],[0,0],[3.33,0],[0,0],[0.68,-3.26],[0,0],[0,0],[0,0],[2.36,-2.36],[0,0],[-1.83,-2.79],[0,0],[0,0],[0,0],[0,-3.33],[0,0],[-3.26,-0.68],[0,0],[0,0],[0,0],[-2.36,-2.36],[0,0],[-2.79,1.83],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[3.34,0],[0,0],[0,0],[0,0],[2.79,1.82],[0,0],[2.36,-2.36],[0,0],[0,0],[0,0],[3.26,-0.68],[0,0],[0,-3.34],[0,0],[0,0],[0,0],[1.82,-2.79],[0,0],[-2.35,-2.36],[0,0],[0,0],[0,0],[-0.68,-3.26],[0,0],[-3.34,0],[0,0],[0,0],[0,0],[-2.79,-1.82],[0,0],[-2.36,2.35],[0,0],[0,0],[0,0],[-3.26,0.68],[0,0],[0,3.34],[0,0],[0,0],[0,0],[-1.82,2.79],[0,0],[2.36,2.36],[0,0],[0,0],[0,0],[0.68,3.26]],"v":[[-11.25,76.14],[11.25,76.14],[11.25,76.12],[18.18,70.49],[20.75,58.17],[26.47,55.8],[37,62.69],[45.88,61.77],[61.79,45.86],[62.71,36.98],[55.82,26.46],[58.19,20.74],[70.51,18.17],[76.14,11.24],[76.14,-11.26],[70.51,-18.19],[58.19,-20.76],[55.82,-26.48],[62.71,-37],[61.79,-45.88],[45.88,-61.79],[37,-62.71],[26.47,-55.82],[20.75,-58.19],[18.18,-70.51],[11.25,-76.14],[-11.25,-76.14],[-18.18,-70.51],[-20.75,-58.19],[-26.47,-55.82],[-37,-62.71],[-45.88,-61.79],[-61.79,-45.88],[-62.71,-37],[-55.82,-26.47],[-58.19,-20.75],[-70.51,-18.18],[-76.14,-11.25],[-76.14,11.25],[-70.51,18.18],[-58.19,20.75],[-55.82,26.47],[-62.71,37],[-61.79,45.88],[-45.88,61.79],[-37,62.71],[-26.47,55.82],[-20.75,58.19],[-18.18,70.51]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[2.16,0.9],[0,0],[1.96,-1.28],[0,0],[0,0],[0,0],[0.9,2.17],[0,0],[2.29,0.48],[0,0],[0,0],[0,0],[-0.9,2.17],[0,0],[1.28,1.96],[0,0],[0,0],[0,0],[-2.17,0.9],[0,0],[-0.48,2.29],[0,0],[0,0],[0,0],[-2.17,-0.9],[0,0],[-1.96,1.28],[0,0],[0,0],[0,0],[-0.9,-2.17],[0,0],[-2.29,-0.48],[0,0],[0,0],[0,0],[0.9,-2.16],[0,0],[-1.28,-1.96],[0,0],[0,0],[0,0],[2.17,-0.9],[0,0],[0.48,-2.29]],"o":[[0,0],[0,0],[0,0],[-0.48,-2.29],[0,0],[-2.17,-0.89],[0,0],[0,0],[0,0],[1.29,-1.96],[0,0],[-0.89,-2.16],[0,0],[0,0],[0,0],[2.3,-0.48],[0,0],[0.9,-2.17],[0,0],[0,0],[0,0],[1.96,1.28],[0,0],[2.16,-0.89],[0,0],[0,0],[0,0],[0.48,2.3],[0,0],[2.17,0.9],[0,0],[0,0],[0,0],[-1.29,1.96],[0,0],[0.89,2.17],[0,0],[0,0],[0,0],[-2.29,0.48],[0,0],[-0.9,2.17],[0,0],[0,0],[0,0],[-1.96,-1.29],[0,0],[-2.17,0.89],[0,0]],"v":[[10.22,67.8],[-10.22,67.8],[-10.21,67.79],[-12.7,55.82],[-16.92,50.73],[-23.88,47.85],[-30.47,48.47],[-40.69,55.17],[-55.14,40.71],[-48.44,30.49],[-47.82,23.9],[-50.7,16.95],[-55.79,12.73],[-67.76,10.24],[-67.76,-10.2],[-55.8,-12.69],[-50.7,-16.91],[-47.82,-23.86],[-48.44,-30.45],[-55.14,-40.67],[-40.69,-55.13],[-30.47,-48.43],[-23.88,-47.81],[-16.92,-50.69],[-12.7,-55.78],[-10.21,-67.74],[10.23,-67.74],[12.72,-55.78],[16.94,-50.68],[23.89,-47.8],[30.48,-48.42],[40.7,-55.12],[55.15,-40.66],[48.45,-30.44],[47.83,-23.85],[50.71,-16.9],[55.8,-12.68],[67.77,-10.19],[67.77,10.25],[55.8,12.74],[50.71,16.96],[47.83,23.92],[48.45,30.51],[55.15,40.73],[40.69,55.18],[30.47,48.48],[23.88,47.86],[16.93,50.74],[12.71,55.83]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":5,"nm":"Version v0.9.0","cl":"9 0","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[165.298,-14,0],"ix":2,"l":2},"a":{"a":0,"k":[2.981,-22.559,0],"ix":1,"l":2},"s":{"a":0,"k":[134.638,134.638,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[1.088,-29.246],[1.088,0.645],[390.702,0.599],[390.702,-29.292]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"t":{"d":{"k":[{"s":{"s":40,"f":"GeistMono-Medium","t":"Version v0.9.0","ca":0,"j":0,"tr":0,"lh":94,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"sm":{"a":0,"k":100,"ix":6},"r":1},"a":{"p":{"a":1,"k":[{"i":{"x":0.25,"y":1},"o":{"x":0.33,"y":0},"t":30,"s":[0,50,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[0,0,0]}],"ix":2}}}]},"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":5,"nm":"Welcome to DeskThing 2","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162.047,114,0],"ix":2,"l":2},"a":{"a":0,"k":[2.981,-22.559,0],"ix":1,"l":2},"s":{"a":0,"k":[134.638,134.638,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3.503,-67.213],[3.237,125.676],[621.645,125.796],[621.91,-67.092]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"t":{"d":{"k":[{"s":{"s":94,"f":"Geist-Bold","t":"Welcome\u0003to DeskThing","ca":0,"j":0,"tr":0,"lh":94,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"sm":{"a":0,"k":100,"ix":6},"r":1},"a":{"p":{"a":1,"k":[{"i":{"x":0.25,"y":1},"o":{"x":0.33,"y":0},"t":15,"s":[-606,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[0,0,0]}],"ix":2}}}]},"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":5,"nm":"Welcome to DeskThing","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162.047,114,0],"ix":2,"l":2},"a":{"a":0,"k":[2.981,-22.559,0],"ix":1,"l":2},"s":{"a":0,"k":[134.638,134.638,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3.503,-67.213],[3.237,125.676],[621.645,125.796],[621.91,-67.092]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"t":{"d":{"k":[{"s":{"s":94,"f":"Geist-Bold","t":"Welcome\u0003to DeskThing","ca":0,"j":0,"tr":0,"lh":94,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"sm":{"a":0,"k":100,"ix":6},"r":1},"a":{"p":{"a":1,"k":[{"i":{"x":0.25,"y":1},"o":{"x":0.33,"y":0},"t":15,"s":[-606,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[0,0,0]}],"ix":2}}}]},"ip":0,"op":300,"st":0,"bm":0}],"markers":[],"chars":[{"ch":"W","size":94,"style":"Bold","w":100.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[23.062,0],[39.377,0],[51.059,-45.721],[62.741,0],[79.257,0],[98.492,-71.503],[82.48,-71.503],[70.294,-21.149],[57.806,-71.503],[44.412,-71.503],[32.025,-21.149],[19.739,-71.503],[3.726,-71.503]],"c":true},"ix":2},"nm":"W","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"W","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"e","size":94,"style":"Bold","w":59.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-16.516,0],[-2.921,10.776],[0,0],[5.035,0],[0.403,8.56],[0,0],[0,0],[16.013,0],[0,-17.02]],"o":[[12.991,0],[0,0],[-1.41,4.431],[-6.949,0],[0,0],[0,0],[0,-18.127],[-16.415,0],[0,17.02]],"v":[[30.917,1.208],[56.497,-15.912],[41.29,-16.818],[31.219,-10.272],[19.336,-22.961],[57.303,-22.961],[57.303,-25.983],[30.817,-55.188],[3.726,-26.99]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-6.345,0],[-0.403,-7.855]],"o":[[0.806,-7.452],[6.244,0],[0,0]],"v":[[19.336,-32.227],[30.817,-44.009],[41.794,-32.227]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"e","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"l","size":94,"style":"Bold","w":30.3,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-8.862,0],[0,0],[0,0],[0,0],[0,3.223],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[-2.82,0],[0,0],[0,0],[0,0],[0,9.97]],"v":[[20.746,0],[29.709,0],[29.709,-11.279],[26.083,-11.279],[21.753,-15.71],[21.753,-71.503],[6.647,-71.503],[6.647,-14.099]],"c":true},"ix":2},"nm":"l","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"l","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"c","size":94,"style":"Bold","w":58.8,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-16.617,0],[-1.511,13.293],[0,0],[5.841,0],[0,10.474],[-7.553,0],[-0.906,-6.647],[0,0],[14.401,0],[0,-17.02]],"o":[[14.703,0],[0,0],[-0.806,7.352],[-7.553,0],[0,-10.474],[5.74,0],[0,0],[-1.611,-13.293],[-16.617,0],[0,17.02]],"v":[[31.119,1.208],[57.504,-20.544],[41.895,-21.149],[31.119,-10.373],[19.235,-26.99],[31.119,-43.506],[41.895,-33.334],[57.504,-34.039],[31.119,-55.188],[3.726,-26.99]],"c":true},"ix":2},"nm":"c","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"c","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"o","size":94,"style":"Bold","w":60.8,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-16.516,0],[0,17.12],[16.516,0],[0,-17.02]],"o":[[16.516,0],[0,-17.02],[-16.516,0],[0,17.12]],"v":[[31.119,1.208],[58.511,-26.99],[31.119,-55.188],[3.726,-26.99]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[7.553,0],[0,10.474],[-7.553,0],[0,-10.474]],"o":[[-7.553,0],[0,-10.474],[7.553,0],[0,10.474]],"v":[[31.119,-10.373],[19.235,-26.99],[31.119,-43.607],[43.002,-26.99]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"o","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"m","size":94,"style":"Bold","w":88.4,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-5.539,0],[0,-8.359],[0,0],[0,0],[0,0],[-5.539,0],[0,-8.459],[0,0],[0,0],[0,0],[11.078,0],[2.518,-7.15],[7.755,0],[2.719,-6.345],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-8.157],[5.841,0],[0,0],[0,0],[0,0],[0,-8.157],[5.64,0],[0,0],[0,0],[0,0],[0,-13.293],[-7.15,0],[-2.316,-6.747],[-6.546,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-30.313],[30.414,-43.204],[38.47,-30.313],[38.47,0],[52.066,0],[52.066,-30.313],[60.727,-43.204],[68.884,-30.515],[68.884,0],[83.99,0],[83.99,-34.744],[65.863,-55.188],[50.656,-44.211],[35.248,-55.188],[20.544,-45.218],[20.242,-53.979],[6.647,-53.979]],"c":true},"ix":2},"nm":"m","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"m","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"\u0003","size":94,"style":"Bold","w":0,"fFamily":"Geist"},{"ch":"t","size":94,"style":"Bold","w":43.6,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.179,0],[0,0],[0,0],[0,0],[0,5.237],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[-4.431,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,11.279]],"v":[[29.004,0],[42.096,0],[42.096,-11.279],[34.543,-11.279],[27.896,-18.027],[27.896,-42.7],[42.096,-42.7],[42.096,-53.979],[27.896,-53.979],[27.896,-66.669],[12.79,-66.669],[12.79,-53.979],[4.129,-53.979],[4.129,-42.7],[12.79,-42.7],[12.79,-16.214]],"c":true},"ix":2},"nm":"t","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"t","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":" ","size":94,"style":"Bold","w":24.2,"data":{},"fFamily":"Geist"},{"ch":"D","size":94,"style":"Bold","w":70.4,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,22.861],[23.163,0],[0,0]],"o":[[0,0],[22.76,0],[0,-22.961],[0,0],[0,0]],"v":[[7.452,0],[32.73,0],[68.28,-35.651],[32.126,-71.503],[7.452,-71.503]],"c":true},"ix":2},"nm":"D","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,-15.509],[13.797,0]],"o":[[0,0],[0,0],[13.797,0],[0,15.509],[0,0]],"v":[[22.76,-12.891],[22.76,-58.612],[32.126,-58.612],[52.469,-35.751],[32.126,-12.891]],"c":true},"ix":2},"nm":"D","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"D","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"s","size":94,"style":"Bold","w":56.1,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-16.718,0],[0,10.474],[17.322,2.518],[0,3.928],[-5.438,0],[-0.705,-5.237],[0,0],[15.207,0],[0,-10.373],[-17.12,-2.82],[0,-3.323],[5.438,0],[0.906,5.136],[0,0]],"o":[[14.703,0],[0,-9.366],[-7.452,-1.108],[0,-3.625],[5.338,0],[0,0],[-1.41,-10.776],[-15.912,0],[0,9.366],[9.265,1.511],[0,3.625],[-6.546,0],[0,0],[0.806,10.574]],"v":[[29.709,1.208],[53.879,-15.106],[30.615,-31.824],[20.544,-38.47],[28.903,-44.513],[38.269,-36.053],[53.476,-36.658],[29.004,-55.188],[5.035,-37.866],[27.292,-21.249],[38.269,-14.804],[29.709,-9.467],[19.034,-17.523],[3.726,-17.02]],"c":true},"ix":2},"nm":"s","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"s","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"k","size":94,"style":"Bold","w":59.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-15.207],[29.205,-23.163],[44.009,0],[60.526,0],[38.974,-31.723],[59.921,-53.979],[41.794,-53.979],[21.753,-31.824],[21.753,-71.503],[6.647,-71.503]],"c":true},"ix":2},"nm":"k","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"k","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"T","size":94,"style":"Bold","w":60.9,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[22.458,0],[37.866,0],[37.866,-58.612],[59.317,-58.612],[59.317,-71.503],[1.108,-71.503],[1.108,-58.612],[22.458,-58.612]],"c":true},"ix":2},"nm":"T","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"T","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"h","size":94,"style":"Bold","w":60.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-6.244,0],[0,-7.956],[0,0],[0,0],[0,0],[12.085,0],[2.921,-5.338],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-8.157],[5.841,0],[0,0],[0,0],[0,0],[0,-11.984],[-6.445,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-30.716],[31.622,-43.204],[39.78,-31.119],[39.78,0],[54.886,0],[54.886,-34.744],[36.658,-55.188],[21.753,-47.333],[21.753,-71.503],[6.647,-71.503]],"c":true},"ix":2},"nm":"h","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"h","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"i","size":94,"style":"Bold","w":26.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[6.345,-60.626],[21.954,-60.626],[21.954,-72.711],[6.345,-72.711]],"c":true},"ix":2},"nm":"i","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-53.979],[6.647,-53.979]],"c":true},"ix":2},"nm":"i","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"i","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"n","size":94,"style":"Bold","w":59.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-6.244,0],[0,-8.661],[0,0],[0,0],[0,0],[11.581,0],[2.921,-7.352],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-8.661],[6.345,0],[0,0],[0,0],[0,0],[0,-12.488],[-7.15,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-29.709],[31.522,-43.204],[39.78,-29.709],[39.78,0],[54.886,0],[54.886,-34.744],[36.557,-55.188],[20.544,-45.016],[20.242,-53.979],[6.647,-53.979]],"c":true},"ix":2},"nm":"n","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"n","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"g","size":94,"style":"Bold","w":62.4,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.408,0],[0,14.502],[0,0],[0,0],[0,0],[7.855,0],[0,-15.509],[-13.293,0],[-2.618,5.035],[0,0],[7.15,0],[1.108,3.525],[0,0]],"o":[[15.61,0],[0,0],[0,0],[0,0],[-2.82,-5.74],[-13.193,0],[0,16.013],[6.949,0],[0,0],[0,7.553],[-6.345,0],[0,0],[2.417,8.862]],"v":[[30.817,16.315],[57.202,-5.338],[57.202,-53.979],[42.398,-53.979],[42.398,-45.621],[26.184,-55.188],[3.726,-28.802],[26.385,-2.518],[41.995,-10.776],[41.995,-5.035],[30.817,5.74],[20.847,0.101],[5.338,1.208]],"c":true},"ix":2},"nm":"g","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[6.949,0],[0,9.265],[-7.251,0],[0.101,-9.467]],"o":[[-7.05,0],[0,-9.164],[7.05,0],[0,9.567]],"v":[[30.716,-13.696],[19.235,-28.903],[30.716,-44.11],[42.096,-28.903]],"c":true},"ix":2},"nm":"g","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"g","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"V","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[23.969,0],[36.456,0],[57.706,-71.503],[46.527,-71.503],[30.212,-14.099],[13.898,-71.503],[2.719,-71.503]],"c":true},"ix":2},"nm":"V","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"V","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"e","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.106,0],[-3.122,10.172],[0,0],[6.345,0],[0.604,10.272],[0,0],[0,0],[14.2,0],[0,-16.919]],"o":[[11.581,0],[0,0],[-1.611,4.935],[-8.359,0],[0,0],[0,0],[0,-17.725],[-14.905,0],[0,17.02]],"v":[[30.917,1.208],[53.879,-15.61],[43.204,-16.315],[31.018,-8.157],[16.718,-23.767],[54.785,-23.767],[54.785,-26.587],[30.615,-54.785],[6.042,-26.788]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-7.553,0],[-0.806,-9.164]],"o":[[1.309,-8.862],[7.15,0],[0,0]],"v":[[16.718,-31.924],[30.615,-45.419],[43.808,-31.924]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"e","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"r","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[-7.755,0],[0,0],[0,0],[0,0],[1.813,-6.949],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,-8.459],[0,0],[0,0],[0,0],[-6.848,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[8.459,0],[48.944,0],[48.944,-8.56],[32.227,-8.56],[32.227,-31.824],[44.009,-44.714],[54.987,-44.714],[54.987,-53.577],[43.607,-53.577],[30.716,-43.304],[30.112,-53.577],[8.459,-53.577],[8.459,-45.016],[22.055,-45.016],[22.055,-8.56],[8.459,-8.56]],"c":true},"ix":2},"nm":"r","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"r","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"s","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-14.401,0],[0,9.668],[16.214,2.82],[0,4.834],[-7.251,0],[-1.108,-5.136],[0,0],[14.2,0],[0,-9.366],[-15.811,-2.921],[0,-4.028],[7.251,0],[1.007,5.942],[0,0]],"o":[[12.991,0],[0,-8.862],[-9.668,-1.813],[0,-4.028],[6.747,0],[0,0],[-1.511,-9.265],[-13.696,0],[0,9.064],[10.675,1.913],[0,4.23],[-7.855,0],[0,0],[0.806,10.071]],"v":[[31.018,1.208],[52.872,-14.301],[32.227,-30.212],[19.336,-38.37],[29.709,-45.621],[41.693,-36.758],[52.066,-37.363],[29.709,-54.785],[8.862,-38.37],[29.608,-21.854],[42.499,-14.2],[31.119,-7.956],[17.926,-17.422],[7.553,-16.818]],"c":true},"ix":2},"nm":"s","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"s","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"i","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[27.997,-61.331],[38.47,-61.331],[38.47,-71.906],[27.997,-71.906]],"c":true},"ix":2},"nm":"i","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[7.654,0],[56.799,0],[56.799,-8.56],[38.873,-8.56],[38.873,-53.577],[8.661,-53.577],[8.661,-45.016],[28.601,-45.016],[28.601,-8.56],[7.654,-8.56]],"c":true},"ix":2},"nm":"i","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"i","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"o","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.005,0],[0,17.12],[14.905,0],[0,-17.12]],"o":[[14.905,0],[0,-17.12],[-15.005,0],[0,17.12]],"v":[[30.212,1.208],[54.987,-26.788],[30.212,-54.785],[5.438,-26.788]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[8.963,0],[0,11.682],[-9.064,0],[0,-11.682]],"o":[[-9.064,0],[0,-11.682],[8.963,0],[0,11.682]],"v":[[30.212,-8.157],[16.113,-26.788],[30.212,-45.419],[44.312,-26.788]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"o","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"n","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-7.452,0],[0,-9.265],[0,0],[0,0],[0,0],[11.984,0],[2.82,-7.452],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-9.265],[7.15,0],[0,0],[0,0],[0,0],[0,-11.481],[-7.452,0],[0,0],[0,0],[0,0]],"v":[[8.057,0],[18.228,0],[18.228,-31.723],[31.522,-45.923],[42.197,-31.924],[42.197,0],[52.368,0],[52.368,-34.543],[34.442,-54.785],[17.624,-43.909],[17.422,-53.577],[8.057,-53.577]],"c":true},"ix":2},"nm":"n","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"n","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":" ","size":40,"style":"Medium","w":60,"data":{},"fFamily":"Geist Mono"},{"ch":"v","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[23.868,0],[36.557,0],[55.893,-53.577],[44.916,-53.577],[30.212,-10.776],[15.509,-53.577],[4.532,-53.577]],"c":true},"ix":2},"nm":"v","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"v","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"0","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.811,0],[0,22.961],[15.811,0],[0,-23.062]],"o":[[15.811,0],[0,-23.062],[-15.811,0],[0,22.961]],"v":[[30.212,1.611],[55.591,-35.651],[30.212,-73.114],[4.834,-35.651]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,5.942],[-8.963,0],[-2.216,-2.719],[0,0]],"o":[[0,-17.422],[3.323,0],[0,0],[-1.309,-4.129]],"v":[[15.811,-35.651],[30.212,-63.245],[38.672,-59.116],[17.725,-20.343]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[8.963,0],[2.316,2.719],[0,0],[0,-6.042]],"o":[[-3.323,0],[0,0],[1.309,4.129],[0,17.221]],"v":[[30.212,-8.258],[21.652,-12.387],[42.7,-51.059],[44.614,-35.651]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"0","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":".","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[23.364,0],[37.061,0],[37.061,-13.293],[23.364,-13.293]],"c":true},"ix":2},"nm":".","mn":"ADBE Vector Shape - Group","hd":false}],"nm":".","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"9","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[16.516,0],[0,-15.811],[-14.2,0],[-3.424,4.431],[9.668,0],[1.41,4.935],[0,0],[-13.193,0],[0,23.666]],"o":[[-15.106,0],[0,14.603],[7.452,0],[-1.41,15.509],[-7.05,0],[0,0],[2.719,10.071],[21.048,0],[0,-20.544]],"v":[[30.011,-73.114],[4.633,-47.534],[27.997,-23.364],[44.714,-31.018],[27.292,-8.258],[15.61,-16.516],[4.733,-15.61],[27.292,1.611],[55.792,-41.089]],"c":true},"ix":2},"nm":"9","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-8.762,0],[0,-8.862],[8.963,0],[0,9.064]],"o":[[8.963,0],[0,9.064],[-8.359,0],[0,-9.769]],"v":[[30.011,-63.144],[44.614,-47.736],[29.205,-32.932],[15.61,-47.736]],"c":true},"ix":2},"nm":"9","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"9","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"}]}
\ No newline at end of file
+{"v":"5.9.0","fr":30,"ip":0,"op":60,"w":1230,"h":390,"nm":"LandingAnimation_Centered","ddd":0,"assets":[],"fonts":{"list":[{"fName":"Geist-Bold","fFamily":"Geist","fStyle":"Bold","ascent":70.9991455078125},{"fName":"GeistMono-Medium","fFamily":"Geist Mono","fStyle":"Medium","ascent":70.9991455078125}]},"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL 1","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[679.5,146.941,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[310.393,146.941,0]}],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"NULL","parent":1,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":0,"s":[0]},{"t":45,"s":[-360]}],"ix":10},"p":{"a":0,"k":[-65.299,89.341,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"InsideMask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,50.002,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-51.694,0],[0,51.694],[51.694,0],[0,-51.694]],"o":[[51.694,0],[0,-51.694],[-51.694,0],[0,51.694]],"v":[[0,93.6],[93.6,0],[0,-93.6],[-93.6,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.117647059262,0.843137264252,0.376470595598,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":0,"s":[0]},{"t":35,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 2","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Inside","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[49.025,49.837,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[242,242,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-23.57],[-23.79,0],[0,23.57],[23.79,0]],"o":[[0,23.57],[23.79,0],[0,-23.57],[-23.79,0]],"v":[[-43.15,0],[0,42.75],[43.15,0],[0,-42.75]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,20.12],[-20.34,0],[0,-20.12],[20.34,0]],"o":[[0,-20.12],[20.34,0],[0,20.12],[-20.34,0]],"v":[[-36.89,0],[0,-36.49],[36.89,0],[0,36.49]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.133333340287,0.772549033165,0.368627458811,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"OutsideMask","parent":2,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,49.998,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-41.36,-133.505],[-31.87,-173.275],[33.27,-173.275],[41.13,-133.005],[65.5,-123.555],[99.81,-145.935],[145.97,-98.215],[123.8,-63.315],[133.25,-40.025],[174.08,-32.005],[174.08,33.405],[133.25,41.925],[123.58,65.195],[146.42,100.095],[99.63,145.815],[64.71,123.385],[41.23,132.975],[32.23,173.285],[-33.15,173.285],[-41.6,132.195],[-65.92,123.285],[-100.17,145.545],[-146.41,99.695],[-124.24,64.555],[-133.95,41.725],[-173.84,32.745],[-174.08,-32.045],[-133.95,-40.025],[-124.1,-64.105],[-146.73,-98.215],[-99.5,-145.935],[-64.2,-124.055]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":25,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.33],"y":[0]},"t":0,"s":[0]},{"t":35,"s":[100]}],"ix":2},"o":{"a":0,"k":4,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 2","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Outside","parent":2,"tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[49.025,49.817,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[242,242,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.33,0],[0,0],[0,0],[-0.68,3.26],[0,0],[0,0],[0,0],[-2.36,2.36],[0,0],[1.83,2.79],[0,0],[0,0],[0,0],[0,3.33],[0,0],[3.26,0.68],[0,0],[0,0],[0,0],[2.36,2.36],[0,0],[2.79,-1.83],[0,0],[0,0],[0,0],[3.33,0],[0,0],[0.68,-3.26],[0,0],[0,0],[0,0],[2.36,-2.36],[0,0],[-1.83,-2.79],[0,0],[0,0],[0,0],[0,-3.33],[0,0],[-3.26,-0.68],[0,0],[0,0],[0,0],[-2.36,-2.36],[0,0],[-2.79,1.83],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[3.34,0],[0,0],[0,0],[0,0],[2.79,1.82],[0,0],[2.36,-2.36],[0,0],[0,0],[0,0],[3.26,-0.68],[0,0],[0,-3.34],[0,0],[0,0],[0,0],[1.82,-2.79],[0,0],[-2.35,-2.36],[0,0],[0,0],[0,0],[-0.68,-3.26],[0,0],[-3.34,0],[0,0],[0,0],[0,0],[-2.79,-1.82],[0,0],[-2.36,2.35],[0,0],[0,0],[0,0],[-3.26,0.68],[0,0],[0,3.34],[0,0],[0,0],[0,0],[-1.82,2.79],[0,0],[2.36,2.36],[0,0],[0,0],[0,0],[0.68,3.26]],"v":[[-11.25,76.14],[11.25,76.14],[11.25,76.12],[18.18,70.49],[20.75,58.17],[26.47,55.8],[37,62.69],[45.88,61.77],[61.79,45.86],[62.71,36.98],[55.82,26.46],[58.19,20.74],[70.51,18.17],[76.14,11.24],[76.14,-11.26],[70.51,-18.19],[58.19,-20.76],[55.82,-26.48],[62.71,-37],[61.79,-45.88],[45.88,-61.79],[37,-62.71],[26.47,-55.82],[20.75,-58.19],[18.18,-70.51],[11.25,-76.14],[-11.25,-76.14],[-18.18,-70.51],[-20.75,-58.19],[-26.47,-55.82],[-37,-62.71],[-45.88,-61.79],[-61.79,-45.88],[-62.71,-37],[-55.82,-26.47],[-58.19,-20.75],[-70.51,-18.18],[-76.14,-11.25],[-76.14,11.25],[-70.51,18.18],[-58.19,20.75],[-55.82,26.47],[-62.71,37],[-61.79,45.88],[-45.88,61.79],[-37,62.71],[-26.47,55.82],[-20.75,58.19],[-18.18,70.51]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[2.16,0.9],[0,0],[1.96,-1.28],[0,0],[0,0],[0,0],[0.9,2.17],[0,0],[2.29,0.48],[0,0],[0,0],[0,0],[-0.9,2.17],[0,0],[1.28,1.96],[0,0],[0,0],[0,0],[-2.17,0.9],[0,0],[-0.48,2.29],[0,0],[0,0],[0,0],[-2.17,-0.9],[0,0],[-1.96,1.28],[0,0],[0,0],[0,0],[-0.9,-2.17],[0,0],[-2.29,-0.48],[0,0],[0,0],[0,0],[0.9,-2.16],[0,0],[-1.28,-1.96],[0,0],[0,0],[0,0],[2.17,-0.9],[0,0],[0.48,-2.29]],"o":[[0,0],[0,0],[0,0],[-0.48,-2.29],[0,0],[-2.17,-0.89],[0,0],[0,0],[0,0],[1.29,-1.96],[0,0],[-0.89,-2.16],[0,0],[0,0],[0,0],[2.3,-0.48],[0,0],[0.9,-2.17],[0,0],[0,0],[0,0],[1.96,1.28],[0,0],[2.16,-0.89],[0,0],[0,0],[0,0],[0.48,2.3],[0,0],[2.17,0.9],[0,0],[0,0],[0,0],[-1.29,1.96],[0,0],[0.89,2.17],[0,0],[0,0],[0,0],[-2.29,0.48],[0,0],[-0.9,2.17],[0,0],[0,0],[0,0],[-1.96,-1.29],[0,0],[-2.17,0.89],[0,0]],"v":[[10.22,67.8],[-10.22,67.8],[-10.21,67.79],[-12.7,55.82],[-16.92,50.73],[-23.88,47.85],[-30.47,48.47],[-40.69,55.17],[-55.14,40.71],[-48.44,30.49],[-47.82,23.9],[-50.7,16.95],[-55.79,12.73],[-67.76,10.24],[-67.76,-10.2],[-55.8,-12.69],[-50.7,-16.91],[-47.82,-23.86],[-48.44,-30.45],[-55.14,-40.67],[-40.69,-55.13],[-30.47,-48.43],[-23.88,-47.81],[-16.92,-50.69],[-12.7,-55.78],[-10.21,-67.74],[10.23,-67.74],[12.72,-55.78],[16.94,-50.68],[23.89,-47.8],[30.48,-48.42],[40.7,-55.12],[55.15,-40.66],[48.45,-30.44],[47.83,-23.85],[50.71,-16.9],[55.8,-12.68],[67.77,-10.19],[67.77,10.25],[55.8,12.74],[50.71,16.96],[47.83,23.92],[48.45,30.51],[55.15,40.73],[40.69,55.18],[30.47,48.48],[23.88,47.86],[16.93,50.74],[12.71,55.83]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":5,"nm":"Version v0.9","cl":"9","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[165.298,-14,0],"ix":2,"l":2},"a":{"a":0,"k":[2.981,-22.559,0],"ix":1,"l":2},"s":{"a":0,"k":[134.638,134.638,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[1.088,-29.246],[1.088,0.645],[390.702,0.599],[390.702,-29.292]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"t":{"d":{"k":[{"s":{"s":40,"f":"GeistMono-Medium","t":"Version v0.9","ca":0,"j":0,"tr":0,"lh":94,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"sm":{"a":0,"k":100,"ix":6},"r":1},"a":{"p":{"a":1,"k":[{"i":{"x":0.25,"y":1},"o":{"x":0.33,"y":0},"t":30,"s":[0,50,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[0,0,0]}],"ix":2}}}]},"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":5,"nm":"Welcome to DeskThing 2","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162.047,114,0],"ix":2,"l":2},"a":{"a":0,"k":[2.981,-22.559,0],"ix":1,"l":2},"s":{"a":0,"k":[134.638,134.638,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3.503,-67.213],[3.237,125.676],[621.645,125.796],[621.91,-67.092]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"t":{"d":{"k":[{"s":{"s":94,"f":"Geist-Bold","t":"Welcome\u0003to DeskThing","ca":0,"j":0,"tr":0,"lh":94,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"sm":{"a":0,"k":100,"ix":6},"r":1},"a":{"p":{"a":1,"k":[{"i":{"x":0.25,"y":1},"o":{"x":0.33,"y":0},"t":15,"s":[-606,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[0,0,0]}],"ix":2}}}]},"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":5,"nm":"Welcome to DeskThing","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[162.047,114,0],"ix":2,"l":2},"a":{"a":0,"k":[2.981,-22.559,0],"ix":1,"l":2},"s":{"a":0,"k":[134.638,134.638,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[3.503,-67.213],[3.237,125.676],[621.645,125.796],[621.91,-67.092]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"Mask 1"}],"t":{"d":{"k":[{"s":{"s":94,"f":"Geist-Bold","t":"Welcome\u0003to DeskThing","ca":0,"j":0,"tr":0,"lh":94,"ls":0,"fc":[1,1,1]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"sm":{"a":0,"k":100,"ix":6},"r":1},"a":{"p":{"a":1,"k":[{"i":{"x":0.25,"y":1},"o":{"x":0.33,"y":0},"t":15,"s":[-606,0,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45,"s":[0,0,0]}],"ix":2}}}]},"ip":0,"op":300,"st":0,"bm":0}],"markers":[],"chars":[{"ch":"W","size":94,"style":"Bold","w":100.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[23.062,0],[39.377,0],[51.059,-45.721],[62.741,0],[79.257,0],[98.492,-71.503],[82.48,-71.503],[70.294,-21.149],[57.806,-71.503],[44.412,-71.503],[32.025,-21.149],[19.739,-71.503],[3.726,-71.503]],"c":true},"ix":2},"nm":"W","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"W","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"e","size":94,"style":"Bold","w":59.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-16.516,0],[-2.921,10.776],[0,0],[5.035,0],[0.403,8.56],[0,0],[0,0],[16.013,0],[0,-17.02]],"o":[[12.991,0],[0,0],[-1.41,4.431],[-6.949,0],[0,0],[0,0],[0,-18.127],[-16.415,0],[0,17.02]],"v":[[30.917,1.208],[56.497,-15.912],[41.29,-16.818],[31.219,-10.272],[19.336,-22.961],[57.303,-22.961],[57.303,-25.983],[30.817,-55.188],[3.726,-26.99]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-6.345,0],[-0.403,-7.855]],"o":[[0.806,-7.452],[6.244,0],[0,0]],"v":[[19.336,-32.227],[30.817,-44.009],[41.794,-32.227]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"e","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"l","size":94,"style":"Bold","w":30.3,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-8.862,0],[0,0],[0,0],[0,0],[0,3.223],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[-2.82,0],[0,0],[0,0],[0,0],[0,9.97]],"v":[[20.746,0],[29.709,0],[29.709,-11.279],[26.083,-11.279],[21.753,-15.71],[21.753,-71.503],[6.647,-71.503],[6.647,-14.099]],"c":true},"ix":2},"nm":"l","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"l","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"c","size":94,"style":"Bold","w":58.8,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-16.617,0],[-1.511,13.293],[0,0],[5.841,0],[0,10.474],[-7.553,0],[-0.906,-6.647],[0,0],[14.401,0],[0,-17.02]],"o":[[14.703,0],[0,0],[-0.806,7.352],[-7.553,0],[0,-10.474],[5.74,0],[0,0],[-1.611,-13.293],[-16.617,0],[0,17.02]],"v":[[31.119,1.208],[57.504,-20.544],[41.895,-21.149],[31.119,-10.373],[19.235,-26.99],[31.119,-43.506],[41.895,-33.334],[57.504,-34.039],[31.119,-55.188],[3.726,-26.99]],"c":true},"ix":2},"nm":"c","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"c","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"o","size":94,"style":"Bold","w":60.8,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-16.516,0],[0,17.12],[16.516,0],[0,-17.02]],"o":[[16.516,0],[0,-17.02],[-16.516,0],[0,17.12]],"v":[[31.119,1.208],[58.511,-26.99],[31.119,-55.188],[3.726,-26.99]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[7.553,0],[0,10.474],[-7.553,0],[0,-10.474]],"o":[[-7.553,0],[0,-10.474],[7.553,0],[0,10.474]],"v":[[31.119,-10.373],[19.235,-26.99],[31.119,-43.607],[43.002,-26.99]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"o","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"m","size":94,"style":"Bold","w":88.4,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-5.539,0],[0,-8.359],[0,0],[0,0],[0,0],[-5.539,0],[0,-8.459],[0,0],[0,0],[0,0],[11.078,0],[2.518,-7.15],[7.755,0],[2.719,-6.345],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-8.157],[5.841,0],[0,0],[0,0],[0,0],[0,-8.157],[5.64,0],[0,0],[0,0],[0,0],[0,-13.293],[-7.15,0],[-2.316,-6.747],[-6.546,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-30.313],[30.414,-43.204],[38.47,-30.313],[38.47,0],[52.066,0],[52.066,-30.313],[60.727,-43.204],[68.884,-30.515],[68.884,0],[83.99,0],[83.99,-34.744],[65.863,-55.188],[50.656,-44.211],[35.248,-55.188],[20.544,-45.218],[20.242,-53.979],[6.647,-53.979]],"c":true},"ix":2},"nm":"m","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"m","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"\u0003","size":94,"style":"Bold","w":0,"fFamily":"Geist"},{"ch":"t","size":94,"style":"Bold","w":43.6,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.179,0],[0,0],[0,0],[0,0],[0,5.237],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[-4.431,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,11.279]],"v":[[29.004,0],[42.096,0],[42.096,-11.279],[34.543,-11.279],[27.896,-18.027],[27.896,-42.7],[42.096,-42.7],[42.096,-53.979],[27.896,-53.979],[27.896,-66.669],[12.79,-66.669],[12.79,-53.979],[4.129,-53.979],[4.129,-42.7],[12.79,-42.7],[12.79,-16.214]],"c":true},"ix":2},"nm":"t","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"t","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":" ","size":94,"style":"Bold","w":24.2,"data":{},"fFamily":"Geist"},{"ch":"D","size":94,"style":"Bold","w":70.4,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,22.861],[23.163,0],[0,0]],"o":[[0,0],[22.76,0],[0,-22.961],[0,0],[0,0]],"v":[[7.452,0],[32.73,0],[68.28,-35.651],[32.126,-71.503],[7.452,-71.503]],"c":true},"ix":2},"nm":"D","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,-15.509],[13.797,0]],"o":[[0,0],[0,0],[13.797,0],[0,15.509],[0,0]],"v":[[22.76,-12.891],[22.76,-58.612],[32.126,-58.612],[52.469,-35.751],[32.126,-12.891]],"c":true},"ix":2},"nm":"D","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"D","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"s","size":94,"style":"Bold","w":56.1,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-16.718,0],[0,10.474],[17.322,2.518],[0,3.928],[-5.438,0],[-0.705,-5.237],[0,0],[15.207,0],[0,-10.373],[-17.12,-2.82],[0,-3.323],[5.438,0],[0.906,5.136],[0,0]],"o":[[14.703,0],[0,-9.366],[-7.452,-1.108],[0,-3.625],[5.338,0],[0,0],[-1.41,-10.776],[-15.912,0],[0,9.366],[9.265,1.511],[0,3.625],[-6.546,0],[0,0],[0.806,10.574]],"v":[[29.709,1.208],[53.879,-15.106],[30.615,-31.824],[20.544,-38.47],[28.903,-44.513],[38.269,-36.053],[53.476,-36.658],[29.004,-55.188],[5.035,-37.866],[27.292,-21.249],[38.269,-14.804],[29.709,-9.467],[19.034,-17.523],[3.726,-17.02]],"c":true},"ix":2},"nm":"s","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"s","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"k","size":94,"style":"Bold","w":59.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-15.207],[29.205,-23.163],[44.009,0],[60.526,0],[38.974,-31.723],[59.921,-53.979],[41.794,-53.979],[21.753,-31.824],[21.753,-71.503],[6.647,-71.503]],"c":true},"ix":2},"nm":"k","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"k","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"T","size":94,"style":"Bold","w":60.9,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[22.458,0],[37.866,0],[37.866,-58.612],[59.317,-58.612],[59.317,-71.503],[1.108,-71.503],[1.108,-58.612],[22.458,-58.612]],"c":true},"ix":2},"nm":"T","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"T","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"h","size":94,"style":"Bold","w":60.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-6.244,0],[0,-7.956],[0,0],[0,0],[0,0],[12.085,0],[2.921,-5.338],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-8.157],[5.841,0],[0,0],[0,0],[0,0],[0,-11.984],[-6.445,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-30.716],[31.622,-43.204],[39.78,-31.119],[39.78,0],[54.886,0],[54.886,-34.744],[36.658,-55.188],[21.753,-47.333],[21.753,-71.503],[6.647,-71.503]],"c":true},"ix":2},"nm":"h","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"h","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"i","size":94,"style":"Bold","w":26.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[6.345,-60.626],[21.954,-60.626],[21.954,-72.711],[6.345,-72.711]],"c":true},"ix":2},"nm":"i","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-53.979],[6.647,-53.979]],"c":true},"ix":2},"nm":"i","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"i","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"n","size":94,"style":"Bold","w":59.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-6.244,0],[0,-8.661],[0,0],[0,0],[0,0],[11.581,0],[2.921,-7.352],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-8.661],[6.345,0],[0,0],[0,0],[0,0],[0,-12.488],[-7.15,0],[0,0],[0,0],[0,0]],"v":[[6.647,0],[21.753,0],[21.753,-29.709],[31.522,-43.204],[39.78,-29.709],[39.78,0],[54.886,0],[54.886,-34.744],[36.557,-55.188],[20.544,-45.016],[20.242,-53.979],[6.647,-53.979]],"c":true},"ix":2},"nm":"n","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"n","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"g","size":94,"style":"Bold","w":62.4,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.408,0],[0,14.502],[0,0],[0,0],[0,0],[7.855,0],[0,-15.509],[-13.293,0],[-2.618,5.035],[0,0],[7.15,0],[1.108,3.525],[0,0]],"o":[[15.61,0],[0,0],[0,0],[0,0],[-2.82,-5.74],[-13.193,0],[0,16.013],[6.949,0],[0,0],[0,7.553],[-6.345,0],[0,0],[2.417,8.862]],"v":[[30.817,16.315],[57.202,-5.338],[57.202,-53.979],[42.398,-53.979],[42.398,-45.621],[26.184,-55.188],[3.726,-28.802],[26.385,-2.518],[41.995,-10.776],[41.995,-5.035],[30.817,5.74],[20.847,0.101],[5.338,1.208]],"c":true},"ix":2},"nm":"g","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[6.949,0],[0,9.265],[-7.251,0],[0.101,-9.467]],"o":[[-7.05,0],[0,-9.164],[7.05,0],[0,9.567]],"v":[[30.716,-13.696],[19.235,-28.903],[30.716,-44.11],[42.096,-28.903]],"c":true},"ix":2},"nm":"g","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"g","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist"},{"ch":"V","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[23.969,0],[36.456,0],[57.706,-71.503],[46.527,-71.503],[30.212,-14.099],[13.898,-71.503],[2.719,-71.503]],"c":true},"ix":2},"nm":"V","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"V","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"e","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.106,0],[-3.122,10.172],[0,0],[6.345,0],[0.604,10.272],[0,0],[0,0],[14.2,0],[0,-16.919]],"o":[[11.581,0],[0,0],[-1.611,4.935],[-8.359,0],[0,0],[0,0],[0,-17.725],[-14.905,0],[0,17.02]],"v":[[30.917,1.208],[53.879,-15.61],[43.204,-16.315],[31.018,-8.157],[16.718,-23.767],[54.785,-23.767],[54.785,-26.587],[30.615,-54.785],[6.042,-26.788]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-7.553,0],[-0.806,-9.164]],"o":[[1.309,-8.862],[7.15,0],[0,0]],"v":[[16.718,-31.924],[30.615,-45.419],[43.808,-31.924]],"c":true},"ix":2},"nm":"e","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"e","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"r","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[-7.755,0],[0,0],[0,0],[0,0],[1.813,-6.949],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,-8.459],[0,0],[0,0],[0,0],[-6.848,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[8.459,0],[48.944,0],[48.944,-8.56],[32.227,-8.56],[32.227,-31.824],[44.009,-44.714],[54.987,-44.714],[54.987,-53.577],[43.607,-53.577],[30.716,-43.304],[30.112,-53.577],[8.459,-53.577],[8.459,-45.016],[22.055,-45.016],[22.055,-8.56],[8.459,-8.56]],"c":true},"ix":2},"nm":"r","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"r","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"s","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-14.401,0],[0,9.668],[16.214,2.82],[0,4.834],[-7.251,0],[-1.108,-5.136],[0,0],[14.2,0],[0,-9.366],[-15.811,-2.921],[0,-4.028],[7.251,0],[1.007,5.942],[0,0]],"o":[[12.991,0],[0,-8.862],[-9.668,-1.813],[0,-4.028],[6.747,0],[0,0],[-1.511,-9.265],[-13.696,0],[0,9.064],[10.675,1.913],[0,4.23],[-7.855,0],[0,0],[0.806,10.071]],"v":[[31.018,1.208],[52.872,-14.301],[32.227,-30.212],[19.336,-38.37],[29.709,-45.621],[41.693,-36.758],[52.066,-37.363],[29.709,-54.785],[8.862,-38.37],[29.608,-21.854],[42.499,-14.2],[31.119,-7.956],[17.926,-17.422],[7.553,-16.818]],"c":true},"ix":2},"nm":"s","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"s","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"i","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[27.997,-61.331],[38.47,-61.331],[38.47,-71.906],[27.997,-71.906]],"c":true},"ix":2},"nm":"i","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[7.654,0],[56.799,0],[56.799,-8.56],[38.873,-8.56],[38.873,-53.577],[8.661,-53.577],[8.661,-45.016],[28.601,-45.016],[28.601,-8.56],[7.654,-8.56]],"c":true},"ix":2},"nm":"i","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"i","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"o","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.005,0],[0,17.12],[14.905,0],[0,-17.12]],"o":[[14.905,0],[0,-17.12],[-15.005,0],[0,17.12]],"v":[[30.212,1.208],[54.987,-26.788],[30.212,-54.785],[5.438,-26.788]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[8.963,0],[0,11.682],[-9.064,0],[0,-11.682]],"o":[[-9.064,0],[0,-11.682],[8.963,0],[0,11.682]],"v":[[30.212,-8.157],[16.113,-26.788],[30.212,-45.419],[44.312,-26.788]],"c":true},"ix":2},"nm":"o","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"o","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"n","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-7.452,0],[0,-9.265],[0,0],[0,0],[0,0],[11.984,0],[2.82,-7.452],[0,0],[0,0]],"o":[[0,0],[0,0],[0,-9.265],[7.15,0],[0,0],[0,0],[0,0],[0,-11.481],[-7.452,0],[0,0],[0,0],[0,0]],"v":[[8.057,0],[18.228,0],[18.228,-31.723],[31.522,-45.923],[42.197,-31.924],[42.197,0],[52.368,0],[52.368,-34.543],[34.442,-54.785],[17.624,-43.909],[17.422,-53.577],[8.057,-53.577]],"c":true},"ix":2},"nm":"n","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"n","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":" ","size":40,"style":"Medium","w":60,"data":{},"fFamily":"Geist Mono"},{"ch":"v","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[23.868,0],[36.557,0],[55.893,-53.577],[44.916,-53.577],[30.212,-10.776],[15.509,-53.577],[4.532,-53.577]],"c":true},"ix":2},"nm":"v","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"v","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"0","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-15.811,0],[0,22.961],[15.811,0],[0,-23.062]],"o":[[15.811,0],[0,-23.062],[-15.811,0],[0,22.961]],"v":[[30.212,1.611],[55.591,-35.651],[30.212,-73.114],[4.834,-35.651]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,5.942],[-8.963,0],[-2.216,-2.719],[0,0]],"o":[[0,-17.422],[3.323,0],[0,0],[-1.309,-4.129]],"v":[[15.811,-35.651],[30.212,-63.245],[38.672,-59.116],[17.725,-20.343]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[8.963,0],[2.316,2.719],[0,0],[0,-6.042]],"o":[[-3.323,0],[0,0],[1.309,4.129],[0,17.221]],"v":[[30.212,-8.258],[21.652,-12.387],[42.7,-51.059],[44.614,-35.651]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"0","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":".","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[23.364,0],[37.061,0],[37.061,-13.293],[23.364,-13.293]],"c":true},"ix":2},"nm":".","mn":"ADBE Vector Shape - Group","hd":false}],"nm":".","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"},{"ch":"9","size":40,"style":"Medium","w":60,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[16.516,0],[0,-15.811],[-14.2,0],[-3.424,4.431],[9.668,0],[1.41,4.935],[0,0],[-13.193,0],[0,23.666]],"o":[[-15.106,0],[0,14.603],[7.452,0],[-1.41,15.509],[-7.05,0],[0,0],[2.719,10.071],[21.048,0],[0,-20.544]],"v":[[30.011,-73.114],[4.633,-47.534],[27.997,-23.364],[44.714,-31.018],[27.292,-8.258],[15.61,-16.516],[4.733,-15.61],[27.292,1.611],[55.792,-41.089]],"c":true},"ix":2},"nm":"9","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-8.762,0],[0,-8.862],[8.963,0],[0,9.064]],"o":[[8.963,0],[0,9.064],[-8.359,0],[0,-9.769]],"v":[[30.011,-63.144],[44.614,-47.736],[29.205,-32.932],[15.61,-47.736]],"c":true},"ix":2},"nm":"9","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"9","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Geist Mono"}]}
\ No newline at end of file
diff --git a/DeskThingServer/src/renderer/src/assets/icons/icon/IconToggle.tsx b/DeskThingServer/src/renderer/src/assets/icons/icon/IconToggle.tsx
index 6ef3df5c..afcc11b0 100644
--- a/DeskThingServer/src/renderer/src/assets/icons/icon/IconToggle.tsx
+++ b/DeskThingServer/src/renderer/src/assets/icons/icon/IconToggle.tsx
@@ -3,9 +3,10 @@ import { useEffect, useRef } from 'react'
interface ToggleProps extends IconProps {
checked: boolean
+ disabled?: boolean
}
-function IconToggle({ checked, ...props }: ToggleProps): JSX.Element {
+function IconToggle({ disabled = false, checked, ...props }: ToggleProps): JSX.Element {
const circleRef = useRef(null)
useEffect(() => {
@@ -29,9 +30,16 @@ function IconToggle({ checked, ...props }: ToggleProps): JSX.Element {
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
+ style={{ opacity: disabled ? 0.5 : 1, cursor: disabled ? 'not-allowed' : 'pointer' }}
>
-
+
ADB -> Restart Server)', weight: 4, minimum: 32 },
{ message: 'Have you tried asking the device nicely?', weight: 1, minimum: 19 },
@@ -55,87 +55,87 @@ export const deviceMessages = [
{
message: "Plot twist: The device was working all along, and we're in a simulation!",
weight: 1,
- minimum: 50
+ minimum: 12
},
{
message: 'Why do we try if it only continues to bring suffering',
weight: 1,
- minimum: 50
+ minimum: 12
},
{
message: 'I aspire to have your level of persistance',
weight: 1,
- minimum: 50
+ minimum: 15
},
{
message: 'I dont think you understand how difficult it is to debug this',
weight: 1,
- minimum: 50
+ minimum: 15
},
{
message: 'Okay I get it - I dont think its working',
weight: 1,
- minimum: 50
+ minimum: 20
},
{
message: "You've been at this for quite awhile now, I think you should take a break",
weight: 1,
- minimum: 50
+ minimum: 20
},
{
message: 'TOUCH GRASS',
weight: 1,
- minimum: 70
+ minimum: 30
},
{
message:
'"Insanity is doing the same thing over and over again and expecting different results,"',
weight: 1,
- minimum: 70
+ minimum: 30
},
{
message: 'Try one more time. I believe in you',
weight: 1,
- minimum: 70
+ minimum: 30
},
{
message: 'Im sure the problem is something simple, you just need to find it',
weight: 1,
- minimum: 70
+ minimum: 30
},
{
message: 'At this point just ask on the discord!',
weight: 1,
- minimum: 120
+ minimum: 50
},
{
message: 'At this point just ask on the discord!',
weight: 1,
- minimum: 120
+ minimum: 50
},
{
message: 'At this point just ask on the discord!',
weight: 1,
- minimum: 120
+ minimum: 50
},
{
message: 'Some people never give up!',
weight: 1,
- minimum: 150
+ minimum: 60
},
{
message: 'Woah! You have a lot of patience. This means youve been trying for a while',
weight: 1,
- minimum: 1000
+ minimum: 60
},
{
message: 'This is an easter egg. Send a screenshot of this over the discord',
weight: 1,
- minimum: 1200
+ minimum: 100
},
{
message: 'There are no more messages. You have reached the end of the line',
weight: 1,
- minimum: 1250
+ minimum: 110
}
]
diff --git a/DeskThingServer/src/renderer/src/components/Connection.tsx b/DeskThingServer/src/renderer/src/components/Connection.tsx
index 7ce26ad6..0111b273 100644
--- a/DeskThingServer/src/renderer/src/components/Connection.tsx
+++ b/DeskThingServer/src/renderer/src/components/Connection.tsx
@@ -30,6 +30,7 @@ const ConnectionComponent: React.FC = ({ client }) =>
const [showLogging, setShowLogging] = useState(false)
const [offline, setOffline] = useState(false)
const refreshADbClients = useClientStore((store) => store.requestADBDevices)
+ const requestClientManifest = useClientStore((store) => store.requestClientManifest)
const devicePort = useSettingsStore((store) => store.settings.devicePort)
useEffect(() => {
@@ -106,9 +107,7 @@ const ConnectionComponent: React.FC = ({ client }) =>
setLogging(reply)
if (reply.final) {
unsubscribe()
- }
- if (!reply.status) {
- unsubscribe()
+ requestClientManifest()
}
})
} catch (error) {
@@ -222,12 +221,13 @@ const ConnectionComponent: React.FC = ({ client }) =>
{!client.connected && (
+
-
+
Configure
diff --git a/DeskThingServer/src/renderer/src/components/NotificationButton.tsx b/DeskThingServer/src/renderer/src/components/NotificationButton.tsx
index 70a3764d..a4267d0a 100644
--- a/DeskThingServer/src/renderer/src/components/NotificationButton.tsx
+++ b/DeskThingServer/src/renderer/src/components/NotificationButton.tsx
@@ -9,6 +9,7 @@ const NotificationButton: React.FC = () => {
const taskNum = useNotificationStore((state) => state.totalTasks)
const logs = useNotificationStore((state) => state.logs)
+ const requests = useNotificationStore((state) => state.requestQueue)
const issues = useNotificationStore((state) => state.issues.length)
const [errors, setErrors] = useState(0)
@@ -30,8 +31,11 @@ const NotificationButton: React.FC = () => {
{taskNum > 0 ? (
0 || issues > 0 ? 'bg-red-500' : 'bg-green-500'} p-1 w-full rounded text-xs`}
+ className={`${errors > 0 || issues > 0 ? 'bg-red-500' : 'bg-green-500'} relative p-1 w-full rounded text-xs`}
>
+ {requests.length > 0 && (
+
+ )}
{taskNum}
) : (
diff --git a/DeskThingServer/src/renderer/src/components/RankableList.tsx b/DeskThingServer/src/renderer/src/components/RankableList.tsx
new file mode 100644
index 00000000..944b2155
--- /dev/null
+++ b/DeskThingServer/src/renderer/src/components/RankableList.tsx
@@ -0,0 +1,74 @@
+import React, { useEffect, useState } from 'react'
+import { SettingOption } from '@shared/types'
+import { IconArrowDown, IconArrowUp } from '@renderer/assets/icons'
+
+type RankableListProps = {
+ options: SettingOption[]
+ value: string[]
+ onChange: (orderedOptions: string[]) => void
+}
+
+const RankableList: React.FC = ({ options, onChange, value }) => {
+ const [orderedOptions, setOrderedOptions] = useState(options)
+
+ useEffect(() => {
+ // Sort options based on the value order
+ const sortedOptions = [...options].sort((a, b) => {
+ const aIndex = value.indexOf(a.value)
+ const bIndex = value.indexOf(b.value)
+ return aIndex - bIndex
+ })
+ setOrderedOptions(sortedOptions)
+ }, [options, value]) // Re-run when options or value change
+
+ const handleMoveUp = (index: number): void => {
+ if (index > 0) {
+ const newOptions = [...orderedOptions]
+ ;[newOptions[index], newOptions[index - 1]] = [newOptions[index - 1], newOptions[index]]
+ setOrderedOptions(newOptions)
+ onChange(newOptions.map((option) => option.value))
+ }
+ }
+
+ const handleMoveDown = (index: number): void => {
+ if (index < orderedOptions.length - 1) {
+ const newOptions = [...orderedOptions]
+ ;[newOptions[index], newOptions[index + 1]] = [newOptions[index + 1], newOptions[index]]
+ setOrderedOptions(newOptions)
+ onChange(newOptions.map((option) => option.value))
+ }
+ }
+
+ return (
+
+ {orderedOptions.map((option, index) => (
+
+
+
+ handleMoveUp(index)}
+ className={`p-1 rounded-md hover:bg-zinc-800 ${index === 0 ? 'opacity-30 cursor-not-allowed' : ''}`}
+ >
+
+
+ handleMoveDown(index)}
+ className={`p-1 rounded-md hover:bg-zinc-800 ${
+ index === orderedOptions.length - 1 ? 'opacity-30 cursor-not-allowed' : ''
+ }`}
+ >
+
+
+
+
+ ))}
+
+ )
+}
+
+export default RankableList
diff --git a/DeskThingServer/src/renderer/src/components/Select.tsx b/DeskThingServer/src/renderer/src/components/Select.tsx
new file mode 100644
index 00000000..a8b4e181
--- /dev/null
+++ b/DeskThingServer/src/renderer/src/components/Select.tsx
@@ -0,0 +1,102 @@
+import { SettingOption } from '@shared/types'
+import React from 'react'
+import ReactSelect, { MultiValue, SingleValue } from 'react-select'
+
+interface SelectProps {
+ options: SettingOption[]
+ value: string[] | string
+ isMulti?: boolean
+ placeholder: string
+ className?: string
+ onChange: (value: SingleValue | MultiValue) => void
+}
+const Select: React.FC = ({
+ options,
+ isMulti,
+ onChange,
+ value,
+ placeholder,
+ className
+}) => {
+ // We can't use tailwind css here because of how classes are passed into the child components
+ const customStyles = {
+ control: (provided) => ({
+ ...provided,
+ backgroundColor: 'rgb(24, 24, 27)', // Tailwind `bg-zinc-900`
+ borderColor: 'rgb(63, 63, 70)', // Tailwind `border-zinc-700`
+ color: 'white',
+ padding: '0.25rem 0.5rem', // Tailwind `p-2`
+ boxShadow: 'none',
+ '&:hover': {
+ borderColor: 'rgb(113, 113, 122)' // Tailwind `border-zinc-600`
+ }
+ }),
+ menu: (provided) => ({
+ ...provided,
+ backgroundColor: 'rgb(24, 24, 27)', // Tailwind `bg-zinc-900`
+ color: 'white',
+ borderRadius: '0.25rem', // Tailwind `rounded-md`
+ padding: '0.5rem' // Tailwind `p-2`
+ }),
+ menuList: (provided) => ({
+ ...provided,
+ padding: 0
+ }),
+ option: (provided, state) => ({
+ ...provided,
+ backgroundColor: state.isSelected
+ ? 'rgb(34, 197, 94)' // Tailwind `bg-green-500`
+ : state.isFocused
+ ? 'rgb(39, 39, 42)' // Tailwind `bg-zinc-800`
+ : 'transparent',
+ color: state.isSelected ? 'white' : 'rgb(229, 231, 235)', // Tailwind `text-gray-200`
+ padding: '0.5rem 1rem', // Tailwind `px-4 py-2`
+ '&:hover': {
+ backgroundColor: 'rgb(39, 39, 42)', // Tailwind `bg-zinc-800`
+ color: 'rgb(229, 231, 235)' // Tailwind `text-gray-200`
+ }
+ }),
+ singleValue: (provided) => ({
+ ...provided,
+ color: 'white'
+ }),
+ multiValue: (provided) => ({
+ ...provided,
+ backgroundColor: 'rgb(63, 63, 70)', // Tailwind `bg-zinc-700`
+ color: 'white'
+ }),
+ multiValueLabel: (provided) => ({
+ ...provided,
+ color: 'rgb(229, 231, 235)' // Tailwind `text-gray-200`
+ }),
+ multiValueRemove: (provided) => ({
+ ...provided,
+ color: 'rgb(229, 231, 235)', // Tailwind `text-gray-200`
+ '&:hover': {
+ backgroundColor: 'rgb(185, 28, 28)', // Tailwind `bg-red-700`
+ color: 'white'
+ }
+ })
+ }
+
+ return (
+ <>
+ value.includes(option.value))
+ : options.find((option) => option.value === value)
+ }
+ options={options}
+ className={className}
+ isMulti={isMulti}
+ closeMenuOnSelect={!isMulti}
+ styles={customStyles}
+ placeholder={placeholder}
+ onChange={onChange}
+ />
+ >
+ )
+}
+
+export default Select
diff --git a/DeskThingServer/src/renderer/src/components/settings/SettingsString.tsx b/DeskThingServer/src/renderer/src/components/settings/SettingsString.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/DeskThingServer/src/renderer/src/listeners/LogDataListener.tsx b/DeskThingServer/src/renderer/src/listeners/LogDataListener.tsx
index 0fed88a1..3c926152 100644
--- a/DeskThingServer/src/renderer/src/listeners/LogDataListener.tsx
+++ b/DeskThingServer/src/renderer/src/listeners/LogDataListener.tsx
@@ -1,47 +1,23 @@
import { useEffect } from 'react'
import { useLogStore, useNotificationStore } from '../stores'
+import { Log } from '@shared/types'
const LogDataListener = (): null => {
const addLog = useLogStore((state) => state.addLog)
const addLogs = useNotificationStore((state) => state.addLog)
useEffect(() => {
- const handleError = (_event, errorData): void => {
- if (typeof errorData != 'string') {
- console.log(errorData)
- errorData = JSON.stringify(errorData)
- }
- addLog('error', errorData)
- addLogs('error', errorData)
- }
-
- const handleLog = (_event, logData): void => {
- if (typeof logData != 'string') {
- console.log(logData)
- logData = JSON.stringify(logData)
- }
- addLog('log', logData)
- }
-
- const handleMessage = (_event, messageData): void => {
- if (typeof messageData != 'string') {
- console.log(messageData)
- messageData = JSON.stringify(messageData)
- }
- addLog('message', messageData)
- addLogs('log', messageData)
+ const handleLog = (_event, log: Log): void => {
+ addLog(log)
+ addLogs(log)
}
// Listen for IPC events
- window.electron.ipcRenderer.on('error', handleError)
window.electron.ipcRenderer.on('log', handleLog)
- window.electron.ipcRenderer.on('message', handleMessage)
// Clean up the IPC listeners when the component unmounts
return () => {
- window.electron.ipcRenderer.removeAllListeners('error')
window.electron.ipcRenderer.removeAllListeners('log')
- window.electron.ipcRenderer.removeAllListeners('message')
}
}, [addLog])
diff --git a/DeskThingServer/src/renderer/src/nav/Nav.tsx b/DeskThingServer/src/renderer/src/nav/Nav.tsx
index 09d019fe..d5a7a6a0 100644
--- a/DeskThingServer/src/renderer/src/nav/Nav.tsx
+++ b/DeskThingServer/src/renderer/src/nav/Nav.tsx
@@ -65,7 +65,7 @@ const Nav: React.FC = () => {
location="Developer"
currentPage={currentPage}
handleNavigation={handleNavigation}
- subDirectories={['App', 'ADB', 'Logs']}
+ subDirectories={['Logs', 'App', 'ADB']}
>
Dev
diff --git a/DeskThingServer/src/renderer/src/overlays/Overlay.tsx b/DeskThingServer/src/renderer/src/overlays/Overlay.tsx
index 6e957cc5..d09d0e8d 100644
--- a/DeskThingServer/src/renderer/src/overlays/Overlay.tsx
+++ b/DeskThingServer/src/renderer/src/overlays/Overlay.tsx
@@ -25,7 +25,7 @@ const Overlay: React.FC = ({ onClose, className, chil
}, [onClose])
return (
-
+
diff --git a/DeskThingServer/src/renderer/src/overlays/SuccessNotification.tsx b/DeskThingServer/src/renderer/src/overlays/SuccessNotification.tsx
new file mode 100644
index 00000000..75488585
--- /dev/null
+++ b/DeskThingServer/src/renderer/src/overlays/SuccessNotification.tsx
@@ -0,0 +1,58 @@
+import { AppReturnData } from '@shared/types'
+import Overlay from './Overlay'
+import Button from '@renderer/components/Button'
+import { IconPlay } from '@renderer/assets/icons'
+import { useState } from 'react'
+
+interface SuccessNotificationProps {
+ appReturnData: AppReturnData
+ setAppReturnData: (data: null) => void
+ runApp: (appId: string) => void
+}
+
+export function SuccessNotification({
+ appReturnData,
+ setAppReturnData,
+ runApp
+}: SuccessNotificationProps): JSX.Element {
+ const [loading, setLoading] = useState(false)
+
+ if (!appReturnData) return <>>
+
+ const onRunClick = async (): Promise => {
+ setLoading(true)
+ setTimeout(() => {
+ setAppReturnData(null)
+ runApp(appReturnData.appId)
+ }, 500)
+ }
+
+ const onClose = (): void => {
+ runApp(appReturnData.appId)
+ setAppReturnData(null)
+ }
+
+ return (
+
+
+
Successfully Downloaded
+
+
+
+
{appReturnData.appName} is installed
+
v{appReturnData.appVersion}
+
+
+
+ Initialize App
+
+
+
+ )
+}
diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx
index 90a472c2..7f13a61d 100644
--- a/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx
+++ b/DeskThingServer/src/renderer/src/overlays/apps/AppDetails.tsx
@@ -51,7 +51,7 @@ const AppDetails: React.FC = ({ app }: AppSettingProps) => {
rel="noreferrer noopener"
className="text-blue-500 hover:text-blue-400"
>
- {app.manifest.homepage}
+ {app.manifest.repository}
diff --git a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx
index 672a9867..d393ce8d 100644
--- a/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx
+++ b/DeskThingServer/src/renderer/src/overlays/apps/AppSettings.tsx
@@ -1,9 +1,12 @@
import React, { useEffect, useState, useCallback, useMemo } from 'react'
import { useAppStore } from '@renderer/stores'
-import { AppDataInterface, SettingsType } from '@shared/types'
+import { AppDataInterface, SettingOption, SettingsString, SettingsType } from '@shared/types'
import { AppSettingProps } from './AppsOverlay'
import Button from '@renderer/components/Button'
-import { IconCheck, IconLoading, IconSave, IconToggle, IconX } from '@renderer/assets/icons'
+import { IconLoading, IconSave, IconToggle } from '@renderer/assets/icons'
+import Select from '@renderer/components/Select'
+import { MultiValue, SingleValue } from 'react-select'
+import RankableList from '@renderer/components/RankableList'
const AppSettings: React.FC
= ({ app }) => {
const getAppData = useAppStore((state) => state.getAppData)
@@ -42,10 +45,16 @@ const AppSettings: React.FC = ({ app }) => {
[]
)
+ const clampValue = (value: number, min?: number, max?: number): number => {
+ if (min !== undefined && value < min) return min
+ if (max !== undefined && value > max) return max
+ return value
+ }
+
const renderSettingInput = useCallback(
(setting: SettingsType, key: string) => {
const commonClasses =
- 'mt-1 block px-3 py-2 bg-black border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'
+ 'mt-1 block px-3 py-2 bg-zinc-900 border border-zinc-700 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm'
switch (setting.type || '') {
case 'string':
@@ -55,6 +64,7 @@ const AppSettings: React.FC = ({ app }) => {
handleSettingChange(key, e.target.value)}
className={commonClasses + ' w-full'}
/>
@@ -71,7 +81,11 @@ const AppSettings: React.FC = ({ app }) => {
value={setting.value as number}
min={setting.min}
max={setting.max}
- onChange={(e) => handleSettingChange(key, Number(e.target.value))}
+ onChange={(e) => {
+ let inputValue = Number(e.target.value)
+ inputValue = clampValue(inputValue, setting.min, setting.max)
+ handleSettingChange(key, inputValue)
+ }}
className={commonClasses}
/>
)}
@@ -90,21 +104,37 @@ const AppSettings: React.FC = ({ app }) => {
)
- case 'select':
+ case 'range':
return (
- {setting.type == 'select' && (
- handleSettingChange(key, e.target.value)}
- className={commonClasses}
- >
- {setting.options?.map((option, index) => (
-
- {option.label}
-
- ))}
-
+ className="w-96 max-w-s"
+ />
+ )}
+
+ )
+ case 'select':
+ return (
+
+ {setting.type == 'select' && (
+
+ {
+ const selectedValue = selected as SingleValue
+ handleSettingChange(key, selectedValue!.value)
+ }}
+ />
+
)}
)
@@ -112,26 +142,34 @@ const AppSettings: React.FC = ({ app }) => {
return (
{setting.type == 'multiselect' && (
-
- {setting.options?.map((option, index) => (
-
{
- e.preventDefault()
- const currentValues = [...(setting.value as string[])]
- if (currentValues.includes(option.value)) {
- currentValues.splice(currentValues.indexOf(option.value), 1)
- } else {
- currentValues.push(option.value)
- }
- handleSettingChange(key, currentValues)
- }}
- >
- {setting.value[index] ? : }
- {option.label}
-
- ))}
+
+ {
+ const selectedValues = selected as MultiValue
+ const currentValues = selectedValues.map((value) => value.value)
+ handleSettingChange(key, currentValues)
+ }}
+ />
+
+ )}
+
+ )
+ case 'ranked':
+ return (
+
+ {setting.type == 'ranked' && (
+
+ {
+ handleSettingChange(key, rankedValues)
+ }}
+ />
)}
@@ -203,12 +241,17 @@ const SettingComponent = ({ setting, children, className }: SettingComponentProp
-
-
- {setting.type?.toUpperCase() || 'Legacy Setting'}
-
-
-
{setting.label}
+
+
+
{setting.type?.toUpperCase() || 'Legacy Setting'}
+ {setting.type === 'number' && (
+
+ MIN: {setting.min} | MAX: {setting.max}
+
+ )}
+
+
+
{setting.label}
{setting.description && (
{setting.description}
@@ -216,7 +259,10 @@ const SettingComponent = ({ setting, children, className }: SettingComponentProp
)}
- {children}
+
+ {setting.type === 'range' &&
{setting.value}
}
+ {children}
+
)
}
diff --git a/DeskThingServer/src/renderer/src/overlays/notifications/EventsPage.tsx b/DeskThingServer/src/renderer/src/overlays/notifications/EventsPage.tsx
index 93756901..0c049114 100644
--- a/DeskThingServer/src/renderer/src/overlays/notifications/EventsPage.tsx
+++ b/DeskThingServer/src/renderer/src/overlays/notifications/EventsPage.tsx
@@ -2,6 +2,7 @@ import React from 'react'
import { useNotificationStore } from '@renderer/stores'
import { IconTrash } from '@renderer/assets/icons'
import Button from '@renderer/components/Button'
+import { MESSAGE_TYPES } from '@shared/types'
const EvensPage: React.FC = () => {
const logs = useNotificationStore((state) => state.logs)
@@ -21,21 +22,27 @@ const EvensPage: React.FC = () => {
readLogs(index)}
- className="pl-2 py-2 rounded-xl hover:bg-zinc-900 relative group border-gray-500 flex items-center justify w-full"
+ className="pl-2 py-2 rounded-xl hover:bg-zinc-900 relative group border-gray-500 flex items-center justify-start w-full"
>
- {log.log}
+ {log.log}
))}
diff --git a/DeskThingServer/src/renderer/src/overlays/notifications/NotificationOverlay.tsx b/DeskThingServer/src/renderer/src/overlays/notifications/NotificationOverlay.tsx
index 3fe835cf..15aa3ebc 100644
--- a/DeskThingServer/src/renderer/src/overlays/notifications/NotificationOverlay.tsx
+++ b/DeskThingServer/src/renderer/src/overlays/notifications/NotificationOverlay.tsx
@@ -60,7 +60,12 @@ const NotificationOverlay: React.FC = () => {
curPage={page}
value={notifState.requestQueue.length}
Icon={
}
- />
+ className="relative"
+ >
+ {notifState.requestQueue.length > 0 && (
+
+ )}
+
(
setPage(page.toLowerCase())}
- className={`gap-2 ${curPage == page.toLowerCase() ? 'bg-zinc-800 hover:bg-zinc-700' : 'bg-zinc-900 hover:bg-zinc-800'} ${value == 0 && 'text-gray-500'} ${className}`}
+ className={`relative gap-2 ${curPage == page.toLowerCase() ? 'bg-zinc-800 hover:bg-zinc-700' : 'bg-zinc-900 hover:bg-zinc-800'} ${value == 0 && 'text-gray-500'} ${className}`}
>
{value > 0 && {value}
}
{Icon}
{page}
{value != 1 && 's'}
+ {children}
)
diff --git a/DeskThingServer/src/renderer/src/overlays/notifications/RequestsPage.tsx b/DeskThingServer/src/renderer/src/overlays/notifications/RequestsPage.tsx
index 1fa94c6e..a01fd665 100644
--- a/DeskThingServer/src/renderer/src/overlays/notifications/RequestsPage.tsx
+++ b/DeskThingServer/src/renderer/src/overlays/notifications/RequestsPage.tsx
@@ -43,7 +43,7 @@ interface RequestProps {
const RequestComponent = ({ request }: RequestProps): React.ReactElement => {
const resolveRequest = useNotificationStore((state) => state.resolveRequest)
const [expanded, setIsExpanded] = useState(false)
- const [focusedIndex, setFocusedIndex] = useState(-1)
+ const [focusedIndex, setFocusedIndex] = useState(0)
const [formData, setFormData] = useState<{ [key: string]: string }>({})
const [allFieldsFilled, setAllFieldsFilled] = useState(false)
@@ -73,7 +73,7 @@ const RequestComponent = ({ request }: RequestProps): React.ReactElement => {
const toggleExpanded = (): void => {
setIsExpanded(!expanded)
- setFocusedIndex(-1)
+ setFocusedIndex(0)
}
useEffect(() => {
diff --git a/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx b/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx
index f0068414..c4b5da86 100644
--- a/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx
+++ b/DeskThingServer/src/renderer/src/overlays/settings/ClientSettings.tsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import { useClientStore, useAppStore } from '@renderer/stores'
+import { useClientStore } from '@renderer/stores'
import { ClientManifest } from '@shared/types'
import Button from '@renderer/components/Button'
import { IconToggle, IconSave, IconLoading } from '@renderer/assets/icons'
@@ -7,7 +7,6 @@ import { IconToggle, IconSave, IconLoading } from '@renderer/assets/icons'
const ClientSettings: React.FC = () => {
const clientSettings = useClientStore((state) => state.clientManifest)
const updateClientSettings = useClientStore((state) => state.updateClientManifest)
- const apps = useAppStore((state) => state.appsList)
const [localSettings, setLocalSettings] = useState(clientSettings)
const [loading, setLoading] = useState(false)
@@ -34,34 +33,6 @@ const ClientSettings: React.FC = () => {
return (
-
-
Default View
- handleSettingChange('default_view', e.target.value)}
- className="focus:text-white bg-zinc-900 text-white rounded px-2 py-2"
- >
- Landing
- Dashboard
- {apps.map((app) => (
-
- {app.manifest?.label || app.name}
-
- ))}
-
-
-
-
Miniplayer Mode
- handleSettingChange('miniplayer', e.target.value)}
- className="focus:text-white bg-zinc-900 text-white rounded px-2 py-2"
- >
- Peek
- Hidden
- Full
-
-
IP Address
diff --git a/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx b/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx
index 9b70bf3d..699c9988 100644
--- a/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx
+++ b/DeskThingServer/src/renderer/src/overlays/settings/MusicSettings.tsx
@@ -3,13 +3,16 @@ import useSettingsStore from '../../stores/settingsStore'
import useAppStore from '../../stores/appStore'
import Button from '@renderer/components/Button'
import { IconLoading, IconSave, IconToggle } from '@renderer/assets/icons'
+import Select from '@renderer/components/Select'
+import { SingleValue } from 'react-select'
+import { SettingOption, Settings } from '@shared/types'
const MusicSettings: React.FC = () => {
- const initialSettings = useSettingsStore((settings) => settings.settings)
const saveSettings = useSettingsStore((settings) => settings.saveSettings)
+ const requestSettings = useSettingsStore((settings) => settings.requestSettings)
const appsList = useAppStore((state) => state.appsList)
const [audioSources, setAudioSources] = useState<{ id: string; name: string }[]>([])
- const [settings, setSettings] = useState(initialSettings)
+ const [settings, setSettings] = useState(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
@@ -20,14 +23,23 @@ const MusicSettings: React.FC = () => {
name: app.manifest?.label || app.name
}))
setAudioSources(sources)
+
+ const fetchSettings = async (): Promise => {
+ const settings = await requestSettings()
+ setSettings(settings)
+ }
+
+ fetchSettings()
}, [appsList])
const handleSettingChange = (key: string, value: string | boolean | number | string[]): void => {
+ if (!settings) return
setSettings({ ...settings, [key]: value })
console.log('Settings Updated:', settings)
}
const handleSave = async (): Promise => {
+ if (!settings) return
setLoading(true)
await saveSettings(settings)
setTimeout(() => {
@@ -43,55 +55,68 @@ const MusicSettings: React.FC = () => {
- handleSettingChange('refreshInterval', settings.refreshInterval === -1 ? 5000 : -1)
+ handleSettingChange('refreshInterval', settings?.refreshInterval === -1 ? 15000 : -1)
}
>
-
Playback Sources
- Playback Sources
+ {
- handleSettingChange('playbackLocation', e.target.value)
+ const value = e as SingleValue
+ handleSettingChange('playbackLocation', value?.value || '')
}}
- defaultValue={'Unset'}
- className="bg-zinc-900 rounded hover:cursor-pointer text-white px-2 py-2"
- >
- {audioSources.map((app) => (
-
- {app.name}
-
- ))}
-
- {'None'}
-
-
+ value={settings ? settings.playbackLocation || '' : 'Disabled'}
+ className="bg-zinc-900 rounded hover:cursor-pointer text-white px-2 py-2 w-full"
+ options={[
+ ...audioSources.map((app) => ({
+ value: app.id,
+ label: app.name
+ })),
+ {
+ value: 'none',
+ label: 'None'
+ },
+ {
+ value: 'disabled',
+ label: 'Disabled'
+ }
+ ]}
+ />
{
const initialSettings = useSettingsStore((settings) => settings.settings)
@@ -9,6 +12,12 @@ const ServerSettings: React.FC = () => {
const [settings, setSettings] = useState(initialSettings)
const [loading, setLoading] = useState(false)
+ const logLevelOptions = [
+ { value: LOGGING_LEVEL.SYSTEM, label: 'SYSTEM' },
+ { value: LOGGING_LEVEL.APPS, label: 'APPS' },
+ { value: LOGGING_LEVEL.PRODUCTION, label: 'PRODUCTION' }
+ ]
+
const handleSettingChange = (key: string, value: string | boolean | number): void => {
setSettings({ ...settings, [key]: value })
}
@@ -63,6 +72,19 @@ const ServerSettings: React.FC = () => {
/>
+
+
Logging Level
+ {
+ const selectedValue = selected as SingleValue
+ handleSettingChange('logLevel', selectedValue?.value || LOGGING_LEVEL.PRODUCTION)
+ }}
+ />
+
Close To Taskbar
{
)
}
-
export default ServerSettings
diff --git a/DeskThingServer/src/renderer/src/pages/Apps/AppsList.tsx b/DeskThingServer/src/renderer/src/pages/Apps/AppsList.tsx
index d35e1ed3..bbcc58e1 100644
--- a/DeskThingServer/src/renderer/src/pages/Apps/AppsList.tsx
+++ b/DeskThingServer/src/renderer/src/pages/Apps/AppsList.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'
import Sidebar from '@renderer/nav/Sidebar'
import Button from '@renderer/components/Button'
-import { IconDownload } from '@renderer/assets/icons'
+import { IconDownload, IconLink } from '@renderer/assets/icons'
import { useAppStore, useNotificationStore, usePageStore } from '@renderer/stores'
import App from '@renderer/components/App'
import MainElement from '@renderer/nav/MainElement'
@@ -80,31 +80,46 @@ const AppsList: React.FC = () => {
{appsList ? (
-
- {order.map((appName, index) => {
- const app = appsList.find((a) => a.name === appName)
- if (!app) return null
- return (
-
handleDragStart(e, app.name)}
- onDragOver={(e) => handleDragOver(e, index)}
- onDragLeave={handleDragLeave}
- onDrop={() => handleDrop(app.name)}
- className={`relative ${
- dragOverIndex === index ? 'border-t-2 border-zinc-500' : ''
- }`}
- >
-
- {index === order.length - 1 && dragOverIndex === index && (
-
- )}
-
- )
- })}
-
+ appsList.length > 0 ? (
+
+ {order.map((appName, index) => {
+ const app = appsList.find((a) => a.name === appName)
+ if (!app) return null
+ return (
+
handleDragStart(e, app.name)}
+ onDragOver={(e) => handleDragOver(e, index)}
+ onDragLeave={handleDragLeave}
+ onDrop={() => handleDrop(app.name)}
+ className={`relative ${
+ dragOverIndex === index ? 'border-t-2 border-zinc-500' : ''
+ }`}
+ >
+
+ {index === order.length - 1 && dragOverIndex === index && (
+
+ )}
+
+ )
+ })}
+
+ ) : (
+ // Shows when the AppsList is initialized but empty
+
+
No apps downloaded yet!
+
+ Downloads Page
+
+
+
+ )
) : (
+ // Shows while retrieving the apps list from the store
Loading...
)}
diff --git a/DeskThingServer/src/renderer/src/pages/Clients/Connections.tsx b/DeskThingServer/src/renderer/src/pages/Clients/Connections.tsx
index 5a3db499..4a86ac97 100644
--- a/DeskThingServer/src/renderer/src/pages/Clients/Connections.tsx
+++ b/DeskThingServer/src/renderer/src/pages/Clients/Connections.tsx
@@ -95,7 +95,9 @@ const ClientConnections: React.FC = () => {
{settings.localIp &&
settings.localIp.map((ip, index) => (
-
{ip + ':' + settings.devicePort}
+
+ {ip + ':' + settings.devicePort}
+
))}
Staged Client
diff --git a/DeskThingServer/src/renderer/src/pages/Dev/Logs.tsx b/DeskThingServer/src/renderer/src/pages/Dev/Logs.tsx
index 421143be..adc7565b 100644
--- a/DeskThingServer/src/renderer/src/pages/Dev/Logs.tsx
+++ b/DeskThingServer/src/renderer/src/pages/Dev/Logs.tsx
@@ -12,13 +12,15 @@ import {
IconWarning
} from '@renderer/assets/icons'
import { useReward } from 'react-rewards'
+import { MESSAGE_TYPES } from '@shared/types/types'
const Logs: React.FC = () => {
const { logList, getLogs } = useLogStore()
- const [filter, setFilter] = useState
(null)
+ const [filter, setFilter] = useState(null)
const logContainerRef = useRef(null)
const [autoScroll, setAutoScroll] = useState(true)
const { reward, isAnimating } = useReward('rewardId', 'confetti')
+ const [hoveredLog, setHoveredLog] = useState(null)
const filteredLogs = filter ? logList.filter((log) => log.type === filter) : logList
@@ -45,14 +47,26 @@ const Logs: React.FC = () => {
}
const handleCopyLogs = (): void => {
- const logsText = filteredLogs.map((log) => `[${log.date}] ${log.type}: ${log.log}`).join('\n')
+ const logsText = filteredLogs
+ .map(
+ (log) =>
+ `[${new Date(log.date || '2024-11-17T11:16:16.970Z').toLocaleTimeString()} ${log.source}]: ${log.log}`
+ )
+ .join('\n')
navigator.clipboard.writeText(logsText)
reward()
}
return (
-
+
+
+
+ {hoveredLog && (
+
{hoveredLog}
+ )}
+
+
@@ -70,25 +84,53 @@ const Logs: React.FC = () => {
setFilter(null)}
- className={`w-full ${filter === null ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
+ className={`w-full ${filter === null ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
>
All
setFilter('message')}
- className={`w-full ${filter === 'message' ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
+ onClick={() => setFilter(MESSAGE_TYPES.MESSAGE)}
+ className={`w-full ${filter === MESSAGE_TYPES.MESSAGE ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
>
Messages
setFilter('error')}
- className={`w-full ${filter === 'error' ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
+ onClick={() => setFilter(MESSAGE_TYPES.ERROR)}
+ className={`w-full ${filter === MESSAGE_TYPES.ERROR ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
>
Errors
+
setFilter(MESSAGE_TYPES.WARNING)}
+ className={`w-full ${filter === MESSAGE_TYPES.WARNING ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
+ >
+
+ Warnings
+
+
setFilter(MESSAGE_TYPES.FATAL)}
+ className={`w-full ${filter === MESSAGE_TYPES.FATAL ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
+ >
+
+ Fatal
+
+
setFilter(MESSAGE_TYPES.DEBUG)}
+ className={`w-full ${filter === MESSAGE_TYPES.DEBUG ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
+ >
+
+ Debug
+
+
setFilter(MESSAGE_TYPES.LOGGING)}
+ className={`w-full ${filter === MESSAGE_TYPES.LOGGING ? 'bg-zinc-800 hover:bg-zinc-700' : 'hover:bg-zinc-900'}`}
+ >
+
+ Logs
+
{
onScroll={handleScroll}
>
diff --git a/DeskThingServer/src/renderer/src/pages/Downloads/AppDownloads.tsx b/DeskThingServer/src/renderer/src/pages/Downloads/AppDownloads.tsx
index e404da2a..3f8ddd25 100644
--- a/DeskThingServer/src/renderer/src/pages/Downloads/AppDownloads.tsx
+++ b/DeskThingServer/src/renderer/src/pages/Downloads/AppDownloads.tsx
@@ -1,19 +1,13 @@
import React, { useState } from 'react'
import Sidebar from '@renderer/nav/Sidebar'
import Button from '@renderer/components/Button'
-import {
- IconDownload,
- IconLink,
- IconLoading,
- IconLogs,
- IconPlay,
- IconUpload
-} from '@renderer/assets/icons'
+import { IconDownload, IconLink, IconLoading, IconLogs, IconUpload } from '@renderer/assets/icons'
import { useAppStore, useGithubStore, usePageStore } from '@renderer/stores'
import MainElement from '@renderer/nav/MainElement'
import { AppReturnData } from '@shared/types'
import DownloadNotification from '@renderer/overlays/DownloadNotification'
import Overlay from '@renderer/overlays/Overlay'
+import { SuccessNotification } from '@renderer/overlays/SuccessNotification'
const AppDownloads: React.FC = () => {
// Getting releases to show
@@ -162,24 +156,11 @@ const AppDownloads: React.FC = () => {
)}
{appReturnData && (
- setAppReturnData(null)}
- className="border bg-zinc-950 border-zinc-800 pt-16 p-5"
- >
- Successfully Downloaded
- {appReturnData.appName} is installed
- v{appReturnData.appVersion}
- {
- setAppReturnData(null)
- runApp(appReturnData.appId)
- }}
- >
-
- Initialize App
-
-
+
)}
diff --git a/DeskThingServer/src/renderer/src/pages/Downloads/ClientDownloads.tsx b/DeskThingServer/src/renderer/src/pages/Downloads/ClientDownloads.tsx
index 180286e8..ab402243 100644
--- a/DeskThingServer/src/renderer/src/pages/Downloads/ClientDownloads.tsx
+++ b/DeskThingServer/src/renderer/src/pages/Downloads/ClientDownloads.tsx
@@ -75,6 +75,9 @@ const ClientDownloads: React.FC = () => {
setClientDownloads(null)
try {
await loadClientUrl(url)
+ await setTimeout(() => {
+ setLoading(false)
+ }, 2000)
} catch (error) {
await setTimeout(() => {
setLoading(false)
diff --git a/DeskThingServer/src/renderer/src/stores/keyStore.ts b/DeskThingServer/src/renderer/src/stores/keyStore.ts
index 71d56f62..79647a57 100644
--- a/DeskThingServer/src/renderer/src/stores/keyStore.ts
+++ b/DeskThingServer/src/renderer/src/stores/keyStore.ts
@@ -5,7 +5,7 @@
* @version 0.9.0
*/
import { create } from 'zustand'
-import { Action, Key, MappingStructure, EventFlavor } from '@shared/types'
+import { Action, Key, MappingStructure, EventMode } from '@shared/types'
interface KeyMapStoreState {
mappingStructure: MappingStructure | null
@@ -35,7 +35,7 @@ interface KeyMapStoreState {
setSelectedProfile: (profile: string) => void
// Update a specific button mapping (e.g., for remapping actions)
- updateButtonMapping: (profile: string, keyId: string, flavor: EventFlavor, action: Action) => void
+ updateButtonMapping: (profile: string, keyId: string, Mode: EventMode, action: Action) => void
}
const useKeyMapStore = create((set) => ({
@@ -112,13 +112,8 @@ const useKeyMapStore = create((set) => ({
}))
},
- // Update a specific button mapping for a key and flavor in the selected profile
- updateButtonMapping: (
- profile: string,
- keyId: string,
- flavor: EventFlavor,
- action: Action
- ): void => {
+ // Update a specific button mapping for a key and Mode in the selected profile
+ updateButtonMapping: (profile: string, keyId: string, Mode: EventMode, action: Action): void => {
set((state) => {
if (!state.mappingStructure) return state
@@ -128,7 +123,7 @@ const useKeyMapStore = create((set) => ({
if (!profileMapping) return state
const mapping = profileMapping.mapping[keyId] || {}
- mapping[flavor] = action
+ mapping[Mode] = action
profileMapping.mapping[keyId] = mapping
updatedMappingStructure.profiles[profile] = profileMapping
diff --git a/DeskThingServer/src/renderer/src/stores/logStore.ts b/DeskThingServer/src/renderer/src/stores/logStore.ts
index 29309098..711b7ee0 100644
--- a/DeskThingServer/src/renderer/src/stores/logStore.ts
+++ b/DeskThingServer/src/renderer/src/stores/logStore.ts
@@ -1,10 +1,5 @@
import { create } from 'zustand'
-
-export interface Log {
- type: string
- log: string
- date: string
-}
+import { Log, MESSAGE_TYPES } from '@shared/types'
interface LogStoreState {
logList: Log[]
@@ -12,14 +7,14 @@ interface LogStoreState {
maxNumLogs: number
getLogs: () => Promise
- addLog: (type: string, log: string) => void
- addLogsFromFile: (logs: string[]) => void
+ addLog: (log: Log) => void
+ addLogsFromFile: (logs: Log[]) => void
}
const useLogStore = create((set, get) => ({
logList: [],
maxLogLength: 1000,
- maxNumLogs: 100,
+ maxNumLogs: 300,
getLogs: async (): Promise => {
const { logList } = get()
@@ -32,54 +27,45 @@ const useLogStore = create((set, get) => ({
}
},
- addLog: (type: string, log: string): void => {
- if (typeof log !== 'string') {
- console.error('Log is not a string')
- log = JSON.stringify(log)
+ addLog: (log: Log): void => {
+ if (typeof log.log !== 'string') {
+ try {
+ if (log.log === null) {
+ log.log = 'null'
+ } else if (log.log === undefined) {
+ log.log = 'undefined'
+ } else if (typeof log.log === 'object') {
+ log.log = JSON.stringify(log.log, null, 2)
+ } else {
+ log.log = String(log.log)
+ }
+ } catch (error) {
+ console.error('Failed to convert log to string:', log, error)
+ log.log = '[Unconvertible Value]'
+ }
}
const { maxLogLength, maxNumLogs } = get()
- const truncatedLog = log.length > maxLogLength ? `${log.substring(0, maxLogLength)}...` : log
+ const truncatedLog =
+ log.log.length > maxLogLength ? `${log.log.substring(0, maxLogLength)}...` : log.log
const newLog = {
- type: type,
- log: truncatedLog,
- date: new Date().toLocaleTimeString()
+ ...log,
+ log: truncatedLog
}
set((state) => ({
- logList: [...state.logList, newLog].slice(-maxNumLogs)
+ logList: [...state.logList, newLog as Log].slice(-maxNumLogs)
}))
- // Emit events (you might want to implement a custom event system or use a library)
- // For now, we'll just log to console
- if (type === 'error' || type === 'message') {
- console.log('New log', newLog)
+ if (log.type != MESSAGE_TYPES.LOGGING) {
+ console.log(newLog)
}
},
-
- addLogsFromFile: (logs: string[]): void => {
- const { maxLogLength, maxNumLogs } = get()
- const parsedLogs = logs
- .map((log) => {
- const parsedLog = parseLog(log)
- if (parsedLog) {
- const truncatedLog =
- parsedLog.log.length > maxLogLength
- ? `${parsedLog.log.substring(0, maxLogLength)}...`
- : parsedLog.log
-
- return {
- type: parsedLog.type,
- log: truncatedLog,
- date: parsedLog.date
- }
- }
- return null
- })
- .filter((log): log is Log => log !== null)
-
+ addLogsFromFile: (logs: Log[]): void => {
+ const { maxNumLogs } = get()
+ console.log('adding logs from file', logs)
set((state) => ({
- logList: [...state.logList, ...parsedLogs].slice(-maxNumLogs)
+ logList: [...state.logList, ...logs].slice(-maxNumLogs)
}))
// Emit update event (for now, just log to console)
@@ -87,20 +73,4 @@ const useLogStore = create((set, get) => ({
}
}))
-function parseLog(log: string): Log | null {
- const logRegex = /^\[(.*?)\]: (\w+) \| (.*)$/
- const match = log.match(logRegex)
- if (match) {
- const [, timestamp, type, message] = match
- return {
- type: type,
- log: message,
- date: timestamp
- }
- } else {
- console.error('Failed to parse log:', log)
- }
- return null
-}
-
export default useLogStore
diff --git a/DeskThingServer/src/renderer/src/stores/notificationStore.ts b/DeskThingServer/src/renderer/src/stores/notificationStore.ts
index 3ce78cab..e39418c5 100644
--- a/DeskThingServer/src/renderer/src/stores/notificationStore.ts
+++ b/DeskThingServer/src/renderer/src/stores/notificationStore.ts
@@ -1,5 +1,5 @@
import { create } from 'zustand'
-import { Log } from './logStore'
+import { Log, MESSAGE_TYPES } from '@shared/types'
export interface AuthScopes {
[key: string]: {
@@ -40,7 +40,7 @@ interface NotificationStoreState {
// Logs
readLog: (index?: number) => void
- addLog: (type: string, message: string) => void
+ addLog: (log: Log) => void
// Tasks
resolveTask: (taskId: string) => void
@@ -92,15 +92,11 @@ const useNotificationStore = create((set, get) => ({
get().calculateTotalTasks()
},
- addLog: (type: string, message: string): void => {
- const newLog = {
- type: type,
- log: message,
- date: new Date().toLocaleTimeString()
- }
+ addLog: (log: Log): void => {
+ if (log.type === MESSAGE_TYPES.LOGGING) return
set((state) => ({
- logs: [newLog, ...state.logs]
+ logs: [log, ...state.logs]
}))
get().calculateTotalTasks()
diff --git a/DeskThingServer/src/renderer/src/stores/settingsStore.ts b/DeskThingServer/src/renderer/src/stores/settingsStore.ts
index ce8b881b..1b0ab543 100644
--- a/DeskThingServer/src/renderer/src/stores/settingsStore.ts
+++ b/DeskThingServer/src/renderer/src/stores/settingsStore.ts
@@ -1,5 +1,5 @@
import { create } from 'zustand'
-import { Settings } from '@shared/types'
+import { LOGGING_LEVEL, Settings } from '@shared/types'
interface SettingsStoreState {
settings: Settings
@@ -19,6 +19,7 @@ const useSettingsStore = create((set, get) => ({
devicePort: -1,
address: '-.-.-.-',
autoStart: true,
+ LogLevel: LOGGING_LEVEL.PRODUCTION,
minimizeApp: true,
autoConfig: false,
globalADB: false,
diff --git a/DeskThingServer/src/shared/types/app.ts b/DeskThingServer/src/shared/types/app.ts
index d6e2c748..571d1dca 100644
--- a/DeskThingServer/src/shared/types/app.ts
+++ b/DeskThingServer/src/shared/types/app.ts
@@ -108,10 +108,21 @@ export interface SettingsBoolean {
description?: string
}
+export interface SettingsRange {
+ value: number
+ type: 'range'
+ label: string
+ min: number
+ max: number
+ step?: number
+ description?: string
+}
+
export interface SettingsString {
value: string
type: 'string'
label: string
+ maxLength?: number
description?: string
}
@@ -120,10 +131,21 @@ export interface SettingsSelect {
type: 'select'
label: string
description?: string
- options: {
- label: string
- value: string
- }[]
+ placeholder?: string
+ options: SettingOption[]
+}
+
+export type SettingOption = {
+ label: string
+ value: string
+}
+
+export interface SettingsRanked {
+ value: string[]
+ type: 'ranked'
+ label: string
+ description?: string
+ options: SettingOption[]
}
export interface SettingsMultiSelect {
@@ -131,10 +153,8 @@ export interface SettingsMultiSelect {
type: 'multiselect'
label: string
description?: string
- options: {
- label: string
- value: string
- }[]
+ placeholder?: string
+ options: SettingOption[]
}
export type SettingsType =
@@ -143,6 +163,8 @@ export type SettingsType =
| SettingsString
| SettingsSelect
| SettingsMultiSelect
+ | SettingsRange
+ | SettingsRanked
export interface AppSettings {
[key: string]: SettingsType
diff --git a/DeskThingServer/src/shared/types/maps.ts b/DeskThingServer/src/shared/types/maps.ts
index 4fcbd4fe..a7d8e45b 100644
--- a/DeskThingServer/src/shared/types/maps.ts
+++ b/DeskThingServer/src/shared/types/maps.ts
@@ -17,10 +17,11 @@ export type Key = {
description?: string // User Readable description
version: string // The version of the key
enabled: boolean // Whether or not the app associated with the key is enabled
- flavors: EventFlavor[] // The flavors of the key
+ Modes: EventMode[] // The Modes of the key
}
-export enum EventFlavor {
+// The different possible modes of an event
+export enum EventMode {
KeyUp,
KeyDown,
ScrollUp,
@@ -35,6 +36,7 @@ export enum EventFlavor {
PressLong
}
+// The button mapping profile stored in the file system
export type ButtonMapping = {
// The ID of the key
version: string
@@ -44,7 +46,7 @@ export type ButtonMapping = {
trigger_app?: string
mapping: {
[key: string]: {
- [flavor in EventFlavor]?: Action
+ [Mode in EventMode]?: Action
}
}
}
diff --git a/DeskThingServer/src/shared/types/types.ts b/DeskThingServer/src/shared/types/types.ts
index d70369bd..e9db5222 100644
--- a/DeskThingServer/src/shared/types/types.ts
+++ b/DeskThingServer/src/shared/types/types.ts
@@ -75,6 +75,7 @@ export type GithubAsset = {
browser_download_url: string
}
+// The Client is how the clients are stored to keep track of them
export interface Client {
ip: string
port?: number
@@ -94,6 +95,7 @@ export interface Client {
miniplayer?: string
}
+// The standard manifest that all the clients should have
export interface ClientManifest {
name: string
id: string
@@ -105,8 +107,6 @@ export interface ClientManifest {
version: string
port: number
ip: string
- default_view: string
- miniplayer: string
compatible_server?: number[]
uuid?: string
version_code?: number
@@ -119,6 +119,7 @@ export interface RepoReleases {
releases: GithubRelease[]
}
+// The socket data that is used for any communication. I.e. between the app-server or server-client
export interface SocketData {
app: string
type: string
@@ -132,10 +133,12 @@ export interface SocketData {
| Settings
}
+// The settings for the app
export interface Settings {
version: string
version_code: number
callbackPort: number
+ LogLevel: LOGGING_LEVEL
devicePort: number
address: string
autoStart: boolean
@@ -151,8 +154,42 @@ export interface Settings {
[key: string]: any // For any additional settings
}
+// Used in the Refresh ADB screen to display little messages for the user
export interface StatusMessage {
message: string
weight: number
minimum: number
}
+
+/**
+ * The MESSAGE_TYPES object defines a set of constants that represent the different types of messages that can be sent or received in the application.
+ * Error, Log, Message, Warning, Fatal, and Debugging.
+ */
+export enum MESSAGE_TYPES {
+ ERROR = 'error',
+ LOGGING = 'log',
+ MESSAGE = 'message',
+ WARNING = 'warning',
+ FATAL = 'fatal',
+ DEBUG = 'debugging'
+}
+
+/**
+ * The LOGGING_LEVEL object defines a set of constants that represent the different levels of logging that can be used in the application.
+ * These levels are used to determine which logs should be displayed in the application.
+ * The levels are: SYSTEM, APPS, and PRODUCTION.
+ * The SYSTEM level is used for system-level logs, the APPS level is used for app and client emitted logs, and the PRODUCTION level is used for only errors, warnings, debugging, and fatal logs.
+ */
+export enum LOGGING_LEVEL {
+ SYSTEM = 'system', // All system-level logs
+ APPS = 'apps', // all app and client emitted logs
+ PRODUCTION = 'production' // Only errors, warnings, debugging, and fatal logs
+}
+
+export interface Log {
+ source: string
+ type: MESSAGE_TYPES
+ log: string
+ trace?: string
+ date?: string
+}
diff --git a/DeskThingServer/tailwind.config.js b/DeskThingServer/tailwind.config.js
index 20fdbbd2..398d7cdc 100644
--- a/DeskThingServer/tailwind.config.js
+++ b/DeskThingServer/tailwind.config.js
@@ -10,7 +10,7 @@ module.exports = {
},
keyframes: {
fade: {
- '0%': { opacity: 0.5, transform: 'translateY(-30px)' },
+ '0%': { opacity: 0, transform: 'translateY(-10px)' },
'100%': { opacity: 1, transform: 'translateY(0px)' }
},
'fade-in-down': {