diff options
| -rw-r--r-- | backend/helpers.py | 2 | ||||
| -rw-r--r-- | backend/locales/en-US.json | 32 | ||||
| -rw-r--r-- | backend/locales/it-IT.json | 30 | ||||
| -rw-r--r-- | backend/utilities.py | 111 | ||||
| -rw-r--r-- | frontend/package.json | 11 | ||||
| -rw-r--r-- | frontend/pnpm-lock.yaml | 700 | ||||
| -rw-r--r-- | frontend/src/components/DeckyState.tsx | 13 | ||||
| -rw-r--r-- | frontend/src/components/modals/DropdownMultiselect.tsx | 121 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/FilePickerError.tsx | 51 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/i18n/TSortOptions.tsx | 46 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/iconCustomizations.ts | 2 | ||||
| -rw-r--r-- | frontend/src/components/modals/filepicker/index.tsx | 393 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/developer/index.tsx | 26 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 28 |
14 files changed, 1072 insertions, 494 deletions
diff --git a/backend/helpers.py b/backend/helpers.py index b2464e8b..a1877fb8 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -159,4 +159,4 @@ async def stop_systemd_unit(unit_name: str) -> bool: return await localplatform.service_stop(unit_name) async def start_systemd_unit(unit_name: str) -> bool: - return await localplatform.service_start(unit_name)
\ No newline at end of file + return await localplatform.service_start(unit_name) diff --git a/backend/locales/en-US.json b/backend/locales/en-US.json index b5c32957..34bde6bd 100644 --- a/backend/locales/en-US.json +++ b/backend/locales/en-US.json @@ -12,9 +12,37 @@ "disabling": "Disabling React DevTools", "enabling": "Enabling React DevTools" }, + "DropdownMultiselect": { + "button": { + "back": "Back" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "The path specified is not valid. Please check it and reenter it correctly.", + "unknown": "An unknown error occurred. The raw error is: {{raw_error}}" + } + }, "FilePickerIndex": { - "folder": { - "select": "Use this folder" + "files": { + "all_files": "All Files", + "file_type": "File Type", + "show_hidden": "Show Hidden Files" + }, + "filter": { + "created_asce": "Created (Oldest)", + "created_desc": "Created (Newest)", + "modified_asce": "Modified (Oldest)", + "modified_desc": "Modified (Newest)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Size (Smallest)", + "size_desc": "Size (Largest)" + }, + "folder": { + "label": "Folder", + "select": "Use this folder", + "show_more": "Show more files" } }, "PluginView": { diff --git a/backend/locales/it-IT.json b/backend/locales/it-IT.json index d0a526c6..bff63fba 100644 --- a/backend/locales/it-IT.json +++ b/backend/locales/it-IT.json @@ -12,9 +12,37 @@ "disabling": "Disabilito i tools di React", "enabling": "Abilito i tools di React" }, + "DropdownMultiselect": { + "button": { + "back": "Indietro" + } + }, + "FilePickerError": { + "errors": { + "file_not_found": "Il percorso specificato non è valido. Controllalo e prova a reinserirlo di nuovo.", + "unknown": "È avvenuto un'errore sconosciuto. L'errore segnalato è {{raw_error}}" + } + }, "FilePickerIndex": { + "files": { + "all_files": "Tutti i file", + "file_type": "Tipo di file", + "show_hidden": "Mostra nascosti" + }, + "filter": { + "created_asce": "Creazione (meno recente)", + "created_desc": "Creazione (più recente)", + "modified_asce": "Modifica (meno recente)", + "modified_desc": "Modifica (più recente)", + "name_asce": "Z-A", + "name_desc": "A-Z", + "size_asce": "Dimensione (più piccolo)", + "size_desc": "Dimensione (più grande)" + }, "folder": { - "select": "Usa questa cartella" + "label": "Cartella", + "select": "Usa questa cartella", + "show_more": "Mostra più file" } }, "PluginCard": { diff --git a/backend/utilities.py b/backend/utilities.py index 45a32d3e..1057ac9d 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -1,16 +1,21 @@ import uuid import os from json.decoder import JSONDecodeError +from os.path import splitext +import re from traceback import format_exc +from stat import FILE_ATTRIBUTE_HIDDEN from asyncio import sleep, start_server, gather, open_connection from aiohttp import ClientSession, web from logging import getLogger from injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab +from pathlib import Path +from localplatform import ON_WINDOWS import helpers import subprocess -from localplatform import service_stop, service_start +from localplatform import service_stop, service_start, get_home_path, get_username class Utilities: def __init__(self, context) -> None: @@ -33,7 +38,8 @@ class Utilities: "filepicker_ls": self.filepicker_ls, "disable_rdt": self.disable_rdt, "enable_rdt": self.enable_rdt, - "get_tab_id": self.get_tab_id + "get_tab_id": self.get_tab_id, + "get_user_info": self.get_user_info, } self.logger = getLogger("Utilities") @@ -189,31 +195,82 @@ class Utilities: await service_stop(helpers.REMOTE_DEBUGGER_UNIT) return True - async def filepicker_ls(self, path, include_files=True): - # def sorter(file): # Modification time - # if os.path.isdir(os.path.join(path, file)) or os.path.isfile(os.path.join(path, file)): - # return os.path.getmtime(os.path.join(path, file)) - # return 0 - # file_names = sorted(os.listdir(path), key=sorter, reverse=True) # TODO provide more sort options - file_names = sorted(os.listdir(path)) # Alphabetical - - files = [] - - for file in file_names: - full_path = os.path.join(path, file) - is_dir = os.path.isdir(full_path) - - if is_dir or include_files: - files.append({ - "isdir": is_dir, - "name": file, - "realpath": os.path.realpath(full_path) - }) + async def filepicker_ls(self, + path : str | None = None, + include_files: bool = True, + include_folders: bool = True, + include_ext: list[str] = [], + include_hidden: bool = False, + order_by: str = "name_asc", + filter_for: str | None = None, + page: int = 1, + max: int = 1000): + + if path == None: + path = get_home_path() + + path = Path(path).resolve() + + files, folders = [], [] + + #Resolving all files/folders in the requested directory + for file in path.iterdir(): + if file.exists(): + filest = file.stat() + is_hidden = file.name.startswith('.') + if ON_WINDOWS and not is_hidden: + is_hidden = bool(filest.st_file_attributes & FILE_ATTRIBUTE_HIDDEN) + if include_folders and file.is_dir(): + if (is_hidden and include_hidden) or not is_hidden: + folders.append({"file": file, "filest": filest, "is_dir": True}) + elif include_files: + # Handle requested extensions if present + if 'all_files' in include_ext or splitext(file.name)[1].lstrip('.') in include_ext: + if (is_hidden and include_hidden) or not is_hidden: + files.append({"file": file, "filest": filest, "is_dir": False}) + # Filter logic + if filter_for is not None: + try: + if re.compile(filter_for): + files = filter(lambda file: re.search(filter_for, file.name) != None, files) + except re.error: + files = filter(lambda file: file.name.find(filter_for) != -1, files) + + # Ordering logic + ord_arg = order_by.split("_") + ord = ord_arg[0] + rev = True if ord_arg[1] == "asc" else False + match ord: + case 'name': + files.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) + folders.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) + case 'modified': + files.sort(key=lambda x: x['filest'].st_mtime, reverse = not rev) + folders.sort(key=lambda x: x['filest'].st_mtime, reverse = not rev) + case 'created': + files.sort(key=lambda x: x['filest'].st_ctime, reverse = not rev) + folders.sort(key=lambda x: x['filest'].st_ctime, reverse = not rev) + case 'size': + files.sort(key=lambda x: x['filest'].st_size, reverse = not rev) + # Folders has no file size, order by name instead + folders.sort(key=lambda x: x['file'].name.casefold()) + + #Constructing the final file list, folders first + all = [{ + "isdir": x['is_dir'], + "name": str(x['file'].name), + "realpath": str(x['file']), + "size": x['filest'].st_size, + "modified": x['filest'].st_mtime, + "created": x['filest'].st_ctime, + } for x in folders + files ] return { - "realpath": os.path.realpath(path), - "files": files + "realpath": str(path), + "files": all[(page-1)*max:(page)*max], + "total": len(all), } + # Based on https://stackoverflow.com/a/46422554/13174603 def start_rdt_proxy(self, ip, port): @@ -289,5 +346,11 @@ class Utilities: await tab.evaluate_js("location.reload();", False, True, False) self.logger.info("React DevTools disabled") + async def get_user_info(self) -> dict: + return { + "username": get_username(), + "path": get_home_path() + } + async def get_tab_id(self, name): return (await get_tab(name)).id diff --git a/frontend/package.json b/frontend/package.json index de0fd5a2..34c6fd2c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,7 @@ "@types/react-router": "5.1.18", "@types/webpack": "^5.28.1", "husky": "^8.0.3", - "i18next-parser": "^7.9.0", + "i18next-parser": "^8.0.0", "import-sort-style-module": "^6.0.0", "inquirer": "^8.2.5", "prettier": "^2.8.8", @@ -33,8 +33,8 @@ "rollup-plugin-delete": "^2.0.0", "rollup-plugin-external-globals": "^0.6.1", "rollup-plugin-polyfill-node": "^0.10.2", - "rollup-plugin-visualizer": "^5.9.0", - "tslib": "^2.5.2", + "rollup-plugin-visualizer": "^5.9.2", + "tslib": "^2.5.3", "typescript": "^4.9.5" }, "importSort": { @@ -45,11 +45,12 @@ }, "dependencies": { "decky-frontend-lib": "3.21.1", - "i18next": "^22.5.0", + "filesize": "^10.0.7", + "i18next": "^23.1.0", "i18next-http-backend": "^2.2.1", "react-file-icon": "^1.3.0", "react-i18next": "^12.3.1", - "react-icons": "^4.8.0", + "react-icons": "^4.9.0", "react-markdown": "^8.0.7", "remark-gfm": "^3.0.1" } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index ad7bb42c..3b1378e9 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -1,12 +1,19 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false dependencies: decky-frontend-lib: specifier: 3.21.1 version: 3.21.1 + filesize: + specifier: ^10.0.7 + version: 10.0.7 i18next: - specifier: ^22.5.0 - version: 22.5.0 + specifier: ^23.1.0 + version: 23.1.0 i18next-http-backend: specifier: ^2.2.1 version: 2.2.1 @@ -15,10 +22,10 @@ dependencies: version: 1.3.0(react-dom@16.14.0)(react@16.14.0) react-i18next: specifier: ^12.3.1 - version: 12.3.1(i18next@22.5.0)(react-dom@16.14.0)(react@16.14.0) + version: 12.3.1(i18next@23.1.0)(react-dom@16.14.0)(react@16.14.0) react-icons: - specifier: ^4.8.0 - version: 4.8.0(react@16.14.0) + specifier: ^4.9.0 + version: 4.9.0(react@16.14.0) react-markdown: specifier: ^8.0.7 version: 8.0.7(@types/react@16.14.0)(react@16.14.0) @@ -44,7 +51,7 @@ devDependencies: version: 4.0.0(rollup@2.79.1) '@rollup/plugin-typescript': specifier: ^8.5.0 - version: 8.5.0(rollup@2.79.1)(tslib@2.5.2)(typescript@4.9.5) + version: 8.5.0(rollup@2.79.1)(tslib@2.5.3)(typescript@4.9.5) '@types/react': specifier: 16.14.0 version: 16.14.0 @@ -61,8 +68,8 @@ devDependencies: specifier: ^8.0.3 version: 8.0.3 i18next-parser: - specifier: ^7.9.0 - version: 7.9.0 + specifier: ^8.0.0 + version: 8.0.0 import-sort-style-module: specifier: ^6.0.0 version: 6.0.0 @@ -94,11 +101,11 @@ devDependencies: specifier: ^0.10.2 version: 0.10.2(rollup@2.79.1) rollup-plugin-visualizer: - specifier: ^5.9.0 - version: 5.9.0(rollup@2.79.1) + specifier: ^5.9.2 + version: 5.9.2(rollup@2.79.1) tslib: - specifier: ^2.5.2 - version: 2.5.2 + specifier: ^2.5.3 + version: 2.5.3 typescript: specifier: ^4.9.5 version: 4.9.5 @@ -113,32 +120,32 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: true - /@babel/code-frame@7.21.4: - resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} + /@babel/code-frame@7.22.5: + resolution: {integrity: sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.18.6 + '@babel/highlight': 7.22.5 dev: true - /@babel/compat-data@7.21.9: - resolution: {integrity: sha512-FUGed8kfhyWvbYug/Un/VPJD41rDIgoVVcR+FuzhzOYyRz5uED+Gd3SLZml0Uw2l2aHFb7ZgdW5mGA3G2cCCnQ==} + /@babel/compat-data@7.22.5: + resolution: {integrity: sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==} engines: {node: '>=6.9.0'} dev: true - /@babel/core@7.21.8: - resolution: {integrity: sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==} + /@babel/core@7.22.5: + resolution: {integrity: sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==} engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.9 - '@babel/helper-compilation-targets': 7.21.5(@babel/core@7.21.8) - '@babel/helper-module-transforms': 7.21.5 - '@babel/helpers': 7.21.5 - '@babel/parser': 7.21.9 - '@babel/template': 7.21.9 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 + '@babel/code-frame': 7.22.5 + '@babel/generator': 7.22.5 + '@babel/helper-compilation-targets': 7.22.5(@babel/core@7.22.5) + '@babel/helper-module-transforms': 7.22.5 + '@babel/helpers': 7.22.5 + '@babel/parser': 7.22.5 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.5 + '@babel/types': 7.22.5 convert-source-map: 1.9.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -148,169 +155,169 @@ packages: - supports-color dev: true - /@babel/generator@7.21.9: - resolution: {integrity: sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==} + /@babel/generator@7.22.5: + resolution: {integrity: sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.5 '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.18 jsesc: 2.5.2 dev: true - /@babel/helper-compilation-targets@7.21.5(@babel/core@7.21.8): - resolution: {integrity: sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==} + /@babel/helper-compilation-targets@7.22.5(@babel/core@7.22.5): + resolution: {integrity: sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.21.9 - '@babel/core': 7.21.8 - '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.5 + '@babel/compat-data': 7.22.5 + '@babel/core': 7.22.5 + '@babel/helper-validator-option': 7.22.5 + browserslist: 4.21.9 lru-cache: 5.1.1 semver: 6.3.0 dev: true - /@babel/helper-environment-visitor@7.21.5: - resolution: {integrity: sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==} + /@babel/helper-environment-visitor@7.22.5: + resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-function-name@7.21.0: - resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} + /@babel/helper-function-name@7.22.5: + resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.21.9 - '@babel/types': 7.21.5 + '@babel/template': 7.22.5 + '@babel/types': 7.22.5 dev: true - /@babel/helper-hoist-variables@7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.5 dev: true - /@babel/helper-module-imports@7.21.4: - resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} + /@babel/helper-module-imports@7.22.5: + resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.5 dev: true - /@babel/helper-module-transforms@7.21.5: - resolution: {integrity: sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==} + /@babel/helper-module-transforms@7.22.5: + resolution: {integrity: sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-module-imports': 7.21.4 - '@babel/helper-simple-access': 7.21.5 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.21.9 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-module-imports': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.5 + '@babel/types': 7.22.5 transitivePeerDependencies: - supports-color dev: true - /@babel/helper-simple-access@7.21.5: - resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} + /@babel/helper-simple-access@7.22.5: + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.5 dev: true - /@babel/helper-split-export-declaration@7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + /@babel/helper-split-export-declaration@7.22.5: + resolution: {integrity: sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.5 dev: true - /@babel/helper-string-parser@7.21.5: - resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-validator-identifier@7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + /@babel/helper-validator-identifier@7.22.5: + resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} engines: {node: '>=6.9.0'} dev: true - /@babel/helper-validator-option@7.21.0: - resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} + /@babel/helper-validator-option@7.22.5: + resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} engines: {node: '>=6.9.0'} dev: true - /@babel/helpers@7.21.5: - resolution: {integrity: sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==} + /@babel/helpers@7.22.5: + resolution: {integrity: sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.21.9 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.5 + '@babel/types': 7.22.5 transitivePeerDependencies: - supports-color dev: true - /@babel/highlight@7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + /@babel/highlight@7.22.5: + resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-validator-identifier': 7.22.5 chalk: 2.4.2 js-tokens: 4.0.0 dev: true - /@babel/parser@7.21.9: - resolution: {integrity: sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==} + /@babel/parser@7.22.5: + resolution: {integrity: sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.22.5 dev: true - /@babel/runtime@7.21.5: - resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==} + /@babel/runtime@7.22.5: + resolution: {integrity: sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 - /@babel/template@7.21.9: - resolution: {integrity: sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==} + /@babel/template@7.22.5: + resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.21.4 - '@babel/parser': 7.21.9 - '@babel/types': 7.21.5 + '@babel/code-frame': 7.22.5 + '@babel/parser': 7.22.5 + '@babel/types': 7.22.5 dev: true - /@babel/traverse@7.21.5: - resolution: {integrity: sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==} + /@babel/traverse@7.22.5: + resolution: {integrity: sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.9 - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.21.9 - '@babel/types': 7.21.5 + '@babel/code-frame': 7.22.5 + '@babel/generator': 7.22.5 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.5 + '@babel/parser': 7.22.5 + '@babel/types': 7.22.5 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color dev: true - /@babel/types@7.21.5: - resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==} + /@babel/types@7.22.5: + resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.21.5 - '@babel/helper-validator-identifier': 7.19.1 + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 to-fast-properties: 2.0.0 dev: true @@ -649,7 +656,7 @@ packages: rollup: 2.79.1 dev: true - /@rollup/plugin-typescript@8.5.0(rollup@2.79.1)(tslib@2.5.2)(typescript@4.9.5): + /@rollup/plugin-typescript@8.5.0(rollup@2.79.1)(tslib@2.5.3)(typescript@4.9.5): resolution: {integrity: sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ==} engines: {node: '>=8.0.0'} peerDependencies: @@ -663,7 +670,7 @@ packages: '@rollup/pluginutils': 3.1.0(rollup@2.79.1) resolve: 1.22.2 rollup: 2.79.1 - tslib: 2.5.2 + tslib: 2.5.3 typescript: 4.9.5 dev: true @@ -711,12 +718,12 @@ packages: /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: - '@types/eslint': 8.40.0 + '@types/eslint': 8.40.2 '@types/estree': 1.0.1 dev: true - /@types/eslint@8.40.0: - resolution: {integrity: sha512-nbq2mvc/tBrK9zQQuItvjJl++GTN5j06DaPtp3hZCpngmG6Q3xoyEmd0TwZI0gAy/G1X0zhGBbr2imsGFdFV0g==} + /@types/eslint@8.40.2: + resolution: {integrity: sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==} dependencies: '@types/estree': 1.0.1 '@types/json-schema': 7.0.12 @@ -734,7 +741,7 @@ packages: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.2.4 + '@types/node': 20.3.1 dev: true /@types/hast@2.3.4: @@ -769,8 +776,8 @@ packages: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: false - /@types/node@20.2.4: - resolution: {integrity: sha512-ni5f8Xlf4PwnT/Z3f0HURc3ZSw8UyrqMqmM3L5ysa7VjHu8c3FOmIo1nKCcLrV/OAmtf3N4kFna/aJqxsfEtnA==} + /@types/node@20.3.1: + resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} dev: true /@types/prop-types@15.7.5: @@ -798,7 +805,7 @@ packages: /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 20.2.4 + '@types/node': 20.3.1 dev: true /@types/symlink-or-copy@1.2.0: @@ -812,9 +819,9 @@ packages: /@types/webpack@5.28.1: resolution: {integrity: sha512-qw1MqGZclCoBrpiSe/hokSgQM/su8Ocpl3L/YHE0L6moyaypg4+5F7Uzq7NgaPKPxUxUbQ4fLPLpDWdR27bCZw==} dependencies: - '@types/node': 20.2.4 + '@types/node': 20.3.1 tapable: 2.2.1 - webpack: 5.84.1 + webpack: 5.87.0 transitivePeerDependencies: - '@swc/core' - esbuild @@ -936,16 +943,16 @@ packages: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} dev: true - /acorn-import-assertions@1.9.0(acorn@8.8.2): + /acorn-import-assertions@1.9.0(acorn@8.9.0): resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} peerDependencies: acorn: ^8 dependencies: - acorn: 8.8.2 + acorn: 8.9.0 dev: true - /acorn@8.8.2: - resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + /acorn@8.9.0: + resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -1096,15 +1103,15 @@ packages: - supports-color dev: true - /browserslist@4.21.5: - resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} + /browserslist@4.21.9: + resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001489 - electron-to-chromium: 1.4.408 + caniuse-lite: 1.0.30001504 + electron-to-chromium: 1.4.433 node-releases: 2.0.12 - update-browserslist-db: 1.0.11(browserslist@4.21.5) + update-browserslist-db: 1.0.11(browserslist@4.21.9) dev: true /buffer-equal@1.0.1: @@ -1154,8 +1161,8 @@ packages: engines: {node: '>=4'} dev: true - /caniuse-lite@1.0.30001489: - resolution: {integrity: sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==} + /caniuse-lite@1.0.30001504: + resolution: {integrity: sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==} dev: true /ccount@2.0.1: @@ -1511,8 +1518,8 @@ packages: stream-shift: 1.0.1 dev: true - /electron-to-chromium@1.4.408: - resolution: {integrity: sha512-vjeaj0u/UYnzA/CIdGXzzcxRLCqRwREYc9YfaWInjIEr7/XPttZ6ShpyqapchEy0S2r6LpLjDBTnNj7ZxnxJKg==} + /electron-to-chromium@1.4.433: + resolution: {integrity: sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==} dev: true /emoji-regex@8.0.0: @@ -1525,8 +1532,8 @@ packages: once: 1.4.0 dev: true - /enhanced-resolve@5.14.1: - resolution: {integrity: sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==} + /enhanced-resolve@5.15.0: + resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 @@ -1552,8 +1559,8 @@ packages: is-arrayish: 0.2.1 dev: true - /es-module-lexer@1.2.1: - resolution: {integrity: sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==} + /es-module-lexer@1.3.0: + resolution: {integrity: sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==} dev: true /esbuild@0.17.19: @@ -1693,6 +1700,11 @@ packages: escape-string-regexp: 1.0.5 dev: true + /filesize@10.0.7: + resolution: {integrity: sha512-iMRG7Qo9nayLoU3PNCiLizYtsy4W1ClrapeCwEgtiQelOAOuRJiw4QaLI+sSr8xr901dgHv+EYP2bCusGZgoiA==} + engines: {node: '>= 10.4.0'} + dev: false + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -1961,12 +1973,12 @@ packages: - encoding dev: false - /i18next-parser@7.9.0: - resolution: {integrity: sha512-yrPJhWGsDBx404T4KLMOTkTgAAEuHvjbxee3HnlqFHALWy/3BOY7or69CxsJOomN3wdrwgg8kWtfIUWR1BZ1nw==} - engines: {node: ^14.13.1 || >=16.0.0 || >=18.0.0, npm: '>=6', yarn: '>=1'} + /i18next-parser@8.0.0: + resolution: {integrity: sha512-WnpIXJlwgUTwnlEFLJpL5E8Y7faWCV/GVqIJXJos15Zwoic5k3QXV3QodP4uYp8W1T1w4y5wXrSEGS1Y4obxCg==} + engines: {node: '>=16.0.0 || >=18.0.0 || >=20.0.0', npm: '>=6', yarn: '>=1'} hasBin: true dependencies: - '@babel/runtime': 7.21.5 + '@babel/runtime': 7.22.5 broccoli-plugin: 4.0.7 cheerio: 1.0.0-rc.12 colors: 1.4.0 @@ -1976,7 +1988,7 @@ packages: esbuild: 0.17.19 fs-extra: 11.1.1 gulp-sort: 2.0.0 - i18next: 22.5.0 + i18next: 22.5.1 js-yaml: 4.1.0 lilconfig: 2.1.0 rsvp: 4.8.5 @@ -1990,10 +2002,17 @@ packages: - supports-color dev: true - /i18next@22.5.0: - resolution: {integrity: sha512-sqWuJFj+wJAKQP2qBQ+b7STzxZNUmnSxrehBCCj9vDOW9RDYPfqCaK1Hbh2frNYQuPziz6O2CGoJPwtzY3vAYA==} + /i18next@22.5.1: + resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==} + dependencies: + '@babel/runtime': 7.22.5 + dev: true + + /i18next@23.1.0: + resolution: {integrity: sha512-CObNPofJpw7zGVGYLd58mtMZUF+NZQl9czYMihbJkStjX+Nlu9kC3PHiC6uE1niP3qxP/3ocLXIBc2zqbAb1dg==} dependencies: - '@babel/runtime': 7.21.5 + '@babel/runtime': 7.22.5 + dev: false /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} @@ -2031,10 +2050,10 @@ packages: /import-sort-parser-babylon@6.0.0: resolution: {integrity: sha512-NyShTiNhTh4Vy7kJUVe6CuvOaQAzzfSIT72wtp3CzGjz8bHjNj59DCAjncuviicmDOgVAgmLuSh1WMcLYAMWGg==} dependencies: - '@babel/core': 7.21.8 - '@babel/parser': 7.21.9 - '@babel/traverse': 7.21.5 - '@babel/types': 7.21.5 + '@babel/core': 7.22.5 + '@babel/parser': 7.22.5 + '@babel/traverse': 7.22.5 + '@babel/types': 7.22.5 find-line-column: 0.5.2 transitivePeerDependencies: - supports-color @@ -2265,7 +2284,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.2.4 + '@types/node': 20.3.1 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -2426,19 +2445,19 @@ packages: unist-util-visit-parents: 5.1.3 dev: false - /mdast-util-from-markdown@1.3.0: - resolution: {integrity: sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==} + /mdast-util-from-markdown@1.3.1: + resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} dependencies: '@types/mdast': 3.0.11 '@types/unist': 2.0.6 decode-named-character-reference: 1.0.2 mdast-util-to-string: 3.2.0 - micromark: 3.1.0 - micromark-util-decode-numeric-character-reference: 1.0.0 - micromark-util-decode-string: 1.0.2 - micromark-util-normalize-identifier: 1.0.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark: 3.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-decode-string: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 unist-util-stringify-position: 3.0.3 uvu: 0.5.6 transitivePeerDependencies: @@ -2451,7 +2470,7 @@ packages: '@types/mdast': 3.0.11 ccount: 2.0.1 mdast-util-find-and-replace: 2.2.2 - micromark-util-character: 1.1.0 + micromark-util-character: 1.2.0 dev: false /mdast-util-gfm-footnote@1.0.2: @@ -2459,7 +2478,7 @@ packages: dependencies: '@types/mdast': 3.0.11 mdast-util-to-markdown: 1.5.0 - micromark-util-normalize-identifier: 1.0.0 + micromark-util-normalize-identifier: 1.1.0 dev: false /mdast-util-gfm-strikethrough@1.0.3: @@ -2474,7 +2493,7 @@ packages: dependencies: '@types/mdast': 3.0.11 markdown-table: 3.0.3 - mdast-util-from-markdown: 1.3.0 + mdast-util-from-markdown: 1.3.1 mdast-util-to-markdown: 1.5.0 transitivePeerDependencies: - supports-color @@ -2490,7 +2509,7 @@ packages: /mdast-util-gfm@2.0.2: resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==} dependencies: - mdast-util-from-markdown: 1.3.0 + mdast-util-from-markdown: 1.3.1 mdast-util-gfm-autolink-literal: 1.0.3 mdast-util-gfm-footnote: 1.0.2 mdast-util-gfm-strikethrough: 1.0.3 @@ -2514,7 +2533,7 @@ packages: '@types/hast': 2.3.4 '@types/mdast': 3.0.11 mdast-util-definitions: 5.1.2 - micromark-util-sanitize-uri: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 trim-lines: 3.0.1 unist-util-generated: 2.0.1 unist-util-position: 4.0.4 @@ -2529,7 +2548,7 @@ packages: longest-streak: 3.1.0 mdast-util-phrasing: 3.0.1 mdast-util-to-string: 3.2.0 - micromark-util-decode-string: 1.0.2 + micromark-util-decode-string: 1.1.0 unist-util-visit: 4.1.2 zwitch: 2.0.4 dev: false @@ -2549,249 +2568,248 @@ packages: engines: {node: '>= 8'} dev: true - /micromark-core-commonmark@1.0.6: - resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==} + /micromark-core-commonmark@1.1.0: + resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} dependencies: decode-named-character-reference: 1.0.2 - micromark-factory-destination: 1.0.0 - micromark-factory-label: 1.0.2 - micromark-factory-space: 1.0.0 - micromark-factory-title: 1.0.2 - micromark-factory-whitespace: 1.0.0 - micromark-util-character: 1.1.0 - micromark-util-chunked: 1.0.0 - micromark-util-classify-character: 1.0.0 - micromark-util-html-tag-name: 1.1.0 - micromark-util-normalize-identifier: 1.0.0 - micromark-util-resolve-all: 1.0.0 - micromark-util-subtokenize: 1.0.2 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-factory-destination: 1.1.0 + micromark-factory-label: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-factory-title: 1.1.0 + micromark-factory-whitespace: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-html-tag-name: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 uvu: 0.5.6 dev: false - /micromark-extension-gfm-autolink-literal@1.0.4: - resolution: {integrity: sha512-WCssN+M9rUyfHN5zPBn3/f0mIA7tqArHL/EKbv3CZK+LT2rG77FEikIQEqBkv46fOqXQK4NEW/Pc7Z27gshpeg==} + /micromark-extension-gfm-autolink-literal@1.0.5: + resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} dependencies: - micromark-util-character: 1.1.0 - micromark-util-sanitize-uri: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-util-character: 1.2.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: false - /micromark-extension-gfm-footnote@1.1.0: - resolution: {integrity: sha512-RWYce7j8+c0n7Djzv5NzGEGitNNYO3uj+h/XYMdS/JinH1Go+/Qkomg/rfxExFzYTiydaV6GLeffGO5qcJbMPA==} + /micromark-extension-gfm-footnote@1.1.2: + resolution: {integrity: sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==} dependencies: - micromark-core-commonmark: 1.0.6 - micromark-factory-space: 1.0.0 - micromark-util-character: 1.1.0 - micromark-util-normalize-identifier: 1.0.0 - micromark-util-sanitize-uri: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 uvu: 0.5.6 dev: false - /micromark-extension-gfm-strikethrough@1.0.5: - resolution: {integrity: sha512-X0oI5eYYQVARhiNfbETy7BfLSmSilzN1eOuoRnrf9oUNsPRrWOAe9UqSizgw1vNxQBfOwL+n2610S3bYjVNi7w==} + /micromark-extension-gfm-strikethrough@1.0.7: + resolution: {integrity: sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==} dependencies: - micromark-util-chunked: 1.0.0 - micromark-util-classify-character: 1.0.0 - micromark-util-resolve-all: 1.0.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 uvu: 0.5.6 dev: false - /micromark-extension-gfm-table@1.0.6: - resolution: {integrity: sha512-92pq7Q+T+4kXH4M6kL+pc8WU23Z9iuhcqmtYFWdFWjm73ZscFpH2xE28+XFpGWlvgq3LUwcN0XC0PGCicYFpgA==} + /micromark-extension-gfm-table@1.0.7: + resolution: {integrity: sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==} dependencies: - micromark-factory-space: 1.0.0 - micromark-util-character: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 uvu: 0.5.6 dev: false /micromark-extension-gfm-tagfilter@1.0.2: resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==} dependencies: - micromark-util-types: 1.0.2 + micromark-util-types: 1.1.0 dev: false - /micromark-extension-gfm-task-list-item@1.0.4: - resolution: {integrity: sha512-9XlIUUVnYXHsFF2HZ9jby4h3npfX10S1coXTnV035QGPgrtNYQq3J6IfIvcCIUAJrrqBVi5BqA/LmaOMJqPwMQ==} + /micromark-extension-gfm-task-list-item@1.0.5: + resolution: {integrity: sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==} dependencies: - micromark-factory-space: 1.0.0 - micromark-util-character: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 uvu: 0.5.6 dev: false /micromark-extension-gfm@2.0.3: resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==} dependencies: - micromark-extension-gfm-autolink-literal: 1.0.4 - micromark-extension-gfm-footnote: 1.1.0 - micromark-extension-gfm-strikethrough: 1.0.5 - micromark-extension-gfm-table: 1.0.6 + micromark-extension-gfm-autolink-literal: 1.0.5 + micromark-extension-gfm-footnote: 1.1.2 + micromark-extension-gfm-strikethrough: 1.0.7 + micromark-extension-gfm-table: 1.0.7 micromark-extension-gfm-tagfilter: 1.0.2 - micromark-extension-gfm-task-list-item: 1.0.4 - micromark-util-combine-extensions: 1.0.0 - micromark-util-types: 1.0.2 + micromark-extension-gfm-task-list-item: 1.0.5 + micromark-util-combine-extensions: 1.1.0 + micromark-util-types: 1.1.0 dev: false - /micromark-factory-destination@1.0.0: - resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==} + /micromark-factory-destination@1.1.0: + resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} dependencies: - micromark-util-character: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: false - /micromark-factory-label@1.0.2: - resolution: {integrity: sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==} + /micromark-factory-label@1.1.0: + resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} dependencies: - micromark-util-character: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 uvu: 0.5.6 dev: false - /micromark-factory-space@1.0.0: - resolution: {integrity: sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==} + /micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} dependencies: - micromark-util-character: 1.1.0 - micromark-util-types: 1.0.2 + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 dev: false - /micromark-factory-title@1.0.2: - resolution: {integrity: sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==} + /micromark-factory-title@1.1.0: + resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} dependencies: - micromark-factory-space: 1.0.0 - micromark-util-character: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 - uvu: 0.5.6 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: false - /micromark-factory-whitespace@1.0.0: - resolution: {integrity: sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==} + /micromark-factory-whitespace@1.1.0: + resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} dependencies: - micromark-factory-space: 1.0.0 - micromark-util-character: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: false - /micromark-util-character@1.1.0: - resolution: {integrity: sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==} + /micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} dependencies: - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: false - /micromark-util-chunked@1.0.0: - resolution: {integrity: sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==} + /micromark-util-chunked@1.1.0: + resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} dependencies: - micromark-util-symbol: 1.0.1 + micromark-util-symbol: 1.1.0 dev: false - /micromark-util-classify-character@1.0.0: - resolution: {integrity: sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==} + /micromark-util-classify-character@1.1.0: + resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} dependencies: - micromark-util-character: 1.1.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: false - /micromark-util-combine-extensions@1.0.0: - resolution: {integrity: sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==} + /micromark-util-combine-extensions@1.1.0: + resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} dependencies: - micromark-util-chunked: 1.0.0 - micromark-util-types: 1.0.2 + micromark-util-chunked: 1.1.0 + micromark-util-types: 1.1.0 dev: false - /micromark-util-decode-numeric-character-reference@1.0.0: - resolution: {integrity: sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==} + /micromark-util-decode-numeric-character-reference@1.1.0: + resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} dependencies: - micromark-util-symbol: 1.0.1 + micromark-util-symbol: 1.1.0 dev: false - /micromark-util-decode-string@1.0.2: - resolution: {integrity: sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==} + /micromark-util-decode-string@1.1.0: + resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} dependencies: decode-named-character-reference: 1.0.2 - micromark-util-character: 1.1.0 - micromark-util-decode-numeric-character-reference: 1.0.0 - micromark-util-symbol: 1.0.1 + micromark-util-character: 1.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-symbol: 1.1.0 dev: false - /micromark-util-encode@1.0.1: - resolution: {integrity: sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==} + /micromark-util-encode@1.1.0: + resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} dev: false - /micromark-util-html-tag-name@1.1.0: - resolution: {integrity: sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==} + /micromark-util-html-tag-name@1.2.0: + resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} dev: false - /micromark-util-normalize-identifier@1.0.0: - resolution: {integrity: sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==} + /micromark-util-normalize-identifier@1.1.0: + resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} dependencies: - micromark-util-symbol: 1.0.1 + micromark-util-symbol: 1.1.0 dev: false - /micromark-util-resolve-all@1.0.0: - resolution: {integrity: sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==} + /micromark-util-resolve-all@1.1.0: + resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} dependencies: - micromark-util-types: 1.0.2 + micromark-util-types: 1.1.0 dev: false - /micromark-util-sanitize-uri@1.1.0: - resolution: {integrity: sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==} + /micromark-util-sanitize-uri@1.2.0: + resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} dependencies: - micromark-util-character: 1.1.0 - micromark-util-encode: 1.0.1 - micromark-util-symbol: 1.0.1 + micromark-util-character: 1.2.0 + micromark-util-encode: 1.1.0 + micromark-util-symbol: 1.1.0 dev: false - /micromark-util-subtokenize@1.0.2: - resolution: {integrity: sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==} + /micromark-util-subtokenize@1.1.0: + resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} dependencies: - micromark-util-chunked: 1.0.0 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-util-chunked: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 uvu: 0.5.6 dev: false - /micromark-util-symbol@1.0.1: - resolution: {integrity: sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==} + /micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} dev: false - /micromark-util-types@1.0.2: - resolution: {integrity: sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==} + /micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} dev: false - /micromark@3.1.0: - resolution: {integrity: sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==} + /micromark@3.2.0: + resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.8 debug: 4.3.4 decode-named-character-reference: 1.0.2 - micromark-core-commonmark: 1.0.6 - micromark-factory-space: 1.0.0 - micromark-util-character: 1.1.0 - micromark-util-chunked: 1.0.0 - micromark-util-combine-extensions: 1.0.0 - micromark-util-decode-numeric-character-reference: 1.0.0 - micromark-util-encode: 1.0.1 - micromark-util-normalize-identifier: 1.0.0 - micromark-util-resolve-all: 1.0.0 - micromark-util-sanitize-uri: 1.1.0 - micromark-util-subtokenize: 1.0.2 - micromark-util-symbol: 1.0.1 - micromark-util-types: 1.0.2 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-combine-extensions: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-encode: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 uvu: 0.5.6 transitivePeerDependencies: - supports-color @@ -3126,7 +3144,7 @@ packages: react-dom: 16.14.0(react@16.14.0) dev: false - /react-i18next@12.3.1(i18next@22.5.0)(react-dom@16.14.0)(react@16.14.0): + /react-i18next@12.3.1(i18next@23.1.0)(react-dom@16.14.0)(react@16.14.0): resolution: {integrity: sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==} peerDependencies: i18next: '>= 19.0.0' @@ -3139,15 +3157,15 @@ packages: react-native: optional: true dependencies: - '@babel/runtime': 7.21.5 + '@babel/runtime': 7.22.5 html-parse-stringify: 3.0.1 - i18next: 22.5.0 + i18next: 23.1.0 react: 16.14.0 react-dom: 16.14.0(react@16.14.0) dev: false - /react-icons@4.8.0(react@16.14.0): - resolution: {integrity: sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==} + /react-icons@4.9.0(react@16.14.0): + resolution: {integrity: sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg==} peerDependencies: react: '*' dependencies: @@ -3235,7 +3253,7 @@ packages: resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} dependencies: '@types/mdast': 3.0.11 - mdast-util-from-markdown: 1.3.0 + mdast-util-from-markdown: 1.3.1 unified: 10.1.2 transitivePeerDependencies: - supports-color @@ -3367,8 +3385,8 @@ packages: rollup: 2.79.1 dev: true - /rollup-plugin-visualizer@5.9.0(rollup@2.79.1): - resolution: {integrity: sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==} + /rollup-plugin-visualizer@5.9.2(rollup@2.79.1): + resolution: {integrity: sha512-waHktD5mlWrYFrhOLbti4YgQCn1uR24nYsNuXxg7LkPH8KdTXVWR9DNY1WU0QqokyMixVXJS4J04HNrVTMP01A==} engines: {node: '>=14'} hasBin: true peerDependencies: @@ -3415,7 +3433,7 @@ packages: /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.5.2 + tslib: 2.5.3 dev: true /sade@1.8.1: @@ -3443,8 +3461,8 @@ packages: loose-envify: 1.4.0 object-assign: 4.1.1 - /schema-utils@3.1.2: - resolution: {integrity: sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==} + /schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} dependencies: '@types/json-schema': 7.0.12 @@ -3517,8 +3535,8 @@ packages: resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} dev: true - /streamx@2.13.2: - resolution: {integrity: sha512-+TWqixPhGDXEG9L/XczSbhfkmwAtGs3BJX5QNU6cvno+pOLKeszByWcnaTu6dg8efsTYqR8ZZuXWHhZfgrxMvA==} + /streamx@2.15.0: + resolution: {integrity: sha512-HcxY6ncGjjklGs1xsP1aR71INYcsXFJet5CU1CHqihQ2J5nOsbd4OjgjHO42w/4QNv9gZb3BueV+Vxok5pLEXg==} dependencies: fast-fifo: 1.2.0 queue-tick: 1.0.1 @@ -3596,10 +3614,10 @@ packages: /teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} dependencies: - streamx: 2.13.2 + streamx: 2.15.0 dev: true - /terser-webpack-plugin@5.3.9(webpack@5.84.1): + /terser-webpack-plugin@5.3.9(webpack@5.87.0): resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -3617,19 +3635,19 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.18 jest-worker: 27.5.1 - schema-utils: 3.1.2 + schema-utils: 3.3.0 serialize-javascript: 6.0.1 - terser: 5.17.6 - webpack: 5.84.1 + terser: 5.18.0 + webpack: 5.87.0 dev: true - /terser@5.17.6: - resolution: {integrity: sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ==} + /terser@5.18.0: + resolution: {integrity: sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==} engines: {node: '>=10'} hasBin: true dependencies: '@jridgewell/source-map': 0.3.3 - acorn: 8.8.2 + acorn: 8.9.0 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -3704,8 +3722,8 @@ packages: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} dev: false - /tslib@2.5.2: - resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==} + /tslib@2.5.3: + resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} dev: true /type-fest@0.21.3: @@ -3807,13 +3825,13 @@ packages: engines: {node: '>= 10.0.0'} dev: true - /update-browserslist-db@1.0.11(browserslist@4.21.5): + /update-browserslist-db@1.0.11(browserslist@4.21.9): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.5 + browserslist: 4.21.9 escalade: 3.1.1 picocolors: 1.0.0 dev: true @@ -3964,8 +3982,8 @@ packages: engines: {node: '>=10.13.0'} dev: true - /webpack@5.84.1: - resolution: {integrity: sha512-ZP4qaZ7vVn/K8WN/p990SGATmrL1qg4heP/MrVneczYtpDGJWlrgZv55vxaV2ul885Kz+25MP2kSXkPe3LZfmg==} + /webpack@5.87.0: + resolution: {integrity: sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -3979,12 +3997,12 @@ packages: '@webassemblyjs/ast': 1.11.6 '@webassemblyjs/wasm-edit': 1.11.6 '@webassemblyjs/wasm-parser': 1.11.6 - acorn: 8.8.2 - acorn-import-assertions: 1.9.0(acorn@8.8.2) - browserslist: 4.21.5 + acorn: 8.9.0 + acorn-import-assertions: 1.9.0(acorn@8.9.0) + browserslist: 4.21.9 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.14.1 - es-module-lexer: 1.2.1 + enhanced-resolve: 5.15.0 + es-module-lexer: 1.3.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -3993,9 +4011,9 @@ packages: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.1.2 + schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.9(webpack@5.84.1) + terser-webpack-plugin: 5.3.9(webpack@5.87.0) watchpack: 2.4.0 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/frontend/src/components/DeckyState.tsx b/frontend/src/components/DeckyState.tsx index 53ef6d2d..920985b3 100644 --- a/frontend/src/components/DeckyState.tsx +++ b/frontend/src/components/DeckyState.tsx @@ -13,6 +13,12 @@ interface PublicDeckyState { hasLoaderUpdate?: boolean; isLoaderUpdating: boolean; versionInfo: VerInfo | null; + userInfo: UserInfo | null; +} + +export interface UserInfo { + username: string; + path: string; } export class DeckyState { @@ -24,6 +30,7 @@ export class DeckyState { private _hasLoaderUpdate: boolean = false; private _isLoaderUpdating: boolean = false; private _versionInfo: VerInfo | null = null; + private _userInfo: UserInfo | null = null; public eventBus = new EventTarget(); @@ -37,6 +44,7 @@ export class DeckyState { hasLoaderUpdate: this._hasLoaderUpdate, isLoaderUpdating: this._isLoaderUpdating, versionInfo: this._versionInfo, + userInfo: this._userInfo, }; } @@ -85,6 +93,11 @@ export class DeckyState { this.notifyUpdate(); } + setUserInfo(userInfo: UserInfo) { + this._userInfo = userInfo; + this.notifyUpdate(); + } + private notifyUpdate() { this.eventBus.dispatchEvent(new Event('update')); } diff --git a/frontend/src/components/modals/DropdownMultiselect.tsx b/frontend/src/components/modals/DropdownMultiselect.tsx new file mode 100644 index 00000000..5defbfa4 --- /dev/null +++ b/frontend/src/components/modals/DropdownMultiselect.tsx @@ -0,0 +1,121 @@ +import { + DialogButton, + DialogCheckbox, + DialogCheckboxProps, + Marquee, + Menu, + MenuItem, + findModuleChild, + showContextMenu, +} from 'decky-frontend-lib'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FaChevronDown } from 'react-icons/fa'; + +const dropDownControlButtonClass = findModuleChild((m) => { + if (typeof m !== 'object') return undefined; + for (const prop in m) { + if (m[prop]?.toString()?.includes('gamepaddropdown_DropDownControlButton')) { + return m[prop]; + } + } +}); + +const DropdownMultiselectItem: FC< + { + value: any; + onSelect: (checked: boolean, value: any) => void; + checked: boolean; + } & DialogCheckboxProps +> = ({ value, onSelect, checked: defaultChecked, ...rest }) => { + const [checked, setChecked] = useState(defaultChecked); + + useEffect(() => { + onSelect?.(checked, value); + }, [checked, onSelect, value]); + + return ( + <MenuItem bInteractableItem onClick={() => setChecked((x) => !x)}> + <DialogCheckbox + style={{ marginBottom: 0, padding: 0 }} + className="decky_DropdownMultiselectItem_DialogCheckbox" + bottomSeparator="none" + {...rest} + onClick={() => setChecked((x) => !x)} + onChange={(checked) => setChecked(checked)} + controlled + checked={checked} + /> + </MenuItem> + ); +}; + +const DropdownMultiselect: FC<{ + items: { + label: string; + value: string; + }[]; + selected: string[]; + onSelect: (selected: any[]) => void; + label: string; +}> = ({ label, items, selected, onSelect }) => { + const [itemsSelected, setItemsSelected] = useState<any>(selected); + const { t } = useTranslation(); + + const handleItemSelect = useCallback((checked, value) => { + setItemsSelected((x: any) => + checked ? [...x.filter((y: any) => y !== value), value] : x.filter((y: any) => y !== value), + ); + }, []); + + useEffect(() => { + onSelect(itemsSelected); + }, [itemsSelected, onSelect]); + + return ( + <DialogButton + style={{ + display: 'flex', + alignItems: 'center', + maxWidth: '100%', + }} + className={dropDownControlButtonClass} + onClick={(evt) => { + evt.preventDefault(); + showContextMenu( + <Menu label={label} cancelText={t('DropdownMultiselect.button.back') as string}> + <style> + {` + /* Inherit color from ".basiccontextmenu" */ + .decky_DropdownMultiselectItem_DialogCheckbox > .DialogToggle_Label { + color: inherit; + } + `} + </style> + <div style={{ marginTop: '10px' }}>{/*FIXME: Hack for missing padding under label menu*/}</div> + {items.map((x) => ( + <DropdownMultiselectItem + key={x.value} + label={x.label} + value={x.value} + checked={itemsSelected.includes(x.value)} + onSelect={handleItemSelect} + /> + ))} + </Menu>, + evt.currentTarget ?? window, + ); + }} + > + <Marquee> + {selected.length > 0 + ? selected.map((x: any) => items[items.findIndex((v) => v.value === x)].label).join(', ') + : '…'} + </Marquee> + <div style={{ flexGrow: 1, minWidth: '1ch' }} /> + <FaChevronDown style={{ height: '1em', flex: '0 0 1em' }} /> + </DialogButton> + ); +}; + +export default DropdownMultiselect; diff --git a/frontend/src/components/modals/filepicker/FilePickerError.tsx b/frontend/src/components/modals/filepicker/FilePickerError.tsx new file mode 100644 index 00000000..bf75afae --- /dev/null +++ b/frontend/src/components/modals/filepicker/FilePickerError.tsx @@ -0,0 +1,51 @@ +import { FC, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { IconContext } from 'react-icons'; +import { FaExclamationTriangle, FaQuestionCircle } from 'react-icons/fa'; + +export enum FileErrorTypes { + FileNotFound, + Unknown, + None, +} + +interface FilePickerErrorProps { + error: FileErrorTypes; + rawError?: string; +} + +const FilePickerError: FC<FilePickerErrorProps> = ({ error, rawError = null }) => { + const [icon, setIcon] = useState<JSX.Element>(<FaQuestionCircle />); + const [text, setText] = useState<string | null>(null); + const { t } = useTranslation(); + + useEffect(() => { + switch (error) { + case FileErrorTypes.FileNotFound: + setText(t('FilePickerError.errors.file_not_found')); + setIcon(<FaExclamationTriangle />); + break; + case FileErrorTypes.Unknown: + setText(t('FilePickerError.errors.unknown', { raw_error: rawError })); + setIcon(<FaQuestionCircle />); + break; + case FileErrorTypes.None: + setText(null); + setIcon(<div></div>); + break; + } + }, [error]); + + return ( + <> + <div style={{ paddingTop: '50px', textAlign: 'center', height: '100%' }}> + <IconContext.Provider value={{ className: 'fileError', size: '128px' }}> + <div style={{ alignSelf: 'center', alignContent: 'center' }}>{icon}</div> + </IconContext.Provider> + <p style={{ height: '32px', paddingTop: '25px', alignSelf: 'flex-start', textAlign: 'center' }}>{text}</p> + </div> + </> + ); +}; + +export default FilePickerError; diff --git a/frontend/src/components/modals/filepicker/i18n/TSortOptions.tsx b/frontend/src/components/modals/filepicker/i18n/TSortOptions.tsx new file mode 100644 index 00000000..5d861d18 --- /dev/null +++ b/frontend/src/components/modals/filepicker/i18n/TSortOptions.tsx @@ -0,0 +1,46 @@ +import { FC } from 'react'; +import { Translation } from 'react-i18next'; + +export enum SortOptions { + name_desc = 'name_desc', + name_asc = 'name_asc', + modified_desc = 'modified_desc', + modified_asc = 'modified_asc', + created_desc = 'created_desc', + created_asc = 'created_asc', + size_desc = 'size_desc', + size_asc = 'size_asc', +} + +interface TSortOptionsProps { + trans_part: SortOptions; +} + +const TSortOptions: FC<TSortOptionsProps> = ({ trans_part }) => { + return ( + <Translation> + {(t, {}) => { + switch (trans_part) { + case SortOptions.name_desc: + return t('FilePickerIndex.filter.name_desc'); + case SortOptions.name_asc: + return t('FilePickerIndex.filter.name_asce'); + case SortOptions.modified_desc: + return t('FilePickerIndex.filter.modified_desc'); + case SortOptions.modified_asc: + return t('FilePickerIndex.filter.modified_asce'); + case SortOptions.created_desc: + return t('FilePickerIndex.filter.created_desc'); + case SortOptions.created_asc: + return t('FilePickerIndex.filter.created_asce'); + case SortOptions.size_desc: + return t('FilePickerIndex.filter.size_desc'); + case SortOptions.size_asc: + return t('FilePickerIndex.filter.size_asce'); + } + }} + </Translation> + ); +}; + +export default TSortOptions; diff --git a/frontend/src/components/modals/filepicker/iconCustomizations.ts b/frontend/src/components/modals/filepicker/iconCustomizations.ts index e09c9e67..69e1652d 100644 --- a/frontend/src/components/modals/filepicker/iconCustomizations.ts +++ b/frontend/src/components/modals/filepicker/iconCustomizations.ts @@ -38,7 +38,7 @@ const imageStyle = { color: '#d18f00', }; -const imageExtList = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tif', 'tiff']; +const imageExtList = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tif', 'tiff', 'apng', 'tga']; styleDef.push([imageStyle, imageExtList]); diff --git a/frontend/src/components/modals/filepicker/index.tsx b/frontend/src/components/modals/filepicker/index.tsx index 629f4ec5..7aada5a5 100644 --- a/frontend/src/components/modals/filepicker/index.tsx +++ b/frontend/src/components/modals/filepicker/index.tsx @@ -1,11 +1,26 @@ -import { DialogButton, Focusable, SteamSpinner, TextField } from 'decky-frontend-lib'; -import { useEffect } from 'react'; -import { FunctionComponent, useState } from 'react'; +import { + ControlsList, + DialogBody, + DialogButton, + DialogControlsSection, + DialogFooter, + Dropdown, + Focusable, + Marquee, + SteamSpinner, + TextField, + ToggleField, +} from 'decky-frontend-lib'; +import { filesize } from 'filesize'; +import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; import { FileIcon, defaultStyles } from 'react-file-icon'; import { useTranslation } from 'react-i18next'; import { FaArrowUp, FaFolder } from 'react-icons/fa'; import Logger from '../../../logger'; +import DropdownMultiselect from '../DropdownMultiselect'; +import FilePickerError, { FileErrorTypes } from './FilePickerError'; +import TSortOption, { SortOptions } from './i18n/TSortOptions'; import { styleDefObj } from './iconCustomizations'; const logger = new Logger('FilePicker'); @@ -13,27 +28,89 @@ const logger = new Logger('FilePicker'); export interface FilePickerProps { startPath: string; includeFiles?: boolean; - regex?: RegExp; + includeFolders?: boolean; + filter?: RegExp | ((file: File) => boolean); + validFileExtensions?: string[]; + allowAllFiles?: boolean; + defaultHidden?: boolean; + max?: number; onSubmit: (val: { path: string; realpath: string }) => void; closeModal?: () => void; } -interface File { +export interface File { isdir: boolean; + ishidden: boolean; name: string; realpath: string; + size: number; + modified: number; + created: number; } interface FileListing { realpath: string; files: File[]; + total: number; } +const sortOptions = [ + { + data: SortOptions.name_desc, + label: <TSortOption trans_part={SortOptions.name_desc} />, + }, + { + data: SortOptions.name_asc, + label: <TSortOption trans_part={SortOptions.name_asc} />, + }, + { + data: SortOptions.modified_desc, + label: <TSortOption trans_part={SortOptions.modified_desc} />, + }, + { + data: SortOptions.modified_asc, + label: <TSortOption trans_part={SortOptions.modified_asc} />, + }, + { + data: SortOptions.created_desc, + label: <TSortOption trans_part={SortOptions.created_desc} />, + }, + { + data: SortOptions.created_asc, + label: <TSortOption trans_part={SortOptions.created_asc} />, + }, + { + data: SortOptions.size_desc, + label: <TSortOption trans_part={SortOptions.size_desc} />, + }, + { + data: SortOptions.size_asc, + label: <TSortOption trans_part={SortOptions.size_asc} />, + }, +]; + function getList( path: string, - includeFiles: boolean = true, + includeFiles: boolean, + includeFolders: boolean = true, + includeExt: string[] | null = null, + includeHidden: boolean = false, + orderBy: SortOptions = SortOptions.name_desc, + filterFor: RegExp | ((file: File) => boolean) | null = null, + pageNumber: number = 1, + max: number = 1000, ): Promise<{ result: FileListing | string; success: boolean }> { - return window.DeckyPluginLoader.callServerMethod('filepicker_ls', { path, include_files: includeFiles }); + return window.DeckyPluginLoader.callServerMethod('filepicker_ls', { + path, + include_files: includeFiles, + include_folders: includeFolders, + include_ext: includeExt ? includeExt : [], + include_hidden: includeHidden, + order_by: orderBy, + filter_for: filterFor, + page: pageNumber, + max: max, + }); } const iconStyles = { @@ -44,126 +121,240 @@ const iconStyles = { const FilePicker: FunctionComponent<FilePickerProps> = ({ startPath, includeFiles = true, - regex, + filter = undefined, + includeFolders = true, + validFileExtensions = undefined, + allowAllFiles = true, + defaultHidden = false, // false by default makes sense for most users + max = 1000, onSubmit, closeModal, }) => { const { t } = useTranslation(); - if (startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path + + if (startPath !== '/' && startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path const [path, setPath] = useState<string>(startPath); - const [listing, setListing] = useState<FileListing>({ files: [], realpath: path }); - const [error, setError] = useState<string | null>(null); + const [listing, setListing] = useState<FileListing>({ files: [], realpath: path, total: 0 }); + const [files, setFiles] = useState<File[]>([]); + const [error, setError] = useState<FileErrorTypes>(FileErrorTypes.None); + const [rawError, setRawError] = useState<string | null>(null); + const [page, setPage] = useState<number>(1); const [loading, setLoading] = useState<boolean>(true); + const [showHidden, setShowHidden] = useState<boolean>(defaultHidden); + const [sort, setSort] = useState<SortOptions>(SortOptions.name_desc); + const [selectedExts, setSelectedExts] = useState<string[] | undefined>(validFileExtensions); + + const validExtsOptions = useMemo(() => { + let validExt: { label: string; value: string }[] = []; + if (validFileExtensions) { + if (allowAllFiles) { + validExt.push({ label: t('FilePickerIndex.files.all_files'), value: 'all_files' }); + } + validExt.push(...validFileExtensions.map((x) => ({ label: x, value: x }))); + } + return validExt; + }, [validFileExtensions, allowAllFiles]); + + function isSelectionValid(validExts: string[], selection: string[]) { + if (validExts.some((el) => selection.includes(el))) return true; + return false; + } + + const handleExtsSelect = useCallback((val: any) => { + // unselect other options if "All Files" is checked + if (allowAllFiles && val.includes('all_files')) { + setSelectedExts(['all_files']); + } else if (validFileExtensions && isSelectionValid(validFileExtensions, val)) { + // If at least one extension is still selected, then assign this selection to the selected values + setSelectedExts(val); + } else { + // Else do nothing + setSelectedExts(selectedExts); + } + }, []); useEffect(() => { (async () => { - if (error) setError(null); setLoading(true); - const listing = await getList(path, includeFiles); + const listing = await getList( + path, + includeFiles, + includeFolders, + selectedExts, + showHidden, + sort, + filter, + page, + max, + ); if (!listing.success) { - setListing({ files: [], realpath: path }); + setListing({ files: [], realpath: path, total: 0 }); setLoading(false); - setError(listing.result as string); - logger.error(listing.result); + const theError = listing.result as string; + switch (theError) { + case theError.match(/\[Errno\s2.*/i)?.input: + case theError.match(/\[WinError\s3.*/i)?.input: + setError(FileErrorTypes.FileNotFound); + break; + default: + setRawError(theError); + setError(FileErrorTypes.Unknown); + break; + } + logger.debug(theError); return; + } else { + setRawError(null); + setError(FileErrorTypes.None); + setFiles((listing.result as FileListing).files); } setLoading(false); setListing(listing.result as FileListing); logger.log('reloaded', path, listing); })(); - }, [path]); + }, [error, path, includeFiles, includeFolders, showHidden, sort, selectedExts, page]); return ( - <div className="deckyFilePicker"> - <Focusable style={{ display: 'flex', flexDirection: 'row', paddingBottom: '10px' }}> - <DialogButton - style={{ - minWidth: 'unset', - width: '40px', - flexGrow: '0', - borderRadius: 'unset', - margin: '0', - padding: '10px', - }} - onClick={() => { - const newPathArr = path.split('/'); - newPathArr.pop(); - let newPath = newPathArr.join('/'); - if (newPath == '') newPath = '/'; - setPath(newPath); - }} - > - <FaArrowUp /> - </DialogButton> - <div style={{ flexGrow: '1', width: '100%' }}> - <TextField - value={path} - onChange={(e) => { - e.target.value && setPath(e.target.value); - }} - style={{ height: '100%' }} - /> - </div> - </Focusable> - <Focusable style={{ display: 'flex', flexDirection: 'column', height: '60vh', overflow: 'scroll' }}> - {loading && <SteamSpinner style={{ height: '100%' }} />} - {!loading && - listing.files - .filter((file) => (includeFiles || file.isdir) && (!regex || regex.test(file.name))) - .map((file) => { - let extension = file.realpath.split('.').pop() as string; - return ( - <DialogButton - style={{ borderRadius: 'unset', margin: '0', padding: '10px' }} - onClick={() => { - const fullPath = `${path}${path.endsWith('/') ? '' : '/'}${file.name}`; - if (file.isdir) setPath(fullPath); - else { - onSubmit({ path: fullPath, realpath: file.realpath }); - closeModal?.(); - } - }} - > - <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'flex-start' }}> - {file.isdir ? ( - <FaFolder style={iconStyles} /> - ) : ( - <div style={iconStyles}> - {file.realpath.includes('.') ? ( - <FileIcon {...defaultStyles[extension]} {...styleDefObj[extension]} extension={''} /> - ) : ( - <FileIcon /> - )} - </div> - )} - <span + <> + <DialogBody className="deckyFilePicker"> + <DialogControlsSection> + <Focusable flow-children="right" style={{ display: 'flex', marginBottom: '1em' }}> + <DialogButton + style={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + minWidth: 'unset', + width: '40px', + borderRadius: 'unset', + margin: '0', + padding: '10px', + }} + onClick={() => { + const newPathArr = path.split('/'); + const lastPath = newPathArr.pop(); + //If I have a single / with spaces, pop the array twice + if (lastPath?.match(/^\/\s*$/) != null) newPathArr.pop(); + let newPath = newPathArr.join('/'); + if (newPath == '') newPath = '/'; + setPath(newPath); + }} + > + <FaArrowUp /> + </DialogButton> + <div style={{ width: '100%' }}> + <TextField + value={path} + onChange={(e) => { + e.target.value && setPath(e.target.value); + }} + style={{ height: '100%' }} + /> + </div> + </Focusable> + <ControlsList alignItems="center" spacing="standard"> + <ToggleField + highlightOnFocus={false} + label={t('FilePickerIndex.files.show_hidden')} + bottomSeparator="none" + checked={showHidden} + onChange={() => setShowHidden((x) => !x)} + /> + <Dropdown rgOptions={sortOptions} selectedOption={sort} onChange={(x) => setSort(x.data)} /> + {validFileExtensions && ( + <DropdownMultiselect + label={t('FilePickerIndex.files.file_type')} + items={validExtsOptions} + selected={selectedExts ? selectedExts : []} + onSelect={handleExtsSelect} + /> + )} + </ControlsList> + </DialogControlsSection> + <DialogControlsSection style={{ marginTop: '1em' }}> + <Focusable + style={{ display: 'flex', gap: '.25em', flexDirection: 'column', height: '60vh', overflow: 'scroll' }} + > + {loading && error === FileErrorTypes.None && <SteamSpinner style={{ height: '100%' }} />} + {!loading && + error === FileErrorTypes.None && + files.map((file) => { + const extension = file.realpath.split('.').pop() as string; + return ( + <DialogButton + key={`${file.realpath}${file.name}`} + style={{ borderRadius: 'unset', margin: '0', padding: '10px' }} + onClick={() => { + const fullPath = `${path}${path.endsWith('/') ? '' : '/'}${file.name}`; + if (file.isdir) setPath(fullPath); + else { + onSubmit({ path: fullPath, realpath: file.realpath }); + closeModal?.(); + } + }} + > + <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'flex-start' }}> + {file.isdir ? ( + <FaFolder style={iconStyles} /> + ) : ( + <div style={iconStyles}> + {file.realpath.includes('.') ? ( + <FileIcon {...defaultStyles[extension]} {...styleDefObj[extension]} extension={''} /> + ) : ( + <FileIcon /> + )} + </div> + )} + <Marquee>{file.name}</Marquee> + </div> + <div style={{ - textOverflow: 'ellipsis', - overflow: 'hidden', + display: 'flex', + opacity: 0.5, + fontSize: '.6em', textAlign: 'left', + lineHeight: 1, + marginTop: '.5em', }} > - {file.name} - </span> - </div> - </DialogButton> - ); - })} - {error} - </Focusable> + {file.isdir ? t('FilePickerIndex.folder.label') : filesize(file.size, { standard: 'iec' })} + <span style={{ marginLeft: 'auto' }}>{new Date(file.modified * 1000).toLocaleString()}</span> + </div> + </DialogButton> + ); + })} + {error !== FileErrorTypes.None && <FilePickerError error={error} rawError={rawError ? rawError : ''} />} + </Focusable> + </DialogControlsSection> + </DialogBody> {!loading && !error && !includeFiles && ( - <DialogButton - className="Primary" - style={{ marginTop: '10px', alignSelf: 'flex-end' }} - onClick={() => { - onSubmit({ path, realpath: listing.realpath }); - closeModal?.(); - }} - > - {t('FilePickerIndex.folder.select')} - </DialogButton> + <DialogFooter> + <DialogButton + className="Primary" + style={{ marginTop: '10px', alignSelf: 'flex-end' }} + onClick={() => { + onSubmit({ path, realpath: listing.realpath }); + closeModal?.(); + }} + > + {t('FilePickerIndex.folder.select')} + </DialogButton> + </DialogFooter> + )} + {page * max < listing.total && ( + <DialogFooter> + <DialogButton + className="Primary" + style={{ marginTop: '10px', alignSelf: 'flex-end' }} + onClick={() => { + setPage(page + 1); + }} + > + {t('FilePickerIndex.folder.show_more')} + </DialogButton> + </DialogFooter> )} - </div> + </> ); }; diff --git a/frontend/src/components/settings/pages/developer/index.tsx b/frontend/src/components/settings/pages/developer/index.tsx index 200f13ab..3e6db2f7 100644 --- a/frontend/src/components/settings/pages/developer/index.tsx +++ b/frontend/src/components/settings/pages/developer/index.tsx @@ -13,26 +13,24 @@ import { useTranslation } from 'react-i18next'; import { FaFileArchive, FaLink, FaReact, FaSteamSymbol, FaTerminal } from 'react-icons/fa'; import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer'; +import Logger from '../../../../logger'; import { installFromURL } from '../../../../store'; import { useSetting } from '../../../../utils/hooks/useSetting'; +import { getSetting } from '../../../../utils/settings'; import RemoteDebuggingSettings from '../general/RemoteDebugging'; -const installFromZip = () => { - window.DeckyPluginLoader.openFilePicker('/home/deck', true).then((val) => { +const logger = new Logger('DeveloperIndex'); + +const installFromZip = async () => { + const path = await getSetting<string>('user_info.user_home', ''); + if (path === '') { + logger.error('The default path has not been found!'); + return; + } + window.DeckyPluginLoader.openFilePicker(path, true, undefined, true, ['zip', 'rar'], false, true).then((val) => { const url = `file://${val.path}`; console.log(`Installing plugin locally from ${url}`); - - if (url.endsWith('.zip')) { - installFromURL(url); - } else { - window.DeckyPluginLoader.toaster.toast({ - //title: t('SettingsDeveloperIndex.toast_zip.title'), - title: 'Decky', - //body: t('SettingsDeveloperIndex.toast_zip.body'), - body: 'Installation failed! Only ZIP files are supported.', - onClick: installFromZip, - }); - } + installFromURL(url); }); }; diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index 6d20c2f0..c4063557 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -12,8 +12,9 @@ import { import { FC, lazy } from 'react'; import { FaExclamationCircle, FaPlug } from 'react-icons/fa'; -import { DeckyState, DeckyStateContextProvider, useDeckyState } from './components/DeckyState'; +import { DeckyState, DeckyStateContextProvider, UserInfo, useDeckyState } from './components/DeckyState'; import LegacyPlugin from './components/LegacyPlugin'; +import { File } from './components/modals/filepicker'; import { deinitFilepickerPatches, initFilepickerPatches } from './components/modals/filepicker/patches'; import MultiplePluginsInstallModal from './components/modals/MultiplePluginsInstallModal'; import PluginInstallModal from './components/modals/PluginInstallModal'; @@ -31,7 +32,7 @@ import TabsHook from './tabs-hook'; import OldTabsHook from './tabs-hook.old'; import Toaster from './toaster'; import { VerInfo, callUpdaterMethod } from './updater'; -import { getSetting } from './utils/settings'; +import { getSetting, setSetting } from './utils/settings'; import TranslationHelper, { TranslationClass } from './utils/TranslationHelper'; const StorePage = lazy(() => import('./components/store/Store')); @@ -99,9 +100,17 @@ class PluginLoader extends Logger { initFilepickerPatches(); + this.getUserInfo(); + this.updateVersion(); } + public async getUserInfo() { + const userInfo = (await this.callServerMethod('get_user_info')).result as UserInfo; + setSetting('user_info.user_name', userInfo.username); + setSetting('user_info.user_home', userInfo.path); + } + public async updateVersion() { const versionInfo = (await callUpdaterMethod('get_version')).result as VerInfo; this.deckyState.setVersionInfo(versionInfo); @@ -268,6 +277,7 @@ class PluginLoader extends Logger { Authentication: window.deckyAuthToken, }, }); + if (res.ok) { try { let plugin_export = await eval(await res.text()); @@ -352,7 +362,12 @@ class PluginLoader extends Logger { openFilePicker( startPath: string, includeFiles?: boolean, - regex?: RegExp, + filter?: RegExp | ((file: File) => boolean), + includeFolders?: boolean, + extensions?: string[], + showHiddenFiles?: boolean, + allowAllFiles?: boolean, + max?: number, ): Promise<{ path: string; realpath: string }> { return new Promise((resolve, reject) => { const Content = ({ closeModal }: { closeModal?: () => void }) => ( @@ -367,9 +382,14 @@ class PluginLoader extends Logger { <FilePicker startPath={startPath} includeFiles={includeFiles} - regex={regex} + includeFolders={includeFolders} + filter={filter} + validFileExtensions={extensions} + allowAllFiles={allowAllFiles} + defaultHidden={showHiddenFiles} onSubmit={resolve} closeModal={closeModal} + max={max} /> </WithSuspense> </ModalRoot> |
