Migration to TypeScript - Stage 2

This commit is contained in:
Prozilla 2024-05-07 21:06:07 +02:00
parent f449e6cb68
commit 79f23694cd
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
174 changed files with 1625 additions and 2020 deletions

View file

@ -60,7 +60,7 @@ See [docs/configuration](configuration/README.md) for more information.
Contains the static build files after [building](#building--deploying) the application. This directory is not included in the GitHub repository.
### Coding style
### Code style
Type | Case | Example
--- | --- | ---
@ -75,6 +75,7 @@ Global constant variables | MACRO_CASE | ```export const NAME = "Prozilla OS";``
Classes | PascalCase | ```class WindowsManager { }```
React components | PascalCase | ```export function WindowsView({ }) { }```
Functions | camelCase | ```function focusWindow() { }```
Types | PascalCase | ```type FooBar = boolean;```
### External files

View file

@ -10,7 +10,7 @@ To see the status and to-do's of each feature, check the [task board](https://pr
## Table of Contents
- [Actions](storage/README.md)
- [Actions](actions/README.md)
- [Apps](apps/README.md)
- [Modals](modals/README.md)
- [Settings](settings/README.md)

View file

@ -62,7 +62,7 @@ The webview template can be used to turn a webpage into an application by simply
```js
// features/apps/apps.js
import { WebView } from "../../components/apps/templates/WebView.jsx";
import { WebView } from "../../components/apps/templates/WebView";
export default class AppsManager {
static APPLICATIONS = [
@ -91,7 +91,7 @@ export function Example() {
```js
// features/apps/apps.js
import { Example } from "../../components/apps/example/Example.jsx";
import { Example } from "../../components/apps/example/Example";
export default class AppsManager {
static APPLICATIONS = [

126
package-lock.json generated
View file

@ -133,7 +133,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/@babel/eslint-parser": {
@ -166,7 +166,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/@babel/generator": {
@ -228,7 +228,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
@ -258,7 +258,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
@ -282,7 +282,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
@ -523,7 +523,7 @@
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz",
"integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==",
"bin": {
"parser": "bin/babel-parser.js"
"parser": "bin/babel-parser"
},
"engines": {
"node": ">=6.0.0"
@ -1678,7 +1678,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/@babel/plugin-transform-shorthand-properties": {
@ -1937,7 +1937,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/@babel/preset-modules": {
@ -2414,7 +2414,7 @@
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
"js-yaml": "bin/js-yaml"
}
},
"node_modules/@eslint/eslintrc/node_modules/type-fest": {
@ -3300,7 +3300,7 @@
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
"integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/@nodelib/fs.scandir": {
@ -5806,7 +5806,7 @@
"dependencies": {
"browserslist": "^4.21.5",
"caniuse-lite": "^1.0.30001464",
"fraction.js": "^4.2.0",
"fraction": "^4.2.0",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
"postcss-value-parser": "^4.2.0"
@ -6147,7 +6147,7 @@
"node": ">= 8.0.0"
}
},
"node_modules/big.js": {
"node_modules/big": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
@ -6289,7 +6289,7 @@
"update-browserslist-db": "^1.0.11"
},
"bin": {
"browserslist": "cli.js"
"browserslist": "cli"
},
"engines": {
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
@ -7329,7 +7329,7 @@
}
}
},
"node_modules/decimal.js": {
"node_modules/decimal": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
@ -7677,7 +7677,7 @@
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
"ejs": "bin/cli"
},
"engines": {
"node": ">=0.10.0"
@ -7977,8 +7977,8 @@
"esutils": "^2.0.2"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
"escodegen": "bin/escodegen",
"esgenerate": "bin/esgenerate"
},
"engines": {
"node": ">=6.0"
@ -8041,7 +8041,7 @@
"text-table": "^0.2.0"
},
"bin": {
"eslint": "bin/eslint.js"
"eslint": "bin/eslint"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -8296,7 +8296,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/eslint-plugin-jsdoc": {
@ -8368,7 +8368,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/eslint-plugin-react": {
@ -8445,7 +8445,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/eslint-plugin-testing-library": {
@ -8687,7 +8687,7 @@
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
"js-yaml": "bin/js-yaml"
}
},
"node_modules/eslint/node_modules/supports-color": {
@ -8733,8 +8733,8 @@
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
"esparse": "bin/esparse",
"esvalidate": "bin/esvalidate"
},
"engines": {
"node": ">=4"
@ -9379,7 +9379,7 @@
"node": ">= 0.6"
}
},
"node_modules/fraction.js": {
"node_modules/fraction": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
@ -9557,8 +9557,8 @@
"globby": "^6.1.0"
},
"bin": {
"gh-pages": "bin/gh-pages.js",
"gh-pages-clean": "bin/gh-pages-clean.js"
"gh-pages": "bin/gh-pages",
"gh-pages-clean": "bin/gh-pages-clean"
},
"engines": {
"node": ">=10"
@ -9904,7 +9904,7 @@
"he": "bin/he"
}
},
"node_modules/highlight.js": {
"node_modules/highlight": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
@ -9920,7 +9920,7 @@
"node": ">= 6.0.0"
}
},
"node_modules/hpack.js": {
"node_modules/hpack": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
"integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
@ -10008,7 +10008,7 @@
"terser": "^5.10.0"
},
"bin": {
"html-minifier-terser": "cli.js"
"html-minifier-terser": "cli"
},
"engines": {
"node": ">=12"
@ -10235,7 +10235,7 @@
"resolve-cwd": "^3.0.0"
},
"bin": {
"import-local-fixture": "fixtures/cli.js"
"import-local-fixture": "fixtures/cli"
},
"engines": {
"node": ">=8"
@ -10292,7 +10292,7 @@
"node": ">= 0.4"
}
},
"node_modules/ipaddr.js": {
"node_modules/ipaddr": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
"integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
@ -10487,7 +10487,7 @@
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"bin": {
"is-docker": "cli.js"
"is-docker": "cli"
},
"engines": {
"node": ">=8"
@ -10834,7 +10834,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/istanbul-lib-report": {
@ -10925,7 +10925,7 @@
"minimatch": "^3.1.2"
},
"bin": {
"jake": "bin/cli.js"
"jake": "bin/cli"
},
"engines": {
"node": ">=10"
@ -11005,7 +11005,7 @@
"jest-cli": "^27.5.1"
},
"bin": {
"jest": "bin/jest.js"
"jest": "bin/jest"
},
"engines": {
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
@ -11144,7 +11144,7 @@
"yargs": "^16.2.0"
},
"bin": {
"jest": "bin/jest.js"
"jest": "bin/jest"
},
"engines": {
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
@ -12877,7 +12877,7 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
"integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==",
"bin": {
"jiti": "bin/jiti.js"
"jiti": "bin/jiti"
}
},
"node_modules/js-tokens": {
@ -12894,7 +12894,7 @@
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
"js-yaml": "bin/js-yaml"
}
},
"node_modules/jsdoc-type-pratt-parser": {
@ -12917,7 +12917,7 @@
"cssom": "^0.4.4",
"cssstyle": "^2.3.0",
"data-urls": "^2.0.0",
"decimal.js": "^10.2.1",
"decimal": "^10.2.1",
"domexception": "^2.0.1",
"escodegen": "^2.0.0",
"form-data": "^3.0.0",
@ -12987,7 +12987,7 @@
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"bin": {
"json5": "lib/cli.js"
"json5": "lib/cli"
},
"engines": {
"node": ">=6"
@ -13118,7 +13118,7 @@
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dependencies": {
"big.js": "^5.2.2",
"big": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
@ -13178,7 +13178,7 @@
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
"loose-envify": "cli"
}
},
"node_modules/lower-case": {
@ -13195,7 +13195,7 @@
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
"dependencies": {
"fault": "^1.0.0",
"highlight.js": "~10.7.0"
"highlight": "~10.7.0"
},
"funding": {
"type": "github",
@ -13215,7 +13215,7 @@
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"bin": {
"lz-string": "bin/bin.js"
"lz-string": "bin/bin"
}
},
"node_modules/magic-string": {
@ -13245,7 +13245,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
}
},
"node_modules/makeerror": {
@ -13334,7 +13334,7 @@
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"bin": {
"mime": "cli.js"
"mime": "cli"
},
"engines": {
"node": ">=4"
@ -13474,7 +13474,7 @@
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
"mkdirp": "bin/cmd"
}
},
"node_modules/ms": {
@ -13491,7 +13491,7 @@
"thunky": "^1.0.2"
},
"bin": {
"multicast-dns": "cli.js"
"multicast-dns": "cli"
}
},
"node_modules/mz": {
@ -15502,13 +15502,13 @@
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
"ipaddr": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/proxy-addr/node_modules/ipaddr.js": {
"node_modules/proxy-addr/node_modules/ipaddr": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
@ -15890,7 +15890,7 @@
"workbox-webpack-plugin": "^6.4.1"
},
"bin": {
"react-scripts": "bin/react-scripts.js"
"react-scripts": "bin/react-scripts"
},
"engines": {
"node": ">=14.0.0"
@ -15937,7 +15937,7 @@
"integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==",
"dependencies": {
"@babel/runtime": "^7.3.1",
"highlight.js": "^10.4.1",
"highlight": "^10.4.1",
"lowlight": "^1.17.0",
"prismjs": "^1.27.0",
"refractor": "^3.6.0"
@ -16314,7 +16314,7 @@
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
"rimraf": "bin"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@ -16575,7 +16575,7 @@
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
"semver": "bin/semver"
},
"engines": {
"node": ">=10"
@ -16954,7 +16954,7 @@
"dependencies": {
"debug": "^4.1.0",
"detect-node": "^2.0.4",
"hpack.js": "^2.1.6",
"hpack": "^2.1.6",
"obuf": "^1.1.2",
"readable-stream": "^3.0.6",
"wbuf": "^1.7.3"
@ -17460,8 +17460,8 @@
"sucrase": "^3.32.0"
},
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
"tailwind": "lib/cli",
"tailwindcss": "lib/cli"
},
"engines": {
"node": ">=14.0.0"
@ -17746,7 +17746,7 @@
"minimist": "^1.2.0"
},
"bin": {
"json5": "lib/cli.js"
"json5": "lib/cli"
}
},
"node_modules/tsconfig-paths/node_modules/strip-bom": {
@ -18031,7 +18031,7 @@
"picocolors": "^1.0.0"
},
"bin": {
"update-browserslist-db": "cli.js"
"update-browserslist-db": "cli"
},
"peerDependencies": {
"browserslist": ">= 4.21.0"
@ -18207,7 +18207,7 @@
"webpack-sources": "^3.2.3"
},
"bin": {
"webpack": "bin/webpack.js"
"webpack": "bin/webpack"
},
"engines": {
"node": ">=10.13.0"
@ -18316,7 +18316,7 @@
"graceful-fs": "^4.2.6",
"html-entities": "^2.3.2",
"http-proxy-middleware": "^2.0.3",
"ipaddr.js": "^2.0.1",
"ipaddr": "^2.0.1",
"launch-editor": "^2.6.0",
"open": "^8.0.9",
"p-retry": "^4.5.0",
@ -18330,7 +18330,7 @@
"ws": "^8.13.0"
},
"bin": {
"webpack-dev-server": "bin/webpack-dev-server.js"
"webpack-dev-server": "bin/webpack-dev-server"
},
"engines": {
"node": ">= 12.13.0"

View file

@ -95,8 +95,6 @@
],
"jsdoc/check-values": "warn",
"jsdoc/empty-tags": "warn",
"jsdoc/check-param-names": "warn",
"jsdoc/check-property-names": "warn",
"jsdoc/check-access": "warn",
"jsdoc/check-alignment": "warn",
"jsdoc/multiline-blocks": "warn",

View file

@ -1,14 +1,14 @@
import styles from "./App.module.css";
import { Taskbar } from "./components/taskbar/Taskbar.jsx";
import { Taskbar } from "./components/taskbar/Taskbar";
import { WindowsManagerProvider } from "./hooks/windows/windowsManagerContext";
import { WindowsView } from "./components/windows/WindowsView.jsx";
import { WindowsView } from "./components/windows/WindowsView";
import { VirtualRootProvider } from "./hooks/virtual-drive/virtualRootContext";
import { Desktop } from "./components/desktop/Desktop.jsx";
import { Desktop } from "./components/desktop/Desktop";
import { SettingsManagerProvider } from "./hooks/settings/settingsManagerContext";
import { ModalsView } from "./components/modals/ModalsView.jsx";
import React, { FC, useEffect } from "react";
import { ModalsView } from "./components/modals/ModalsView";
import { FC, useEffect } from "react";
import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext";
import { TrackingManager } from "./features/tracking/trackingManager.js";
import { TrackingManager } from "./features/tracking/trackingManager";
import { ModalsManagerProvider } from "./hooks/modals/modalsManagerContext";
TrackingManager.initialize();

View file

@ -1,13 +1,16 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import styles from "./Button.module.css";
import { faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { ReactNode } from "react";
/**
* @param {object} props
* @param {string} props.className
* @param {string} props.href
*/
export function Button(props) {
interface ButtonProps {
className?: string;
href?: string;
children?: ReactNode;
[key: string]: any;
}
export function Button(props: ButtonProps) {
let { className = "" } = props;
const { href, children } = props;

View file

@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import styles from "./DropdownButton.module.css";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
import { formatShortcut } from "../../../features/_utils/string.utils.js";
import OutsideClickListener from "../../../hooks/_utils/outsideClick";
import { formatShortcut } from "../../../features/_utils/string.utils";
/**
* @param {object} props
@ -25,7 +25,7 @@ export function DropdownButton({ label, options, shortcuts }) {
</button>
{open && options
? (<div className={styles.Dropdown}>
{Object.entries(options).map(([label, callback]) =>
{Object.entries(options).map(([label, callback]: [label: string, callback: Function]) =>
<button key={label} tabIndex={tabIndex} onClick={() => {
setOpen(false);
callback();

View file

@ -1,5 +1,5 @@
import { useState } from "react";
import { INTERACTIBLE_DOUBLE_CLICK_DELAY } from "../../../config/utils.config.js";
import { INTERACTIBLE_DOUBLE_CLICK_DELAY } from "../../../config/utils.config";
let timeoutId = null;

View file

@ -1,4 +1,5 @@
import { clamp } from "../../../features/math/clamp.js";
import { CSSProperties } from "react";
import { clamp } from "../../../features/math/clamp";
import styles from "./ProgressBar.module.css";
/**
@ -9,12 +10,21 @@ import styles from "./ProgressBar.module.css";
* @param {string} props.align
* @param {string} props.className
*/
export function ProgressBar({ fillPercentage, fillColor, backgroundColor, align = "left", className = "" }) {
interface ProgressBarProps {
fillPercentage: number;
fillColor?: string;
backgroundColor?: string;
align?: "left";
className?: string;
}
export function ProgressBar({ fillPercentage, fillColor, backgroundColor, align = "left", className = "" }: ProgressBarProps) {
return (
<div className={`${styles.Container} ${className}`} style={{ backgroundColor: backgroundColor }}>
<div
className={`${styles.Fill} ${align}`}
style={{ backgroundColor: fillColor, "--fill": `${clamp(fillPercentage, 0.1, 100)}%` }}
style={{ backgroundColor: fillColor, "--fill": `${clamp(fillPercentage, 0.1, 100)}%` } as CSSProperties}
/>
</div>
);

View file

@ -1,25 +1,31 @@
import { Children, cloneElement, isValidElement } from "react";
import { useShortcuts } from "../../hooks/_utils/keyboard.js";
import { Children, cloneElement, isValidElement, ReactNode } from "react";
import { useShortcuts } from "../../hooks/_utils/keyboard";
import styles from "./Actions.module.css";
import { useScreenBounds } from "../../hooks/_utils/screen.js";
import { useScreenBounds } from "../../hooks/_utils/screen";
export const STYLES = {
CONTEXT_MENU: styles["Context-menu"],
SHORTCUTS_LISTENER: styles["Shortcuts-listener"],
};
/**
* @callback actionsType
* @param {object} props
* @param {string=} props.className
* @param {Function} props.onAnyTrigger
* @param {import("react").ElementType} props.children
* @param {*} props.triggerParams
* @param {boolean} props.avoidTaskbar
*/
export interface ActionProps {
actionId?: string;
label?: string;
icon?: string|object;
shortcut?: string[];
onTrigger?: (event: Event, triggerParams: any, ...args: any[]) => void;
children?: ReactNode;
}
export interface ActionsProps {
className?: string;
onAnyTrigger?: (event: Event, triggerParams: any, ...args: any[]) => void;
children?: ReactNode;
triggerParams?: any;
avoidTaskbar?: boolean;
}
/**
* @type {actionsType}
* @example
* <ClickAction
* label="Reload"
@ -30,7 +36,7 @@ export const STYLES = {
* }}
* />
*/
export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskbar = true }) {
export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskbar = true }: ActionsProps): ReactNode {
const isListener = (className === STYLES.SHORTCUTS_LISTENER);
const { ref, initiated, alignLeft, alignTop } = useScreenBounds({ avoidTaskbar });
@ -38,7 +44,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
const options = {};
const shortcuts = {};
const iterateOverChildren = (children) => {
const iterateOverChildren = (children: ReactNode) => {
let actionId = 0;
const newChildren = Children.map(children, (child) => {
if (!isValidElement(child))
@ -88,7 +94,7 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
if (!initiated)
classNames.push(styles.Uninitiated);
return (<div ref={ref} className={classNames.join(" ")}>
return <div ref={ref} className={classNames.join(" ")}>
{iterateOverChildren(children)}
</div>);
</div>;
}

View file

@ -1,24 +1,22 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { formatShortcut } from "../../../features/_utils/string.utils.js";
import { formatShortcut } from "../../../features/_utils/string.utils";
import styles from "../Actions.module.css";
import { ImagePreview } from "../../apps/file-explorer/directory-list/ImagePreview.jsx";
import { memo } from "react";
import { ImagePreview } from "../../apps/file-explorer/directory-list/ImagePreview";
import { memo, MouseEventHandler } from "react";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { ActionProps } from "../Actions";
/**
* @param {object} props
* @param {string} props.actionId
* @param {string} props.label
* @param {string[]} props.shortcut
* @param {Function} props.onTrigger
* @param {string|object} props.icon
*/
export const ClickAction = memo(({ actionId, label, shortcut, onTrigger, icon }) => {
return (<button key={actionId} className={styles.Button} tabIndex={0} onClick={onTrigger}>
interface ClickActionProps extends ActionProps {
icon?: string | object;
}
export const ClickAction = memo(({ actionId, label, shortcut, onTrigger, icon }: ClickActionProps) => {
return (<button key={actionId} className={styles.Button} tabIndex={0} onClick={onTrigger as unknown as MouseEventHandler}>
<span className={styles.Label}>
{icon && <div className={styles.Icon}>
{typeof icon == "string"
? <ImagePreview source={icon} className={styles["Image-icon"]}/>
: <FontAwesomeIcon icon={icon}/>
: <FontAwesomeIcon icon={icon as unknown as IconProp}/>
}
</div>}
<p>{label}</p>

View file

@ -1,22 +1,17 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import styles from "../Actions.module.css";
import { faCaretRight } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import { faCaretRight, IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { ReactNode, useState } from "react";
import { ActionProps } from "../Actions";
/**
* @param {object} props
* @param {string} props.label
* @param {string|object} props.icon
* @param {*} props.children
*/
export function DropdownAction({ label, icon, children }) {
export function DropdownAction({ label, icon, children }: ActionProps): ReactNode {
const [showContent, setShowContent] = useState(false);
const classNames = [styles.Dropdown];
if (showContent)
classNames.push(styles.Active);
return (<div
return <div
key={label}
className={classNames.join(" ")}
tabIndex={0}
@ -24,12 +19,12 @@ export function DropdownAction({ label, icon, children }) {
onMouseLeave={() => { setShowContent(false); }}
>
<span className={styles.Label}>
{icon && <div className={styles.Icon}><FontAwesomeIcon icon={icon}/></div>}
{icon && <div className={styles.Icon}><FontAwesomeIcon icon={icon as IconDefinition}/></div>}
<p>{label}</p>
</span>
<div className={styles["Dropdown-arrow"]}><FontAwesomeIcon icon={faCaretRight}/></div>
<div className={styles["Dropdown-content"]}>
{children}
</div>
</div>);
</div>;
}

View file

@ -1,9 +1,10 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { formatShortcut } from "../../../features/_utils/string.utils.js";
import { formatShortcut } from "../../../features/_utils/string.utils";
import styles from "../Actions.module.css";
import { faCircleDot } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import { ReactNode, useState } from "react";
import { faCircle } from "@fortawesome/free-regular-svg-icons";
import { ActionProps } from "../Actions";
/**
* @param {object} props
@ -15,14 +16,23 @@ import { faCircle } from "@fortawesome/free-regular-svg-icons";
* @param {number} props.initialIndex
* @param {Function} props.onTrigger
*/
export function RadioAction({ actionId, options, initialIndex, onTrigger }) {
interface RadioActionProps extends ActionProps {
options: {
label: string;
shortcut?: string[]
}[];
initialIndex: number;
}
export function RadioAction({ actionId, options, initialIndex, onTrigger }: RadioActionProps): ReactNode {
const [activeIndex, setActiveIndex] = useState(initialIndex ?? 0);
return (<div key={actionId}>
{options.map(({ label, shortcut }, index) =>
<button key={label} className={styles.Button} tabIndex={0} onClick={(event) => {
setActiveIndex(index);
onTrigger(event, index);
onTrigger(event as unknown as Event, index);
}}>
<span className={styles.Label}>
<div className={styles.Icon}>

View file

@ -1,23 +1,20 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { formatShortcut } from "../../../features/_utils/string.utils.js";
import { formatShortcut } from "../../../features/_utils/string.utils";
import styles from "../Actions.module.css";
import { useState } from "react";
import { ReactNode, useState } from "react";
import { faSquare } from "@fortawesome/free-regular-svg-icons";
import { faSquareCheck } from "@fortawesome/free-solid-svg-icons";
import { ActionProps } from "../Actions";
/**
* @param {object} props
* @param {string} props.actionId
* @param {string} props.label
* @param {string[]} props.shortcut
* @param {Function} props.onTrigger
* @param {boolean} props.initialValue
*/
export function ToggleAction({ actionId, label, shortcut, initialValue, onTrigger }) {
interface ToggleActionProps extends ActionProps {
initialValue: boolean;
}
export function ToggleAction({ actionId, label, shortcut, initialValue, onTrigger }: ToggleActionProps): ReactNode {
const [active, setActive] = useState(initialValue ?? false);
return (<button key={actionId} className={styles.Button} tabIndex={0} onClick={(event) => {
onTrigger(event, !active);
onTrigger(event as unknown as Event, !active);
setActive(!active);
}}>
<span className={styles.Label}>

View file

@ -1,13 +1,13 @@
import { useShortcuts } from "../../../../hooks/_utils/keyboard.js";
import { DropdownButton } from "../../../_utils/dropdown-button/DropdownButton.jsx";
import { useShortcuts } from "../../../../hooks/_utils/keyboard";
import { DropdownButton } from "../../../_utils/dropdown-button/DropdownButton";
import styles from "./HeaderMenu.module.css";
/**
* @param {object} props
* @param {Object<string, Object<string, Function>>} props.options
* @param {Object<string, Object<string, string[]>>} props.shortcuts
*/
export function HeaderMenu({ options, shortcuts }) {
interface HeaderMenuProps {
options: Record<string, Record<string, Function>> | Record<string, Function>;
shortcuts: Record<string, Record<string, string[]>> | Record<string, string[]>;
}
export function HeaderMenu({ options, shortcuts }: HeaderMenuProps) {
useShortcuts({ options, shortcuts });
return (

View file

@ -1,50 +0,0 @@
import { forwardRef, useEffect, useState } from "react";
import styles from "./WebView.module.css";
/**
* @param {object} props
* @param {string} props.source
*/
export const WebView = forwardRef(({ source, focus, ...props }, ref) => {
const [hovered, setHovered] = useState(false);
useEffect(() => {
window.focus();
const onBlur = (event) => {
if (hovered) {
focus?.(event);
}
};
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("blur", onBlur);
};
}, [focus, hovered]);
const onMouseOver = () => {
setHovered(true);
};
const onMouseOut = () => {
window.focus();
setHovered(false);
};
return (
<div className={styles.Container} onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
<iframe
ref={ref}
src={source}
title={props.title ?? "Web view"}
className={styles["Web-view"]}
referrerPolicy="no-referrer"
sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-same-origin allow-scripts"
{...props}
/>
</div>
);
});

View file

@ -0,0 +1,48 @@
import { FC, forwardRef, useEffect, useState } from "react";
import styles from "./WebView.module.css";
import { WindowProps } from "../../../windows/WindowView";
interface WebViewProps extends WindowProps {
source: string;
}
export const WebView: FC<WebViewProps> = forwardRef<HTMLIFrameElement>(({ source, focus, ...props }: WebViewProps, ref) => {
const [hovered, setHovered] = useState(false);
useEffect(() => {
window.focus();
const onBlur = (event) => {
if (hovered) {
focus?.(event);
}
};
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("blur", onBlur);
};
}, [focus, hovered]);
const onMouseOver = () => {
setHovered(true);
};
const onMouseOut = () => {
window.focus();
setHovered(false);
};
return <div className={styles.Container} onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
<iframe
ref={ref}
src={source}
title={props.title ?? "Web view"}
className={styles["Web-view"]}
referrerPolicy="no-referrer"
sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-same-origin allow-scripts"
{...props}
/>
</div>;
}) as FC;

View file

@ -1,11 +1,11 @@
import { useEffect, useRef, useState } from "react";
import styles from "./Browser.module.css";
import { WebView } from "../_utils/web-view/WebView.jsx";
import { WebView } from "../_utils/web-view/WebView";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCaretLeft, faCaretRight, faHome, faRotateRight } from "@fortawesome/free-solid-svg-icons";
import { HOME_URL, SEARCH_URL } from "../../../config/apps/browser.config.js";
import { isValidUrl } from "../../../features/_utils/browser.utils.js";
import { useHistory } from "../../../hooks/_utils/history.js";
import { HOME_URL, SEARCH_URL } from "../../../config/apps/browser.config";
import { isValidUrl } from "../../../features/_utils/browser.utils";
import { useHistory } from "../../../hooks/_utils/history";
/** @type {import("../../windows/WindowView.jsx").windowProps} */
export function Browser({ startUrl, focus }) {

View file

@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from "react";
import { Button } from "../../_utils/button/Button.jsx";
import { Button } from "../../_utils/button/Button";
import styles from "./Calculator.module.css";
/** @type {import("../../windows/WindowView.jsx").windowProps} */

View file

@ -1,41 +1,45 @@
import { useCallback, useEffect, useState } from "react";
import { FC, useCallback, useEffect, useState } from "react";
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
import styles from "./FileExplorer.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowUp, faCaretLeft, faCaretRight, faCircleInfo, faCog, faDesktop, faFileLines, faHouse, faImage, faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons";
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
import { useContextMenu } from "../../../hooks/modals/contextMenu.js";
import { QuickAccessButton } from "./QuickAccessButton.jsx";
import { useWindowedModal } from "../../../hooks/modals/windowedModal.js";
import Vector2 from "../../../features/math/vector2.js";
import { DIALOG_CONTENT_TYPES } from "../../../config/modals.config.js";
import { DirectoryList } from "./directory-list/DirectoryList.jsx";
import { Actions } from "../../actions/Actions.jsx";
import { ClickAction } from "../../actions/actions/ClickAction.jsx";
import { useContextMenu } from "../../../hooks/modals/contextMenu";
import { QuickAccessButton } from "./QuickAccessButton";
import { useWindowedModal } from "../../../hooks/modals/windowedModal";
import Vector2 from "../../../features/math/vector2";
import { DIALOG_CONTENT_TYPES } from "../../../config/modals.config";
import { DirectoryList, OnSelectionChangeParams } from "./directory-list/DirectoryList";
import { Actions } from "../../actions/Actions";
import { ClickAction } from "../../actions/actions/ClickAction";
import utilStyles from "../../../styles/utils.module.css";
import { DialogBox } from "../../modals/dialog-box/DialogBox.jsx";
import AppsManager from "../../../features/apps/appsManager.js";
import { APPS, APP_ICONS, APP_NAMES } from "../../../config/apps.config.js";
import { TITLE_SEPARATOR } from "../../../config/windows.config.js";
import { FileProperties } from "../../modals/file-properties/FileProperties.jsx";
import { useHistory } from "../../../hooks/_utils/history.js";
import { Divider } from "../../actions/actions/Divider.jsx";
import { CODE_FORMATS } from "../../../config/apps/textEditor.config.js";
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config.js";
import { DialogBox } from "../../modals/dialog-box/DialogBox";
import AppsManager from "../../../features/apps/appsManager";
import { APPS, APP_ICONS, APP_NAMES } from "../../../config/apps.config";
import { TITLE_SEPARATOR } from "../../../config/windows.config";
import { FileProperties } from "../../modals/file-properties/FileProperties";
import { useHistory } from "../../../hooks/_utils/history";
import { Divider } from "../../actions/actions/Divider";
import { CODE_FORMATS } from "../../../config/apps/textEditor.config";
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config";
import { WindowProps } from "../../windows/WindowView";
import { VirtualFolder } from "../../../features/virtual-drive/folder/virtualFolder";
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
import { VirtualFolderLink } from "../../../features/virtual-drive/folder/virtualFolderLink";
/**
* @param {import("../../windows/WindowView.jsx").windowProps} props
* @param {string} props.startPath
* @param {*} props.Footer
* @param {number} props.selectorMode
* @param {import("./directory-list/DirectoryList.jsx").onSelectionChange} props.onSelectionChange
* @param {Function} props.onSelectionFinish
*/
export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChange, onSelectionFinish }) {
interface FileExplorerProps extends WindowProps {
startPath?: string;
selectorMode?: number;
Footer: FC;
onSelectionChange: (params: OnSelectionChangeParams) => void;
onSelectionFinish: Function;
}
export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChange, onSelectionFinish }: FileExplorerProps) {
const isSelector = (Footer != null && selectorMode != null && selectorMode !== SELECTOR_MODE.NONE);
const virtualRoot = useVirtualRoot();
const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate(startPath ?? "~"));
const [currentDirectory, setCurrentDirectory] = useState<VirtualFolder>(virtualRoot.navigate(startPath ?? "~") as VirtualFolder);
const [path, setPath] = useState(currentDirectory?.path ?? "");
const windowsManager = useWindowsManager();
const [showHidden] = useState(true);
@ -44,7 +48,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
const { openWindowedModal } = useWindowedModal();
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label={!isSelector ? "Open" : "Select"} onTrigger={(event, file) => {
<ClickAction label={!isSelector ? "Open" : "Select"} onTrigger={(event, file: VirtualFile) => {
if (isSelector) {
onSelectionChange?.({ files: [file.id], directory: currentDirectory });
onSelectionFinish?.();
@ -97,7 +101,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
const directory = absolute ? virtualRoot.navigate(path) : currentDirectory.navigate(path);
if (directory != null) {
setCurrentDirectory(directory);
setCurrentDirectory(directory as VirtualFolder);
setPath(directory.root ? "/" : directory.path);
pushState(directory.path);
}
@ -110,7 +114,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
const path = history[stateIndex];
const directory = virtualRoot.navigate(path);
if (directory != null) {
setCurrentDirectory(directory);
setCurrentDirectory(directory as VirtualFolder);
setPath(directory.root ? "/" : directory.path);
}
}, [history, stateIndex, virtualRoot]);
@ -142,7 +146,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
return;
}
setCurrentDirectory(directory);
setCurrentDirectory(directory as VirtualFolder);
setPath(directory.root ? "/" : directory.path);
}
};
@ -175,7 +179,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
tabIndex={0}
className={styles["Icon-button"]}
onClick={() => { changeDirectory(".."); }}
disabled={currentDirectory.root}
disabled={currentDirectory.isRoot}
>
<FontAwesomeIcon icon={faArrowUp}/>
</button>
@ -235,16 +239,16 @@ export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChang
className={styles.Main}
showHidden={showHidden}
onOpenFile={(event, file) => {
event.preventDefault();
(event as Event).preventDefault();
if (isSelector)
return onSelectionFinish?.();
const options = {};
const options: Record<string, string> = {};
if (file.extension === "md" || CODE_FORMATS.includes(file.extension))
options.mode = "view";
windowsManager.openFile(file, options);
}}
onOpenFolder={(event, folder) => {
changeDirectory(folder.linkedPath ?? folder.name);
changeDirectory((folder as VirtualFolderLink).linkedPath ?? folder.name);
}}
onContextMenuFile={onContextMenuFile}
onContextMenuFolder={onContextMenuFolder}

View file

@ -1,45 +1,36 @@
import { useEffect, useRef, useState } from "react";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile.js";
import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder.js";
import { Interactable } from "../../../_utils/interactable/Interactable.jsx";
import { ReactNode, useEffect, useRef, useState } from "react";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile";
import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder";
import { Interactable } from "../../../_utils/interactable/Interactable";
import styles from "./DirectoryList.module.css";
import { ImagePreview } from "./ImagePreview.jsx";
import { ImagePreview } from "./ImagePreview";
/**
* @callback fileEvent
* @param {object} event
* @param {VirtualFile} file
*/
export interface OnSelectionChangeParams {
files?: string[];
folders?: string[];
directory?: VirtualFolder;
};
/**
* @callback folderEvent
* @param {object} event
* @param {VirtualFolder} folder
*/
type FileEventHandler = (event: object, file: VirtualFile) => void;
type FolderEventHandler = (event: object, folder: VirtualFolder) => void;
/**
* @callback onSelectionChange
* @param {object} selection
* @param {string[]} selection.files
* @param {string[]} selection.folders
*/
interface DirectoryListProps {
directory: VirtualFolder;
showHidden?: boolean;
folderClassName?: string;
fileClassName?: string;
className?: string;
onContextMenuFile?: FileEventHandler;
onContextMenuFolder?: FolderEventHandler;
onOpenFile?: FileEventHandler;
onOpenFolder?: FolderEventHandler;
allowMultiSelect?: boolean;
onSelectionChange?: (params: OnSelectionChangeParams) => void;
[key: string]: any;
}
/**
* @param {object} props
* @param {VirtualFolder} props.directory
* @param {boolean} props.showHidden
* @param {string} props.folderClassName
* @param {string} props.fileClassName
* @param {string} props.className
* @param {fileEvent} props.onContextMenuFile
* @param {folderEvent} props.onContextMenuFolder
* @param {fileEvent} props.onOpenFile
* @param {folderEvent} props.onOpenFolder
* @param {boolean} props.allowMultiSelect
* @param {onSelectionChange} props.onSelectionChange
*/
export function DirectoryList({ directory, showHidden = false, folderClassName, fileClassName, className,
onContextMenuFile, onContextMenuFolder, onOpenFile, onOpenFolder, allowMultiSelect = true, onSelectionChange, ...props }) {
onContextMenuFile, onContextMenuFolder, onOpenFile, onOpenFolder, allowMultiSelect = true, onSelectionChange, ...props }: DirectoryListProps): ReactNode {
const [selectedFolders, setSelectedFolders] = useState([]);
const [selectedFiles, setSelectedFiles] = useState([]);

View file

@ -1,10 +1,16 @@
import { useState } from "react";
import styles from "./ImagePreview.module.css";
import { ReactSVG } from "react-svg";
import AppsManager from "../../../../features/apps/appsManager.js";
import { APPS } from "../../../../config/apps.config.js";
import AppsManager from "../../../../features/apps/appsManager";
import { APPS } from "../../../../config/apps.config";
export function ImagePreview({ source, className, onError, ...props }) {
interface ImagePreviewProps {
source: string;
className?: string;
onError?: Function;
}
export function ImagePreview({ source, className, onError, ...props }: ImagePreviewProps) {
const [loadingFailed, setLoadingFailed] = useState(false);
const onLoadingError = () => {

View file

@ -2,8 +2,8 @@
import { useEffect } from "react";
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
import styles from "./MediaViewer.module.css";
import { APPS } from "../../../config/apps.config.js";
import { IMAGE_FORMATS } from "../../../config/apps/mediaViewer.config.js";
import { APPS } from "../../../config/apps.config";
import { IMAGE_FORMATS } from "../../../config/apps/mediaViewer.config";
/**
* @param {import("../../windows/WindowView.jsx").windowProps} props

View file

@ -3,10 +3,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircleInfo, faHardDrive, faPalette, faShapes } from "@fortawesome/free-solid-svg-icons";
import utilStyles from "../../../styles/utils.module.css";
import { Tab, TabList, TabPanel, Tabs } from "react-tabs";
import { AppearanceSettings } from "./tabs/AppearanceSettings.jsx";
import { AboutSettings } from "./tabs/AboutSettings.jsx";
import { StorageTab } from "./tabs/StorageSettings.jsx";
import { AppsSettings } from "./tabs/AppsSettings.jsx";
import { AppearanceSettings } from "./tabs/AppearanceSettings";
import { AboutSettings } from "./tabs/AboutSettings";
import { StorageTab } from "./tabs/StorageSettings";
import { AppsSettings } from "./tabs/AppsSettings";
/**
* @param {import("../../windows/WindowView.jsx").windowProps} props
@ -38,10 +38,10 @@ export function Settings({ tab, modalsManager }) {
</Tab>
</TabList>
<TabPanel className={styles["Tab-panel"]}>
<AppsSettings modalsManager={modalsManager}/>
<AppsSettings/>
</TabPanel>
<TabPanel className={styles["Tab-panel"]}>
<AppearanceSettings modalsManager={modalsManager}/>
<AppearanceSettings/>
</TabPanel>
<TabPanel className={styles["Tab-panel"]}>
<StorageTab/>

View file

@ -1,10 +1,10 @@
import { Button } from "../../../_utils/button/Button.jsx";
import { Button } from "../../../_utils/button/Button";
import styles from "../Settings.module.css";
import utilStyles from "../../../../styles/utils.module.css";
import Vector2 from "../../../../features/math/vector2.js";
import Vector2 from "../../../../features/math/vector2";
import { useWindowsManager } from "../../../../hooks/windows/windowsManagerContext";
import { useVirtualRoot } from "../../../../hooks/virtual-drive/virtualRootContext";
import { NAME } from "../../../../config/branding.config.js";
import { NAME } from "../../../../config/branding.config";
export function AboutSettings() {
const windowsManager = useWindowsManager();

View file

@ -1,15 +1,15 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import AppsManager from "../../../../features/apps/appsManager.js";
import { ImagePreview } from "../../file-explorer/directory-list/ImagePreview.jsx";
import AppsManager from "../../../../features/apps/appsManager";
import { ImagePreview } from "../../file-explorer/directory-list/ImagePreview";
import styles from "../Settings.module.css";
import { faEllipsisVertical, faThumbTack } from "@fortawesome/free-solid-svg-icons";
import { useWindowsManager } from "../../../../hooks/windows/windowsManagerContext";
import { useContextMenu } from "../../../../hooks/modals/contextMenu.js";
import { Actions } from "../../../actions/Actions.jsx";
import { ClickAction } from "../../../actions/actions/ClickAction.jsx";
import { removeFromArray } from "../../../../features/_utils/array.utils.js";
import { useContextMenu } from "../../../../hooks/modals/contextMenu";
import { Actions } from "../../../actions/Actions";
import { ClickAction } from "../../../actions/actions/ClickAction";
import { removeFromArray } from "../../../../features/_utils/array.utils";
import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext";
import { SettingsManager } from "../../../../features/settings/settingsManager.js";
import { SettingsManager } from "../../../../features/settings/settingsManager";
export function AppOption({ app, pins, setPins }) {
const isPinned = pins.includes(app.id);

View file

@ -1,23 +1,20 @@
import { useEffect, useState } from "react";
import { SettingsManager } from "../../../../features/settings/settingsManager.js";
import { SettingsManager } from "../../../../features/settings/settingsManager";
import styles from "../Settings.module.css";
import utilStyles from "../../../../styles/utils.module.css";
import { useVirtualRoot } from "../../../../hooks/virtual-drive/virtualRootContext";
import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext";
import { WALLPAPERS_PATH } from "../../../../config/apps/settings.config.js";
import { useWindowedModal } from "../../../../hooks/modals/windowedModal.js";
import ModalsManager from "../../../../features/modals/modalsManager.js";
import { Button } from "../../../_utils/button/Button.jsx";
import { FileSelector } from "../../../modals/file-selector/FileSelector.jsx";
import { SELECTOR_MODE } from "../../../../config/apps/fileExplorer.config.js";
import { DEFAULT_FILE_SELECTOR_SIZE } from "../../../../config/modals.config.js";
import { IMAGE_FORMATS } from "../../../../config/apps/mediaViewer.config.js";
import { WALLPAPERS_PATH } from "../../../../config/apps/settings.config";
import { useWindowedModal } from "../../../../hooks/modals/windowedModal";
import { Button } from "../../../_utils/button/Button";
import { FileSelector } from "../../../modals/file-selector/FileSelector";
import { SELECTOR_MODE } from "../../../../config/apps/fileExplorer.config";
import { DEFAULT_FILE_SELECTOR_SIZE } from "../../../../config/modals.config";
import { IMAGE_FORMATS } from "../../../../config/apps/mediaViewer.config";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile";
import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder";
/**
* @param {object} props
* @param {ModalsManager} props.modalsManager
*/
export function AppearanceSettings({ modalsManager }) {
export function AppearanceSettings() {
const virtualRoot = useVirtualRoot();
const settingsManager = useSettingsManager();
const [wallpaper, setWallpaper] = useState(null);
@ -44,7 +41,7 @@ export function AppearanceSettings({ modalsManager }) {
Modal: (props) => <FileSelector
type={SELECTOR_MODE.SINGLE}
allowedFormats={IMAGE_FORMATS}
onFinish={(file) => {
onFinish={(file: VirtualFile) => {
settings.set("wallpaper", file.source);
}}
{...props}
@ -55,13 +52,13 @@ export function AppearanceSettings({ modalsManager }) {
Browse
</Button>
<div className={styles["Input"]}>
{virtualRoot.navigate(WALLPAPERS_PATH)?.getFiles()?.toReversed().map(({ id, source }) =>
{(virtualRoot.navigate(WALLPAPERS_PATH) as VirtualFolder)?.getFiles()?.toReversed().map(({ id, source }) =>
<label className={styles["Image-select"]} key={id}>
<input
type="radio"
value={source}
aria-label="Wallpaper image"
checked={source === wallpaper ? "checked" : ""}
checked={source === wallpaper}
onChange={onChange}
tabIndex={0}
/>

View file

@ -1,16 +1,11 @@
import { useEffect, useState } from "react";
import styles from "../Settings.module.css";
import AppsManager from "../../../../features/apps/appsManager.js";
import AppsManager from "../../../../features/apps/appsManager";
import { useSettingsManager } from "../../../../hooks/settings/settingsManagerContext";
import { SettingsManager } from "../../../../features/settings/settingsManager.js";
import ModalsManager from "../../../../features/modals/modalsManager.js";
import { AppOption } from "./AppOption.jsx";
import { SettingsManager } from "../../../../features/settings/settingsManager";
import { AppOption } from "./AppOption";
/**
* @param {object} props
* @param {ModalsManager} props.modalsManager
*/
export function AppsSettings({ modalsManager }) {
export function AppsSettings() {
const settingsManager = useSettingsManager();
const [pins, setPins] = useState([]);
@ -26,7 +21,7 @@ export function AppsSettings({ modalsManager }) {
{AppsManager.APPS.sort((a, b) =>
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
).map((app) =>
<AppOption key={app.id} app={app} modalsManager={modalsManager} pins={pins} setPins={setPins}/>
<AppOption key={app.id} app={app} pins={pins} setPins={setPins}/>
)}
</div>;
}

View file

@ -1,10 +1,10 @@
import styles from "../Settings.module.css";
import utilStyles from "../../../../styles/utils.module.css";
import { round } from "../../../../features/math/round.js";
import { ProgressBar } from "../../../_utils/progress-bar/ProgressBar.jsx";
import { Button } from "../../../_utils/button/Button.jsx";
import { round } from "../../../../features/math/round";
import { ProgressBar } from "../../../_utils/progress-bar/ProgressBar";
import { Button } from "../../../_utils/button/Button";
import { useVirtualRoot } from "../../../../hooks/virtual-drive/virtualRootContext";
import { StorageManager } from "../../../../features/storage/storageManager.js";
import { StorageManager } from "../../../../features/storage/storageManager";
export function StorageTab() {
const virtualRoot = useVirtualRoot();

View file

@ -57,7 +57,7 @@ function createClass(bundle) {
* @returns {object} returns the style object
*/
function createStyle(bundle) {
const style = {};
const style: Record<string, string> = {};
if (bundle.bg) {
style.backgroundColor = `rgb(${bundle.bg})`;
}

View file

@ -1,17 +1,18 @@
import { useState } from "react";
import { CSSProperties, MutableRefObject, useState } from "react";
import styles from "./Terminal.module.css";
import Ansi from "./Ansi.jsx";
import Ansi from "./Ansi";
/**
* @param {object} props
* @param {string} props.value
* @param {string} props.prefix
* @param {Function} props.onChange
* @param {Function} props.onKeyUp
* @param {Function} props.onKeyDown
* @param {import("react").MutableRefObject} props.inputRef
*/
export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRef }) {
interface InputLineProps {
value: string;
prefix: string;
onChange: Function;
onKeyUp?: Function;
onKeyDown?: Function;
inputRef: MutableRefObject<HTMLInputElement>;
}
export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRef }: InputLineProps) {
const [cursorPosition, setCursorPosition] = useState(0);
const checkCursorPosition = () => {
@ -21,7 +22,7 @@ export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRe
return (
<span className={styles.Input}>
{prefix && <Ansi className={[styles.Prefix]} useClasses>{prefix}</Ansi>}
<span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition }}>
<span className={styles["Input-container"]} style={{ "--cursor-offset": cursorPosition } as CSSProperties}>
<span aria-hidden="true">{value}</span>
<input
id="input"
@ -32,9 +33,9 @@ export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRe
checkCursorPosition();
}}
ref={inputRef}
onKeyUp={onKeyUp}
onKeyUp={(event) => { onKeyUp?.(event); }}
onKeyDown={(event) => {
onKeyDown(event);
onKeyDown?.(event);
checkCursorPosition();
}}
onClick={checkCursorPosition}
@ -46,7 +47,6 @@ export function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown, inputRe
spellCheck={false}
autoComplete="off"
autoFocus
size=""
/>
</span>
</span>

View file

@ -1,18 +0,0 @@
import { forwardRef } from "react";
import Ansi from "./Ansi.jsx";
import styles from "./Terminal.module.css";
/**
* @param {object} props
* @param {string} props.text
* @param {*} props.ref
*/
export const OutputLine = forwardRef(({ text }, ref) => {
const lines = text?.split("\n");
return (<div ref={ref}>
{lines.map((line, index) =>
<Ansi key={index} className={styles.Output} useClasses>{line === "" ? " " : line}</Ansi>
)}
</div>);
});

View file

@ -0,0 +1,17 @@
import { FC, forwardRef } from "react";
import Ansi from "./Ansi";
import styles from "./Terminal.module.css";
interface OutputLineProps {
text: string;
}
export const OutputLine: FC<OutputLineProps> = forwardRef<HTMLDivElement>(({ text }: OutputLineProps, ref) => {
const lines = text?.split("\n");
return <div ref={ref}>
{lines.map((line, index) =>
<Ansi key={index} className={styles.Output} useClasses>{line === "" ? " " : line}</Ansi>
)}
</div>;
}) as FC;

View file

@ -1,19 +1,23 @@
import { useEffect, useRef, useState } from "react";
import styles from "./Terminal.module.css";
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
import { clamp } from "../../../features/math/clamp.js";
import { OutputLine } from "./OutputLine.jsx";
import { InputLine } from "./InputLine.jsx";
import { ANSI, HOSTNAME, USERNAME } from "../../../config/apps/terminal.config.js";
import CommandsManager from "../../../features/apps/terminal/commands.js";
import { removeFromArray } from "../../../features/_utils/array.utils.js";
import Stream from "../../../features/apps/terminal/stream.js";
import { formatError } from "../../../features/apps/terminal/_utils/terminal.utils.js";
import { clamp } from "../../../features/math/clamp";
import { OutputLine } from "./OutputLine";
import { InputLine } from "./InputLine";
import { ANSI, HOSTNAME, USERNAME } from "../../../config/apps/terminal.config";
import CommandsManager from "../../../features/apps/terminal/commands";
import { removeFromArray } from "../../../features/_utils/array.utils";
import Stream from "../../../features/apps/terminal/stream";
import { formatError } from "../../../features/apps/terminal/_utils/terminal.utils";
import { WindowProps } from "../../windows/WindowView";
import { VirtualFolder } from "../../../features/virtual-drive/folder/virtualFolder";
/**
* @param {import("../../windows/WindowView.jsx").windowProps} props
*/
export function Terminal({ startPath, input, setTitle, close: exit, active }) {
interface TerminalProps extends WindowProps {
startPath: string;
input: string;
}
export function Terminal({ startPath, input, setTitle, close: exit, active }: TerminalProps) {
const [inputKey, setInputKey] = useState(0);
const [inputValue, setInputValue] = useState(input ?? "");
const [history, setHistory] = useState([]);
@ -73,7 +77,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
let lastOutput = null;
stream.on(Stream.EVENT_NAMES.NEW, (text) => {
stream.on(Stream.EVENT_NAMES.new, (text) => {
let output = text;
pipes.forEach((pipe) => {
if (output instanceof Stream)
@ -93,7 +97,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
setStreamOutput(output);
});
stream.on(Stream.EVENT_NAMES.STOP, () => {
stream.on(Stream.EVENT_NAMES.stop, () => {
document.removeEventListener("keydown", onKeyDown);
promptOutput(lastOutput);
@ -175,7 +179,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
promptOutput,
pushHistory,
virtualRoot,
currentDirectory,
currentDirectory: currentDirectory as VirtualFolder,
setCurrentDirectory,
username: USERNAME,
hostname: HOSTNAME,
@ -328,7 +332,6 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
onKeyDown={onKeyDown}
onChange={onChange}
inputRef={inputRef}
history={history}
/>
: <OutputLine text={streamOutput ?? ""}/>
}

View file

@ -1,18 +1,18 @@
import { useEffect, useRef, useState } from "react";
import styles from "./TextEditor.module.css";
import { HeaderMenu } from "../_utils/header-menu/HeaderMenu.jsx";
import { HeaderMenu } from "../_utils/header-menu/HeaderMenu";
import Markdown from "markdown-to-jsx";
import { CODE_FORMATS, DEFAULT_ZOOM, EXTENSION_TO_LANGUAGE, ZOOM_FACTOR } from "../../../config/apps/textEditor.config.js";
import AppsManager from "../../../features/apps/appsManager.js";
import { TITLE_SEPARATOR } from "../../../config/windows.config.js";
import { MarkdownLink } from "./overrides/MarkdownLink.jsx";
import { MarkdownImage } from "./overrides/MarkdownImage.jsx";
import { CODE_FORMATS, DEFAULT_ZOOM, EXTENSION_TO_LANGUAGE, ZOOM_FACTOR } from "../../../config/apps/textEditor.config";
import AppsManager from "../../../features/apps/appsManager";
import { TITLE_SEPARATOR } from "../../../config/windows.config";
import { MarkdownLink } from "./overrides/MarkdownLink";
import { MarkdownImage } from "./overrides/MarkdownImage";
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
import SyntaxHighlighter from "react-syntax-highlighter";
import { useWindowedModal } from "../../../hooks/modals/windowedModal.js";
import { DEFAULT_FILE_SELECTOR_SIZE } from "../../../config/modals.config.js";
import { FileSelector } from "../../modals/file-selector/FileSelector.jsx";
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config.js";
import { useWindowedModal } from "../../../hooks/modals/windowedModal";
import { DEFAULT_FILE_SELECTOR_SIZE } from "../../../config/modals.config";
import { FileSelector } from "../../modals/file-selector/FileSelector";
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config";
const OVERRIDES = {
a: MarkdownLink,
@ -58,7 +58,7 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
setContent(newContent);
if (ref.current) {
ref.current.scrollTo(0, 0);
(ref.current as HTMLElement).scrollTo(0, 0);
}
})();
}, [app.id, currentFile, setIconUrl]);

View file

@ -1,8 +1,8 @@
import { useMemo } from "react";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile.js";
import ModalsManager from "../../../../features/modals/modalsManager.js";
import App from "../../../../features/apps/app.js";
import WindowsManager from "../../../../features/windows/windowsManager.js";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile";
import ModalsManager from "../../../../features/modals/modalsManager";
import App from "../../../../features/apps/app";
import WindowsManager from "../../../../features/windows/windowsManager";
/**
* @param {object} props

View file

@ -1,17 +1,17 @@
import { useMemo } from "react";
import { faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { useContextMenu } from "../../../../hooks/modals/contextMenu.js";
import { Actions } from "../../../actions/Actions.jsx";
import { ClickAction } from "../../../actions/actions/ClickAction.jsx";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile.js";
import { useWindowedModal } from "../../../../hooks/modals/windowedModal.js";
import AppsManager from "../../../../features/apps/appsManager.js";
import App from "../../../../features/apps/app.js";
import { DialogBox } from "../../../modals/dialog-box/DialogBox.jsx";
import { DIALOG_CONTENT_TYPES } from "../../../../config/modals.config.js";
import Vector2 from "../../../../features/math/vector2.js";
import WindowsManager from "../../../../features/windows/windowsManager.js";
import { APPS } from "../../../../config/apps.config.js";
import { useContextMenu } from "../../../../hooks/modals/contextMenu";
import { Actions } from "../../../actions/Actions";
import { ClickAction } from "../../../actions/actions/ClickAction";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile";
import { useWindowedModal } from "../../../../hooks/modals/windowedModal";
import AppsManager from "../../../../features/apps/appsManager";
import App from "../../../../features/apps/app";
import { DialogBox } from "../../../modals/dialog-box/DialogBox";
import { DIALOG_CONTENT_TYPES } from "../../../../config/modals.config";
import Vector2 from "../../../../features/math/vector2";
import WindowsManager from "../../../../features/windows/windowsManager";
import { APPS } from "../../../../config/apps.config";
/**
* @param {object} props

View file

@ -1,47 +1,47 @@
import { memo, useState } from "react";
import { SettingsManager } from "../../features/settings/settingsManager.js";
import { SettingsManager } from "../../features/settings/settingsManager";
import { useSettingsManager } from "../../hooks/settings/settingsManagerContext";
import styles from "./Desktop.module.css";
import { useEffect } from "react";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
import { FALLBACK_ICON_SIZE, FALLBACK_WALLPAPER } from "../../config/desktop.config.js";
import { reloadViewport } from "../../features/_utils/browser.utils.js";
import { useContextMenu } from "../../hooks/modals/contextMenu";
import { FALLBACK_ICON_SIZE, FALLBACK_WALLPAPER } from "../../config/desktop.config";
import { reloadViewport } from "../../features/_utils/browser.utils";
import { useVirtualRoot } from "../../hooks/virtual-drive/virtualRootContext";
import { DirectoryList } from "../apps/file-explorer/directory-list/DirectoryList.jsx";
import { APPS, APP_ICONS, APP_NAMES } from "../../config/apps.config.js";
import Vector2 from "../../features/math/vector2.js";
import { Actions } from "../actions/Actions.jsx";
import { ClickAction } from "../actions/actions/ClickAction.jsx";
import { DirectoryList } from "../apps/file-explorer/directory-list/DirectoryList";
import { APPS, APP_ICONS, APP_NAMES } from "../../config/apps.config";
import Vector2 from "../../features/math/vector2";
import { Actions } from "../actions/Actions";
import { ClickAction } from "../actions/actions/ClickAction";
import { faArrowsRotate, faEye, faFolder, faPaintBrush, faTerminal, faTrash } from "@fortawesome/free-solid-svg-icons";
import { ToggleAction } from "../actions/actions/ToggleAction.jsx";
import { DropdownAction } from "../actions/actions/DropdownAction.jsx";
import { RadioAction } from "../actions/actions/RadioAction.jsx";
import { Divider } from "../actions/actions/Divider.jsx";
import { isValidInteger } from "../../features/_utils/number.utils.js";
import { useWindowedModal } from "../../hooks/modals/windowedModal.js";
import { Share } from "../modals/share/Share.jsx";
import { ToggleAction } from "../actions/actions/ToggleAction";
import { DropdownAction } from "../actions/actions/DropdownAction";
import { RadioAction } from "../actions/actions/RadioAction";
import { Divider } from "../actions/actions/Divider";
import { isValidInteger } from "../../features/_utils/number.utils";
import { useWindowedModal } from "../../hooks/modals/windowedModal";
import { Share } from "../modals/share/Share";
import ModalsManager from "../../features/modals/modalsManager";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext";
import { VirtualFolder } from "../../features/virtual-drive/folder/virtualFolder";
import { VirtualFolderLink } from "../../features/virtual-drive/folder/virtualFolderLink";
export const Desktop = memo(() => {
const settingsManager = useSettingsManager();
const [wallpaper, setWallpaper] = useState(null);
const modalsManager = useModalsManager();
const windowsManager = useWindowsManager();
const virtualRoot = useVirtualRoot();
const [showIcons, setShowIcons] = useState(false);
const [iconSize, setIconSize] = useState(FALLBACK_ICON_SIZE);
const { openWindowedModal } = useWindowedModal({ modalsManager });
const { openWindowedModal } = useWindowedModal();
const directory = virtualRoot.navigate("~/Desktop");
const { onContextMenu, ShortcutsListener } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu, ShortcutsListener } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<DropdownAction label="View" icon={faEye}>
<RadioAction initialIndex={iconSize} onTrigger={(event, params, value) => {
<RadioAction initialIndex={iconSize} onTrigger={(event, params, value: string) => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
settings.set("icon-size", parseInt(value));
settings.set("icon-size", value);
}} options={[
{ label: "Small icons" },
{ label: "Medium icons" },
@ -75,7 +75,7 @@ export const Desktop = memo(() => {
}}/>
</Actions>
});
const { onContextMenu: onContextMenuFile } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu: onContextMenuFile } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Open" onTrigger={(event, file) => {
file.open(windowsManager);
@ -88,7 +88,7 @@ export const Desktop = memo(() => {
}}/>
</Actions>
});
const { onContextMenu: onContextMenuFolder } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu: onContextMenuFolder } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Open" onTrigger={(event, folder) => {
folder.open(windowsManager);
@ -138,7 +138,7 @@ export const Desktop = memo(() => {
onContextMenu={onContextMenu}
>
{showIcons && <DirectoryList
directory={directory}
directory={directory as VirtualFolder}
className={styles.Content}
style={{
"--scale": `${iconScale}rem`
@ -146,9 +146,9 @@ export const Desktop = memo(() => {
fileClassName={styles["Item"]}
folderClassName={styles["Item"]}
onOpenFile={(event, file) => {
event.preventDefault();
(event as Event).preventDefault();
const options = {};
const options: Record<string, any> = {};
if (file.name === "info.md")
options.size = new Vector2(575, 675);
if (file.extension === "md")
@ -156,7 +156,7 @@ export const Desktop = memo(() => {
windowsManager.openFile(file, options);
}}
onOpenFolder={(event, { linkedPath, path }) => {
onOpenFolder={(event, { linkedPath, path }: VirtualFolderLink & VirtualFolder) => {
windowsManager.open(APPS.FILE_EXPLORER, { startPath: linkedPath ?? path });
}}
onContextMenuFile={onContextMenuFile}

View file

@ -1,22 +1,25 @@
import { memo } from "react";
import { Modal as ModalType } from "../../features/modals/modal.js";
import OutsideClickListener from "../../hooks/_utils/outsideClick.js";
import { CSSProperties, FC, memo, ReactNode } from "react";
import Modal from "../../features/modals/modal";
import OutsideClickListener from "../../hooks/_utils/outsideClick";
import styles from "./ModalView.module.css";
import { useEffect } from "react";
/**
* @typedef {object} modalProps
* @param {object} props
* @param {ModalType} props.modal
* @param {Modal} props.modal
* @param {*} props.params
* @param {Function} props.onFinish
*/
/**
* @param {object} root
* @param {ModalType} root.modal
*/
export const ModalView = memo(({ modal }) => {
export interface ModalProps {
modal: Modal;
params?: Record<string, any>;
children?: ReactNode;
[key: string]: any;
}
export const ModalView: FC<ModalProps> = memo(({ modal }) => {
useEffect(() => {
const onDismiss = (event) => {
if (event.key === "Escape")
@ -32,7 +35,7 @@ export const ModalView = memo(({ modal }) => {
const Container = () => (<div
className={styles.Container}
style={{ "--position-x": modal.position.x, "--position-y": modal.position.y }}
style={{ "--position-x": modal.position.x, "--position-y": modal.position.y } as CSSProperties}
>
<modal.element modal={modal} {...modal.props}/>
</div>);

View file

@ -1,5 +1,5 @@
import { memo, useEffect, useRef, useState } from "react";
import { ModalView } from "./ModalView.jsx";
import { ModalView } from "./ModalView";
import styles from "./ModalsView.module.css";
import { useModals } from "../../hooks/modals/modalsContext";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext";

View file

@ -1,13 +1,14 @@
import { useEffect, useRef, useState } from "react";
import { useScreenDimensions } from "../../../hooks/_utils/screen.js";
import Vector2 from "../../../features/math/vector2.js";
import { useScreenDimensions } from "../../../hooks/_utils/screen";
import Vector2 from "../../../features/math/vector2";
import styles from "./WindowedModal.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import Draggable from "react-draggable";
import { ReactSVG } from "react-svg";
import utilStyles from "../../../styles/utils.module.css";
import Modal from "../../../features/modals/modal.js";
import Modal from "../../../features/modals/modal";
import { ModalProps } from "../ModalView";
/**
* @param {object} props
@ -15,7 +16,7 @@ import Modal from "../../../features/modals/modal.js";
* @param {*} props.params
* @param {*} props.children
*/
export function WindowedModal({ modal, params, children, ...props }) {
export function WindowedModal({ modal, params, children, ...props }: ModalProps) {
const { iconUrl, title } = params;
const nodeRef = useRef(null);

View file

@ -1,11 +1,12 @@
import { DIALOG_CONTENT_TYPES } from "../../../config/modals.config.js";
import { WindowedModal } from "../_utils/WindowedModal.jsx";
import { DIALOG_CONTENT_TYPES } from "../../../config/modals.config";
import { WindowedModal } from "../_utils/WindowedModal";
import { ModalProps } from "../ModalView";
import styles from "./DialogBox.module.css";
export function DialogBox({ modal, params, children, ...props }) {
const onClick = (event) => {
export function DialogBox({ modal, params, children, ...props }: ModalProps) {
const onClick = (event: MouseEvent) => {
event.preventDefault();
const type = parseInt(event.target.getAttribute("data-type"));
const type = parseInt((event.target as HTMLElement).getAttribute("data-type"));
switch (type) {
case DIALOG_CONTENT_TYPES.CloseButton:

View file

@ -1,19 +1,17 @@
import { ImagePreview } from "../../apps/file-explorer/directory-list/ImagePreview.jsx";
import { WindowedModal } from "../_utils/WindowedModal.jsx";
import { ImagePreview } from "../../apps/file-explorer/directory-list/ImagePreview";
import { WindowedModal } from "../_utils/WindowedModal";
import styles from "./FileProperties.module.css";
import utilStyles from "../../../styles/utils.module.css";
import Modal from "../../../features/modals/modal.js";
import { StorageManager } from "../../../features/storage/storageManager";
import AppsManager from "../../../features/apps/appsManager";
import { ModalProps } from "../ModalView.js";
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile.js";
import { StorageManager } from "../../../features/storage/storageManager.js";
import AppsManager from "../../../features/apps/appsManager.js";
/**
* @param {object} props
* @param {Modal} props.modal
* @param {object} props.params
* @param {VirtualFile} props.file
*/
export function FileProperties({ modal, params, file, ...props }) {
interface FilePropetiesProps extends ModalProps {
file: VirtualFile;
}
export function FileProperties({ modal, params, file, ...props }: FilePropetiesProps) {
const associatedApp = AppsManager.getAppByFileExtension(file.extension);
return <WindowedModal className={styles.Container} modal={modal} params={params} {...props}>

View file

@ -1,31 +1,22 @@
import { useState } from "react";
import { APP_ICONS } from "../../../config/apps.config.js";
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config.js";
import { Button } from "../../_utils/button/Button.jsx";
import { FileExplorer } from "../../apps/file-explorer/FileExplorer.jsx";
import { WindowedModal } from "../_utils/WindowedModal.jsx";
import { APP_ICONS } from "../../../config/apps.config";
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config";
import { Button } from "../../_utils/button/Button";
import { FileExplorer } from "../../apps/file-explorer/FileExplorer";
import { WindowedModal } from "../_utils/WindowedModal";
import styles from "./FileSelector.module.css";
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile.js";
import Modal from "../../../features/modals/modal.js";
import { useModalsManager } from "../../../hooks/modals/modalsManagerContext";
import { VirtualFile } from "../../../features/virtual-drive/file/virtualFile";
import { ModalProps } from "../ModalView";
/**
* @callback onFinish
* @param {VirtualFile|VirtualFile[]} result
*/
interface FileSelectorProps extends ModalProps {
type: number;
allowedFormats?: string[];
onFinish: (result: VirtualFile | VirtualFile[]) => void;
}
/**
* @param {object} props
* @param {Modal} props.modal
* @param {*} props.params
* @param {number} props.type
* @param {string[]=} props.allowedFormats
* @param {onFinish} props.onFinish
*/
export function FileSelector({ modal, params, type, allowedFormats, onFinish, ...props }) {
export function FileSelector({ modal, params, type, allowedFormats, onFinish, ...props }: FileSelectorProps) {
const multi = (type === SELECTOR_MODE.MULTIPLE);
const modalsManager = useModalsManager();
const [selection, setSelection] = useState(multi ? [] : null);
const [directory, setDirectory] = useState(null);
@ -58,7 +49,6 @@ export function FileSelector({ modal, params, type, allowedFormats, onFinish, ..
...params,
}} {...props}>
<FileExplorer
modalsManager={modalsManager}
selectorMode={type}
Footer={() =>
<div className={styles.Footer}>

View file

@ -1,16 +1,16 @@
import { useEffect, useState } from "react";
import ModalsManager from "../../../features/modals/modalsManager.js";
import { WindowedModal } from "../_utils/WindowedModal.jsx";
import ModalsManager from "../../../features/modals/modalsManager";
import { WindowedModal } from "../_utils/WindowedModal";
import styles from "./Share.module.css";
import { copyToClipboard, generateUrl } from "../../../features/_utils/browser.utils.js";
import AppsManager from "../../../features/apps/appsManager.js";
import { copyToClipboard, generateUrl } from "../../../features/_utils/browser.utils";
import AppsManager from "../../../features/apps/appsManager";
import utilStyles from "../../../styles/utils.module.css";
import { Button } from "../../_utils/button/Button.jsx";
import Option from "./Option.jsx";
import { Button } from "../../_utils/button/Button";
import Option from "./Option";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSquare } from "@fortawesome/free-regular-svg-icons";
import { faSquareCheck } from "@fortawesome/free-solid-svg-icons";
import { useAlert } from "../../../hooks/modals/alert.js";
import { useAlert } from "../../../hooks/modals/alert";
const APP_OPTIONS = {
"terminal": [
@ -39,7 +39,7 @@ export function Share({ modal, params, ...props }) {
const [fullscreen, setFullscreen] = useState(params.fullscreen ?? false);
const [options, setOptions] = useState({});
const [url, setUrl] = useState(null);
const { alert } = useAlert({ modalsManager: modal.modalsManager });
const { alert } = useAlert();
useEffect(() => {
setUrl(generateUrl({

View file

@ -1,30 +1,29 @@
import { memo, useEffect, useMemo, useRef, useState } from "react";
import { CSSProperties, memo, useEffect, useMemo, useRef, useState } from "react";
import styles from "./Taskbar.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons";
import AppsManager from "../../features/apps/appsManager.js";
import AppsManager from "../../features/apps/appsManager";
import { ReactSVG } from "react-svg";
import { HomeMenu } from "./menus/HomeMenu.jsx";
import OutsideClickListener from "../../hooks/_utils/outsideClick.js";
import { Battery } from "./indicators/Battery.jsx";
import { Network } from "./indicators/Network.jsx";
import { Volume } from "./indicators/Volume.jsx";
import { SearchMenu } from "./menus/SearchMenu.jsx";
import { Calendar } from "./indicators/Calendar.jsx";
import { useScrollWithShadow } from "../../hooks/_utils/scrollWithShadows.js";
import { AppButton } from "./app-icon/AppIcon.jsx";
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
import { Actions } from "../actions/Actions.jsx";
import { ClickAction } from "../actions/actions/ClickAction.jsx";
import { APPS, APP_NAMES } from "../../config/apps.config.js";
import { HomeMenu } from "./menus/HomeMenu";
import OutsideClickListener from "../../hooks/_utils/outsideClick";
import { Battery } from "./indicators/Battery";
import { Network } from "./indicators/Network";
import { Volume } from "./indicators/Volume";
import { SearchMenu } from "./menus/SearchMenu";
import { Calendar } from "./indicators/Calendar";
import { useScrollWithShadow } from "../../hooks/_utils/scrollWithShadows";
import { AppButton } from "./app-icon/AppIcon";
import { useContextMenu } from "../../hooks/modals/contextMenu";
import { Actions } from "../actions/Actions";
import { ClickAction } from "../actions/actions/ClickAction";
import { APPS, APP_NAMES } from "../../config/apps.config";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
import { TASKBAR_HEIGHT } from "../../config/taskbar.config.js";
import { TASKBAR_HEIGHT } from "../../config/taskbar.config";
import { useSettingsManager } from "../../hooks/settings/settingsManagerContext";
import { SettingsManager } from "../../features/settings/settingsManager.js";
import { SettingsManager } from "../../features/settings/settingsManager";
import { useWindows } from "../../hooks/windows/windowsContext";
import { ZIndexManager } from "../../features/z-index/zIndexManager.js";
import { useZIndex } from "../../hooks/z-index/zIndex.js";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext";
import { ZIndexManager } from "../../features/z-index/zIndexManager";
import { useZIndex } from "../../hooks/z-index/zIndex";
export const Taskbar = memo(() => {
const ref = useRef(null);
@ -40,10 +39,9 @@ export const Taskbar = memo(() => {
color: { a: 25 }
} });
const inputRef = useRef(null);
const modalsManager = useModalsManager();
const windowsManager = useWindowsManager();
const windows = useWindows();
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu } = useContextMenu({ Actions: (props) =>
<Actions avoidTaskbar={false} {...props}>
<ClickAction label={`Open ${APP_NAMES.SETTINGS}`} icon={faCog} onTrigger={() => {
windowsManager.open(APPS.SETTINGS);
@ -70,14 +68,13 @@ export const Taskbar = memo(() => {
const shouldBeShown = (pins.includes(app.id) || isActive);
return (<AppButton
windowsManager={windowsManager}
modalsManager={modalsManager}
pins={pins}
app={app}
key={app.id}
active={isActive}
visible={shouldBeShown}
/>);
}), [modalsManager, pins, windows, windowsManager]);
}), [pins, windows, windowsManager]);
useEffect(() => {
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
@ -131,11 +128,11 @@ export const Taskbar = memo(() => {
return (<>
<div
style={{ "--taskbar-height": `${TASKBAR_HEIGHT}px`, zIndex }}
style={{ "--taskbar-height": `${TASKBAR_HEIGHT}px`, zIndex } as CSSProperties}
className={styles["Taskbar"]}
data-allow-context-menu={true}
onContextMenu={(event) => {
if (event.target.getAttribute("data-allow-context-menu"))
if ((event.target as HTMLElement).getAttribute("data-allow-context-menu"))
onContextMenu(event);
}}
>
@ -193,4 +190,4 @@ export const Taskbar = memo(() => {
</div>
</div>
</>);
}, []);
});

View file

@ -1,29 +1,30 @@
import { memo } from "react";
import App from "../../../features/apps/app.js";
import { FC, memo } from "react";
import App from "../../../features/apps/app";
import styles from "./AppIcon.module.css";
import { ReactSVG } from "react-svg";
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext";
import { useContextMenu } from "../../../hooks/modals/contextMenu.js";
import ModalsManager from "../../../features/modals/modalsManager.js";
import { Actions } from "../../actions/Actions.jsx";
import { ClickAction } from "../../actions/actions/ClickAction.jsx";
import { useContextMenu } from "../../../hooks/modals/contextMenu";
import { Actions } from "../../actions/Actions";
import { ClickAction } from "../../actions/actions/ClickAction";
import { faThumbTack, faTimes } from "@fortawesome/free-solid-svg-icons";
import { SettingsManager } from "../../../features/settings/settingsManager.js";
import { removeFromArray } from "../../../features/_utils/array.utils.js";
import AppsManager from "../../../features/apps/appsManager.js";
import { SettingsManager } from "../../../features/settings/settingsManager";
import { removeFromArray } from "../../../features/_utils/array.utils";
import AppsManager from "../../../features/apps/appsManager";
import WindowsManager from "../../../features/windows/windowsManager";
/**
* @param {object} props
* @param {App} props.app
* @param {ModalsManager} props.modalsManager
* @param {string[]} props.pins
* @param {boolean} props.focused
*/
export const AppButton = memo(({ app, windowsManager, modalsManager, pins, active, visible }) => {
interface AppButtonProps {
app: App;
windowsManager: WindowsManager;
pins: string[];
active: boolean;
visible: boolean;
}
export const AppButton: FC<AppButtonProps> = memo(({ app, windowsManager, pins, active, visible }: AppButtonProps) => {
const isPinned = pins.includes(app.id);
const settingsManager = useSettingsManager();
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
const { onContextMenu } = useContextMenu({ Actions: (props) =>
<Actions avoidTaskbar={false} {...props}>
<ClickAction label={app.name} icon={AppsManager.getAppIconUrl(app.id)} onTrigger={() => {
windowsManager.open(app.id);

View file

@ -2,15 +2,15 @@ import { faBatteryEmpty, faBatteryFull, faBatteryHalf, faBatteryQuarter, faBatte
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react";
import styles from "./Battery.module.css";
import { UtilMenu } from "../menus/UtilMenu.jsx";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
import { UtilMenu } from "../menus/UtilMenu";
import OutsideClickListener from "../../../hooks/_utils/outsideClick";
/**
* @param {object} props
* @param {boolean} props.hideUtilMenus
* @param {Function} props.showUtilMenu
*/
export function Battery({ hideUtilMenus, showUtilMenu }) {
interface BatteryProps {
hideUtilMenus: boolean;
showUtilMenu: Function;
}
export function Battery({ hideUtilMenus, showUtilMenu }: BatteryProps) {
const [isCharging, setIsCharging] = useState(true);
const [percentage, setPercentage] = useState(100);
const [showMenu, setShowMenu] = useState(false);
@ -18,7 +18,7 @@ export function Battery({ hideUtilMenus, showUtilMenu }) {
// const [dischargingTime, setDischargingTime] = useState(0);
useEffect(() => {
navigator.getBattery?.()?.then((battery) => {
(navigator as any).getBattery?.()?.then((battery) => {
const updateIsCharging = () => {
setIsCharging(battery.charging);
};

View file

@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import styles from "./Calendar.module.css";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
import { UtilMenu } from "../menus/UtilMenu.jsx";
import OutsideClickListener from "../../../hooks/_utils/outsideClick";
import { UtilMenu } from "../menus/UtilMenu";
/**
* @param {object} props

View file

@ -1,8 +1,8 @@
import { faWifi } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
import { UtilMenu } from "../menus/UtilMenu.jsx";
import OutsideClickListener from "../../../hooks/_utils/outsideClick";
import { UtilMenu } from "../menus/UtilMenu";
import styles from "./Network.module.css";
/**

View file

@ -1,8 +1,8 @@
import { faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useEffect, useState } from "react";
import OutsideClickListener from "../../../hooks/_utils/outsideClick.js";
import { UtilMenu } from "../menus/UtilMenu.jsx";
import OutsideClickListener from "../../../hooks/_utils/outsideClick";
import { UtilMenu } from "../menus/UtilMenu";
import styles from "./Volume.module.css";
/**

View file

@ -3,16 +3,16 @@ import styles from "./HomeMenu.module.css";
import appStyles from "./AppList.module.css";
import { faCircleInfo, faFileLines, faGear, faImage, faPowerOff } from "@fortawesome/free-solid-svg-icons";
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
import AppsManager from "../../../features/apps/appsManager.js";
import AppsManager from "../../../features/apps/appsManager";
import { ReactSVG } from "react-svg";
import { closeViewport } from "../../../features/_utils/browser.utils.js";
import { useKeyboardListener } from "../../../hooks/_utils/keyboard.js";
import { closeViewport } from "../../../features/_utils/browser.utils";
import { useKeyboardListener } from "../../../hooks/_utils/keyboard";
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
import { useEffect, useState } from "react";
import Vector2 from "../../../features/math/vector2.js";
import Vector2 from "../../../features/math/vector2";
import utilStyles from "../../../styles/utils.module.css";
import { APPS } from "../../../config/apps.config.js";
import { NAME } from "../../../config/branding.config.js";
import { APPS } from "../../../config/apps.config";
import { NAME } from "../../../config/branding.config";
/**
* @param {object} props

View file

@ -1,10 +1,10 @@
import styles from "./SearchMenu.module.css";
import appStyles from "./AppList.module.css";
import AppsManager from "../../../features/apps/appsManager.js";
import AppsManager from "../../../features/apps/appsManager";
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
import { ReactSVG } from "react-svg";
import { useEffect, useState } from "react";
import { useKeyboardListener } from "../../../hooks/_utils/keyboard.js";
import { useKeyboardListener } from "../../../hooks/_utils/keyboard";
/**
* @param {object} props

View file

@ -1,28 +0,0 @@
import { useEffect, useState } from "react";
import { useModalsManager } from "../../hooks/modals/modalsManagerContext";
import { useAlert } from "../../hooks/modals/alert.js";
import AppsManager from "../../features/apps/appsManager.js";
import Vector2 from "../../features/math/vector2.js";
export default function WindowFallbackView({ error, resetErrorBoundary, app, closeWindow }) {
const modalsManager = useModalsManager();
const { alert } = useAlert({ modalsManager });
const [alerted, setAlerted] = useState(false);
useEffect(() => {
if (alerted)
return;
setAlerted(true);
closeWindow();
alert({
title: `${app.name} has stopped working`,
text: `${error.name}: ${error.message}`,
iconUrl: AppsManager.getAppIconUrl(app.id),
size: new Vector2(350, 150),
single: true
});
}, [alerted, alert, app.id, app.name, error.message, closeWindow, error.name]);
return;
}

View file

@ -0,0 +1,35 @@
import { ReactNode, useEffect, useState } from "react";
import { useAlert } from "../../hooks/modals/alert";
import AppsManager from "../../features/apps/appsManager";
import Vector2 from "../../features/math/vector2";
import App from "../../features/apps/app";
export interface WindowFallbackViewProps {
error?: Error;
resetErrorBoundary?: any;
app?: App;
closeWindow?: Function
}
// I don't know why this component's type needs to be ReactNode instead of FC, it has something to do with the way it's implemented
export default function WindowFallbackView({ error, resetErrorBoundary, app, closeWindow }: WindowFallbackViewProps): ReactNode {
const { alert } = useAlert();
const [alerted, setAlerted] = useState(false);
useEffect(() => {
if (alerted)
return;
setAlerted(true);
closeWindow();
alert({
title: `${app.name} has stopped working`,
text: `${error.name}: ${error.message}`,
iconUrl: AppsManager.getAppIconUrl(app.id),
size: new Vector2(350, 150),
single: true
});
}, [alerted, alert, app.id, app.name, error.message, closeWindow, error.name]);
return;
}

View file

@ -4,52 +4,41 @@ import { faExpand, faMinus, faWindowMaximize as fasWindowMaximize, faTimes, faXm
import { ReactSVG } from "react-svg";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
import Draggable from "react-draggable";
import { memo, useEffect, useRef, useState } from "react";
import App from "../../features/apps/app.js";
import Vector2 from "../../features/math/vector2.js";
import { FC, memo, MouseEventHandler, useEffect, useRef, useState } from "react";
import Vector2 from "../../features/math/vector2";
import { faWindowMaximize } from "@fortawesome/free-regular-svg-icons";
import utilStyles from "../../styles/utils.module.css";
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
import AppsManager from "../../features/apps/appsManager.js";
import { ClickAction } from "../actions/actions/ClickAction.jsx";
import { Actions } from "../actions/Actions.jsx";
import { useScreenDimensions } from "../../hooks/_utils/screen.js";
import { NAME } from "../../config/branding.config.js";
import { setViewportIcon, setViewportTitle } from "../../features/_utils/browser.utils.js";
import { ZIndexManager } from "../../features/z-index/zIndexManager.js";
import { useContextMenu } from "../../hooks/modals/contextMenu";
import AppsManager from "../../features/apps/appsManager";
import { ClickAction } from "../actions/actions/ClickAction";
import { Actions } from "../actions/Actions";
import { useScreenDimensions } from "../../hooks/_utils/screen";
import { NAME } from "../../config/branding.config";
import { setViewportIcon, setViewportTitle } from "../../features/_utils/browser.utils";
import { ZIndexManager } from "../../features/z-index/zIndexManager";
import { useZIndex } from "../../hooks/z-index/zIndex";
import { useWindowedModal } from "../../hooks/modals/windowedModal.js";
import { Divider } from "../actions/actions/Divider.jsx";
import ModalsManager from "../../features/modals/modalsManager.js";
import { Share } from "../modals/share/Share.jsx";
import { useWindowedModal } from "../../hooks/modals/windowedModal";
import { Divider } from "../actions/actions/Divider";
import ModalsManager from "../../features/modals/modalsManager";
import { Share } from "../modals/share/Share";
import { ErrorBoundary } from "react-error-boundary";
import WindowFallbackView from "./WindowFallbackView.jsx";
import WindowFallbackView from "./WindowFallbackView";
import { WindowOptions } from "../../features/windows/windowsManager";
/**
* @typedef {object} windowProps
* @property {App} app
* @property {Function} setTitle
* @property {Function} setIconUrl
* @property {Function} close
* @property {Function} focus
* @property {boolean} active
* @property {import("../../features/modals/modalsManager.js").default} modalsManager
*/
export interface WindowProps extends WindowOptions {
fullscreen?: boolean;
onInteract?: Function
setTitle?: Function;
setIconUrl?: Function;
close?: Function;
focus?: Function;
active?: boolean;
minimized?: boolean;
toggleMinimized?: Function;
index?: number;
}
/**
* @param {object} props
* @param {string} props.id
* @param {App} props.app
* @param {Vector2} props.size
* @param {Vector2} props.position
* @param {boolean} props.focused
* @param {Function} props.onInteract
* @param {object} props.options
* @param {boolean} props.active
* @param {boolean} props.minimized
* @param {Function} props.toggleMinimized
*/
export const WindowView = memo(({ id, app, size, position, onInteract, options, active, fullscreen, minimized, toggleMinimized, index }) => {
export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onInteract, options, active, fullscreen, minimized, toggleMinimized, index }) => {
const windowsManager = useWindowsManager();
const nodeRef = useRef(null);
const { openWindowedModal } = useWindowedModal();
@ -64,7 +53,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
const { onContextMenu, ShortcutsListener } = useContextMenu({ Actions: (props) =>
<Actions {...props}>
<ClickAction label="Minimize" icon={faMinus} onTrigger={toggleMinimized}/>
<ClickAction label="Minimize" icon={faMinus} onTrigger={() => { toggleMinimized(); }}/>
<ClickAction label="Maximize" icon={faExpand} shortcut={["F11"]} onTrigger={() => {
setMaximized(!maximized);
}}/>
@ -117,7 +106,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
};
}, [active, minimized, iconUrl, title]);
const close = (event) => {
const close = (event?: Event) => {
event?.preventDefault();
windowsManager.close(id);
};
@ -157,7 +146,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
cancel="button"
nodeRef={nodeRef}
disabled={maximized}
onStart={focus}
onStart={(event) => { focus(event); }}
grid={[1, 1]}
>
<div
@ -182,7 +171,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
/>
<p className={utilStyles["Text-semibold"]}>{title}</p>
<button aria-label="Minimize" className={styles["Header-button"]} tabIndex={0} id="minimize-window"
onClick={toggleMinimized}
onClick={toggleMinimized as MouseEventHandler}
>
<FontAwesomeIcon icon={faMinus}/>
</button>
@ -196,7 +185,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
<FontAwesomeIcon icon={maximized ? fasWindowMaximize : faWindowMaximize}/>
</button>
<button aria-label="Close" className={`${styles["Header-button"]} ${styles["Exit-button"]}`} tabIndex={0} id="close-window"
onClick={close}>
onClick={(event) => { close(event as unknown as Event); }}>
<FontAwesomeIcon icon={faXmark}/>
</button>
</div>

View file

@ -1,23 +1,23 @@
import { useWindows } from "../../hooks/windows/windowsContext";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext";
import { memo, useEffect, useState } from "react";
import { WindowView } from "./WindowView.jsx";
import { FC, memo, useEffect, useState } from "react";
import { WindowProps, WindowView } from "./WindowView";
import { useSettingsManager } from "../../hooks/settings/settingsManagerContext";
import { SettingsManager } from "../../features/settings/settingsManager";
import { NAME, TAG_LINE } from "../../config/branding.config.js";
import { getViewportParams, setViewportIcon, setViewportTitle } from "../../features/_utils/browser.utils.js";
import { removeDuplicatesFromArray } from "../../features/_utils/array.utils.js";
import { NAME, TAG_LINE } from "../../config/branding.config";
import { getViewportParams, setViewportIcon, setViewportTitle } from "../../features/_utils/browser.utils";
import { removeDuplicatesFromArray } from "../../features/_utils/array.utils";
import { WindowOptions } from "../../features/windows/windowsManager";
export const WindowsView = memo(() => {
export const WindowsView: FC = memo(() => {
const settingsManager = useSettingsManager();
const windows = useWindows();
const windowsManager = useWindowsManager();
const [sortedWindows, setSortedWindows] = useState([]);
const [sortedWindows, setSortedWindows] = useState<WindowProps[]>([]);
// Sort windows
useEffect(() => {
setSortedWindows([...windows].sort((windowA, windowB) =>
// @ts-ignore
setSortedWindows([...windows].sort((windowA: WindowOptions, windowB: WindowOptions) =>
windowA.lastInteraction - windowB.lastInteraction
));
}, [windows]);
@ -65,7 +65,7 @@ export const WindowsView = memo(() => {
}, [settingsManager, windowsManager]);
return (<div>
{windows.map((window) => {
{windows.map((window: WindowProps) => {
const { id, app, size, position, options, minimized, fullscreen } = window;
const index = sortedWindows.indexOf(window);
return <WindowView

View file

@ -1,2 +1,2 @@
export const HOME_URL = "https://os.prozilla.dev/";
export const HOME_URL = "https://prozilla.dev/";
export const SEARCH_URL = "https://www.google.com/search?igu=1";

View file

@ -1,4 +1,4 @@
import { ANSI } from "./apps/terminal.config.js";
import { ANSI } from "./apps/terminal.config";
export const NAME = "Prozilla OS";
export const TAG_LINE = "Web-based Operating System";

View file

@ -1,4 +1,4 @@
import Vector2 from "../features/math/vector2.js";
import Vector2 from "../features/math/vector2";
export const DIALOG_CONTENT_TYPES = {
CloseButton: 0

View file

@ -1,26 +0,0 @@
/**
* @param {*} item
* @param {*[]} array
*/
export function removeFromArray(item, array) {
const index = array.indexOf(item);
if (index !== -1) {
array.splice(index, 1);
}
}
/**
* @param {*[]} array
* @returns {*}
*/
export function randomFromArray(array) {
return array[Math.floor(Math.random() * array.length)];
}
/**
* @param {*[]} array
* @returns {*[]}
*/
export function removeDuplicatesFromArray(array) {
return array.filter((item, index) => array.indexOf(item) === index);
}

View file

@ -0,0 +1,14 @@
export function removeFromArray(item: any, array: any[]) {
const index = array.indexOf(item);
if (index !== -1) {
array.splice(index, 1);
}
}
export function randomFromArray(array: any[]): any {
return array[Math.floor(Math.random() * array.length)];
}
export function removeDuplicatesFromArray(array: any[]): any[] {
return array.filter((item, index) => array.indexOf(item) === index);
}

View file

@ -1,10 +1,9 @@
import { NAME } from "../../config/branding.config.js";
import { NAME } from "../../config/branding.config";
/**
* Simulates closing the viewport by opening a blank page
* @param {boolean} requireConfirmation
*/
export function closeViewport(requireConfirmation = false) {
export function closeViewport(requireConfirmation: boolean = false) {
if (requireConfirmation && window.confirm(`Are you sure you want to shut down ${NAME}?`)) {
console.info("Closing viewport");
window.open("about:blank", "_self");
@ -13,18 +12,13 @@ export function closeViewport(requireConfirmation = false) {
/**
* Reloads the viewport
* @param {boolean} bypassCache
*/
export function reloadViewport(bypassCache = false) {
export function reloadViewport() {
console.info("Reloading viewport");
window.location.reload(bypassCache);
window.location.reload();
}
/**
* @param {string} string
* @returns {boolean}
*/
export function isValidUrl(string) {
export function isValidUrl(string: string): boolean {
try {
new URL(string);
return true;
@ -33,18 +27,12 @@ export function isValidUrl(string) {
}
}
/**
* @param {string} title
*/
export function setViewportTitle(title) {
export function setViewportTitle(title: string) {
document.title = title;
}
/**
* @param {string} url
*/
export function setViewportIcon(url) {
let link = document.querySelector("link[rel~='icon']");
export function setViewportIcon(url: string) {
let link: HTMLLinkElement = document.querySelector("link[rel~='icon']");
if (!link) {
link = document.createElement("link");
link.rel = "icon";
@ -53,10 +41,7 @@ export function setViewportIcon(url) {
link.href = url;
}
/**
* @returns {URLSearchParams}
*/
export function getViewportParams() {
export function getViewportParams(): Record<string, string> {
const query = window.location.search.slice(1);
const params = {};
@ -70,12 +55,7 @@ export function getViewportParams() {
return params;
}
/**
* @param {object} options
* @param {string} options.appId
* @param {boolean} options.fullscreen
*/
export function generateUrl(options) {
export function generateUrl(options: { appId: string; fullscreen: boolean; }) {
const baseUrl = window.location.origin + "/";
if (!options || Object.keys(options).length === 0)
@ -92,7 +72,7 @@ export function generateUrl(options) {
if (extraOptions && Object.keys(extraOptions).length > 0) {
Object.entries(extraOptions).forEach(([key, value]) => {
if (key && value)
params.set(key, encodeURIComponent(value));
params.set(key, encodeURIComponent(value as string));
});
}
@ -103,11 +83,6 @@ export function generateUrl(options) {
return url;
}
/**
* @param {string} string
* @param {Function} onSuccess
* @param {Function} onFail
*/
export function copyToClipboard(string, onSuccess, onFail) {
export function copyToClipboard(string: string, onSuccess: (value: void) => void, onFail: (value: void) => void) {
navigator.clipboard.writeText(string).then(onSuccess, onFail);
}

View file

@ -11,12 +11,10 @@ const TIME_INDICATORS = {
/**
* Format a time
* @param {number} time - Time in milliseconds
* @param {number} maxLength - The maximum amount of units, e.g.: 3 => years, months, days
* @param {boolean} allowAffixes
* @returns {string}
* @param time - Time in milliseconds
* @param maxLength - The maximum amount of units, e.g.: 3 => years, months, days
*/
export const formatTime = (time, maxLength = 3, allowAffixes) => {
export function formatTime(time: number, maxLength: number = 3, allowAffixes: boolean): string {
const result = [];
const formatResult = (result, inPast) => {
@ -83,12 +81,10 @@ export const formatTime = (time, maxLength = 3, allowAffixes) => {
/**
* Format a time relative to now
* @param {Date} date - The date
* @param {number} maxLength - The maximum amount of units, e.g.: 3 => years, months, days
* @param {boolean} allowAffixes
* @returns {string}
* @param date - The date
* @param maxLength - The maximum amount of units, e.g.: 3 => years, months, days
*/
export const formatRelativeTime = (date, maxLength = 3, allowAffixes) => {
const difference = date - new Date();
export function formatRelativeTime(date: Date, maxLength: number = 3, allowAffixes: boolean): string {
const difference = date.valueOf() - Date.now();
return formatTime(difference, maxLength, allowAffixes);
};

View file

@ -1,49 +0,0 @@
export class EventEmitter {
/**
* @type {Object<string, string>}
*/
static EVENT_NAMES = {};
/**
* @type {Object<string, Array<Function>>}
*/
#events = {};
/**
* Add event listener for an event
* @param {EventEmitter.EVENT_NAMES} eventName
* @param {Function} callback
*/
on(eventName, callback) {
if (!this.#events[eventName]) {
this.#events[eventName] = [];
}
this.#events[eventName].push(callback);
}
/**
* Remove event listener for an event
* @param {string} eventName
* @param {Function} callback
*/
off(eventName, callback) {
if (this.#events[eventName]) {
this.#events[eventName] = this.#events[eventName].filter(
(listener) => listener !== callback
);
}
}
/**
* Dispatch event
* @param {string} eventName
* @param {*} data
*/
emit(eventName, data) {
if (this.#events[eventName]) {
this.#events[eventName].forEach((listener) => {
listener(data);
});
}
}
}

View file

@ -0,0 +1,39 @@
export type EventNamesMap = Record<string, string>;
export class EventEmitter<EventMap extends EventNamesMap> {
static EVENT_NAMES: EventNamesMap = {};
#events: Record<string, Array<Function>> = {};
/**
* Add event listener for an event
*/
on<Key extends keyof EventMap>(eventName: Key, callback: (data: any) => void) {
if (!this.#events[eventName as string]) {
this.#events[eventName as string] = [];
}
this.#events[eventName as string].push(callback);
}
/**
* Remove event listener for an event
*/
off<Key extends keyof EventMap>(eventName: Key, callback: (data: any) => void) {
if (this.#events[eventName as string]) {
this.#events[eventName as string] = this.#events[eventName as string].filter(
(listener) => listener !== callback
);
}
}
/**
* Dispatch event
*/
emit<Key extends keyof EventMap>(eventName: Key, data?: any) {
if (this.#events[eventName as string]) {
this.#events[eventName as string].forEach((listener) => {
listener(data);
});
}
}
}

View file

@ -1,3 +0,0 @@
export function isValidInteger(number) {
return (parseInt(number) || parseInt(number) === 0);
}

View file

@ -0,0 +1,3 @@
export function isValidInteger(number: number | string): number| boolean {
return (typeof number === "number" || parseInt(number) || parseInt(number) === 0);
}

View file

@ -1,8 +1,4 @@
/**
* @param {string[]} shortcut
* @returns {string}
*/
export function formatShortcut(shortcut) {
export function formatShortcut(shortcut: string[]): string {
const specialKeys = [];
const singleKeys = [];

View file

@ -1,26 +0,0 @@
import React from "react";
export default class App {
/**
* @param {string} name
* @param {string} id
* @param {React.ReactElement} windowContent
* @param {object | null} windowOptions - Default window options
*/
constructor(name, id, windowContent, windowOptions) {
Object.assign(this, { name, id, windowContent, windowOptions });
if (this.windowContent == null)
console.warn(`App (${this.id}) is missing the windowContent property.`);
}
WindowContent = (props) => {
props = { ...props, ...this.windowOptions };
if (this.windowContent == null) {
return null;
}
return <this.windowContent {...props}/>;
};
}

33
src/features/apps/app.tsx Normal file
View file

@ -0,0 +1,33 @@
import React from "react";
import Vector2 from "../math/vector2";
import { WindowProps } from "../../components/windows/WindowView";
export default class App {
name: string;
id: string;
windowContent: React.FC;
windowOptions?: {
size: Vector2
};
isActive: boolean = false;
/**
* @param windowOptions - Default window options
*/
constructor(name: string, id: string, windowContent: React.FC, windowOptions?: object | null) {
Object.assign(this, { name, id, windowContent, windowOptions });
if (this.windowContent == null)
console.warn(`App (${this.id}) is missing the windowContent property.`);
}
WindowContent = (props: React.JSX.IntrinsicAttributes & WindowProps) => {
props = { ...props, ...this.windowOptions };
if (this.windowContent == null) {
return null;
}
return <this.windowContent {...props}/>;
};
}

View file

@ -1,23 +1,21 @@
import App from "./app.js";
import { FileExplorer } from "../../components/apps/file-explorer/FileExplorer.jsx";
import { MediaViewer } from "../../components/apps/media-viewer/MediaViewer.jsx";
import { WebView } from "../../components/apps/_utils/web-view/WebView.jsx";
import { Terminal } from "../../components/apps/terminal/Terminal.jsx";
import { TextEditor } from "../../components/apps/text-editor/TextEditor.jsx";
import { Settings } from "../../components/apps/settings/Settings.jsx";
import { Calculator } from "../../components/apps/calculator/Calculator.jsx";
import Vector2 from "../math/vector2.js";
import { APPS, APP_NAMES } from "../../config/apps.config.js";
import { Browser } from "../../components/apps/browser/Browser.jsx";
import { IMAGE_FORMATS } from "../../config/apps/mediaViewer.config.js";
import App from "./app";
import { FileExplorer } from "../../components/apps/file-explorer/FileExplorer";
import { MediaViewer } from "../../components/apps/media-viewer/MediaViewer";
import { WebView } from "../../components/apps/_utils/web-view/WebView";
import { Terminal } from "../../components/apps/terminal/Terminal";
import { TextEditor } from "../../components/apps/text-editor/TextEditor";
import { Settings } from "../../components/apps/settings/Settings";
import { Calculator } from "../../components/apps/calculator/Calculator";
import Vector2 from "../math/vector2";
import { APPS, APP_NAMES } from "../../config/apps.config";
import { Browser } from "../../components/apps/browser/Browser";
import { IMAGE_FORMATS } from "../../config/apps/mediaViewer.config";
export default class AppsManager {
/** @type {App[]} */
static APPS = [
static APPS: App[] = [
new App(APP_NAMES.TERMINAL, APPS.TERMINAL, Terminal),
new App(APP_NAMES.SETTINGS, APPS.SETTINGS, Settings),
new App(APP_NAMES.MEDIA_VIEWER, APPS.MEDIA_VIEWER, MediaViewer),
// new App("Browser", "browser"),
new App(APP_NAMES.CALCULATOR, APPS.CALCULATOR, Calculator, { size: new Vector2(400, 600) }),
new App(APP_NAMES.TEXT_EDITOR, APPS.TEXT_EDITOR, TextEditor),
// new App("Code Editor", "code-editor"),
@ -39,11 +37,7 @@ export default class AppsManager {
}),
];
/**
* @param {string} id
* @returns {App | null}
*/
static getAppById(id) {
static getAppById(id: string): App | null {
let application = null;
this.APPS.forEach((app) => {
@ -58,10 +52,8 @@ export default class AppsManager {
/**
* Get the app associated with a file extension
* @param {string} fileExtension
* @returns {App}
*/
static getAppByFileExtension(fileExtension) {
static getAppByFileExtension(fileExtension: string): App {
let app = null;
if (IMAGE_FORMATS.includes(fileExtension))
@ -78,11 +70,8 @@ export default class AppsManager {
/**
* Returns the url of an icon inside the icons folder or the default icon of an app
* @param {string} appId
* @param {string | null} iconName
* @returns {string}
*/
static getAppIconUrl(appId, iconName) {
static getAppIconUrl(appId: string, iconName?: string): string {
if (iconName == null) {
return `${process.env.PUBLIC_URL}/assets/apps/icons/${appId}.svg`;
} else {

View file

@ -1,19 +0,0 @@
import { ANSI } from "../../../../config/apps/terminal.config.js";
/**
* @param {string} commandName
* @param {string} error
* @returns {string}
*/
export function formatError(commandName, error) {
return `${ANSI.fg.red}${commandName}: ${error}${ANSI.reset}`;
}
/**
* @param {string} string
* @returns {string}
*/
export function removeAnsi(string) {
// eslint-disable-next-line no-control-regex
return string.replace(/\u001b\[([0-9]+)m/gm, "");
}

View file

@ -0,0 +1,10 @@
import { ANSI } from "../../../../config/apps/terminal.config";
export function formatError(commandName: string, error: string): string {
return `${ANSI.fg.red}${commandName}: ${error}${ANSI.reset}`;
}
export function removeAnsi(string: string): string {
// eslint-disable-next-line no-control-regex
return string.replace(/\u001b\[([0-9]+)m/gm, "");
}

View file

@ -1,5 +1,6 @@
import { VirtualFolder } from "../../virtual-drive/folder/virtualFolder.js";
import { VirtualRoot } from "../../virtual-drive/root/virtualRoot.js";
import { VirtualFolder } from "../../virtual-drive/folder/virtualFolder";
import { VirtualRoot } from "../../virtual-drive/root/virtualRoot";
import Stream from "./stream";
type Option = {
long: string,
@ -7,19 +8,20 @@ type Option = {
isInput: boolean
};
type Execute = (args: string[], options: {
promptOutput: void,
pushHistory: void,
virtualRoot: VirtualRoot,
currentDirectory: VirtualFolder,
setCurrentDirectory: void,
username: string,
hostname: string,
rawInputValue: string,
options: string[],
exit: void,
inputs: string[]
}) => string | { blank: boolean } | void;
type Execute = (args?: string[], options?: {
promptOutput?: Function,
pushHistory?: Function,
virtualRoot?: VirtualRoot,
currentDirectory?: VirtualFolder,
setCurrentDirectory?: Function,
username?: string,
hostname?: string,
rawInputValue?: string,
options?: string[],
exit?: Function,
inputs?: Record<string, string>;
timestamp: number,
}) => string | { blank: boolean } | void | Stream;
type Manual = {
purpose?: string,

View file

@ -7,7 +7,7 @@ let commands = [];
*/
const loadCommands = () => {
commands = [];
const context = require.context("./commands", false, /\.js$/);
const context = require.context("./commands", false, /\.ts$/);
context.keys().forEach((key) => {
const commandModule = context(key);
const commandName = Object.keys(commandModule)[0];
@ -24,11 +24,7 @@ loadCommands();
export default class CommandsManager {
static COMMANDS = commands;
/**
* @param {string} name
* @returns {Command}
*/
static find(name) {
static find(name: string): Command {
let matchCommand = null;
this.COMMANDS.forEach((command) => {
@ -41,11 +37,7 @@ export default class CommandsManager {
return matchCommand;
}
/**
* @param {string} pattern
* @returns {Command[]}
*/
static search(pattern) {
static search(pattern: string): Command[] {
const matches = this.COMMANDS.filter((command) => command.name.match(pattern));
return matches;
}

View file

@ -1,5 +1,5 @@
import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js";
import { formatError } from "../_utils/terminal.utils.js";
import { VirtualFile } from "../../../virtual-drive/file/virtualFile";
import { formatError } from "../_utils/terminal.utils";
import Command from "../command";
export const cat = new Command()

View file

@ -1,4 +1,4 @@
import { formatError } from "../_utils/terminal.utils.js";
import { formatError } from "../_utils/terminal.utils";
import Command from "../command";
export const cd = new Command()

View file

@ -1,5 +1,5 @@
import Command from "../command";
import CommandsManager from "../commands.js";
import CommandsManager from "../commands";
export const compgen = new Command()
.setManual({

View file

@ -1,4 +1,4 @@
import { MAX_WIDTH } from "../../../../config/apps/terminal.config.js";
import { MAX_WIDTH } from "../../../../config/apps/terminal.config";
import Command from "../command";
const COW = `
@ -29,7 +29,7 @@ export const cowsay = new Command()
maxLineWidth = line.length;
};
const nextLine = (word) => {
const nextLine = (word?: string) => {
addLine(currentLine);
if (word) {

Some files were not shown because too many files have changed in this diff Show more