From 7562938151e833685c861ef5d09c7f0c64e335d4 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Fri, 21 Feb 2025 18:04:48 +0000 Subject: [PATCH 01/27] Proof of concept for settings page --- .gitignore | 1 + scripts/generate_settings_page.py | 83 ++++++++++++++++++++++++ scripts/settings.html | 103 ++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 scripts/generate_settings_page.py create mode 100644 scripts/settings.html diff --git a/.gitignore b/.gitignore index 701de43..4b8779f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ dist* docs/_build/ docs/source/autoapi/ docs/source/modules/autogen/ +scripts/settings_page.html diff --git a/scripts/generate_settings_page.py b/scripts/generate_settings_page.py new file mode 100644 index 0000000..2b74cf9 --- /dev/null +++ b/scripts/generate_settings_page.py @@ -0,0 +1,83 @@ +import os +from auto_archiver.core.module import ModuleFactory +from auto_archiver.core.consts import MODULE_TYPES +import jinja2 + +# Get available modules +module_factory = ModuleFactory() +available_modules = module_factory.available_modules() + +modules_by_type = {} +# Categorize modules by type +for module in available_modules: + for type in module.type: + modules_by_type.setdefault(type, []).append(module) + + +module_sections = "" +# Add module sections +for module_type in MODULE_TYPES: + module_sections += f"

{module_type}

" + for module in modules_by_type[module_type]: + module_name = module.name + module_sections += f""" +
+ + +
+ """ + module_sections += "
" + +# Add module configuration sections + +all_modules_ordered_by_type = sorted(available_modules, key=lambda x: (MODULE_TYPES.index(x.type[0]), not x.requires_setup)) + +module_configs = "" + +for module in all_modules_ordered_by_type: + if not module.configs: + continue + module_configs += f"

{module.display_name} Configuration

" + for option, value in module.configs.items(): + # create a human readable label + option = option.replace('_', ' ').title() + + # type - if value has 'choices', then it's a select + if 'choices' in value: + module_configs += f""" +
+ +
" + elif value.get('type') == 'bool' or isinstance(value.get('default', None), bool): + module_configs += f""" +
+ + +
+ """ + else: + module_configs += f""" +
+ + +
+ """ + module_configs += "
" + +# format the settings.html jinja page with the module sections and module configuration sections +settings_page = "settings.html" +template_loader = jinja2.FileSystemLoader(searchpath="./") +template_env = jinja2.Environment(loader=template_loader) +template = template_env.get_template(settings_page) +html_content = template.render(module_sections=module_sections, module_configs=module_configs) + +# Write HTML content to file +output_file = '/Users/patrick/Developer/auto-archiver/scripts/settings_page.html' +with open(output_file, 'w') as file: + file.write(html_content) + +print(f"Settings page generated at {output_file}") \ No newline at end of file diff --git a/scripts/settings.html b/scripts/settings.html new file mode 100644 index 0000000..bd11edd --- /dev/null +++ b/scripts/settings.html @@ -0,0 +1,103 @@ + + + + + + Module Settings + + + + + +

Module Settings

+
+ + + +

Steps

+ + {{module_sections}} + +

Module Configuration Section

+ {{module_configs}} + + +
+ + \ No newline at end of file From 1c17629ac6b46ad5917bbbc23e0c74ce28771806 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Fri, 21 Feb 2025 18:54:27 +0000 Subject: [PATCH 02/27] Tweaks --- scripts/generate_settings_page.py | 20 +++++++++++--------- scripts/settings.html | 2 ++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/generate_settings_page.py b/scripts/generate_settings_page.py index 2b74cf9..022dcc9 100644 --- a/scripts/generate_settings_page.py +++ b/scripts/generate_settings_page.py @@ -10,18 +10,20 @@ available_modules = module_factory.available_modules() modules_by_type = {} # Categorize modules by type for module in available_modules: - for type in module.type: + for type in module.manifest.get('type', []): modules_by_type.setdefault(type, []).append(module) module_sections = "" # Add module sections for module_type in MODULE_TYPES: - module_sections += f"

{module_type}

" + module_sections += f"

{module_type.title()}s

" + # make this section in rows, max 8 modules per row for module in modules_by_type[module_type]: + module_name = module.name module_sections += f""" -
+
@@ -43,29 +45,29 @@ for module in all_modules_ordered_by_type: option = option.replace('_', ' ').title() # type - if value has 'choices', then it's a select + module_configs += "
" if 'choices' in value: module_configs += f""" -
" + module_configs += "" elif value.get('type') == 'bool' or isinstance(value.get('default', None), bool): module_configs += f""" -
-
""" else: module_configs += f""" -
-
""" + # add help text + if 'help' in value: + module_configs += f"
{value.get('help')}
" + module_configs += "
" module_configs += "
" # format the settings.html jinja page with the module sections and module configuration sections diff --git a/scripts/settings.html b/scripts/settings.html index bd11edd..c5bdea9 100644 --- a/scripts/settings.html +++ b/scripts/settings.html @@ -7,6 +7,8 @@ - - - -

Module Settings

-
- - - -

Steps

- - {{module_sections}} - -

Module Configuration Section

- {{module_configs}} - - -
- - \ No newline at end of file diff --git a/scripts/html/settings.html b/scripts/html/settings.html deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/settings/.gitignore b/scripts/settings/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/scripts/settings/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/scripts/settings/index.html b/scripts/settings/index.html new file mode 100644 index 0000000..d2e8d75 --- /dev/null +++ b/scripts/settings/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + Auto Archiver Settings + + +
+ + + diff --git a/scripts/settings/package-lock.json b/scripts/settings/package-lock.json new file mode 100644 index 0000000..6ef055b --- /dev/null +++ b/scripts/settings/package-lock.json @@ -0,0 +1,3683 @@ +{ + "name": "material-ui-vite-ts", + "version": "5.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "material-ui-vite-ts", + "version": "5.0.0", + "dependencies": { + "@emotion/react": "latest", + "@emotion/styled": "latest", + "@mui/icons-material": "latest", + "@mui/material": "latest", + "react": "latest", + "react-dom": "latest", + "react-markdown": "^10.0.0", + "yaml": "^2.7.0" + }, + "devDependencies": { + "@types/react": "latest", + "@types/react-dom": "latest", + "@vitejs/plugin-react": "latest", + "typescript": "latest", + "vite": "latest", + "vite-plugin-singlefile": "^2.1.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", + "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", + "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz", + "integrity": "sha512-zoXvHU1YuoodgMlPS+epP084Pqv9V+Vg+5IGv9n/7IIFVQ2nkTngYHYxElCq8pdTTbDcgji+nNh0lxri2abWgA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.5.tgz", + "integrity": "sha512-4A//t8Nrc+4u4pbVhGarIFU98zpuB5AV9hTNzgXx1ySZJ1tWtx+i/1SbQ8PtGJxWeXlljhwimZJNPQ3x0CiIFw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.4.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.5.tgz", + "integrity": "sha512-5eyEgSXocIeV1JkXs8mYyJXU0aFyXZIWI5kq2g/mCnIgJe594lkOBNAKnCIaGVfQTu2T6TTEHF8/hHIqpiIRGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.4.5", + "@mui/system": "^6.4.3", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.3", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.4.3", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz", + "integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz", + "integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz", + "integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.3", + "@mui/styled-engine": "^6.4.3", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.3", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz", + "integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001701", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz", + "integrity": "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.105", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.105.tgz", + "integrity": "sha512-ccp7LocdXx3yBhwiG0qTQ7XFrK48Ua2pxIxBdJO8cbddp/MvbBtPFzvnTchtyHQTsgqqczO8cdmAIbpMa0u2+g==", + "dev": true, + "license": "ISC" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.5.tgz", + "integrity": "sha512-gHD+HoFxOMmmXLuq9f2dZDMQHVcplCVpMfBNRpJsF03yyLZvJGzsFORe8orVuYDX9k2w0VH0uF8oryFd1whqKQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz", + "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.0.0.tgz", + "integrity": "sha512-4mTz7Sya/YQ1jYOrkwO73VcFdkFJ8L8I9ehCxdcV0XrClHyOJGKbBk5FR4OOOG+HnyKw5u+C/Aby9TwinCteYA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", + "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-singlefile": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.1.0.tgz", + "integrity": "sha512-7tJo+UgZABlKpY/nubth/wxJ4+pUGREPnEwNOknxwl2MM0zTvF14KTU4Ln1lc140gjLLV5mjDrvuoquU7OZqCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">18.0.0" + }, + "peerDependencies": { + "rollup": "^4.28.1", + "vite": "^5.4.11 || ^6.0.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/scripts/settings/package.json b/scripts/settings/package.json new file mode 100644 index 0000000..6608693 --- /dev/null +++ b/scripts/settings/package.json @@ -0,0 +1,29 @@ +{ + "name": "material-ui-vite-ts", + "private": true, + "version": "5.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "latest", + "@emotion/styled": "latest", + "@mui/icons-material": "latest", + "@mui/material": "latest", + "react": "latest", + "react-dom": "latest", + "react-markdown": "^10.0.0", + "yaml": "^2.7.0" + }, + "devDependencies": { + "@types/react": "latest", + "@types/react-dom": "latest", + "@vitejs/plugin-react": "latest", + "typescript": "latest", + "vite": "latest", + "vite-plugin-singlefile": "^2.1.0" + } +} diff --git a/scripts/settings/public/vite.svg b/scripts/settings/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/scripts/settings/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx new file mode 100644 index 0000000..6e6e813 --- /dev/null +++ b/scripts/settings/src/App.tsx @@ -0,0 +1,364 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import Container from '@mui/material/Container'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import Link from '@mui/material/Link'; +import { modules, steps, configs, module_types } from './schema.json'; +import { + Checkbox, + Select, + MenuItem, + FormControl, + FormControlLabel, + InputLabel, + FormHelperText, + Stack, + TextField, + Card, + CardContent, + CardActions, + Button, + Dialog, + DialogTitle, + DialogContent, +} from '@mui/material'; +import Grid from '@mui/material/Grid2'; + +import Accordion from '@mui/material/Accordion'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import ReactMarkdown from 'react-markdown'; +import { parseDocument, ParsedNode, Document } from 'yaml' +import { set } from 'yaml/dist/schema/yaml-1.1/set'; + +Object.defineProperty(String.prototype, 'capitalize', { + value: function() { + return this.charAt(0).toUpperCase() + this.slice(1); + }, + enumerable: false +}); + +function FileDrop({ setYamlFile }) { + + const [showError, setShowError] = useState(false); + const [label, setLabel] = useState("Drag and drop your orchestration.yaml file here, or click to select a file."); + + function openYAMLFile(event: any) { + let file = event.target.files[0]; + if (file.type !== 'application/x-yaml') { + setShowError(true); + setLabel("Invalid type, only YAML files are accepted.") + return; + } + let reader = new FileReader(); + reader.onload = function(e) { + let contents = e.target.result; + try { + let document = parseDocument(contents); + if (document.errors.length > 0) { + // not a valid yaml file + setShowError(true); + setLabel("Invalid file. Make sure your Orchestration is a valid YAML file with a 'steps' section in it.") + return; + } else { + setShowError(false); + setLabel("File loaded successfully.") + } + setYamlFile(document); + } catch (e) { + console.error(e); + } + } + reader.readAsText(file); + } + return ( + <> +
+ + + + {label} + +
+ + ); +} + + +function ModuleCheckbox({ module, toggleModule, enabledModules, configValues }: { module: object, toggleModule: any, enabledModules: any, configValues: any }) { + let name = module.name; + const [helpOpen, setHelpOpen] = useState(false); + const [configOpen, setConfigOpen] = useState(false); + if (name == 'metadata_enricher') { + console.log("hi"); + } + return ( + <> + + + } + label={module.display_name} /> + + + + {enabledModules.includes(name) && module.configs && name != 'cli_feeder' ? ( + + ) : null} + + + setHelpOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} + + + + {module.configs && name != 'cli_feeder' && } + + ) +} + + +function ConfigPanel({ module, open, setOpen, configValues }: { module: any, open: boolean, setOpen: any, configValues: any }) { + return ( + <> + setOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {Object.keys(module.configs).map((config_value: any) => { + let config_args = module.configs[config_value]; + let config_name = config_value.replace(/_/g," "); + return ( + + + { config_args.type === 'bool' ? + } label={config_name} /> + : + ( config_args.type === 'int' ? + + : + ( + config_args.choices !== undefined ? + <> + {config_name} + + + : + + ) + ) + } + {config_args.help} + + + ); + })} + + + + + ); +} + +function ModuleTypes({ stepType, toggleModule, enabledModules, configValues }: { stepType: string, toggleModule: any, enabledModules: any, configValues: any }) { + const [showError, setShowError] = useState(false); + + const _toggleModule = (event: any) => { + // make sure that 'feeder' and 'formatter' types only have one value + let name = event.target.id; + if (stepType === 'feeder' || stepType === 'formatter') { + let checked = event.target.checked; + // check how many modules of this type are enabled + let modules = steps[stepType].filter((m: string) => (m !== name && enabledModules.includes(m)) || (checked && m === name)); + if (modules.length > 1) { + setShowError(true); + } else { + setShowError(false); + } + } else { + setShowError(false); + } + toggleModule(event); + } + return ( + <> + + {stepType}s + + {showError ? Only one {stepType} can be enabled at a time. : null} + + {steps[stepType].map((name: string) => { + let m = modules[name]; + return ( + + + + ); + })} + + + ); +} + + +export default function App() { + const [yamlFile, setYamlFile] = useState(new Document()); + const [enabledModules, setEnabledModules] = useState<[]>([]); + const [configValues, setConfigValues] = useState<{}>( + Object.keys(modules).reduce((acc, module) => { + acc[module] = {}; + return acc; + }, {}) + ); + + const saveSettings = function(copy: boolean = false) { + // edit the yamlFile + + // generate the steps config + let stepsConfig = {} + module_types.forEach((stepType: string) => { + stepsConfig[stepType] = enabledModules.filter((m: string) => steps[stepType].includes(m)); + } + ); + + // create a yaml file from + const finalYaml = { + 'steps': stepsConfig + }; + + Object.keys(configValues).map((module: string) => { + let module_values = configValues[module]; + if (module_values) { + finalYaml[module] = module_values; + } + }); + let newFile = new Document(finalYaml); + if (copy) { + navigator.clipboard.writeText(String(newFile)).then(() => { + alert("Settings copied to clipboard."); + }); + } else { + // offer the file for download + const blob = new Blob([String(newFile)], { type: 'application/x-yaml' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'orchestration.yaml'; + a.click(); + } + } + + const toggleModule = function (event: any) { + let module = event.target.id; + let checked = event.target.checked + + if (checked) { + setEnabledModules([...enabledModules, module]); + } else { + setEnabledModules(enabledModules.filter((m: string) => m !== module)); + } + } + + useEffect(() => { + // load the configs, and set the default values if they exist + let newConfigValues = {}; + Object.keys(modules).map((module: string) => { + let m = modules[module]; + let configs = m.configs; + if (!configs) { + return; + } + newConfigValues[module] = {}; + Object.keys(configs).map((config: string) => { + let config_args = configs[config]; + if (config_args.default !== undefined) { + newConfigValues[module][config] = config_args.default; + } + }); + }) + setConfigValues(newConfigValues); + }, []); + + useEffect(() => { + if (!yamlFile || yamlFile.contents == null) { + return; + } + + let settings = yamlFile.toJS(); + // make a deep copy of settings + let newEnabledModules = Object.keys(settings['steps']).map((step: string) => { + return settings['steps'][step]; + }).flat(); + newEnabledModules = newEnabledModules.filter((m: string, i: number) => newEnabledModules.indexOf(m) === i); + setEnabledModules(newEnabledModules); + }, [yamlFile]); + + + + return ( + + + + Auto Archiver Settings + + + + 1. Select your
orchestration.yaml
settings file. +
+ +
+ + + 2. Choose the Modules you wish to enable/disable + + {Object.keys(steps).map((stepType: string) => { + return ( + + ); + })} + + + + 3. Configure your Enabled Modules + + + Next to each module you've enabled, you can click 'Configure' to set the module's settings. + + + + + 4. Save your settings + + + + +
+
+ ); +} diff --git a/scripts/settings/src/ProTip.tsx b/scripts/settings/src/ProTip.tsx new file mode 100644 index 0000000..217b5bf --- /dev/null +++ b/scripts/settings/src/ProTip.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import Link from '@mui/material/Link'; +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; +import Typography from '@mui/material/Typography'; + +function LightBulbIcon(props: SvgIconProps) { + return ( + + + + ); +} + +export default function ProTip() { + return ( + + + {'Pro tip: See more '} + templates + {' in the Material UI documentation.'} + + ); +} diff --git a/scripts/settings/src/main.tsx b/scripts/settings/src/main.tsx new file mode 100644 index 0000000..ad50afc --- /dev/null +++ b/scripts/settings/src/main.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom/client'; +import { ThemeProvider } from '@mui/material/styles'; +import { CssBaseline } from '@mui/material'; +import theme from './theme'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + , +); diff --git a/scripts/settings/src/schema.json b/scripts/settings/src/schema.json new file mode 100644 index 0000000..e02067c --- /dev/null +++ b/scripts/settings/src/schema.json @@ -0,0 +1,2095 @@ +{ + "modules": { + "gsheet_feeder": { + "name": "gsheet_feeder", + "display_name": "Google Sheets Feeder", + "manifest": { + "name": "Google Sheets Feeder", + "author": "Bellingcat", + "type": [ + "feeder" + ], + "requires_setup": true, + "description": "\n GsheetsFeeder \n A Google Sheets-based feeder for the Auto Archiver.\n\n This reads data from Google Sheets and filters rows based on user-defined rules.\n The filtered rows are processed into `Metadata` objects.\n\n ### Features\n - Validates the sheet structure and filters rows based on input configurations.\n - Processes only worksheets allowed by the `allow_worksheets` and `block_worksheets` configurations.\n - Ensures only rows with valid URLs and unprocessed statuses are included for archival.\n - Supports organizing stored files into folder paths based on sheet and worksheet names.\n\n ### Setup\n - Requires a Google Service Account JSON file for authentication, which should be stored in `secrets/gsheets_service_account.json`.\n To set up a service account, follow the instructions [here](https://gspread.readthedocs.io/en/latest/oauth2.html).\n - Define the `sheet` or `sheet_id` configuration to specify the sheet to archive.\n - Customize the column names in your Google sheet using the `columns` configuration.\n ", + "dependencies": { + "python": [ + "loguru", + "gspread", + "slugify" + ] + }, + "entry_point": "gsheet_feeder::GsheetsFeeder", + "version": "1.0", + "configs": { + "sheet": { + "default": null, + "help": "name of the sheet to archive" + }, + "sheet_id": { + "default": null, + "help": "the id of the sheet to archive (alternative to 'sheet' config)" + }, + "header": { + "default": 1, + "help": "index of the header row (starts at 1)", + "type": "int" + }, + "service_account": { + "default": "secrets/service_account.json", + "help": "service account JSON file path. Learn how to create one: https://gspread.readthedocs.io/en/latest/oauth2.html", + "required": true + }, + "columns": { + "default": { + "url": "link", + "status": "archive status", + "folder": "destination folder", + "archive": "archive location", + "date": "archive date", + "thumbnail": "thumbnail", + "timestamp": "upload timestamp", + "title": "upload title", + "text": "text content", + "screenshot": "screenshot", + "hash": "hash", + "pdq_hash": "perceptual hashes", + "wacz": "wacz", + "replaywebpage": "replaywebpage" + }, + "help": "Custom names for the columns in your Google sheet. If you don't want to use the default column names, change them with this setting", + "type": "json_loader" + }, + "allow_worksheets": { + "default": [], + "help": "A list of worksheet names that should be processed (overrides worksheet_block), leave empty so all are allowed" + }, + "block_worksheets": { + "default": [], + "help": "A list of worksheet names for worksheets that should be explicitly blocked from being processed" + }, + "use_sheet_names_in_stored_paths": { + "default": true, + "help": "if True the stored files path will include 'workbook_name/worksheet_name/...'", + "type": "bool" + } + } + }, + "configs": { + "sheet": { + "default": null, + "help": "name of the sheet to archive" + }, + "sheet_id": { + "default": null, + "help": "the id of the sheet to archive (alternative to 'sheet' config)" + }, + "header": { + "default": 1, + "help": "index of the header row (starts at 1)", + "type": "int" + }, + "service_account": { + "default": "secrets/service_account.json", + "help": "service account JSON file path. Learn how to create one: https://gspread.readthedocs.io/en/latest/oauth2.html", + "required": true + }, + "columns": { + "default": { + "url": "link", + "status": "archive status", + "folder": "destination folder", + "archive": "archive location", + "date": "archive date", + "thumbnail": "thumbnail", + "timestamp": "upload timestamp", + "title": "upload title", + "text": "text content", + "screenshot": "screenshot", + "hash": "hash", + "pdq_hash": "perceptual hashes", + "wacz": "wacz", + "replaywebpage": "replaywebpage" + }, + "help": "Custom names for the columns in your Google sheet. If you don't want to use the default column names, change them with this setting", + "type": "json_loader" + }, + "allow_worksheets": { + "default": [], + "help": "A list of worksheet names that should be processed (overrides worksheet_block), leave empty so all are allowed" + }, + "block_worksheets": { + "default": [], + "help": "A list of worksheet names for worksheets that should be explicitly blocked from being processed" + }, + "use_sheet_names_in_stored_paths": { + "default": true, + "help": "if True the stored files path will include 'workbook_name/worksheet_name/...'", + "type": "bool" + } + } + }, + "atlos_feeder": { + "name": "atlos_feeder", + "display_name": "Atlos Feeder", + "manifest": { + "name": "Atlos Feeder", + "author": "Bellingcat", + "type": [ + "feeder" + ], + "requires_setup": true, + "description": "\n AtlosFeeder: A feeder module that integrates with the Atlos API to fetch source material URLs for archival.\n\n ### Features\n - Connects to the Atlos API to retrieve a list of source material URLs.\n - Filters source materials based on visibility, processing status, and metadata.\n - Converts filtered source materials into `Metadata` objects with the relevant `atlos_id` and URL.\n - Iterates through paginated results using a cursor for efficient API interaction.\n\n ### Notes\n - Requires an Atlos API endpoint and a valid API token for authentication.\n - Ensures only unprocessed, visible, and ready-to-archive URLs are returned.\n - Handles pagination transparently when retrieving data from the Atlos API.\n ", + "dependencies": { + "python": [ + "loguru", + "requests" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_token": { + "type": "str", + "required": true, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "configs": { + "api_token": { + "type": "str", + "required": true, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "csv_feeder": { + "name": "csv_feeder", + "display_name": "CSV Feeder", + "manifest": { + "name": "CSV Feeder", + "author": "Bellingcat", + "type": [ + "feeder" + ], + "requires_setup": true, + "description": "\n Reads URLs from CSV files and feeds them into the archiving process.\n\n ### Features\n - Supports reading URLs from multiple input files, specified as a comma-separated list.\n - Allows specifying the column number or name to extract URLs from.\n - Skips header rows if the first value is not a valid URL.\n\n ### Setup\n - Input files should be formatted with one URL per line, with or without a header row.\n - If you have a header row, you can specify the column number or name to read URLs from using the 'column' config option.\n ", + "dependencies": { + "python": [ + "loguru" + ], + "bin": [ + "" + ] + }, + "entry_point": "csv_feeder::CSVFeeder", + "version": "1.0", + "configs": { + "files": { + "default": null, + "help": "Path to the input file(s) to read the URLs from, comma separated. Input files should be formatted with one URL per line", + "required": true, + "type": "valid_file", + "nargs": "+" + }, + "column": { + "default": null, + "help": "Column number or name to read the URLs from, 0-indexed" + } + } + }, + "configs": { + "files": { + "default": null, + "help": "Path to the input file(s) to read the URLs from, comma separated. Input files should be formatted with one URL per line", + "required": true, + "type": "valid_file", + "nargs": "+" + }, + "column": { + "default": null, + "help": "Column number or name to read the URLs from, 0-indexed" + } + } + }, + "cli_feeder": { + "name": "cli_feeder", + "display_name": "Command Line Feeder", + "manifest": { + "name": "Command Line Feeder", + "author": "Bellingcat", + "type": [ + "feeder" + ], + "requires_setup": false, + "description": "\nThe Command Line Feeder is the default enabled feeder for the Auto Archiver. It allows you to pass URLs directly to the orchestrator from the command line \nwithout the need to specify any additional configuration or command line arguments:\n\n`auto-archiver --feeder cli_feeder -- \"https://example.com/1/,https://example.com/2/\"`\n\nYou can pass multiple URLs by separating them with a space. The URLs will be processed in the order they are provided.\n\n`auto-archiver --feeder cli_feeder -- https://example.com/1/ https://example.com/2/`\n", + "dependencies": {}, + "entry_point": "cli_feeder::CLIFeeder", + "version": "1.0", + "configs": { + "urls": { + "default": null, + "help": "URL(s) to archive, either a single URL or a list of urls, should not come from config.yaml" + } + } + }, + "configs": { + "urls": { + "default": null, + "help": "URL(s) to archive, either a single URL or a list of urls, should not come from config.yaml" + } + } + }, + "instagram_api_extractor": { + "name": "instagram_api_extractor", + "display_name": "Instagram API Extractor", + "manifest": { + "name": "Instagram API Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\nArchives various types of Instagram content using the Instagrapi API.\n\nRequires setting up an Instagrapi API deployment and providing an access token and API endpoint.\n\n### Features\n- Connects to an Instagrapi API deployment to fetch Instagram profiles, posts, stories, highlights, reels, and tagged content.\n- Supports advanced configuration options, including:\n - Full profile download (all posts, stories, highlights, and tagged content).\n - Limiting the number of posts to fetch for large profiles.\n - Minimising JSON output to remove empty fields and redundant data.\n- Provides robust error handling and retries for API calls.\n- Ensures efficient media scraping, including handling nested or carousel media items.\n- Adds downloaded media and metadata to the result for further processing.\n\n### Notes\n- Requires a valid Instagrapi API token (`access_token`) and API endpoint (`api_endpoint`).\n- Full-profile downloads can be limited by setting `full_profile_max_posts`.\n- Designed to fetch content in batches for large profiles, minimising API load.\n", + "dependencies": { + "python": [ + "requests", + "loguru", + "retrying", + "tqdm" + ] + }, + "entry_point": "instagram_api_extractor::InstagramAPIExtractor", + "version": "1.0", + "configs": { + "access_token": { + "default": null, + "help": "a valid instagrapi-api token" + }, + "api_endpoint": { + "required": true, + "help": "API endpoint to use" + }, + "full_profile": { + "default": false, + "type": "bool", + "help": "if true, will download all posts, tagged posts, stories, and highlights for a profile, if false, will only download the profile pic and information." + }, + "full_profile_max_posts": { + "default": 0, + "type": "int", + "help": "Use to limit the number of posts to download when full_profile is true. 0 means no limit. limit is applied softly since posts are fetched in batch, once to: posts, tagged posts, and highlights" + }, + "minimize_json_output": { + "default": true, + "type": "bool", + "help": "if true, will remove empty values from the json output" + } + } + }, + "configs": { + "access_token": { + "default": null, + "help": "a valid instagrapi-api token" + }, + "api_endpoint": { + "required": true, + "help": "API endpoint to use" + }, + "full_profile": { + "default": false, + "type": "bool", + "help": "if true, will download all posts, tagged posts, stories, and highlights for a profile, if false, will only download the profile pic and information." + }, + "full_profile_max_posts": { + "default": 0, + "type": "int", + "help": "Use to limit the number of posts to download when full_profile is true. 0 means no limit. limit is applied softly since posts are fetched in batch, once to: posts, tagged posts, and highlights" + }, + "minimize_json_output": { + "default": true, + "type": "bool", + "help": "if true, will remove empty values from the json output" + } + } + }, + "instagram_tbot_extractor": { + "name": "instagram_tbot_extractor", + "display_name": "Instagram Telegram Bot Extractor", + "manifest": { + "name": "Instagram Telegram Bot Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\nThe `InstagramTbotExtractor` module uses a Telegram bot (`instagram_load_bot`) to fetch and archive Instagram content,\nsuch as posts and stories. It leverages the Telethon library to interact with the Telegram API, sending Instagram URLs\nto the bot and downloading the resulting media and metadata. The downloaded content is stored as `Media` objects and\nreturned as part of a `Metadata` object.\n\n### Features\n- Supports archiving Instagram posts and stories through the Telegram bot.\n- Downloads and saves media files (e.g., images, videos) in a temporary directory.\n- Captures and returns metadata, including titles and descriptions, as a `Metadata` object.\n- Automatically manages Telegram session files for secure access.\n\n### Setup\n\nTo use the `InstagramTbotExtractor`, you need to provide the following configuration settings:\n- **API ID and Hash**: Telegram API credentials obtained from [my.telegram.org/apps](https://my.telegram.org/apps).\n- **Session File**: Optional path to store the Telegram session file for future use.\n- The session file is created automatically and should be unique for each instance.\n- You may need to enter your Telegram credentials (phone) and use the a 2FA code sent to you the first time you run the extractor.:\n```2025-01-30 00:43:49.348 | INFO | auto_archiver.modules.instagram_tbot_extractor.instagram_tbot_extractor:setup:36 - SETUP instagram_tbot_extractor checking login...\nPlease enter your phone (or bot token): +447123456789\nPlease enter the code you received: 00000\nSigned in successfully as E C; remember to not break the ToS or you will risk an account ban!\n```\n ", + "dependencies": { + "python": [ + "loguru", + "telethon" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_id": { + "default": null, + "help": "telegram API_ID value, go to https://my.telegram.org/apps" + }, + "api_hash": { + "default": null, + "help": "telegram API_HASH value, go to https://my.telegram.org/apps" + }, + "session_file": { + "default": "secrets/anon-insta", + "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value." + }, + "timeout": { + "default": 45, + "type": "int", + "help": "timeout to fetch the instagram content in seconds." + } + } + }, + "configs": { + "api_id": { + "default": null, + "help": "telegram API_ID value, go to https://my.telegram.org/apps" + }, + "api_hash": { + "default": null, + "help": "telegram API_HASH value, go to https://my.telegram.org/apps" + }, + "session_file": { + "default": "secrets/anon-insta", + "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value." + }, + "timeout": { + "default": 45, + "type": "int", + "help": "timeout to fetch the instagram content in seconds." + } + } + }, + "twitter_api_extractor": { + "name": "twitter_api_extractor", + "display_name": "Twitter API Extractor", + "manifest": { + "name": "Twitter API Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\n The `TwitterApiExtractor` fetches tweets and associated media using the Twitter API. \n It supports multiple API configurations for extended rate limits and reliable access. \n Features include URL expansion, media downloads (e.g., images, videos), and structured output \n via `Metadata` and `Media` objects. Requires Twitter API credentials such as bearer tokens \n or consumer key/secret and access token/secret.\n \n ### Features\n - Fetches tweets and their metadata, including text, creation timestamp, and author information.\n - Downloads media attachments (e.g., images, videos) in high quality.\n - Supports multiple API configurations for improved rate limiting.\n - Expands shortened URLs (e.g., `t.co` links).\n - Outputs structured metadata and media using `Metadata` and `Media` objects.\n \n ### Setup\n To use the `TwitterApiExtractor`, you must provide valid Twitter API credentials via configuration:\n - **Bearer Token(s)**: A single token or a list for rate-limited API access.\n - **Consumer Key and Secret**: Required for user-authenticated API access.\n - **Access Token and Secret**: Complements the consumer key for enhanced API capabilities.\n \n Credentials can be obtained by creating a Twitter developer account at [Twitter Developer Platform](https://developer.twitter.com/en).\n ", + "dependencies": { + "python": [ + "requests", + "loguru", + "pytwitter", + "slugify" + ], + "bin": [ + "" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "bearer_token": { + "default": null, + "help": "[deprecated: see bearer_tokens] twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret" + }, + "bearer_tokens": { + "default": [], + "help": " a list of twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret, if provided you can still add those for better rate limits. CSV of bearer tokens if provided via the command line" + }, + "consumer_key": { + "default": null, + "help": "twitter API consumer_key" + }, + "consumer_secret": { + "default": null, + "help": "twitter API consumer_secret" + }, + "access_token": { + "default": null, + "help": "twitter API access_token" + }, + "access_secret": { + "default": null, + "help": "twitter API access_secret" + } + } + }, + "configs": { + "bearer_token": { + "default": null, + "help": "[deprecated: see bearer_tokens] twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret" + }, + "bearer_tokens": { + "default": [], + "help": " a list of twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret, if provided you can still add those for better rate limits. CSV of bearer tokens if provided via the command line" + }, + "consumer_key": { + "default": null, + "help": "twitter API consumer_key" + }, + "consumer_secret": { + "default": null, + "help": "twitter API consumer_secret" + }, + "access_token": { + "default": null, + "help": "twitter API access_token" + }, + "access_secret": { + "default": null, + "help": "twitter API access_secret" + } + } + }, + "instagram_extractor": { + "name": "instagram_extractor", + "display_name": "Instagram Extractor", + "manifest": { + "name": "Instagram Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\n Uses the [Instaloader library](https://instaloader.github.io/as-module.html) to download content from Instagram. This class handles both individual posts\n and user profiles, downloading as much information as possible, including images, videos, text, stories,\n highlights, and tagged posts. \n Authentication is required via username/password or a session file.\n \n ", + "dependencies": { + "python": [ + "instaloader", + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "username": { + "required": true, + "help": "a valid Instagram username" + }, + "password": { + "required": true, + "help": "the corresponding Instagram account password" + }, + "download_folder": { + "default": "instaloader", + "help": "name of a folder to temporarily download content to" + }, + "session_file": { + "default": "secrets/instaloader.session", + "help": "path to the instagram session which saves session credentials" + } + } + }, + "configs": { + "username": { + "required": true, + "help": "a valid Instagram username" + }, + "password": { + "required": true, + "help": "the corresponding Instagram account password" + }, + "download_folder": { + "default": "instaloader", + "help": "name of a folder to temporarily download content to" + }, + "session_file": { + "default": "secrets/instaloader.session", + "help": "path to the instagram session which saves session credentials" + } + } + }, + "telethon_extractor": { + "name": "telethon_extractor", + "display_name": "Telethon Extractor", + "manifest": { + "name": "Telethon Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\nThe `TelethonExtractor` uses the Telethon library to archive posts and media from Telegram channels and groups. \nIt supports private and public channels, downloading grouped posts with media, and can join channels using invite links \nif provided in the configuration. \n\n### Features\n- Fetches posts and metadata from Telegram channels and groups, including private channels.\n- Downloads media attachments (e.g., images, videos, audio) from individual posts or grouped posts.\n- Handles channel invites to join channels dynamically during setup.\n- Utilizes Telethon's capabilities for reliable Telegram interactions.\n- Outputs structured metadata and media using `Metadata` and `Media` objects.\n\n### Setup\nTo use the `TelethonExtractor`, you must configure the following:\n- **API ID and API Hash**: Obtain these from [my.telegram.org](https://my.telegram.org/apps).\n- **Session File**: Optional, but records login sessions for future use (default: `secrets/anon.session`).\n- **Bot Token**: Optional, allows access to additional content (e.g., large videos) but limits private channel archiving.\n- **Channel Invites**: Optional, specify a JSON string of invite links to join channels during setup.\n\n### First Time Login\nThe first time you run, you will be prompted to do a authentication with the phone number associated, alternatively you can put your `anon.session` in the root.\n\n\n", + "dependencies": { + "python": [ + "telethon", + "loguru", + "tqdm" + ], + "bin": [ + "" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_id": { + "default": null, + "help": "telegram API_ID value, go to https://my.telegram.org/apps" + }, + "api_hash": { + "default": null, + "help": "telegram API_HASH value, go to https://my.telegram.org/apps" + }, + "bot_token": { + "default": null, + "help": "optional, but allows access to more content such as large videos, talk to @botfather" + }, + "session_file": { + "default": "secrets/anon", + "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value." + }, + "join_channels": { + "default": true, + "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck" + }, + "channel_invites": { + "default": {}, + "help": "(JSON string) private channel invite links (format: t.me/joinchat/HASH OR t.me/+HASH) and (optional but important to avoid hanging for minutes on startup) channel id (format: CHANNEL_ID taken from a post url like https://t.me/c/CHANNEL_ID/1), the telegram account will join any new channels on setup", + "type": "json_loader" + } + } + }, + "configs": { + "api_id": { + "default": null, + "help": "telegram API_ID value, go to https://my.telegram.org/apps" + }, + "api_hash": { + "default": null, + "help": "telegram API_HASH value, go to https://my.telegram.org/apps" + }, + "bot_token": { + "default": null, + "help": "optional, but allows access to more content such as large videos, talk to @botfather" + }, + "session_file": { + "default": "secrets/anon", + "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value." + }, + "join_channels": { + "default": true, + "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck" + }, + "channel_invites": { + "default": {}, + "help": "(JSON string) private channel invite links (format: t.me/joinchat/HASH OR t.me/+HASH) and (optional but important to avoid hanging for minutes on startup) channel id (format: CHANNEL_ID taken from a post url like https://t.me/c/CHANNEL_ID/1), the telegram account will join any new channels on setup", + "type": "json_loader" + } + } + }, + "vk_extractor": { + "name": "vk_extractor", + "display_name": "VKontakte Extractor", + "manifest": { + "name": "VKontakte Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\nThe `VkExtractor` fetches posts, text, and images from VK (VKontakte) social media pages. \nThis archiver is specialized for `/wall` posts and uses the `VkScraper` library to extract \nand download content. Note that VK videos are handled separately by the `YTDownloader`.\n\n### Features\n- Extracts text, timestamps, and metadata from VK `/wall` posts.\n- Downloads associated images and attaches them to the resulting `Metadata` object.\n- Processes multiple segments of VK URLs that contain mixed content (e.g., wall, photo).\n- Outputs structured metadata and media using `Metadata` and `Media` objects.\n\n### Setup\nTo use the `VkArchiver`, you must provide valid VKontakte login credentials and session information:\n- **Username**: A valid VKontakte account username.\n- **Password**: The corresponding password for the VKontakte account.\n- **Session File**: Optional. Path to a session configuration file (`.json`) for persistent VK login.\n\nCredentials can be set in the configuration file or directly via environment variables. Ensure you \nhave access to the VKontakte API by creating an account at [VKontakte](https://vk.com/).\n", + "dependencies": { + "python": [ + "loguru", + "vk_url_scraper" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "username": { + "required": true, + "help": "valid VKontakte username" + }, + "password": { + "required": true, + "help": "valid VKontakte password" + }, + "session_file": { + "default": "secrets/vk_config.v2.json", + "help": "valid VKontakte password" + } + }, + "depends": [ + "core", + "utils" + ] + }, + "configs": { + "username": { + "required": true, + "help": "valid VKontakte username" + }, + "password": { + "required": true, + "help": "valid VKontakte password" + }, + "session_file": { + "default": "secrets/vk_config.v2.json", + "help": "valid VKontakte password" + } + } + }, + "generic_extractor": { + "name": "generic_extractor", + "display_name": "Generic Extractor", + "manifest": { + "name": "Generic Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": false, + "description": "\nThis is the generic extractor used by auto-archiver, which uses `yt-dlp` under the hood.\n\nThis module is responsible for downloading and processing media content from platforms\nsupported by `yt-dlp`, such as YouTube, Facebook, and others. It provides functionality\nfor retrieving videos, subtitles, comments, and other metadata, and it integrates with\nthe broader archiving framework.\n\n### Features\n- Supports downloading videos and playlists.\n- Retrieves metadata like titles, descriptions, upload dates, and durations.\n- Downloads subtitles and comments when enabled.\n- Configurable options for handling live streams, proxies, and more.\n- Supports authentication of websites using the 'authentication' settings from your orchestration.\n\n### Dropins\n- For websites supported by `yt-dlp` that also contain posts in addition to videos\n (e.g. Facebook, Twitter, Bluesky), dropins can be created to extract post data and create \n metadata objects. Some dropins are included in this generic_archiver by default, but\ncustom dropins can be created to handle additional websites and passed to the archiver\nvia the command line using the `--dropins` option (TODO!).\n", + "dependencies": { + "python": [ + "yt_dlp", + "requests", + "loguru", + "slugify" + ] + }, + "entry_point": "", + "version": "0.1.0", + "configs": { + "subtitles": { + "default": true, + "help": "download subtitles if available", + "type": "bool" + }, + "comments": { + "default": false, + "help": "download all comments if available, may lead to large metadata", + "type": "bool" + }, + "livestreams": { + "default": false, + "help": "if set, will download live streams, otherwise will skip them; see --max-filesize for more control", + "type": "bool" + }, + "live_from_start": { + "default": false, + "help": "if set, will download live streams from their earliest available moment, otherwise starts now.", + "type": "bool" + }, + "proxy": { + "default": "", + "help": "http/socks (https seems to not work atm) proxy to use for the webdriver, eg https://proxy-user:password@proxy-ip:port" + }, + "end_means_success": { + "default": true, + "help": "if True, any archived content will mean a 'success', if False this archiver will not return a 'success' stage; this is useful for cases when the yt-dlp will archive a video but ignore other types of content like images or text only pages that the subsequent archivers can retrieve.", + "type": "bool" + }, + "allow_playlist": { + "default": false, + "help": "If True will also download playlists, set to False if the expectation is to download a single video.", + "type": "bool" + }, + "max_downloads": { + "default": "inf", + "help": "Use to limit the number of videos to download when a channel or long page is being extracted. 'inf' means no limit." + } + } + }, + "configs": { + "subtitles": { + "default": true, + "help": "download subtitles if available", + "type": "bool" + }, + "comments": { + "default": false, + "help": "download all comments if available, may lead to large metadata", + "type": "bool" + }, + "livestreams": { + "default": false, + "help": "if set, will download live streams, otherwise will skip them; see --max-filesize for more control", + "type": "bool" + }, + "live_from_start": { + "default": false, + "help": "if set, will download live streams from their earliest available moment, otherwise starts now.", + "type": "bool" + }, + "proxy": { + "default": "", + "help": "http/socks (https seems to not work atm) proxy to use for the webdriver, eg https://proxy-user:password@proxy-ip:port" + }, + "end_means_success": { + "default": true, + "help": "if True, any archived content will mean a 'success', if False this archiver will not return a 'success' stage; this is useful for cases when the yt-dlp will archive a video but ignore other types of content like images or text only pages that the subsequent archivers can retrieve.", + "type": "bool" + }, + "allow_playlist": { + "default": false, + "help": "If True will also download playlists, set to False if the expectation is to download a single video.", + "type": "bool" + }, + "max_downloads": { + "default": "inf", + "help": "Use to limit the number of videos to download when a channel or long page is being extracted. 'inf' means no limit." + } + } + }, + "telegram_extractor": { + "name": "telegram_extractor", + "display_name": "Telegram Extractor", + "manifest": { + "name": "Telegram Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": false, + "description": " \n The `TelegramExtractor` retrieves publicly available media content from Telegram message links without requiring login credentials. \n It processes URLs to fetch images and videos embedded in Telegram messages, ensuring a structured output using `Metadata` \n and `Media` objects. Recommended for scenarios where login-based archiving is not viable, although `telethon_archiver` \n is advised for more comprehensive functionality, and higher quality media extraction.\n \n ### Features\n- Extracts images and videos from public Telegram message links (`t.me`).\n- Processes HTML content of messages to retrieve embedded media.\n- Sets structured metadata, including timestamps, content, and media details.\n- Does not require user authentication for Telegram.\n\n ", + "dependencies": { + "python": [ + "requests", + "bs4", + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "wayback_extractor_enricher": { + "name": "wayback_extractor_enricher", + "display_name": "Wayback Machine Enricher (and Extractor)", + "manifest": { + "name": "Wayback Machine Enricher (and Extractor)", + "author": "Bellingcat", + "type": [ + "enricher", + "extractor" + ], + "requires_setup": true, + "description": "\n Submits the current URL to the Wayback Machine for archiving and returns either a job ID or the completed archive URL.\n\n ### Features\n - Archives URLs using the Internet Archive's Wayback Machine API.\n - Supports conditional archiving based on the existence of prior archives within a specified time range.\n - Provides proxies for HTTP and HTTPS requests.\n - Fetches and confirms the archive URL or provides a job ID for later status checks.\n\n ### Notes\n - Requires a valid Wayback Machine API key and secret.\n - Handles rate-limiting by Wayback Machine and retries status checks with exponential backoff.\n \n ### Steps to Get an Wayback API Key:\n - Sign up for an account at [Internet Archive](https://archive.org/account/signup).\n - Log in to your account.\n - Navigte to your [account settings](https://archive.org/account).\n - or: https://archive.org/developers/tutorial-get-ia-credentials.html\n - Under Wayback Machine API Keys, generate a new key.\n - Note down your API key and secret, as they will be required for authentication.\n ", + "dependencies": { + "python": [ + "loguru", + "requests" + ] + }, + "entry_point": "wayback_extractor_enricher::WaybackExtractorEnricher", + "version": "1.0", + "configs": { + "timeout": { + "default": 15, + "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually." + }, + "if_not_archived_within": { + "default": null, + "help": "only tell wayback to archive if no archive is available before the number of seconds specified, use None to ignore this option. For more information: https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA" + }, + "key": { + "required": true, + "help": "wayback API key. to get credentials visit https://archive.org/account/s3.php" + }, + "secret": { + "required": true, + "help": "wayback API secret. to get credentials visit https://archive.org/account/s3.php" + }, + "proxy_http": { + "default": null, + "help": "http proxy to use for wayback requests, eg http://proxy-user:password@proxy-ip:port" + }, + "proxy_https": { + "default": null, + "help": "https proxy to use for wayback requests, eg https://proxy-user:password@proxy-ip:port" + } + } + }, + "configs": { + "timeout": { + "default": 15, + "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually." + }, + "if_not_archived_within": { + "default": null, + "help": "only tell wayback to archive if no archive is available before the number of seconds specified, use None to ignore this option. For more information: https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA" + }, + "key": { + "required": true, + "help": "wayback API key. to get credentials visit https://archive.org/account/s3.php" + }, + "secret": { + "required": true, + "help": "wayback API secret. to get credentials visit https://archive.org/account/s3.php" + }, + "proxy_http": { + "default": null, + "help": "http proxy to use for wayback requests, eg http://proxy-user:password@proxy-ip:port" + }, + "proxy_https": { + "default": null, + "help": "https proxy to use for wayback requests, eg https://proxy-user:password@proxy-ip:port" + } + } + }, + "wacz_extractor_enricher": { + "name": "wacz_extractor_enricher", + "display_name": "WACZ Enricher (and Extractor)", + "manifest": { + "name": "WACZ Enricher (and Extractor)", + "author": "Bellingcat", + "type": [ + "enricher", + "extractor" + ], + "requires_setup": true, + "description": "\n Creates .WACZ archives of web pages using the `browsertrix-crawler` tool, with options for media extraction and screenshot saving.\n [Browsertrix-crawler](https://crawler.docs.browsertrix.com/user-guide/) is a headless browser-based crawler that archives web pages in WACZ format.\n\n ### Features\n - Archives web pages into .WACZ format using Docker or direct invocation of `browsertrix-crawler`.\n - Supports custom profiles for archiving private or dynamic content.\n - Extracts media (images, videos, audio) and screenshots from the archive, optionally adding them to the enrichment pipeline.\n - Generates metadata from the archived page's content and structure (e.g., titles, text).\n\n ### Notes\n - Requires Docker for running `browsertrix-crawler` .\n - Configurable via parameters for timeout, media extraction, screenshots, and proxy settings.\n ", + "dependencies": { + "python": [ + "loguru", + "jsonlines", + "warcio" + ], + "bin": [ + "docker" + ] + }, + "entry_point": "wacz_extractor_enricher::WaczExtractorEnricher", + "version": "1.0", + "configs": { + "profile": { + "default": null, + "help": "browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix-crawler#creating-and-using-browser-profiles)." + }, + "docker_commands": { + "default": null, + "help": "if a custom docker invocation is needed" + }, + "timeout": { + "default": 120, + "help": "timeout for WACZ generation in seconds", + "type": "int" + }, + "extract_media": { + "default": false, + "type": "bool", + "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "extract_screenshot": { + "default": true, + "type": "bool", + "help": "If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "socks_proxy_host": { + "default": null, + "help": "SOCKS proxy host for browsertrix-crawler, use in combination with socks_proxy_port. eg: user:password@host" + }, + "socks_proxy_port": { + "default": null, + "type": "int", + "help": "SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234" + }, + "proxy_server": { + "default": null, + "help": "SOCKS server proxy URL, in development" + } + } + }, + "configs": { + "profile": { + "default": null, + "help": "browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix-crawler#creating-and-using-browser-profiles)." + }, + "docker_commands": { + "default": null, + "help": "if a custom docker invocation is needed" + }, + "timeout": { + "default": 120, + "help": "timeout for WACZ generation in seconds", + "type": "int" + }, + "extract_media": { + "default": false, + "type": "bool", + "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "extract_screenshot": { + "default": true, + "type": "bool", + "help": "If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "socks_proxy_host": { + "default": null, + "help": "SOCKS proxy host for browsertrix-crawler, use in combination with socks_proxy_port. eg: user:password@host" + }, + "socks_proxy_port": { + "default": null, + "type": "int", + "help": "SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234" + }, + "proxy_server": { + "default": null, + "help": "SOCKS server proxy URL, in development" + } + } + }, + "metadata_enricher": { + "name": "metadata_enricher", + "display_name": "Media Metadata Enricher", + "manifest": { + "name": "Media Metadata Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": true, + "description": "\n Extracts metadata information from files using ExifTool.\n\n ### Features\n - Uses ExifTool to extract detailed metadata from media files.\n - Processes file-specific data like camera settings, geolocation, timestamps, and other embedded metadata.\n - Adds extracted metadata to the corresponding `Media` object within the `Metadata`.\n\n ### Notes\n - Requires ExifTool to be installed and accessible via the system's PATH.\n - Skips enrichment for files where metadata extraction fails.\n ", + "dependencies": { + "python": [ + "loguru" + ], + "bin": [ + "exiftool" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "timestamping_enricher": { + "name": "timestamping_enricher", + "display_name": "Timestamping Enricher", + "manifest": { + "name": "Timestamping Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": true, + "description": "\n Generates RFC3161-compliant timestamp tokens using Time Stamp Authorities (TSA) for archived files.\n\n ### Features\n - Creates timestamp tokens to prove the existence of files at a specific time, useful for legal and authenticity purposes.\n - Aggregates file hashes into a text file and timestamps the concatenated data.\n - Uses multiple Time Stamp Authorities (TSAs) to ensure reliability and redundancy.\n - Validates timestamping certificates against trusted Certificate Authorities (CAs) using the `certifi` trust store.\n\n ### Notes\n - Should be run after the `hash_enricher` to ensure file hashes are available.\n - Requires internet access to interact with the configured TSAs.\n ", + "dependencies": { + "python": [ + "loguru", + "slugify", + "tsp_client", + "asn1crypto", + "certvalidator", + "certifi" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "tsa_urls": { + "default": [ + "http://timestamp.digicert.com", + "http://timestamp.identrust.com", + "http://timestamp.globalsign.com/tsa/r6advanced1", + "http://tss.accv.es:8318/tsa" + ], + "help": "List of RFC3161 Time Stamp Authorities to use, separate with commas if passed via the command line." + } + } + }, + "configs": { + "tsa_urls": { + "default": [ + "http://timestamp.digicert.com", + "http://timestamp.identrust.com", + "http://timestamp.globalsign.com/tsa/r6advanced1", + "http://tss.accv.es:8318/tsa" + ], + "help": "List of RFC3161 Time Stamp Authorities to use, separate with commas if passed via the command line." + } + } + }, + "screenshot_enricher": { + "name": "screenshot_enricher", + "display_name": "Screenshot Enricher", + "manifest": { + "name": "Screenshot Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": true, + "description": "\n Captures screenshots and optionally saves web pages as PDFs using a WebDriver.\n\n ### Features\n - Takes screenshots of web pages, with configurable width, height, and timeout settings.\n - Optionally saves pages as PDFs, with additional configuration for PDF printing options.\n - Bypasses URLs detected as authentication walls.\n - Integrates seamlessly with the metadata enrichment pipeline, adding screenshots and PDFs as media.\n\n ### Notes\n - Requires a WebDriver (e.g., ChromeDriver) installed and accessible via the system's PATH.\n ", + "dependencies": { + "python": [ + "loguru", + "selenium" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "width": { + "default": 1280, + "help": "width of the screenshots" + }, + "height": { + "default": 720, + "help": "height of the screenshots" + }, + "timeout": { + "default": 60, + "help": "timeout for taking the screenshot" + }, + "sleep_before_screenshot": { + "default": 4, + "help": "seconds to wait for the pages to load before taking screenshot" + }, + "http_proxy": { + "default": "", + "help": "http proxy to use for the webdriver, eg http://proxy-user:password@proxy-ip:port" + }, + "save_to_pdf": { + "default": false, + "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter" + }, + "print_options": { + "default": {}, + "help": "options to pass to the pdf printer" + } + } + }, + "configs": { + "width": { + "default": 1280, + "help": "width of the screenshots" + }, + "height": { + "default": 720, + "help": "height of the screenshots" + }, + "timeout": { + "default": 60, + "help": "timeout for taking the screenshot" + }, + "sleep_before_screenshot": { + "default": 4, + "help": "seconds to wait for the pages to load before taking screenshot" + }, + "http_proxy": { + "default": "", + "help": "http proxy to use for the webdriver, eg http://proxy-user:password@proxy-ip:port" + }, + "save_to_pdf": { + "default": false, + "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter" + }, + "print_options": { + "default": {}, + "help": "options to pass to the pdf printer" + } + } + }, + "whisper_enricher": { + "name": "whisper_enricher", + "display_name": "Whisper Enricher", + "manifest": { + "name": "Whisper Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": true, + "description": "\n Integrates with a Whisper API service to transcribe, translate, or detect the language of audio and video files.\n\n ### Features\n - Submits audio or video files to a Whisper API deployment for processing.\n - Supports operations such as transcription, translation, and language detection.\n - Optionally generates SRT subtitle files for video content.\n - Integrates with S3-compatible storage systems to make files publicly accessible for processing.\n - Handles job submission, status checking, artifact retrieval, and cleanup.\n\n ### Notes\n - Requires a Whisper API endpoint and API key for authentication.\n - Only compatible with S3-compatible storage systems for media file accessibility.\n - ** This stores the media files in S3 prior to enriching them as Whisper requires public URLs to access the media files.\n - Handles multiple jobs and retries for failed or incomplete processing.\n ", + "dependencies": { + "python": [ + "s3_storage", + "loguru", + "requests" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_endpoint": { + "required": true, + "help": "WhisperApi api endpoint, eg: https://whisperbox-api.com/api/v1, a deployment of https://github.com/bellingcat/whisperbox-transcribe." + }, + "api_key": { + "required": true, + "help": "WhisperApi api key for authentication" + }, + "include_srt": { + "default": false, + "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)." + }, + "timeout": { + "default": 90, + "help": "How many seconds to wait at most for a successful job completion." + }, + "action": { + "default": "translate", + "help": "which Whisper operation to execute", + "choices": [ + "transcribe", + "translate", + "language_detection" + ] + } + } + }, + "configs": { + "api_endpoint": { + "required": true, + "help": "WhisperApi api endpoint, eg: https://whisperbox-api.com/api/v1, a deployment of https://github.com/bellingcat/whisperbox-transcribe." + }, + "api_key": { + "required": true, + "help": "WhisperApi api key for authentication" + }, + "include_srt": { + "default": false, + "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)." + }, + "timeout": { + "default": 90, + "help": "How many seconds to wait at most for a successful job completion." + }, + "action": { + "default": "translate", + "help": "which Whisper operation to execute", + "choices": [ + "transcribe", + "translate", + "language_detection" + ] + } + } + }, + "thumbnail_enricher": { + "name": "thumbnail_enricher", + "display_name": "Thumbnail Enricher", + "manifest": { + "name": "Thumbnail Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": "\n Generates thumbnails for video files to provide visual previews.\n\n ### Features\n - Processes video files and generates evenly distributed thumbnails.\n - Calculates the number of thumbnails based on video duration, `thumbnails_per_minute`, and `max_thumbnails`.\n - Distributes thumbnails equally across the video's duration and stores them as media objects.\n - Adds metadata for each thumbnail, including timestamps and IDs.\n\n ### Notes\n - Requires `ffmpeg` to be installed and accessible via the system's PATH.\n - Handles videos without pre-existing duration metadata by probing with `ffmpeg`.\n - Skips enrichment for non-video media files.\n ", + "dependencies": { + "python": [ + "loguru", + "ffmpeg" + ], + "bin": [ + "ffmpeg" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "thumbnails_per_minute": { + "default": 60, + "type": "int", + "help": "how many thumbnails to generate per minute of video, can be limited by max_thumbnails" + }, + "max_thumbnails": { + "default": 16, + "type": "int", + "help": "limit the number of thumbnails to generate per video, 0 means no limit" + } + } + }, + "configs": { + "thumbnails_per_minute": { + "default": 60, + "type": "int", + "help": "how many thumbnails to generate per minute of video, can be limited by max_thumbnails" + }, + "max_thumbnails": { + "default": 16, + "type": "int", + "help": "limit the number of thumbnails to generate per video, 0 means no limit" + } + } + }, + "meta_enricher": { + "name": "meta_enricher", + "display_name": "Archive Metadata Enricher", + "manifest": { + "name": "Archive Metadata Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": " \n Adds metadata information about the archive operations, Adds metadata about archive operations, including file sizes and archive duration./\n To be included at the end of all enrichments.\n \n ### Features\n- Calculates the total size of all archived media files, storing the result in human-readable and byte formats.\n- Computes the duration of the archival process, storing the elapsed time in seconds.\n- Ensures all enrichments are performed only if the `Metadata` object contains valid data.\n- Adds detailed metadata to provide insights into file sizes and archival performance.\n\n### Notes\n- Skips enrichment if no media or metadata is available in the `Metadata` object.\n- File sizes are calculated using the `os.stat` module, ensuring accurate byte-level reporting.\n", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "pdq_hash_enricher": { + "name": "pdq_hash_enricher", + "display_name": "PDQ Hash Enricher", + "manifest": { + "name": "PDQ Hash Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": "\n PDQ Hash Enricher for generating perceptual hashes of media files.\n\n ### Features\n - Calculates perceptual hashes for image files using the PDQ hashing algorithm.\n - Enables detection of duplicate or near-duplicate visual content.\n - Processes images stored in `Metadata` objects, adding computed hashes to the corresponding `Media` entries.\n - Skips non-image media or files unsuitable for hashing (e.g., corrupted or unsupported formats).\n\n ### Notes\n - Best used after enrichers like `thumbnail_enricher` or `screenshot_enricher` to ensure images are available.\n - Uses the `pdqhash` library to compute 256-bit perceptual hashes, which are stored as hexadecimal strings.\n ", + "dependencies": { + "python": [ + "loguru", + "pdqhash", + "numpy", + "PIL" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "ssl_enricher": { + "name": "ssl_enricher", + "display_name": "SSL Certificate Enricher", + "manifest": { + "name": "SSL Certificate Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": "\n Retrieves SSL certificate information for a domain and stores it as a file.\n\n ### Features\n - Fetches SSL certificates for domains using the HTTPS protocol.\n - Stores certificates in PEM format and adds them as media to the metadata.\n - Skips enrichment if no media has been archived, based on the `skip_when_nothing_archived` configuration.\n\n ### Notes\n - Requires the target URL to use the HTTPS scheme; other schemes are not supported.\n ", + "dependencies": { + "python": [ + "loguru", + "slugify" + ] + }, + "entry_point": "ssl_enricher::SSLEnricher", + "version": "1.0", + "configs": { + "skip_when_nothing_archived": { + "default": true, + "type": "bool", + "help": "if true, will skip enriching when no media is archived" + } + } + }, + "configs": { + "skip_when_nothing_archived": { + "default": true, + "type": "bool", + "help": "if true, will skip enriching when no media is archived" + } + } + }, + "hash_enricher": { + "name": "hash_enricher", + "display_name": "Hash Enricher", + "manifest": { + "name": "Hash Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": "\nGenerates cryptographic hashes for media files to ensure data integrity and authenticity.\n\n### Features\n- Calculates cryptographic hashes (SHA-256 or SHA3-512) for media files stored in `Metadata` objects.\n- Ensures content authenticity, integrity validation, and duplicate identification.\n- Efficiently processes large files by reading file bytes in configurable chunk sizes.\n- Supports dynamic configuration of hash algorithms and chunk sizes.\n- Updates media metadata with the computed hash value in the format `:`.\n\n### Notes\n- Default hash algorithm is SHA-256, but SHA3-512 is also supported.\n- Chunk size defaults to 16 MB but can be adjusted based on memory requirements.\n- Useful for workflows requiring hash-based content validation or deduplication.\n", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "algorithm": { + "default": "SHA-256", + "help": "hash algorithm to use", + "choices": [ + "SHA-256", + "SHA3-512" + ] + }, + "chunksize": { + "default": 16000000, + "help": "number of bytes to use when reading files in chunks (if this value is too large you will run out of RAM), default is 16MB", + "type": "int" + } + } + }, + "configs": { + "algorithm": { + "default": "SHA-256", + "help": "hash algorithm to use", + "choices": [ + "SHA-256", + "SHA3-512" + ] + }, + "chunksize": { + "default": 16000000, + "help": "number of bytes to use when reading files in chunks (if this value is too large you will run out of RAM), default is 16MB", + "type": "int" + } + } + }, + "atlos_db": { + "name": "atlos_db", + "display_name": "Atlos Database", + "manifest": { + "name": "Atlos Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": true, + "description": "\nHandles integration with the Atlos platform for managing archival results.\n\n### Features\n- Outputs archival results to the Atlos API for storage and tracking.\n- Updates failure status with error details when archiving fails.\n- Processes and formats metadata, including ISO formatting for datetime fields.\n- Skips processing for items without an Atlos ID.\n\n### Setup\nRequired configs:\n- atlos_url: Base URL for the Atlos API.\n- api_token: Authentication token for API access.\n", + "dependencies": { + "python": [ + "loguru", + "" + ], + "bin": [ + "" + ] + }, + "entry_point": "atlos_db::AtlosDb", + "version": "1.0", + "configs": { + "api_token": { + "default": null, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": true, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "configs": { + "api_token": { + "default": null, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": true, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "api_db": { + "name": "api_db", + "display_name": "Auto Archiver API Database", + "manifest": { + "name": "Auto Archiver API Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": true, + "description": "\n Provides integration with the Auto Archiver API for querying and storing archival data.\n\n### Features\n- **API Integration**: Supports querying for existing archives and submitting results.\n- **Duplicate Prevention**: Avoids redundant archiving when `use_api_cache` is disabled.\n- **Configurable**: Supports settings like API endpoint, authentication token, tags, and permissions.\n- **Tagging and Metadata**: Adds tags and manages metadata for archives.\n- **Optional Storage**: Archives results conditionally based on configuration.\n\n### Setup\nRequires access to an Auto Archiver API instance and a valid API token.\n ", + "dependencies": { + "python": [ + "requests", + "loguru" + ] + }, + "entry_point": "api_db::AAApiDb", + "version": "1.0", + "configs": { + "api_endpoint": { + "required": true, + "help": "API endpoint where calls are made to" + }, + "api_token": { + "default": null, + "help": "API Bearer token." + }, + "public": { + "default": false, + "type": "bool", + "help": "whether the URL should be publicly available via the API" + }, + "author_id": { + "default": null, + "help": "which email to assign as author" + }, + "group_id": { + "default": null, + "help": "which group of users have access to the archive in case public=false as author" + }, + "use_api_cache": { + "default": true, + "type": "bool", + "help": "if False then the API database will be queried prior to any archiving operations and stop if the link has already been archived" + }, + "store_results": { + "default": true, + "type": "bool", + "help": "when set, will send the results to the API database." + }, + "tags": { + "default": [], + "help": "what tags to add to the archived URL" + } + } + }, + "configs": { + "api_endpoint": { + "required": true, + "help": "API endpoint where calls are made to" + }, + "api_token": { + "default": null, + "help": "API Bearer token." + }, + "public": { + "default": false, + "type": "bool", + "help": "whether the URL should be publicly available via the API" + }, + "author_id": { + "default": null, + "help": "which email to assign as author" + }, + "group_id": { + "default": null, + "help": "which group of users have access to the archive in case public=false as author" + }, + "use_api_cache": { + "default": true, + "type": "bool", + "help": "if False then the API database will be queried prior to any archiving operations and stop if the link has already been archived" + }, + "store_results": { + "default": true, + "type": "bool", + "help": "when set, will send the results to the API database." + }, + "tags": { + "default": [], + "help": "what tags to add to the archived URL" + } + } + }, + "gsheet_db": { + "name": "gsheet_db", + "display_name": "Google Sheets Database", + "manifest": { + "name": "Google Sheets Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": true, + "description": "\n GsheetsDatabase:\n Handles integration with Google Sheets for tracking archival tasks.\n\n### Features\n- Updates a Google Sheet with the status of the archived URLs, including in progress, success or failure, and method used.\n- Saves metadata such as title, text, timestamp, hashes, screenshots, and media URLs to designated columns.\n- Formats media-specific metadata, such as thumbnails and PDQ hashes for the sheet.\n- Skips redundant updates for empty or invalid data fields.\n\n### Notes\n- Currently works only with metadata provided by GsheetFeeder. \n- Requires configuration of a linked Google Sheet and appropriate API credentials.\n ", + "dependencies": { + "python": [ + "loguru", + "gspread", + "slugify" + ] + }, + "entry_point": "gsheet_db::GsheetsDb", + "version": "1.0", + "configs": { + "allow_worksheets": { + "default": [], + "help": "(CSV) only worksheets whose name is included in allow are included (overrides worksheet_block), leave empty so all are allowed" + }, + "block_worksheets": { + "default": [], + "help": "(CSV) explicitly block some worksheets from being processed" + }, + "use_sheet_names_in_stored_paths": { + "default": true, + "type": "bool", + "help": "if True the stored files path will include 'workbook_name/worksheet_name/...'" + } + } + }, + "configs": { + "allow_worksheets": { + "default": [], + "help": "(CSV) only worksheets whose name is included in allow are included (overrides worksheet_block), leave empty so all are allowed" + }, + "block_worksheets": { + "default": [], + "help": "(CSV) explicitly block some worksheets from being processed" + }, + "use_sheet_names_in_stored_paths": { + "default": true, + "type": "bool", + "help": "if True the stored files path will include 'workbook_name/worksheet_name/...'" + } + } + }, + "console_db": { + "name": "console_db", + "display_name": "Console Database", + "manifest": { + "name": "Console Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": false, + "description": "\nProvides a simple database implementation that outputs archival results and status updates to the console.\n\n### Features\n- Logs the status of archival tasks directly to the console, including:\n - started\n - failed (with error details)\n - aborted\n - done (with optional caching status)\n- Useful for debugging or lightweight setups where no external database is required.\n\n### Setup\nNo additional configuration is required.\n", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "csv_db": { + "name": "csv_db", + "display_name": "CSV Database", + "manifest": { + "name": "CSV Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": false, + "description": "\nHandles exporting archival results to a CSV file.\n\n### Features\n- Saves archival metadata as rows in a CSV file.\n- Automatically creates the CSV file with a header if it does not exist.\n- Appends new metadata entries to the existing file.\n\n### Setup\nRequired config:\n- csv_file: Path to the CSV file where results will be stored (default: \"db.csv\").\n", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "csv_db::CSVDb", + "version": "1.0", + "configs": { + "csv_file": { + "default": "db.csv", + "help": "CSV file name to save metadata to" + } + } + }, + "configs": { + "csv_file": { + "default": "db.csv", + "help": "CSV file name to save metadata to" + } + } + }, + "gdrive_storage": { + "name": "gdrive_storage", + "display_name": "Google Drive Storage", + "manifest": { + "name": "Google Drive Storage", + "author": "Dave Mateer", + "type": [ + "storage" + ], + "requires_setup": true, + "description": "\n \n GDriveStorage: A storage module for saving archived content to Google Drive.\n\n Source Documentation: https://davemateer.com/2022/04/28/google-drive-with-python\n\n ### Features\n - Saves media files to Google Drive, organizing them into folders based on the provided path structure.\n - Supports OAuth token-based authentication or service account credentials for API access.\n - Automatically creates folders in Google Drive if they don't exist.\n - Retrieves CDN URLs for stored files, enabling easy sharing and access.\n\n ### Notes\n - Requires setup with either a Google OAuth token or a service account JSON file.\n - Files are uploaded to the specified `root_folder_id` and organized by the `media.key` structure.\n - Automatically handles Google Drive API token refreshes for long-running jobs.\n \n ## Overview\nThis module integrates Google Drive as a storage backend, enabling automatic folder creation and file uploads. It supports authentication via **service accounts** (recommended for automation) or **OAuth tokens** (for user-based authentication).\n\n## Features\n- Saves files to Google Drive, organizing them into structured folders.\n- Supports both **service account** and **OAuth token** authentication.\n- Automatically creates folders if they don't exist.\n- Generates public URLs for easy file sharing.\n\n## Setup Guide\n1. **Enable Google Drive API**\n - Create a Google Cloud project at [Google Cloud Console](https://console.cloud.google.com/)\n - Enable the **Google Drive API**.\n\n2. **Set Up a Google Drive Folder**\n - Create a folder in **Google Drive** and copy its **folder ID** from the URL.\n - Add the **folder ID** to your configuration (`orchestration.yaml`):\n ```yaml\n root_folder_id: \"FOLDER_ID\"\n ```\n\n3. **Authentication Options**\n - **Option 1: Service Account (Recommended)**\n - Create a **service account** in Google Cloud IAM.\n - Download the JSON key file and save it as:\n ```\n secrets/service_account.json\n ```\n - **Share your Drive folder** with the service account\u2019s `client_email` (found in the JSON file).\n \n - **Option 2: OAuth Token (User Authentication)**\n - Create OAuth **Desktop App credentials** in Google Cloud.\n - Save the credentials as:\n ```\n secrets/oauth_credentials.json\n ```\n - Generate an OAuth token by running:\n ```sh\n python scripts/create_update_gdrive_oauth_token.py -c secrets/oauth_credentials.json\n ```\n\n \n Notes on the OAuth token:\n Tokens are refreshed after 1 hour however keep working for 7 days (tbc)\n so as long as the job doesn't last for 7 days then this method of refreshing only once per run will work\n see this link for details on the token:\n https://davemateer.com/2022/04/28/google-drive-with-python#tokens\n \n \n", + "dependencies": { + "python": [ + "loguru", + "googleapiclient", + "google" + ] + }, + "entry_point": "gdrive_storage::GDriveStorage", + "version": "1.0", + "configs": { + "path_generator": { + "default": "url", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "root_folder_id": { + "required": true, + "help": "root google drive folder ID to use as storage, found in URL: 'https://drive.google.com/drive/folders/FOLDER_ID'" + }, + "oauth_token": { + "default": null, + "help": "JSON filename with Google Drive OAuth token: check auto-archiver repository scripts folder for create_update_gdrive_oauth_token.py. NOTE: storage used will count towards owner of GDrive folder, therefore it is best to use oauth_token_filename over service_account." + }, + "service_account": { + "default": "secrets/service_account.json", + "help": "service account JSON file path, same as used for Google Sheets. NOTE: storage used will count towards the developer account." + } + } + }, + "configs": { + "path_generator": { + "default": "url", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "root_folder_id": { + "required": true, + "help": "root google drive folder ID to use as storage, found in URL: 'https://drive.google.com/drive/folders/FOLDER_ID'" + }, + "oauth_token": { + "default": null, + "help": "JSON filename with Google Drive OAuth token: check auto-archiver repository scripts folder for create_update_gdrive_oauth_token.py. NOTE: storage used will count towards owner of GDrive folder, therefore it is best to use oauth_token_filename over service_account." + }, + "service_account": { + "default": "secrets/service_account.json", + "help": "service account JSON file path, same as used for Google Sheets. NOTE: storage used will count towards the developer account." + } + } + }, + "atlos_storage": { + "name": "atlos_storage", + "display_name": "Atlos Storage", + "manifest": { + "name": "Atlos Storage", + "author": "Bellingcat", + "type": [ + "storage" + ], + "requires_setup": true, + "description": "\n Stores media files in a [Atlos](https://www.atlos.org/).\n\n ### Features\n - Saves media files to Atlos, organizing them into folders based on the provided path structure.\n\n ### Notes\n - Requires setup with Atlos credentials.\n - Files are uploaded to the specified `root_folder_id` and organized by the `media.key` structure.\n ", + "dependencies": { + "python": [ + "loguru", + "boto3" + ], + "bin": [] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_token": { + "default": null, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": true, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "configs": { + "api_token": { + "default": null, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": true, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "s3_storage": { + "name": "s3_storage", + "display_name": "S3 Storage", + "manifest": { + "name": "S3 Storage", + "author": "Bellingcat", + "type": [ + "storage" + ], + "requires_setup": true, + "description": "\n S3Storage: A storage module for saving media files to an S3-compatible object storage.\n\n ### Features\n - Uploads media files to an S3 bucket with customizable configurations.\n - Supports `random_no_duplicate` mode to avoid duplicate uploads by checking existing files based on SHA-256 hashes.\n - Automatically generates unique paths for files when duplicates are found.\n - Configurable endpoint and CDN URL for different S3-compatible providers.\n - Supports both private and public file storage, with public files being readable online.\n\n ### Notes\n - Requires S3 credentials (API key and secret) and a bucket name to function.\n - The `random_no_duplicate` option ensures no duplicate uploads by leveraging hash-based folder structures.\n - Uses `boto3` for interaction with the S3 API.\n - Depends on the `HashEnricher` module for hash calculation.\n ", + "dependencies": { + "python": [ + "hash_enricher", + "boto3", + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "path_generator": { + "default": "flat", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "bucket": { + "default": null, + "help": "S3 bucket name" + }, + "region": { + "default": null, + "help": "S3 region name" + }, + "key": { + "default": null, + "help": "S3 API key" + }, + "secret": { + "default": null, + "help": "S3 API secret" + }, + "random_no_duplicate": { + "default": false, + "type": "bool", + "help": "if set, it will override `path_generator`, `filename_generator` and `folder`. It will check if the file already exists and if so it will not upload it again. Creates a new root folder path `no-dups/`" + }, + "endpoint_url": { + "default": "https://{region}.digitaloceanspaces.com", + "help": "S3 bucket endpoint, {region} are inserted at runtime" + }, + "cdn_url": { + "default": "https://{bucket}.{region}.cdn.digitaloceanspaces.com/{key}", + "help": "S3 CDN url, {bucket}, {region} and {key} are inserted at runtime" + }, + "private": { + "default": false, + "type": "bool", + "help": "if true S3 files will not be readable online" + } + } + }, + "configs": { + "path_generator": { + "default": "flat", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "bucket": { + "default": null, + "help": "S3 bucket name" + }, + "region": { + "default": null, + "help": "S3 region name" + }, + "key": { + "default": null, + "help": "S3 API key" + }, + "secret": { + "default": null, + "help": "S3 API secret" + }, + "random_no_duplicate": { + "default": false, + "type": "bool", + "help": "if set, it will override `path_generator`, `filename_generator` and `folder`. It will check if the file already exists and if so it will not upload it again. Creates a new root folder path `no-dups/`" + }, + "endpoint_url": { + "default": "https://{region}.digitaloceanspaces.com", + "help": "S3 bucket endpoint, {region} are inserted at runtime" + }, + "cdn_url": { + "default": "https://{bucket}.{region}.cdn.digitaloceanspaces.com/{key}", + "help": "S3 CDN url, {bucket}, {region} and {key} are inserted at runtime" + }, + "private": { + "default": false, + "type": "bool", + "help": "if true S3 files will not be readable online" + } + } + }, + "local_storage": { + "name": "local_storage", + "display_name": "Local Storage", + "manifest": { + "name": "Local Storage", + "author": "Bellingcat", + "type": [ + "storage" + ], + "requires_setup": false, + "description": "\n LocalStorage: A storage module for saving archived content locally on the filesystem.\n\n ### Features\n - Saves archived media files to a specified folder on the local filesystem.\n - Maintains file metadata during storage using `shutil.copy2`.\n - Supports both absolute and relative paths for stored files, configurable via `save_absolute`.\n - Automatically creates directories as needed for storing files.\n\n ### Notes\n - Default storage folder is `./archived`, but this can be changed via the `save_to` configuration.\n - The `save_absolute` option can reveal the file structure in output formats; use with caution.\n ", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "path_generator": { + "default": "flat", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "save_to": { + "default": "./local_archive", + "help": "folder where to save archived content" + }, + "save_absolute": { + "default": false, + "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)" + } + } + }, + "configs": { + "path_generator": { + "default": "flat", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "save_to": { + "default": "./local_archive", + "help": "folder where to save archived content" + }, + "save_absolute": { + "default": false, + "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)" + } + } + }, + "mute_formatter": { + "name": "mute_formatter", + "display_name": "Mute Formatter", + "manifest": { + "name": "Mute Formatter", + "author": "Bellingcat", + "type": [ + "formatter" + ], + "requires_setup": true, + "description": " Default formatter.\n ", + "dependencies": {}, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "html_formatter": { + "name": "html_formatter", + "display_name": "HTML Formatter", + "manifest": { + "name": "HTML Formatter", + "author": "Bellingcat", + "type": [ + "formatter" + ], + "requires_setup": false, + "description": " ", + "dependencies": { + "python": [ + "hash_enricher", + "loguru", + "jinja2" + ], + "bin": [ + "" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "detect_thumbnails": { + "default": true, + "help": "if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00'", + "type": "bool" + } + } + }, + "configs": { + "detect_thumbnails": { + "default": true, + "help": "if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00'", + "type": "bool" + } + } + } + }, + "steps": { + "feeder": [ + "cli_feeder", + "gsheet_feeder", + "atlos_feeder", + "csv_feeder" + ], + "extractor": [ + "wayback_extractor_enricher", + "wacz_extractor_enricher", + "instagram_api_extractor", + "instagram_tbot_extractor", + "generic_extractor", + "twitter_api_extractor", + "instagram_extractor", + "telethon_extractor", + "vk_extractor", + "telegram_extractor" + ], + "enricher": [ + "wayback_extractor_enricher", + "wacz_extractor_enricher", + "metadata_enricher", + "timestamping_enricher", + "thumbnail_enricher", + "screenshot_enricher", + "meta_enricher", + "pdq_hash_enricher", + "whisper_enricher", + "ssl_enricher", + "hash_enricher" + ], + "database": [ + "console_db", + "atlos_db", + "api_db", + "csv_db", + "gsheet_db" + ], + "storage": [ + "local_storage", + "gdrive_storage", + "atlos_storage", + "s3_storage" + ], + "formatter": [ + "html_formatter", + "mute_formatter" + ] + }, + "configs": [ + "gsheet_feeder", + "atlos_feeder", + "csv_feeder", + "cli_feeder", + "instagram_api_extractor", + "instagram_tbot_extractor", + "twitter_api_extractor", + "instagram_extractor", + "telethon_extractor", + "vk_extractor", + "generic_extractor", + "wayback_extractor_enricher", + "wacz_extractor_enricher", + "timestamping_enricher", + "screenshot_enricher", + "whisper_enricher", + "thumbnail_enricher", + "ssl_enricher", + "hash_enricher", + "atlos_db", + "api_db", + "gsheet_db", + "csv_db", + "gdrive_storage", + "atlos_storage", + "s3_storage", + "local_storage", + "html_formatter" + ], + "module_types": [ + "feeder", + "extractor", + "enricher", + "database", + "storage", + "formatter" + ] +} \ No newline at end of file diff --git a/scripts/settings/src/theme.tsx b/scripts/settings/src/theme.tsx new file mode 100644 index 0000000..7ba9892 --- /dev/null +++ b/scripts/settings/src/theme.tsx @@ -0,0 +1,20 @@ +import { createTheme } from '@mui/material/styles'; +import { red } from '@mui/material/colors'; + +// A custom theme for this app +const theme = createTheme({ + cssVariables: true, + palette: { + primary: { + main: '#556cd6', + }, + secondary: { + main: '#19857b', + }, + error: { + main: red.A400, + }, + }, +}); + +export default theme; diff --git a/scripts/settings/src/vite-env.d.ts b/scripts/settings/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/scripts/settings/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/scripts/settings/tsconfig.json b/scripts/settings/tsconfig.json new file mode 100644 index 0000000..3d0a51a --- /dev/null +++ b/scripts/settings/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/scripts/settings/tsconfig.node.json b/scripts/settings/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/scripts/settings/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/scripts/settings/vite.config.ts b/scripts/settings/vite.config.ts new file mode 100644 index 0000000..1787623 --- /dev/null +++ b/scripts/settings/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { viteSingleFile } from "vite-plugin-singlefile" + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), viteSingleFile()], +}); diff --git a/src/auto_archiver/modules/csv_db/__manifest__.py b/src/auto_archiver/modules/csv_db/__manifest__.py index 507ce14..d9733b2 100644 --- a/src/auto_archiver/modules/csv_db/__manifest__.py +++ b/src/auto_archiver/modules/csv_db/__manifest__.py @@ -6,7 +6,7 @@ }, 'entry_point': 'csv_db::CSVDb', "configs": { - "csv_file": {"default": "db.csv", "help": "CSV file name"} + "csv_file": {"default": "db.csv", "help": "CSV file name to save metadata to"}, }, "description": """ Handles exporting archival results to a CSV file. diff --git a/src/auto_archiver/modules/html_formatter/__manifest__.py b/src/auto_archiver/modules/html_formatter/__manifest__.py index ec19cf8..6e51c7a 100644 --- a/src/auto_archiver/modules/html_formatter/__manifest__.py +++ b/src/auto_archiver/modules/html_formatter/__manifest__.py @@ -7,7 +7,9 @@ "bin": [""] }, "configs": { - "detect_thumbnails": {"default": True, "help": "if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00'"} + "detect_thumbnails": {"default": True, + "help": "if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00'", + "type": "bool"}, }, "description": """ """, } diff --git a/src/auto_archiver/modules/ssl_enricher/__manifest__.py b/src/auto_archiver/modules/ssl_enricher/__manifest__.py index 9028f14..097cd21 100644 --- a/src/auto_archiver/modules/ssl_enricher/__manifest__.py +++ b/src/auto_archiver/modules/ssl_enricher/__manifest__.py @@ -7,7 +7,9 @@ }, 'entry_point': 'ssl_enricher::SSLEnricher', "configs": { - "skip_when_nothing_archived": {"default": True, "help": "if true, will skip enriching when no media is archived"}, + "skip_when_nothing_archived": {"default": True, + "type": 'bool', + "help": "if true, will skip enriching when no media is archived"}, }, "description": """ Retrieves SSL certificate information for a domain and stores it as a file. diff --git a/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py index b8d6201..a78a3a9 100644 --- a/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py @@ -17,11 +17,17 @@ "configs": { "profile": {"default": None, "help": "browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix-crawler#creating-and-using-browser-profiles)."}, "docker_commands": {"default": None, "help":"if a custom docker invocation is needed"}, - "timeout": {"default": 120, "help": "timeout for WACZ generation in seconds"}, - "extract_media": {"default": False, "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched."}, - "extract_screenshot": {"default": True, "help": "If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched."}, + "timeout": {"default": 120, "help": "timeout for WACZ generation in seconds", "type": "int"}, + "extract_media": {"default": False, + "type": 'bool', + "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "extract_screenshot": {"default": True, + "type": 'bool', + "help": "If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, "socks_proxy_host": {"default": None, "help": "SOCKS proxy host for browsertrix-crawler, use in combination with socks_proxy_port. eg: user:password@host"}, - "socks_proxy_port": {"default": None, "help": "SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234"}, + "socks_proxy_port": {"default": None, "type":"int", "help": "SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234"}, "proxy_server": {"default": None, "help": "SOCKS server proxy URL, in development"}, }, "description": """ From f58f110436a65a37a8cfacadf8bc4bafa3b18817 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 26 Feb 2025 17:59:13 +0000 Subject: [PATCH 06/27] Check at least 1 URL provided for new cli_feeder module rewrite --- src/auto_archiver/modules/cli_feeder/cli_feeder.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/auto_archiver/modules/cli_feeder/cli_feeder.py b/src/auto_archiver/modules/cli_feeder/cli_feeder.py index 1f1fe26..20ca6ae 100644 --- a/src/auto_archiver/modules/cli_feeder/cli_feeder.py +++ b/src/auto_archiver/modules/cli_feeder/cli_feeder.py @@ -5,6 +5,11 @@ from auto_archiver.core.metadata import Metadata class CLIFeeder(Feeder): + def setup(self) -> None: + self.urls = self.config['urls'] + if not self.urls: + raise ValueError("No URLs provided. Please provide at least one URL via the command line, or set up an alternative feeder. Use --help for more information.") + def __iter__(self) -> Metadata: urls = self.config['urls'] for url in urls: From efe9fdf915d7c5c16c0fb9c266595e55923cc2ae Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 13:02:50 +0000 Subject: [PATCH 07/27] Tidy ups to config editor page --- scripts/settings/package-lock.json | 174 ++++++++---- scripts/settings/package.json | 6 +- scripts/settings/src/App.tsx | 267 +++++++----------- scripts/settings/src/StepCard.tsx | 200 +++++++++++++ scripts/settings/src/schema.json | 2 + .../modules/local_storage/__manifest__.py | 4 +- 6 files changed, 434 insertions(+), 219 deletions(-) create mode 100644 scripts/settings/src/StepCard.tsx diff --git a/scripts/settings/package-lock.json b/scripts/settings/package-lock.json index 6ef055b..cd40c14 100644 --- a/scripts/settings/package-lock.json +++ b/scripts/settings/package-lock.json @@ -8,12 +8,14 @@ "name": "material-ui-vite-ts", "version": "5.0.0", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@emotion/react": "latest", "@emotion/styled": "latest", "@mui/icons-material": "latest", "@mui/material": "latest", - "react": "latest", - "react-dom": "latest", + "react": "19.0.0", + "react-dom": "19.0.0", "react-markdown": "^10.0.0", "yaml": "^2.7.0" }, @@ -322,6 +324,59 @@ "node": ">=6.9.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -942,9 +997,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz", - "integrity": "sha512-zoXvHU1YuoodgMlPS+epP084Pqv9V+Vg+5IGv9n/7IIFVQ2nkTngYHYxElCq8pdTTbDcgji+nNh0lxri2abWgA==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.6.tgz", + "integrity": "sha512-rho5Q4IscbrVmK9rCrLTJmjLjfH6m/NcqKr/mchvck0EIXlyYUB9+Z0oVmkt/+Mben43LMRYBH8q/Uzxj/c4Vw==", "license": "MIT", "funding": { "type": "opencollective", @@ -952,9 +1007,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.5.tgz", - "integrity": "sha512-4A//t8Nrc+4u4pbVhGarIFU98zpuB5AV9hTNzgXx1ySZJ1tWtx+i/1SbQ8PtGJxWeXlljhwimZJNPQ3x0CiIFw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.6.tgz", + "integrity": "sha512-rGJBvIQQbQAlyKYljHQ8wAQS/K2/uYwvemcpygnAmCizmCI4zSF9HQPuiG8Ql4YLZ6V/uKjA3WHIYmF/8sV+pQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -967,7 +1022,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.4.5", + "@mui/material": "^6.4.6", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -978,16 +1033,16 @@ } }, "node_modules/@mui/material": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.5.tgz", - "integrity": "sha512-5eyEgSXocIeV1JkXs8mYyJXU0aFyXZIWI5kq2g/mCnIgJe594lkOBNAKnCIaGVfQTu2T6TTEHF8/hHIqpiIRGA==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.6.tgz", + "integrity": "sha512-6UyAju+DBOdMogfYmLiT3Nu7RgliorimNBny1pN/acOjc+THNFVE7hlxLyn3RDONoZJNDi/8vO4AQQr6dLAXqA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.5", - "@mui/system": "^6.4.3", + "@mui/core-downloads-tracker": "^6.4.6", + "@mui/system": "^6.4.6", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.3", + "@mui/utils": "^6.4.6", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -1006,7 +1061,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.4.3", + "@mui/material-pigment-css": "^6.4.6", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1027,13 +1082,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz", - "integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz", + "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.3", + "@mui/utils": "^6.4.6", "prop-types": "^15.8.1" }, "engines": { @@ -1054,9 +1109,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz", - "integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.6.tgz", + "integrity": "sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -1088,16 +1143,16 @@ } }, "node_modules/@mui/system": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz", - "integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.6.tgz", + "integrity": "sha512-FQjWwPec7pMTtB/jw5f9eyLynKFZ6/Ej9vhm5kGdtmts1z5b7Vyn3Rz6kasfYm1j2TfrfGnSXRvvtwVWxjpz6g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.3", - "@mui/styled-engine": "^6.4.3", + "@mui/private-theming": "^6.4.6", + "@mui/styled-engine": "^6.4.6", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.3", + "@mui/utils": "^6.4.6", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1142,9 +1197,9 @@ } }, "node_modules/@mui/utils": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz", - "integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz", + "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -1642,6 +1697,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -1881,9 +1937,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.105", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.105.tgz", - "integrity": "sha512-ccp7LocdXx3yBhwiG0qTQ7XFrK48Ua2pxIxBdJO8cbddp/MvbBtPFzvnTchtyHQTsgqqczO8cdmAIbpMa0u2+g==", + "version": "1.5.107", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.107.tgz", + "integrity": "sha512-dJr1o6yCntRkXElnhsHh1bAV19bo/hKyFf7tCcWgpXbuFIF0Lakjgqv5LRfSDaNzAII8Fnxg2tqgHkgCvxdbxw==", "dev": true, "license": "ISC" }, @@ -1980,6 +2036,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2205,6 +2262,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2896,6 +2954,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -3026,6 +3085,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -3090,28 +3150,24 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/react-is": { @@ -3281,13 +3337,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -3374,6 +3427,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3401,6 +3455,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -3503,9 +3563,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { diff --git a/scripts/settings/package.json b/scripts/settings/package.json index 6608693..aec7706 100644 --- a/scripts/settings/package.json +++ b/scripts/settings/package.json @@ -9,12 +9,14 @@ "preview": "vite preview" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@emotion/react": "latest", "@emotion/styled": "latest", "@mui/icons-material": "latest", "@mui/material": "latest", - "react": "latest", - "react-dom": "latest", + "react": "19.0.0", + "react-dom": "19.0.0", "react-markdown": "^10.0.0", "yaml": "^2.7.0" }, diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 6e6e813..126836c 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -3,42 +3,34 @@ import { useEffect, useState } from 'react'; import Container from '@mui/material/Container'; import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; -import Link from '@mui/material/Link'; -import { modules, steps, configs, module_types } from './schema.json'; + +// +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragOverlay +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + rectSortingStrategy +} from "@dnd-kit/sortable"; + + +import { modules, steps, module_types } from './schema.json'; import { - Checkbox, - Select, - MenuItem, - FormControl, - FormControlLabel, - InputLabel, - FormHelperText, Stack, - TextField, - Card, - CardContent, - CardActions, Button, - Dialog, - DialogTitle, - DialogContent, } from '@mui/material'; import Grid from '@mui/material/Grid2'; -import Accordion from '@mui/material/Accordion'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ReactMarkdown from 'react-markdown'; -import { parseDocument, ParsedNode, Document } from 'yaml' -import { set } from 'yaml/dist/schema/yaml-1.1/set'; - -Object.defineProperty(String.prototype, 'capitalize', { - value: function() { - return this.charAt(0).toUpperCase() + this.slice(1); - }, - enumerable: false -}); +import { parseDocument, Document } from 'yaml' +import StepCard from './StepCard'; function FileDrop({ setYamlFile }) { @@ -86,117 +78,21 @@ function FileDrop({ setYamlFile }) { ); } - -function ModuleCheckbox({ module, toggleModule, enabledModules, configValues }: { module: object, toggleModule: any, enabledModules: any, configValues: any }) { - let name = module.name; - const [helpOpen, setHelpOpen] = useState(false); - const [configOpen, setConfigOpen] = useState(false); - if (name == 'metadata_enricher') { - console.log("hi"); - } - return ( - <> - - - } - label={module.display_name} /> - - - - {enabledModules.includes(name) && module.configs && name != 'cli_feeder' ? ( - - ) : null} - - - setHelpOpen(false)} - maxWidth="lg" - > - - {module.display_name} - - - - {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} - - - - {module.configs && name != 'cli_feeder' && } - - ) -} - - -function ConfigPanel({ module, open, setOpen, configValues }: { module: any, open: boolean, setOpen: any, configValues: any }) { - return ( - <> - setOpen(false)} - maxWidth="lg" - > - - {module.display_name} - - - - {Object.keys(module.configs).map((config_value: any) => { - let config_args = module.configs[config_value]; - let config_name = config_value.replace(/_/g," "); - return ( - - - { config_args.type === 'bool' ? - } label={config_name} /> - : - ( config_args.type === 'int' ? - - : - ( - config_args.choices !== undefined ? - <> - {config_name} - - - : - - ) - ) - } - {config_args.help} - - - ); - })} - - - - - ); -} - -function ModuleTypes({ stepType, toggleModule, enabledModules, configValues }: { stepType: string, toggleModule: any, enabledModules: any, configValues: any }) { +function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }: { stepType: string, setEnabledModules: any, enabledModules: any, configValues: any }) { const [showError, setShowError] = useState(false); + const [activeId, setActiveId] = useState(null); + const [items, setItems] = useState(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); - const _toggleModule = (event: any) => { + const toggleModule = (event: any) => { // make sure that 'feeder' and 'formatter' types only have one value let name = event.target.id; + let checked = event.target.checked; if (stepType === 'feeder' || stepType === 'formatter') { - let checked = event.target.checked; // check how many modules of this type are enabled - let modules = steps[stepType].filter((m: string) => (m !== name && enabledModules.includes(m)) || (checked && m === name)); - if (modules.length > 1) { + const checkedModules = enabledModules[stepType].filter(([m, enabled]: [string, boolean]) => { + return (m !== name && enabled) || (checked && m === name) + }); + if (checkedModules.length > 1) { setShowError(true); } else { setShowError(false); @@ -204,24 +100,85 @@ function ModuleTypes({ stepType, toggleModule, enabledModules, configValues }: { } else { setShowError(false); } - toggleModule(event); + let newEnabledModules = { ...enabledModules }; + newEnabledModules[stepType] = enabledModules[stepType].map(([m, enabled]: [string, boolean]) => { + return (m === name) ? [m, checked] : [m, enabled]; + } + ); + setEnabledModules(newEnabledModules); } + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates + }) + ); + + const handleDragStart = (event) => { + setActiveId(event.active.id); + }; + + const handleDragEnd = (event) => { + setActiveId(null); + const { active, over } = event; + + if (active.id !== over.id) { + const oldIndex = items.indexOf(active.id); + const newIndex = items.indexOf(over.id); + + let newArray = arrayMove(items, oldIndex, newIndex); + // set it also on steps + let newEnabledModules = { ...enabledModules }; + newEnabledModules[stepType] = enabledModules[stepType].sort((a, b) => { + return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); + }) + setEnabledModules(newEnabledModules); + setItems(newArray); + } + }; return ( <> + {stepType}s + + Select the {stepType}s you wish to enable. You can drag and drop them to reorder them. + + {showError ? Only one {stepType} can be enabled at a time. : null} + + - {steps[stepType].map((name: string) => { + + {items.map((name: string) => { let m = modules[name]; return ( - - - + ); })} + + {activeId ? ( +
+ + ) : null} +
+
+
); } @@ -229,7 +186,7 @@ function ModuleTypes({ stepType, toggleModule, enabledModules, configValues }: { export default function App() { const [yamlFile, setYamlFile] = useState(new Document()); - const [enabledModules, setEnabledModules] = useState<[]>([]); + const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(module_types.map(type => [type, steps[type].map((name: string) => [name, false])]))); const [configValues, setConfigValues] = useState<{}>( Object.keys(modules).reduce((acc, module) => { acc[module] = {}; @@ -241,15 +198,14 @@ export default function App() { // edit the yamlFile // generate the steps config - let stepsConfig = {} - module_types.forEach((stepType: string) => { - stepsConfig[stepType] = enabledModules.filter((m: string) => steps[stepType].includes(m)); - } - ); + let stepsConfig = enabledModules; // create a yaml file from const finalYaml = { - 'steps': stepsConfig + 'steps': Object.keys(stepsConfig).reduce((acc, stepType) => { + acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); + return acc; + }, {}) }; Object.keys(configValues).map((module: string) => { @@ -274,17 +230,6 @@ export default function App() { } } - const toggleModule = function (event: any) { - let module = event.target.id; - let checked = event.target.checked - - if (checked) { - setEnabledModules([...enabledModules, module]); - } else { - setEnabledModules(enabledModules.filter((m: string) => m !== module)); - } - } - useEffect(() => { // load the configs, and set the default values if they exist let newConfigValues = {}; @@ -339,7 +284,9 @@ export default function App() { {Object.keys(steps).map((stepType: string) => { return ( - + + + ); })} @@ -355,8 +302,10 @@ export default function App() { 4. Save your settings + + diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx new file mode 100644 index 0000000..d401d25 --- /dev/null +++ b/scripts/settings/src/StepCard.tsx @@ -0,0 +1,200 @@ +import { useState } from "react"; +import { useSortable } from "@dnd-kit/sortable"; +import ReactMarkdown from 'react-markdown'; + +import { CSS } from "@dnd-kit/utilities"; + +import { + Card, + CardContent, + CardActions, + CardHeader, + Button, + Dialog, + DialogTitle, + DialogContent, + Box, + IconButton, + Checkbox, + Select, + MenuItem, + FormControl, + FormControlLabel, + InputLabel, + FormHelperText, + TextField, + Stack, + Typography, + } from '@mui/material'; +import Grid from '@mui/material/Grid2'; +import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; +import { set } from "yaml/dist/schema/yaml-1.1/set"; + + +Object.defineProperty(String.prototype, 'capitalize', { + value: function() { + return this.charAt(0).toUpperCase() + this.slice(1); + }, + enumerable: false +}); + +const StepCard = ({ + type, + module, + toggleModule, + enabledModules, + configValues +}: { + type: string, + module: object, + toggleModule: any, + enabledModules: any, + configValues: any +}) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging + } = useSortable({ id: module.name }); + + + const style = { + transform: CSS.Transform.toString(transform), + transition, + zIndex: isDragging ? "100" : "auto", + opacity: isDragging ? 0.3 : 1 + }; + + let name = module.name; + const [helpOpen, setHelpOpen] = useState(false); + const [configOpen, setConfigOpen] = useState(false); + const enabled = enabledModules[type].find((m: any) => m[0] === name)[1]; + + return ( + + + } + label={module.display_name} /> + } + action ={ + + + + } + /> + + + {enabled && module.configs && name != 'cli_feeder' ? ( + + ) : null} + + + + setHelpOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} + + + + {module.configs && name != 'cli_feeder' && } + + ) + } + + +function ConfigPanel({ module, open, setOpen, configValues }: { module: any, open: boolean, setOpen: any, configValues: any }) { + + function setConfigValue(config: any, value: any) { + configValues[module.name][config] = value; + } + return ( + <> + setOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {Object.keys(module.configs).map((config_value: any) => { + const config_args = module.configs[config_value]; + const config_name = config_value.replace(/_/g," "); + const config_display_name = config_name.capitalize(); + const value = configValues[module.name][config_value] || config_args.default; + return ( + + {config_display_name} + + { config_args.type === 'bool' ? + { + setConfigValue(config_value, e.target.checked); + }} + /> + : + ( + config_args.choices !== undefined ? + + : + ( config_args.type === 'json_loader' ? + { + try { + val = JSON.parse(e.target.value); + setConfigValue(config_value, val); + } catch (e) { + console.log(e); + } + } + } type='text' /> + : + { + setConfigValue(config_value, e.target.value); + }} /> + ) + ) + } + {config_args.help} + + + ); + })} + + + + + ); + } + +export default StepCard; \ No newline at end of file diff --git a/scripts/settings/src/schema.json b/scripts/settings/src/schema.json index e02067c..e4f470d 100644 --- a/scripts/settings/src/schema.json +++ b/scripts/settings/src/schema.json @@ -1914,6 +1914,7 @@ }, "save_absolute": { "default": false, + "type": "bool", "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)" } } @@ -1942,6 +1943,7 @@ }, "save_absolute": { "default": false, + "type": "bool", "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)" } } diff --git a/src/auto_archiver/modules/local_storage/__manifest__.py b/src/auto_archiver/modules/local_storage/__manifest__.py index 6d9cf53..4f6c2df 100644 --- a/src/auto_archiver/modules/local_storage/__manifest__.py +++ b/src/auto_archiver/modules/local_storage/__manifest__.py @@ -17,7 +17,9 @@ "choices": ["random", "static"], }, "save_to": {"default": "./local_archive", "help": "folder where to save archived content"}, - "save_absolute": {"default": False, "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)"}, + "save_absolute": {"default": False, + "type": "bool", + "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)"}, }, "description": """ LocalStorage: A storage module for saving archived content locally on the filesystem. From 1e92c03b1d0216e4522f4072d1815a59f8d8f825 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 15:21:11 +0000 Subject: [PATCH 08/27] Tweaks to settings page + more declarations in manifests --- scripts/generate_settings_page.py | 2 +- scripts/settings/src/App.tsx | 52 +++++++++++++------ scripts/settings/src/StepCard.tsx | 20 ++++--- scripts/settings/src/schema.json | 30 ++++++++--- .../screenshot_enricher/__manifest__.py | 24 ++++++--- .../__manifest__.py | 1 + 6 files changed, 91 insertions(+), 38 deletions(-) diff --git a/scripts/generate_settings_page.py b/scripts/generate_settings_page.py index 632d782..cb3d452 100644 --- a/scripts/generate_settings_page.py +++ b/scripts/generate_settings_page.py @@ -31,7 +31,7 @@ output_schame = { 'configs': module.configs or None } ) for module in all_modules_ordered_by_type), - 'steps': dict((module_type, [module.name for module in modules_by_type[module_type]]) for module_type in MODULE_TYPES), + 'steps': dict((f"{module_type}s", [module.name for module in modules_by_type[module_type]]) for module_type in MODULE_TYPES), 'configs': [m.name for m in all_modules_ordered_by_type if m.configs], 'module_types': MODULE_TYPES, } diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 126836c..1b12ba9 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -81,7 +81,12 @@ function FileDrop({ setYamlFile }) { function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }: { stepType: string, setEnabledModules: any, enabledModules: any, configValues: any }) { const [showError, setShowError] = useState(false); const [activeId, setActiveId] = useState(null); - const [items, setItems] = useState(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); + const [items, setItems] = useState([]); + + useEffect(() => { + setItems(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); + } + , [enabledModules]); const toggleModule = (event: any) => { // make sure that 'feeder' and 'formatter' types only have one value @@ -100,11 +105,12 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues } else { setShowError(false); } - let newEnabledModules = { ...enabledModules }; - newEnabledModules[stepType] = enabledModules[stepType].map(([m, enabled]: [string, boolean]) => { - return (m === name) ? [m, checked] : [m, enabled]; - } - ); + let newEnabledModules = Object.fromEntries(Object.keys(enabledModules).map((type : string) => { + return [type, enabledModules[type].map(([m, enabled]: [string, boolean]) => { + return (m === name) ? [m, checked] : [m, enabled]; + })]; + } + )); setEnabledModules(newEnabledModules); } @@ -134,17 +140,16 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); }) setEnabledModules(newEnabledModules); - setItems(newArray); } }; return ( <> - {stepType}s + {stepType} - Select the {stepType}s you wish to enable. You can drag and drop them to reorder them. + Select the {stepType} you wish to enable. You can drag and drop them to reorder them. {showError ? Only one {stepType} can be enabled at a time. : null} @@ -186,7 +191,7 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues export default function App() { const [yamlFile, setYamlFile] = useState(new Document()); - const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(module_types.map(type => [type, steps[type].map((name: string) => [name, false])]))); + const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(Object.keys(steps).map(type => [type, steps[type].map((name: string) => [name, false])]))); const [configValues, setConfigValues] = useState<{}>( Object.keys(modules).reduce((acc, module) => { acc[module] = {}; @@ -202,7 +207,7 @@ export default function App() { // create a yaml file from const finalYaml = { - 'steps': Object.keys(stepsConfig).reduce((acc, stepType) => { + 'steps': Object.keys(steps).reduce((acc, stepType) => { acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); return acc; }, {}) @@ -257,10 +262,27 @@ export default function App() { let settings = yamlFile.toJS(); // make a deep copy of settings - let newEnabledModules = Object.keys(settings['steps']).map((step: string) => { - return settings['steps'][step]; - }).flat(); - newEnabledModules = newEnabledModules.filter((m: string, i: number) => newEnabledModules.indexOf(m) === i); + let stepSettings = settings['steps']; + let newEnabledModules = Object.fromEntries(Object.keys(steps).map((type: string) => { + return [type, steps[type].map((name: string) => { + return [name, stepSettings[type].indexOf(name) !== -1]; + }).sort((a, b) => { + let aIndex = stepSettings[type].indexOf(a[0]); + let bIndex = stepSettings[type].indexOf(b[0]); + if (aIndex === -1 && bIndex === -1) { + return a - b; + } + if (bIndex === -1) { + return -1; + } + if (aIndex === -1) { + return 1; + } + return aIndex - bIndex; + })]; + }).sort((a, b) => { + return module_types.indexOf(a[0]) - module_types.indexOf(b[0]); + })); setEnabledModules(newEnabledModules); }, [yamlFile]); diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx index d401d25..870926a 100644 --- a/scripts/settings/src/StepCard.tsx +++ b/scripts/settings/src/StepCard.tsx @@ -20,7 +20,7 @@ import { MenuItem, FormControl, FormControlLabel, - InputLabel, + Textarea, FormHelperText, TextField, Stack, @@ -141,13 +141,15 @@ function ConfigPanel({ module, open, setOpen, configValues }: { module: any, ope const value = configValues[module.name][config_value] || config_args.default; return ( - {config_display_name} + {config_display_name} - { config_args.type === 'bool' ? + { config_args.type === 'bool' ? + { setConfigValue(config_value, e.target.checked); }} + />} label={config_args.help} /> : ( @@ -167,16 +169,16 @@ function ConfigPanel({ module, open, setOpen, configValues }: { module: any, ope : ( config_args.type === 'json_loader' ? - { try { - val = JSON.parse(e.target.value); + let val = JSON.parse(e.target.value); setConfigValue(config_value, val); } catch (e) { console.log(e); } } - } type='text' /> + } /> : { @@ -185,8 +187,10 @@ function ConfigPanel({ module, open, setOpen, configValues }: { module: any, ope ) ) } - {config_args.help} - + {config_args.type !== 'bool' && ( + {config_args.help} + )} + ); })} diff --git a/scripts/settings/src/schema.json b/scripts/settings/src/schema.json index e4f470d..87ec55b 100644 --- a/scripts/settings/src/schema.json +++ b/scripts/settings/src/schema.json @@ -799,6 +799,7 @@ "configs": { "timeout": { "default": 15, + "type": "int", "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually." }, "if_not_archived_within": { @@ -826,6 +827,7 @@ "configs": { "timeout": { "default": 15, + "type": "int", "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually." }, "if_not_archived_within": { @@ -1046,18 +1048,22 @@ "configs": { "width": { "default": 1280, + "type": "int", "help": "width of the screenshots" }, "height": { "default": 720, + "type": "int", "help": "height of the screenshots" }, "timeout": { "default": 60, + "type": "int", "help": "timeout for taking the screenshot" }, "sleep_before_screenshot": { "default": 4, + "type": "int", "help": "seconds to wait for the pages to load before taking screenshot" }, "http_proxy": { @@ -1066,29 +1072,35 @@ }, "save_to_pdf": { "default": false, + "type": "bool", "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter" }, "print_options": { "default": {}, - "help": "options to pass to the pdf printer" + "help": "options to pass to the pdf printer, in JSON format. See https://www.selenium.dev/documentation/webdriver/interactions/print_page/ for more information", + "type": "json_loader" } } }, "configs": { "width": { "default": 1280, + "type": "int", "help": "width of the screenshots" }, "height": { "default": 720, + "type": "int", "help": "height of the screenshots" }, "timeout": { "default": 60, + "type": "int", "help": "timeout for taking the screenshot" }, "sleep_before_screenshot": { "default": 4, + "type": "int", "help": "seconds to wait for the pages to load before taking screenshot" }, "http_proxy": { @@ -1097,11 +1109,13 @@ }, "save_to_pdf": { "default": false, + "type": "bool", "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter" }, "print_options": { "default": {}, - "help": "options to pass to the pdf printer" + "help": "options to pass to the pdf printer, in JSON format. See https://www.selenium.dev/documentation/webdriver/interactions/print_page/ for more information", + "type": "json_loader" } } }, @@ -2007,13 +2021,13 @@ } }, "steps": { - "feeder": [ + "feeders": [ "cli_feeder", "gsheet_feeder", "atlos_feeder", "csv_feeder" ], - "extractor": [ + "extractors": [ "wayback_extractor_enricher", "wacz_extractor_enricher", "instagram_api_extractor", @@ -2025,7 +2039,7 @@ "vk_extractor", "telegram_extractor" ], - "enricher": [ + "enrichers": [ "wayback_extractor_enricher", "wacz_extractor_enricher", "metadata_enricher", @@ -2038,20 +2052,20 @@ "ssl_enricher", "hash_enricher" ], - "database": [ + "databases": [ "console_db", "atlos_db", "api_db", "csv_db", "gsheet_db" ], - "storage": [ + "storages": [ "local_storage", "gdrive_storage", "atlos_storage", "s3_storage" ], - "formatter": [ + "formatters": [ "html_formatter", "mute_formatter" ] diff --git a/src/auto_archiver/modules/screenshot_enricher/__manifest__.py b/src/auto_archiver/modules/screenshot_enricher/__manifest__.py index 9829844..c6a196c 100644 --- a/src/auto_archiver/modules/screenshot_enricher/__manifest__.py +++ b/src/auto_archiver/modules/screenshot_enricher/__manifest__.py @@ -6,13 +6,25 @@ "python": ["loguru", "selenium"], }, "configs": { - "width": {"default": 1280, "help": "width of the screenshots"}, - "height": {"default": 720, "help": "height of the screenshots"}, - "timeout": {"default": 60, "help": "timeout for taking the screenshot"}, - "sleep_before_screenshot": {"default": 4, "help": "seconds to wait for the pages to load before taking screenshot"}, + "width": {"default": 1280, + "type": "int", + "help": "width of the screenshots"}, + "height": {"default": 720, + "type": "int", + "help": "height of the screenshots"}, + "timeout": {"default": 60, + "type": "int", + "help": "timeout for taking the screenshot"}, + "sleep_before_screenshot": {"default": 4, + "type": "int", + "help": "seconds to wait for the pages to load before taking screenshot"}, "http_proxy": {"default": "", "help": "http proxy to use for the webdriver, eg http://proxy-user:password@proxy-ip:port"}, - "save_to_pdf": {"default": False, "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter"}, - "print_options": {"default": {}, "help": "options to pass to the pdf printer"} + "save_to_pdf": {"default": False, + "type": "bool", + "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter"}, + "print_options": {"default": {}, + "help": "options to pass to the pdf printer, in JSON format. See https://www.selenium.dev/documentation/webdriver/interactions/print_page/ for more information", + "type": "json_loader"}, }, "description": """ Captures screenshots and optionally saves web pages as PDFs using a WebDriver. diff --git a/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py b/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py index 38a5610..62a7e8a 100644 --- a/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py @@ -9,6 +9,7 @@ "configs": { "timeout": { "default": 15, + "type": "int", "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually.", }, "if_not_archived_within": { From 2ec44f41703588af42755fddb8f254d2ad22cc83 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 15:42:37 +0000 Subject: [PATCH 09/27] Documentation on building the settings page --- .../development/developer_guidelines.md | 1 + docs/source/development/release.md | 5 +++ docs/source/development/settings_page.md | 20 +++++++++++ ...gs_page.py => generate_settings_schema.py} | 4 ++- scripts/settings/package.json | 2 +- scripts/settings/src/App.tsx | 33 ++++++++++++------- 6 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 docs/source/development/settings_page.md rename scripts/{generate_settings_page.py => generate_settings_schema.py} (87%) diff --git a/docs/source/development/developer_guidelines.md b/docs/source/development/developer_guidelines.md index e72193a..0014d8f 100644 --- a/docs/source/development/developer_guidelines.md +++ b/docs/source/development/developer_guidelines.md @@ -31,4 +31,5 @@ docker_development testing docs release +settings_page ``` \ No newline at end of file diff --git a/docs/source/development/release.md b/docs/source/development/release.md index 403dcb9..694af78 100644 --- a/docs/source/development/release.md +++ b/docs/source/development/release.md @@ -13,3 +13,8 @@ manual release to docker hub * `docker image tag auto-archiver bellingcat/auto-archiver:latest` * `docker push bellingcat/auto-archiver` + + +### Building the Settings Page + +The Settings page is built as part of the python-publish workflow and packaged within the app. \ No newline at end of file diff --git a/docs/source/development/settings_page.md b/docs/source/development/settings_page.md new file mode 100644 index 0000000..29c722a --- /dev/null +++ b/docs/source/development/settings_page.md @@ -0,0 +1,20 @@ +# Settings Page + +The settings page (viewable here TODO: add link), is an easy-to-use UI for users to edit their auto-archiver settings. + +The single-file app is built using React and vite. To get started developing the package, follow these steps: + +1. Make sure you have Node v22 installed. + +```{note} Tip: if you don't have node installed: + +Use `nvm` to manage your node installations. Use: +`curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash` to install `nvm` and then `nvm i 22` to install Node v22 +``` + +2. Generate the `schema.json` file for the currently installed modules using `python scripts/generate_settings_schema.py` +3. Go to the settings folder `cd scripts/settings/` and build your environment with `npm i` +4. Run a development version of the page with `npm run dev` +5. Build a release version of the page with `npm run build` + +A release version creates a single-file app called `dist/index.html` \ No newline at end of file diff --git a/scripts/generate_settings_page.py b/scripts/generate_settings_schema.py similarity index 87% rename from scripts/generate_settings_page.py rename to scripts/generate_settings_schema.py index cb3d452..e04e404 100644 --- a/scripts/generate_settings_page.py +++ b/scripts/generate_settings_schema.py @@ -1,4 +1,5 @@ import json +import os from auto_archiver.core.module import ModuleFactory from auto_archiver.core.consts import MODULE_TYPES @@ -36,6 +37,7 @@ output_schame = { 'module_types': MODULE_TYPES, } -output_file = 'schema.json' +current_file_dir = os.path.dirname(os.path.abspath(__file__)) +output_file = os.path.join(current_file_dir, 'settings/src/schema.json') with open(output_file, 'w') as file: json.dump(output_schame, file, indent=4, cls=SchemaEncoder) \ No newline at end of file diff --git a/scripts/settings/package.json b/scripts/settings/package.json index aec7706..fc7bb7b 100644 --- a/scripts/settings/package.json +++ b/scripts/settings/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", "preview": "vite preview" }, "dependencies": { diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 1b12ba9..ab1caeb 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -21,6 +21,8 @@ import { rectSortingStrategy } from "@dnd-kit/sortable"; +import type { DragStartEvent, DragEndEvent, UniqueIdentifier } from "@dnd-kit/core"; + import { modules, steps, module_types } from './schema.json'; import { @@ -32,7 +34,16 @@ import Grid from '@mui/material/Grid2'; import { parseDocument, Document } from 'yaml' import StepCard from './StepCard'; -function FileDrop({ setYamlFile }) { +// create a Typescript interface for module +interface Module { + name: string; + description: string; + configs: object; + manifest: object; +} + + +function FileDrop({ setYamlFile }: { setYamlFile: React.Dispatch> }) { const [showError, setShowError] = useState(false); const [label, setLabel] = useState("Drag and drop your orchestration.yaml file here, or click to select a file."); @@ -46,9 +57,9 @@ function FileDrop({ setYamlFile }) { } let reader = new FileReader(); reader.onload = function(e) { - let contents = e.target.result; + let contents = e.target ? e.target.result : ''; try { - let document = parseDocument(contents); + let document = parseDocument(contents as string); if (document.errors.length > 0) { // not a valid yaml file setShowError(true); @@ -79,8 +90,8 @@ function FileDrop({ setYamlFile }) { } function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }: { stepType: string, setEnabledModules: any, enabledModules: any, configValues: any }) { - const [showError, setShowError] = useState(false); - const [activeId, setActiveId] = useState(null); + const [showError, setShowError] = useState(false); + const [activeId, setActiveId] = useState(); const [items, setItems] = useState([]); useEffect(() => { @@ -121,17 +132,17 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }) ); - const handleDragStart = (event) => { + const handleDragStart = (event: DragStartEvent) => { setActiveId(event.active.id); }; - const handleDragEnd = (event) => { - setActiveId(null); + const handleDragEnd = (event: DragEndEvent) => { + setActiveId(undefined); const { active, over } = event; - if (active.id !== over.id) { - const oldIndex = items.indexOf(active.id); - const newIndex = items.indexOf(over.id); + if (active.id !== over?.id) { + const oldIndex = items.indexOf(active.id as string); + const newIndex = items.indexOf(over?.id as string); let newArray = arrayMove(items, oldIndex, newIndex); // set it also on steps From 15da907e811ba3f26a641bfd24abf0ba6aa8565c Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 15:58:30 +0000 Subject: [PATCH 10/27] Add a bit of typescripting --- scripts/settings/src/App.tsx | 21 ++++++++++----------- scripts/settings/src/StepCard.tsx | 7 ++----- scripts/settings/src/types.d.ts | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 scripts/settings/src/types.d.ts diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index ab1caeb..1c3f0e3 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -24,6 +24,8 @@ import { import type { DragStartEvent, DragEndEvent, UniqueIdentifier } from "@dnd-kit/core"; +import { Module } from './types'; + import { modules, steps, module_types } from './schema.json'; import { Stack, @@ -34,14 +36,6 @@ import Grid from '@mui/material/Grid2'; import { parseDocument, Document } from 'yaml' import StepCard from './StepCard'; -// create a Typescript interface for module -interface Module { - name: string; - description: string; - configs: object; - manifest: object; -} - function FileDrop({ setYamlFile }: { setYamlFile: React.Dispatch> }) { @@ -174,7 +168,7 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues {items.map((name: string) => { - let m = modules[name]; + let m: Module = modules[name]; return ( ); @@ -203,7 +197,12 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues export default function App() { const [yamlFile, setYamlFile] = useState(new Document()); const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(Object.keys(steps).map(type => [type, steps[type].map((name: string) => [name, false])]))); - const [configValues, setConfigValues] = useState<{}>( + const [configValues, setConfigValues] = useState<{ + [key: string]: { + [key: string + ]: any + } + }>( Object.keys(modules).reduce((acc, module) => { acc[module] = {}; return acc; @@ -218,7 +217,7 @@ export default function App() { // create a yaml file from const finalYaml = { - 'steps': Object.keys(steps).reduce((acc, stepType) => { + 'steps': Object.keys(steps).reduce((acc, stepType: string) => { acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); return acc; }, {}) diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx index 870926a..f326a50 100644 --- a/scripts/settings/src/StepCard.tsx +++ b/scripts/settings/src/StepCard.tsx @@ -6,7 +6,6 @@ import { CSS } from "@dnd-kit/utilities"; import { Card, - CardContent, CardActions, CardHeader, Button, @@ -20,7 +19,6 @@ import { MenuItem, FormControl, FormControlLabel, - Textarea, FormHelperText, TextField, Stack, @@ -28,8 +26,7 @@ import { } from '@mui/material'; import Grid from '@mui/material/Grid2'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; -import { set } from "yaml/dist/schema/yaml-1.1/set"; - +import { Module } from "./types"; Object.defineProperty(String.prototype, 'capitalize', { value: function() { @@ -46,7 +43,7 @@ const StepCard = ({ configValues }: { type: string, - module: object, + module: Module, toggleModule: any, enabledModules: any, configValues: any diff --git a/scripts/settings/src/types.d.ts b/scripts/settings/src/types.d.ts new file mode 100644 index 0000000..64cba2f --- /dev/null +++ b/scripts/settings/src/types.d.ts @@ -0,0 +1,18 @@ +export interface Config { + name: string; + description: string; + type: string?; + default: any; +} + +interface Manifest { + description: string; +} + +export interface Module { + name: string; + description: string; + configs: { [key: string]: Config }; + manifest: Manifest; + display_name: string; +} \ No newline at end of file From 1141c00e9ab8fc0d4b0c09b2e0e208531b8e6220 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:23:38 +0000 Subject: [PATCH 11/27] Remove unused files, set up for RTD --- .readthedocs.yaml | 4 + docs/source/installation/config_editor.md | 5 + docs/source/installation/settings.html | 372 ++++++++++++++++++++++ docs/source/installation/setup.md | 1 + scripts/generate_settings_schema.py | 4 +- scripts/settings/index.html | 1 - scripts/settings/public/vite.svg | 1 - scripts/settings/src/App.tsx | 244 +++++++------- scripts/settings/src/ProTip.tsx | 23 -- scripts/settings/src/StepCard.tsx | 277 ++++++++-------- scripts/settings/src/types.d.ts | 5 +- scripts/settings/src/vite-env.d.ts | 1 - 12 files changed, 655 insertions(+), 283 deletions(-) create mode 100644 docs/source/installation/config_editor.md create mode 100644 docs/source/installation/settings.html delete mode 100644 scripts/settings/public/vite.svg delete mode 100644 scripts/settings/src/ProTip.tsx delete mode 100644 scripts/settings/src/vite-env.d.ts diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 434f805..39504d8 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,6 +9,7 @@ build: os: ubuntu-22.04 tools: python: "3.10" + node: "22" jobs: post_install: - pip install poetry @@ -17,6 +18,9 @@ build: # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs + # install node dependencies and build the settings + - cd scripts/settings && npm install && npm run build && cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. + sphinx: configuration: docs/source/conf.py diff --git a/docs/source/installation/config_editor.md b/docs/source/installation/config_editor.md new file mode 100644 index 0000000..a23ebce --- /dev/null +++ b/docs/source/installation/config_editor.md @@ -0,0 +1,5 @@ +# Configuration Editor + +```{raw} html +:file: settings.html +``` \ No newline at end of file diff --git a/docs/source/installation/settings.html b/docs/source/installation/settings.html new file mode 100644 index 0000000..973a1e0 --- /dev/null +++ b/docs/source/installation/settings.html @@ -0,0 +1,372 @@ + + + + + + + + + + Auto Archiver Settings + + + +
+ + diff --git a/docs/source/installation/setup.md b/docs/source/installation/setup.md index 8d1a6f5..e5c96a6 100644 --- a/docs/source/installation/setup.md +++ b/docs/source/installation/setup.md @@ -6,6 +6,7 @@ installation.md configurations.md +config_editor.md authentication.md requirements.md config_cheatsheet.md diff --git a/scripts/generate_settings_schema.py b/scripts/generate_settings_schema.py index e04e404..92853cd 100644 --- a/scripts/generate_settings_schema.py +++ b/scripts/generate_settings_schema.py @@ -23,7 +23,7 @@ for module in available_modules: all_modules_ordered_by_type = sorted(available_modules, key=lambda x: (MODULE_TYPES.index(x.type[0]), not x.requires_setup)) -output_schame = { +output_schema = { 'modules': dict((module.name, { 'name': module.name, @@ -40,4 +40,4 @@ output_schame = { current_file_dir = os.path.dirname(os.path.abspath(__file__)) output_file = os.path.join(current_file_dir, 'settings/src/schema.json') with open(output_file, 'w') as file: - json.dump(output_schame, file, indent=4, cls=SchemaEncoder) \ No newline at end of file + json.dump(output_schema, file, indent=4, cls=SchemaEncoder) \ No newline at end of file diff --git a/scripts/settings/index.html b/scripts/settings/index.html index d2e8d75..b0cfa48 100644 --- a/scripts/settings/index.html +++ b/scripts/settings/index.html @@ -2,7 +2,6 @@ - diff --git a/scripts/settings/public/vite.svg b/scripts/settings/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/scripts/settings/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 1c3f0e3..26d306b 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -27,7 +27,7 @@ import type { DragStartEvent, DragEndEvent, UniqueIdentifier } from "@dnd-kit/co import { Module } from './types'; import { modules, steps, module_types } from './schema.json'; -import { +import { Stack, Button, } from '@mui/material'; @@ -50,7 +50,7 @@ function FileDrop({ setYamlFile }: { setYamlFile: React.Dispatch -
+
- - - {label} - -
+ + + {label} + +
); } @@ -91,13 +91,13 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues useEffect(() => { setItems(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); } - , [enabledModules]); + , [enabledModules]); const toggleModule = (event: any) => { // make sure that 'feeder' and 'formatter' types only have one value let name = event.target.id; let checked = event.target.checked; - if (stepType === 'feeder' || stepType === 'formatter') { + if (stepType === 'feeders' || stepType === 'formatters') { // check how many modules of this type are enabled const checkedModules = enabledModules[stepType].filter(([m, enabled]: [string, boolean]) => { return (m !== name && enabled) || (checked && m === name) @@ -110,11 +110,11 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues } else { setShowError(false); } - let newEnabledModules = Object.fromEntries(Object.keys(enabledModules).map((type : string) => { + let newEnabledModules = Object.fromEntries(Object.keys(enabledModules).map((type: string) => { return [type, enabledModules[type].map(([m, enabled]: [string, boolean]) => { return (m === name) ? [m, checked] : [m, enabled]; })]; - } + } )); setEnabledModules(newEnabledModules); } @@ -135,61 +135,62 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues const { active, over } = event; if (active.id !== over?.id) { - const oldIndex = items.indexOf(active.id as string); - const newIndex = items.indexOf(over?.id as string); + const oldIndex = items.indexOf(active.id as string); + const newIndex = items.indexOf(over?.id as string); - let newArray = arrayMove(items, oldIndex, newIndex); - // set it also on steps - let newEnabledModules = { ...enabledModules }; - newEnabledModules[stepType] = enabledModules[stepType].sort((a, b) => { - return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); - }) - setEnabledModules(newEnabledModules); + let newArray = arrayMove(items, oldIndex, newIndex); + // set it also on steps + let newEnabledModules = { ...enabledModules }; + newEnabledModules[stepType] = enabledModules[stepType].sort((a, b) => { + return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); + }) + setEnabledModules(newEnabledModules); } }; return ( <> - - - {stepType} - - - Select the {stepType} you wish to enable. You can drag and drop them to reorder them. - + + + {stepType} + + + Select the {stepType} you wish to enable. You can drag and move to reorder. + Learn more about {stepType} here. + - {showError ? Only one {stepType} can be enabled at a time. : null} + {showError ? Only one {stepType.slice(0,-1)} can be enabled at a time. : null} - - - {items.map((name: string) => { - let m: Module = modules[name]; - return ( - - ); - })} - - {activeId ? ( -
+ sensors={sensors} + collisionDetection={closestCenter} + onDragEnd={handleDragEnd} + onDragStart={handleDragStart} + > + + + {items.map((name: string) => { + let m: Module = modules[name]; + return ( + + ); + })} + + {activeId ? ( +
- ) : null} -
-
-
+ ) : null} +
+
+
- + ); } @@ -209,41 +210,41 @@ export default function App() { }, {}) ); - const saveSettings = function(copy: boolean = false) { + const saveSettings = function (copy: boolean = false) { // edit the yamlFile // generate the steps config let stepsConfig = enabledModules; - // create a yaml file from - const finalYaml = { - 'steps': Object.keys(steps).reduce((acc, stepType: string) => { - acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); - return acc; - }, {}) - }; + // create a yaml file from + const finalYaml = { + 'steps': Object.keys(steps).reduce((acc, stepType: string) => { + acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); + return acc; + }, {}) + }; - Object.keys(configValues).map((module: string) => { - let module_values = configValues[module]; - if (module_values) { - finalYaml[module] = module_values; - } - }); - let newFile = new Document(finalYaml); - if (copy) { - navigator.clipboard.writeText(String(newFile)).then(() => { - alert("Settings copied to clipboard."); - }); - } else { - // offer the file for download - const blob = new Blob([String(newFile)], { type: 'application/x-yaml' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'orchestration.yaml'; - a.click(); + Object.keys(configValues).map((module: string) => { + let module_values = configValues[module]; + if (module_values) { + finalYaml[module] = module_values; } + }); + let newFile = new Document(finalYaml); + if (copy) { + navigator.clipboard.writeText(String(newFile)).then(() => { + alert("Settings copied to clipboard."); + }); + } else { + // offer the file for download + const blob = new Blob([String(newFile)], { type: 'application/x-yaml' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'orchestration.yaml'; + a.click(); } + } useEffect(() => { // load the configs, and set the default values if they exist @@ -262,7 +263,7 @@ export default function App() { } }); }) - setConfigValues(newConfigValues); + setConfigValues(newConfigValues); }, []); useEffect(() => { @@ -274,25 +275,25 @@ export default function App() { // make a deep copy of settings let stepSettings = settings['steps']; let newEnabledModules = Object.fromEntries(Object.keys(steps).map((type: string) => { - return [type, steps[type].map((name: string) => { - return [name, stepSettings[type].indexOf(name) !== -1]; - }).sort((a, b) => { - let aIndex = stepSettings[type].indexOf(a[0]); - let bIndex = stepSettings[type].indexOf(b[0]); - if (aIndex === -1 && bIndex === -1) { - return a - b; - } - if (bIndex === -1) { - return -1; - } - if (aIndex === -1) { - return 1; - } - return aIndex - bIndex; - })]; + return [type, steps[type].map((name: string) => { + return [name, stepSettings[type].indexOf(name) !== -1]; }).sort((a, b) => { - return module_types.indexOf(a[0]) - module_types.indexOf(b[0]); - })); + let aIndex = stepSettings[type].indexOf(a[0]); + let bIndex = stepSettings[type].indexOf(b[0]); + if (aIndex === -1 && bIndex === -1) { + return a - b; + } + if (bIndex === -1) { + return -1; + } + if (aIndex === -1) { + return 1; + } + return aIndex - bIndex; + })]; + }).sort((a, b) => { + return module_types.indexOf(a[0]) - module_types.indexOf(b[0]); + })); setEnabledModules(newEnabledModules); }, [yamlFile]); @@ -301,44 +302,41 @@ export default function App() { return ( - - Auto Archiver Settings - - - 1. Select your
orchestration.yaml
settings file. -
- + + 1. Select your orchestration.yaml settings file. + +
- - 2. Choose the Modules you wish to enable/disable - + + 2. Choose the Modules you wish to enable/disable + {Object.keys(steps).map((stepType: string) => { return ( - + ); })} - - 3. Configure your Enabled Modules - - - Next to each module you've enabled, you can click 'Configure' to set the module's settings. - + + 3. Configure your Enabled Modules + + + Next to each module you've enabled, you can click 'Configure' to set the module's settings. + 4. Save your settings - - + + - +
); diff --git a/scripts/settings/src/ProTip.tsx b/scripts/settings/src/ProTip.tsx deleted file mode 100644 index 217b5bf..0000000 --- a/scripts/settings/src/ProTip.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from 'react'; -import Link from '@mui/material/Link'; -import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; -import Typography from '@mui/material/Typography'; - -function LightBulbIcon(props: SvgIconProps) { - return ( - - - - ); -} - -export default function ProTip() { - return ( - - - {'Pro tip: See more '} - templates - {' in the Material UI documentation.'} - - ); -} diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx index f326a50..fe69359 100644 --- a/scripts/settings/src/StepCard.tsx +++ b/scripts/settings/src/StepCard.tsx @@ -4,7 +4,7 @@ import ReactMarkdown from 'react-markdown'; import { CSS } from "@dnd-kit/utilities"; -import { +import { Card, CardActions, CardHeader, @@ -23,17 +23,22 @@ import { TextField, Stack, Typography, - } from '@mui/material'; +} from '@mui/material'; import Grid from '@mui/material/Grid2'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; -import { Module } from "./types"; +import HelpIconOutlined from '@mui/icons-material/HelpOutline'; +import { Module, Config } from "./types"; -Object.defineProperty(String.prototype, 'capitalize', { - value: function() { + +// adds 'capitalize' method to String prototype +declare global { + interface String { + capitalize(): string; + } +} +String.prototype.capitalize = function (this: string) { return this.charAt(0).toUpperCase() + this.slice(1); - }, - enumerable: false -}); +}; const StepCard = ({ type, @@ -46,7 +51,7 @@ const StepCard = ({ module: Module, toggleModule: any, enabledModules: any, - configValues: any + configValues: any }) => { const { attributes, @@ -55,147 +60,157 @@ const StepCard = ({ transform, transition, isDragging - } = useSortable({ id: module.name }); + } = useSortable({ id: module.name }); - const style = { + const style = { transform: CSS.Transform.toString(transform), transition, zIndex: isDragging ? "100" : "auto", opacity: isDragging ? 0.3 : 1 - }; - + }; + let name = module.name; const [helpOpen, setHelpOpen] = useState(false); const [configOpen, setConfigOpen] = useState(false); const enabled = enabledModules[type].find((m: any) => m[0] === name)[1]; - - return ( - - - } - label={module.display_name} /> - } - action ={ - - - - } - /> - - - {enabled && module.configs && name != 'cli_feeder' ? ( - - ) : null} - - - setHelpOpen(false)} - maxWidth="lg" - > - - {module.display_name} - - - - {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} - - - - {module.configs && name != 'cli_feeder' && } - + return ( + + + } + label={module.display_name} /> + } + /> + + + + setHelpOpen(true)}> + + + {enabled && module.configs && name != 'cli_feeder' ? ( + + ) : null} + + + + + + + + setHelpOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} + + + + {module.configs && name != 'cli_feeder' && } + ) - } - - -function ConfigPanel({ module, open, setOpen, configValues }: { module: any, open: boolean, setOpen: any, configValues: any }) { +} +function ConfigField({ config_value, module, configValues }: { config_value: any, module: Module, configValues: any }) { function setConfigValue(config: any, value: any) { - configValues[module.name][config] = value; + configValues[module.name][config] = value; } + const config_args: Config = module.configs[config_value]; + const config_name: string = config_value.replace(/_/g, " "); + const config_display_name = config_name.capitalize(); + const value = configValues[module.name][config_value] || config_args.default; return ( - <> - setOpen(false)} - maxWidth="lg" - > - - {module.display_name} - - - - {Object.keys(module.configs).map((config_value: any) => { - const config_args = module.configs[config_value]; - const config_name = config_value.replace(/_/g," "); - const config_display_name = config_name.capitalize(); - const value = configValues[module.name][config_value] || config_args.default; - return ( - - {config_display_name} - - { config_args.type === 'bool' ? - { - setConfigValue(config_value, e.target.checked); - }} - />} label={config_args.help} - /> + + {config_display_name} {config_args.required && (`(required)`)} + + {config_args.type === 'bool' ? + { + setConfigValue(config_value, e.target.checked); + }} + />} label={config_args.help} + /> : ( - config_args.choices !== undefined ? - { + setConfigValue(config_value, e.target.value); + }} + > + {config_args.choices.map((choice: any) => { + return ( + {choice} + ); + })} + + : + (config_args.type === 'json_loader' ? + { + try { + let val = JSON.parse(e.target.value); + setConfigValue(config_value, val); + } catch (e) { + console.log(e); + } + } + } /> + : + { + setConfigValue(config_value, e.target.value); + }} + required={config_args.required} + /> + ) + ) + } + {config_args.type !== 'bool' && ( + {config_args.help.capitalize()} + )} + + + ) +} + +function ConfigPanel({ module, open, setOpen, configValues }: { module: Module, open: boolean, setOpen: any, configValues: any }) { + + return ( + <> + setOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {Object.keys(module.configs).map((config_value: any) => { + return ( + + ); })} - - : - ( config_args.type === 'json_loader' ? - { - try { - let val = JSON.parse(e.target.value); - setConfigValue(config_value, val); - } catch (e) { - console.log(e); - } - } - } /> - : - { - setConfigValue(config_value, e.target.value); - }} /> - ) - ) - } - {config_args.type !== 'bool' && ( - {config_args.help} - )} - - - ); - })} - - - - + + + + ); - } +} export default StepCard; \ No newline at end of file diff --git a/scripts/settings/src/types.d.ts b/scripts/settings/src/types.d.ts index 64cba2f..fdf80fc 100644 --- a/scripts/settings/src/types.d.ts +++ b/scripts/settings/src/types.d.ts @@ -3,6 +3,9 @@ export interface Config { description: string; type: string?; default: any; + help: string; + choices: string[]; + required: boolean; } interface Manifest { @@ -15,4 +18,4 @@ export interface Module { configs: { [key: string]: Config }; manifest: Manifest; display_name: string; -} \ No newline at end of file +} diff --git a/scripts/settings/src/vite-env.d.ts b/scripts/settings/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/scripts/settings/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// From 4ee1e75aa22b054b942cd1a7d394d7fdf3ad3877 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:24:34 +0000 Subject: [PATCH 12/27] Fix readthedocs config file --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 39504d8..003a1ae 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ build: os: ubuntu-22.04 tools: python: "3.10" - node: "22" + nodejs: "22" jobs: post_install: - pip install poetry From 65a9885d86cc7af6389508686a214e1a5a2abc78 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:33:04 +0000 Subject: [PATCH 13/27] A few more manifest types --- src/auto_archiver/modules/gsheet_feeder/__manifest__.py | 4 +++- src/auto_archiver/modules/local_storage/__manifest__.py | 2 +- .../modules/telethon_extractor/__manifest__.py | 4 +++- .../modules/wacz_extractor_enricher/__manifest__.py | 4 +++- .../modules/whisper_enricher/__manifest__.py | 8 ++++++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/auto_archiver/modules/gsheet_feeder/__manifest__.py b/src/auto_archiver/modules/gsheet_feeder/__manifest__.py index 130b9f6..1d8f244 100644 --- a/src/auto_archiver/modules/gsheet_feeder/__manifest__.py +++ b/src/auto_archiver/modules/gsheet_feeder/__manifest__.py @@ -12,7 +12,9 @@ "default": None, "help": "the id of the sheet to archive (alternative to 'sheet' config)", }, - "header": {"default": 1, "help": "index of the header row (starts at 1)", "type": "int"}, + "header": {"default": 1, + "type": "int", + "help": "index of the header row (starts at 1)", "type": "int"}, "service_account": { "default": "secrets/service_account.json", "help": "service account JSON file path. Learn how to create one: https://gspread.readthedocs.io/en/latest/oauth2.html", diff --git a/src/auto_archiver/modules/local_storage/__manifest__.py b/src/auto_archiver/modules/local_storage/__manifest__.py index 4f6c2df..8ad6381 100644 --- a/src/auto_archiver/modules/local_storage/__manifest__.py +++ b/src/auto_archiver/modules/local_storage/__manifest__.py @@ -18,7 +18,7 @@ }, "save_to": {"default": "./local_archive", "help": "folder where to save archived content"}, "save_absolute": {"default": False, - "type": "bool", + "type": "bool", "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)"}, }, "description": """ diff --git a/src/auto_archiver/modules/telethon_extractor/__manifest__.py b/src/auto_archiver/modules/telethon_extractor/__manifest__.py index 458428b..5e58203 100644 --- a/src/auto_archiver/modules/telethon_extractor/__manifest__.py +++ b/src/auto_archiver/modules/telethon_extractor/__manifest__.py @@ -14,7 +14,9 @@ "api_hash": {"default": None, "help": "telegram API_HASH value, go to https://my.telegram.org/apps"}, "bot_token": {"default": None, "help": "optional, but allows access to more content such as large videos, talk to @botfather"}, "session_file": {"default": "secrets/anon", "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value."}, - "join_channels": {"default": True, "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck"}, + "join_channels": {"default": True, + "type": "bool", + "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck"}, "channel_invites": { "default": {}, "help": "(JSON string) private channel invite links (format: t.me/joinchat/HASH OR t.me/+HASH) and (optional but important to avoid hanging for minutes on startup) channel id (format: CHANNEL_ID taken from a post url like https://t.me/c/CHANNEL_ID/1), the telegram account will join any new channels on setup", diff --git a/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py index a78a3a9..9b373b9 100644 --- a/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py @@ -17,7 +17,9 @@ "configs": { "profile": {"default": None, "help": "browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix-crawler#creating-and-using-browser-profiles)."}, "docker_commands": {"default": None, "help":"if a custom docker invocation is needed"}, - "timeout": {"default": 120, "help": "timeout for WACZ generation in seconds", "type": "int"}, + "timeout": {"default": 120, + "type": "int", + "help": "timeout for WACZ generation in seconds", "type": "int"}, "extract_media": {"default": False, "type": 'bool', "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." diff --git a/src/auto_archiver/modules/whisper_enricher/__manifest__.py b/src/auto_archiver/modules/whisper_enricher/__manifest__.py index 98e743e..0e09d03 100644 --- a/src/auto_archiver/modules/whisper_enricher/__manifest__.py +++ b/src/auto_archiver/modules/whisper_enricher/__manifest__.py @@ -10,8 +10,12 @@ "help": "WhisperApi api endpoint, eg: https://whisperbox-api.com/api/v1, a deployment of https://github.com/bellingcat/whisperbox-transcribe."}, "api_key": {"required": True, "help": "WhisperApi api key for authentication"}, - "include_srt": {"default": False, "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)."}, - "timeout": {"default": 90, "help": "How many seconds to wait at most for a successful job completion."}, + "include_srt": {"default": False, + "type": "bool", + "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)."}, + "timeout": {"default": 90, + "type": "int", + "help": "How many seconds to wait at most for a successful job completion."}, "action": {"default": "translate", "help": "which Whisper operation to execute", "choices": ["transcribe", "translate", "language_detection"]}, From 3eb4ab41b86305855c308ee93501f409765b3f42 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:37:37 +0000 Subject: [PATCH 14/27] Also generate the schema on each run --- .readthedocs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 003a1ae..6e9d174 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -18,6 +18,8 @@ build: # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs + # generate the config editor page. Schema then HTML + - poetry run python scripts/generate_settings_schema.py # install node dependencies and build the settings - cd scripts/settings && npm install && npm run build && cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. From 54a2a19dd726b1844b468fe516c59a491270630e Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:42:21 +0000 Subject: [PATCH 15/27] Also build auto-archiver --- .readthedocs.yaml | 2 +- scripts/settings/src/schema.json | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6e9d174..4efddd9 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -19,7 +19,7 @@ build: - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs # generate the config editor page. Schema then HTML - - poetry run python scripts/generate_settings_schema.py + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry run python scripts/generate_settings_schema.py # install node dependencies and build the settings - cd scripts/settings && npm install && npm run build && cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. diff --git a/scripts/settings/src/schema.json b/scripts/settings/src/schema.json index 87ec55b..8376c9f 100644 --- a/scripts/settings/src/schema.json +++ b/scripts/settings/src/schema.json @@ -31,8 +31,8 @@ }, "header": { "default": 1, - "help": "index of the header row (starts at 1)", - "type": "int" + "type": "int", + "help": "index of the header row (starts at 1)" }, "service_account": { "default": "secrets/service_account.json", @@ -85,8 +85,8 @@ }, "header": { "default": 1, - "help": "index of the header row (starts at 1)", - "type": "int" + "type": "int", + "help": "index of the header row (starts at 1)" }, "service_account": { "default": "secrets/service_account.json", @@ -559,6 +559,7 @@ }, "join_channels": { "default": true, + "type": "bool", "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck" }, "channel_invites": { @@ -587,6 +588,7 @@ }, "join_channels": { "default": true, + "type": "bool", "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck" }, "channel_invites": { @@ -887,8 +889,8 @@ }, "timeout": { "default": 120, - "help": "timeout for WACZ generation in seconds", - "type": "int" + "type": "int", + "help": "timeout for WACZ generation in seconds" }, "extract_media": { "default": false, @@ -926,8 +928,8 @@ }, "timeout": { "default": 120, - "help": "timeout for WACZ generation in seconds", - "type": "int" + "type": "int", + "help": "timeout for WACZ generation in seconds" }, "extract_media": { "default": false, @@ -1150,10 +1152,12 @@ }, "include_srt": { "default": false, + "type": "bool", "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)." }, "timeout": { "default": 90, + "type": "int", "help": "How many seconds to wait at most for a successful job completion." }, "action": { @@ -1178,10 +1182,12 @@ }, "include_srt": { "default": false, + "type": "bool", "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)." }, "timeout": { "default": 90, + "type": "int", "help": "How many seconds to wait at most for a successful job completion." }, "action": { From 7620a671d1711ce6f35a411b9aa78ba1f37bead0 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 22:02:44 +0000 Subject: [PATCH 16/27] Overwrite settings_base file --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 4efddd9..6dc9fe5 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -21,7 +21,7 @@ build: # generate the config editor page. Schema then HTML - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry run python scripts/generate_settings_schema.py # install node dependencies and build the settings - - cd scripts/settings && npm install && npm run build && cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. + - cd scripts/settings && npm install && npm run build && yes | cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. sphinx: From 6ba79049d9e90bea70e233575c027a233c82afb3 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 22:16:33 +0000 Subject: [PATCH 17/27] Capitalize help text --- scripts/settings/src/StepCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx index fe69359..f55458f 100644 --- a/scripts/settings/src/StepCard.tsx +++ b/scripts/settings/src/StepCard.tsx @@ -139,7 +139,7 @@ function ConfigField({ config_value, module, configValues }: { config_value: any onChange={(e) => { setConfigValue(config_value, e.target.checked); }} - />} label={config_args.help} + />} label={config_args.help.capitalize()} /> : ( From cc14e5cb9fa4e66a7d08e4b5fce99d67127a12eb Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 3 Mar 2025 09:06:40 +0000 Subject: [PATCH 18/27] Remove extra html/head tag from page - now it's embedded in RTD --- docs/source/installation/settings.html | 24 ++++-------------------- scripts/settings/index.html | 18 +----------------- scripts/settings/src/App.tsx | 2 +- 3 files changed, 6 insertions(+), 38 deletions(-) diff --git a/docs/source/installation/settings.html b/docs/source/installation/settings.html index 973a1e0..832938d 100644 --- a/docs/source/installation/settings.html +++ b/docs/source/installation/settings.html @@ -1,17 +1,4 @@ - - - - - - - - - - Auto Archiver Settings - - - +`)})})]}),t.configs&&v!="cli_feeder"&&$.jsx(Y3,{module:t,open:S,setOpen:k,configValues:o})]})};function K3({config_value:e,module:t,configValues:n}){function i(d,p){n[t.name][d]=p}const o=t.configs[e],u=e.replace(/_/g," ").capitalize(),f=n[t.name][e]||o.default;return $.jsxs(Pr,{children:[$.jsxs(dn,{variant:"body1",style:{fontWeight:"bold"},children:[u," ",o.required&&"(required)"," "]}),$.jsxs(tx,{size:"small",children:[o.type==="bool"?$.jsx(nx,{control:$.jsx(Yw,{defaultChecked:f,size:"small",id:`${t}.${e}`,onChange:d=>{i(e,d.target.checked)}}),label:o.help.capitalize()}):o.choices!==void 0?$.jsx(ag,{size:"small",id:`${t}.${e}`,defaultValue:f,onChange:d=>{i(e,d.target.value)},children:o.choices.map(d=>$.jsx(VN,{value:d,children:d},`${t}.${e}.${d}`))}):o.type==="json_loader"?$.jsx(zS,{multiline:!0,size:"small",id:`${t}.${e}`,defaultValue:JSON.stringify(f,null,2),rows:6,onChange:d=>{try{let p=JSON.parse(d.target.value);i(e,p)}catch(p){console.log(p)}}}):$.jsx(zS,{size:"small",id:`${t}.${e}`,defaultValue:f,type:o.type==="int"?"number":"text",onChange:d=>{i(e,d.target.value)},required:o.required}),o.type!=="bool"&&$.jsx(rx,{children:o.help.capitalize()})]})]})}function Y3({module:e,open:t,setOpen:n,configValues:i}){return $.jsx($.Fragment,{children:$.jsxs(Zw,{open:t,onClose:()=>n(!1),maxWidth:"lg",children:[$.jsx(ex,{children:e.display_name}),$.jsx(Jw,{children:$.jsx(cx,{direction:"column",spacing:1,children:Object.keys(e.configs).map(o=>$.jsx(K3,{config_value:o,module:e,configValues:i},o))})})]})})}function X3({setYamlFile:e}){const[t,n]=T.useState(!1),[i,o]=T.useState("Drag and drop your orchestration.yaml file here, or click to select a file.");function l(u){let f=u.target.files[0];if(f.type!=="application/x-yaml"){n(!0),o("Invalid type, only YAML files are accepted.");return}let d=new FileReader;d.onload=function(p){let m=p.target?p.target.result:"";try{let g=Cz(m);if(g.errors.length>0){n(!0),o("Invalid file. Make sure your Orchestration is a valid YAML file with a 'steps' section in it.");return}else n(!1),o("File loaded successfully.");e(g)}catch(g){console.error(g)}},d.readAsText(f)}return $.jsx($.Fragment,{children:$.jsxs("div",{style:{width:"100%",border:"dashed",textAlign:"center",borderWidth:"1px",padding:"20px"},children:[$.jsx("input",{name:"file",type:"file",accept:".yaml",onChange:l}),$.jsx(dn,{style:{marginTop:"20px"},variant:"body1",color:t?"error":"",children:i})]})})}function W3({stepType:e,setEnabledModules:t,enabledModules:n,configValues:i}){const[o,l]=T.useState(!1),[u,f]=T.useState(),[d,p]=T.useState([]);T.useEffect(()=>{p(n[e].map(([w,S])=>w))},[n]);const m=w=>{let S=w.target.id,k=w.target.checked;(e==="feeders"||e==="formatters")&&n[e].filter(([_,O])=>_!==S&&O||k&&_===S).length>1?l(!0):l(!1);let A=Object.fromEntries(Object.keys(n).map(N=>[N,n[N].map(([_,O])=>_===S?[_,k]:[_,O])]));t(A)},g=DD(IS(fg),IS(ug,{coordinateGetter:c5})),v=w=>{f(w.active.id)},b=w=>{f(void 0);const{active:S,over:k}=w;if(S.id!==(k==null?void 0:k.id)){const A=d.indexOf(S.id),N=d.indexOf(k==null?void 0:k.id);let _=pg(d,A,N),O={...n};O[e]=n[e].sort((R,M)=>_.indexOf(R[0])-_.indexOf(M[0])),t(O)}};return $.jsxs($.Fragment,{children:[$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{id:e,variant:"h6",style:{textTransform:"capitalize"},children:e}),$.jsxs(dn,{variant:"body1",children:["Select the ",e," you wish to enable. You can drag and move to reorder. Learn more about ",e," ",$.jsx("a",{href:`https://auto-archiver.readthedocs.io/en/latest/modules/${e.slice(0,-1)}.html`,target:"_blank",children:"here"}),"."]})]}),o?$.jsxs(dn,{variant:"body1",color:"error",children:["Only one ",e.slice(0,-1)," can be enabled at a time."]}):null,$.jsx(ML,{sensors:g,collisionDetection:BD,onDragEnd:b,onDragStart:v,children:$.jsx(ix,{container:!0,spacing:1,children:$.jsxs(e5,{items:d,strategy:hg,children:[d.map(w=>{let S=Dc[w];return $.jsx(G3,{type:e,module:S,toggleModule:m,enabledModules:n,configValues:i},w)}),$.jsx(WL,{children:u?$.jsx("div",{style:{width:"100%",height:"100%",backgroundColor:"grey",opacity:.1}}):null})]})},e)})]})}function Q3(){const[e,t]=T.useState(new qo),[n,i]=T.useState(Object.fromEntries(Object.keys(xo).map(f=>[f,xo[f].map(d=>[d,!1])]))),[o,l]=T.useState(Object.keys(Dc).reduce((f,d)=>(f[d]={},f),{})),u=function(f=!1){let d=n;const p={steps:Object.keys(xo).reduce((g,v)=>(g[v]=d[v].filter(([b,w])=>w).map(([b,w])=>b),g),{})};Object.keys(o).map(g=>{let v=o[g];v&&(p[g]=v)});let m=new qo(p);if(f)navigator.clipboard.writeText(String(m)).then(()=>{alert("Settings copied to clipboard.")});else{const g=new Blob([String(m)],{type:"application/x-yaml"}),v=URL.createObjectURL(g),b=document.createElement("a");b.href=v,b.download="orchestration.yaml",b.click()}};return T.useEffect(()=>{let f={};Object.keys(Dc).map(d=>{let m=Dc[d].configs;m&&(f[d]={},Object.keys(m).map(g=>{let v=m[g];v.default!==void 0&&(f[d][g]=v.default)}))}),l(f)},[]),T.useEffect(()=>{if(!e||e.contents==null)return;let d=e.toJS().steps,p=Object.fromEntries(Object.keys(xo).map(m=>[m,xo[m].map(g=>[g,d[m].indexOf(g)!==-1]).sort((g,v)=>{let b=d[m].indexOf(g[0]),w=d[m].indexOf(v[0]);return b===-1&&w===-1?g-v:w===-1?-1:b===-1?1:b-w})]).sort((m,g)=>QS.indexOf(m[0])-QS.indexOf(g[0])));i(p)},[e]),$.jsx(hM,{maxWidth:"lg",children:$.jsxs(Pr,{sx:{my:4},children:[$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{variant:"h5",children:"1. Select your orchestration.yaml settings file."}),$.jsx(X3,{setYamlFile:t})]}),$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{variant:"h5",children:"2. Choose the Modules you wish to enable/disable"}),Object.keys(xo).map(f=>$.jsx(Pr,{sx:{my:4},children:$.jsx(W3,{stepType:f,setEnabledModules:i,enabledModules:n,configValues:o})},f))]}),$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{variant:"h5",children:"3. Configure your Enabled Modules"}),$.jsx(dn,{variant:"body1",children:"Next to each module you've enabled, you can click 'Configure' to set the module's settings."})]}),$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{variant:"h5",children:"4. Save your settings"}),$.jsxs(cx,{direction:"row",spacing:2,sx:{my:2},children:[$.jsx(rm,{variant:"contained",color:"primary",onClick:()=>u(!0),children:"Copy Settings to Clipboard"}),$.jsx(rm,{variant:"contained",color:"primary",onClick:()=>u(),children:"Save Settings to File"})]})]})]})})}wT.createRoot(document.getElementById("root")).render($.jsx(T.StrictMode,{children:$.jsxs(bR,{theme:bD,children:[$.jsx(vM,{}),$.jsx(Q3,{})]})})); +
- - diff --git a/scripts/settings/index.html b/scripts/settings/index.html index b0cfa48..22ff169 100644 --- a/scripts/settings/index.html +++ b/scripts/settings/index.html @@ -1,19 +1,3 @@ - - - - - - - - - - Auto Archiver Settings - - +
- - diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 26d306b..bc5ee21 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -154,7 +154,7 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues {stepType} - Select the {stepType} you wish to enable. You can drag and move to reorder. + Select the {stepType} you wish to enable. Drag to re-order. Learn more about {stepType} here. From 9845804277d0af624eac65d2eec9907672c0deba Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 3 Mar 2025 09:18:19 +0000 Subject: [PATCH 19/27] Fix up TODO plus add comments on integration into RTD page --- docs/source/development/settings_page.md | 19 +++++++++++++++---- scripts/settings/index.html | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/source/development/settings_page.md b/docs/source/development/settings_page.md index 29c722a..41271b9 100644 --- a/docs/source/development/settings_page.md +++ b/docs/source/development/settings_page.md @@ -1,6 +1,6 @@ -# Settings Page +# Configuration Editor -The settings page (viewable here TODO: add link), is an easy-to-use UI for users to edit their auto-archiver settings. +The [configuration editor](../installation/config_editor.md), is an easy-to-use UI for users to edit their auto-archiver settings. The single-file app is built using React and vite. To get started developing the package, follow these steps: @@ -14,7 +14,18 @@ Use `nvm` to manage your node installations. Use: 2. Generate the `schema.json` file for the currently installed modules using `python scripts/generate_settings_schema.py` 3. Go to the settings folder `cd scripts/settings/` and build your environment with `npm i` -4. Run a development version of the page with `npm run dev` +4. Run a development version of the page with `npm run dev` and then open localhost:5173. 5. Build a release version of the page with `npm run build` -A release version creates a single-file app called `dist/index.html` \ No newline at end of file +A release version creates a single-file app called `dist/index.html`. This file should be copied to `docs/source/installation/settings_base.html` so that it can be integrated into the sphinx docs. + +```{note} + +The single-file app dist/index.html does not include any `` or `` tags as it is designed to be built into a RTD docs page. Edit `index.html` in the settings folder if you wish to modify the built page. +``` + +## Readthedocs Integration + +The configuration editor is built as part of the RTD deployment (see `.readthedocs.yaml` file). This command is run every time RTD is built: + +`cd scripts/settings && npm install && npm run build && yes | cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../..` \ No newline at end of file diff --git a/scripts/settings/index.html b/scripts/settings/index.html index 22ff169..2f7639d 100644 --- a/scripts/settings/index.html +++ b/scripts/settings/index.html @@ -1,3 +1,23 @@ + + +
+ + From a88a37d0a5ada1e6343ec8fcd29cd40cd0745c59 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 3 Mar 2025 11:56:23 +0000 Subject: [PATCH 20/27] Hook in to RTD theme to set react theme --- docs/source/installation/settings.html | 48438 ++++++++++++++++++++++- scripts/settings/index.html | 14 +- scripts/settings/src/App.tsx | 2 +- scripts/settings/src/StepCard.tsx | 1 + scripts/settings/src/main.tsx | 39 +- scripts/settings/src/theme.tsx | 20 - scripts/settings/vite.config.ts | 4 + 7 files changed, 48282 insertions(+), 236 deletions(-) delete mode 100644 scripts/settings/src/theme.tsx diff --git a/docs/source/installation/settings.html b/docs/source/installation/settings.html index 832938d..b1bf1ff 100644 --- a/docs/source/installation/settings.html +++ b/docs/source/installation/settings.html @@ -1,4 +1,81 @@ - +`; +const rotateAnimation = typeof circularRotateKeyframe !== "string" ? css` + animation: ${circularRotateKeyframe} 1.4s linear infinite; + ` : null; +const dashAnimation = typeof circularDashKeyframe !== "string" ? css` + animation: ${circularDashKeyframe} 1.4s ease-in-out infinite; + ` : null; +const useUtilityClasses$u = (ownerState) => { + const { + classes, + variant, + color: color2, + disableShrink + } = ownerState; + const slots = { + root: ["root", variant, `color${capitalize(color2)}`], + svg: ["svg"], + circle: ["circle", `circle${capitalize(variant)}`, disableShrink && "circleDisableShrink"] + }; + return composeClasses(slots, getCircularProgressUtilityClass, classes); +}; +const CircularProgressRoot = styled("span", { + name: "MuiCircularProgress", + slot: "Root", + overridesResolver: (props, styles2) => { + const { + ownerState + } = props; + return [styles2.root, styles2[ownerState.variant], styles2[`color${capitalize(ownerState.color)}`]]; + } +})(memoTheme(({ + theme +}) => ({ + display: "inline-block", + variants: [{ + props: { + variant: "determinate" + }, + style: { + transition: theme.transitions.create("transform") + } + }, { + props: { + variant: "indeterminate" + }, + style: rotateAnimation || { + animation: `${circularRotateKeyframe} 1.4s linear infinite` + } + }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()).map(([color2]) => ({ + props: { + color: color2 + }, + style: { + color: (theme.vars || theme).palette[color2].main + } + }))] +}))); +const CircularProgressSVG = styled("svg", { + name: "MuiCircularProgress", + slot: "Svg", + overridesResolver: (props, styles2) => styles2.svg +})({ + display: "block" + // Keeps the progress centered +}); +const CircularProgressCircle = styled("circle", { + name: "MuiCircularProgress", + slot: "Circle", + overridesResolver: (props, styles2) => { + const { + ownerState + } = props; + return [styles2.circle, styles2[`circle${capitalize(ownerState.variant)}`], ownerState.disableShrink && styles2.circleDisableShrink]; + } +})(memoTheme(({ + theme +}) => ({ + stroke: "currentColor", + variants: [{ + props: { + variant: "determinate" + }, + style: { + transition: theme.transitions.create("stroke-dashoffset") + } + }, { + props: { + variant: "indeterminate" + }, + style: { + // Some default value that looks fine waiting for the animation to kicks in. + strokeDasharray: "80px, 200px", + strokeDashoffset: 0 + // Add the unit to fix a Edge 16 and below bug. + } + }, { + props: ({ + ownerState + }) => ownerState.variant === "indeterminate" && !ownerState.disableShrink, + style: dashAnimation || { + // At runtime for Pigment CSS, `bufferAnimation` will be null and the generated keyframe will be used. + animation: `${circularDashKeyframe} 1.4s ease-in-out infinite` + } + }] +}))); +const CircularProgress = /* @__PURE__ */ reactExports.forwardRef(function CircularProgress2(inProps, ref) { + const props = useDefaultProps({ + props: inProps, + name: "MuiCircularProgress" + }); + const { + className, + color: color2 = "primary", + disableShrink = false, + size = 40, + style: style2, + thickness = 3.6, + value = 0, + variant = "indeterminate", + ...other + } = props; + const ownerState = { + ...props, + color: color2, + disableShrink, + size, + thickness, + value, + variant + }; + const classes = useUtilityClasses$u(ownerState); + const circleStyle = {}; + const rootStyle = {}; + const rootProps = {}; + if (variant === "determinate") { + const circumference = 2 * Math.PI * ((SIZE - thickness) / 2); + circleStyle.strokeDasharray = circumference.toFixed(3); + rootProps["aria-valuenow"] = Math.round(value); + circleStyle.strokeDashoffset = `${((100 - value) / 100 * circumference).toFixed(3)}px`; + rootStyle.transform = "rotate(-90deg)"; + } + return /* @__PURE__ */ jsxRuntimeExports.jsx(CircularProgressRoot, { + className: clsx(classes.root, className), + style: { + width: size, + height: size, + ...rootStyle, + ...style2 + }, + ownerState, + ref, + role: "progressbar", + ...rootProps, + ...other, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircularProgressSVG, { + className: classes.svg, + ownerState, + viewBox: `${SIZE / 2} ${SIZE / 2} ${SIZE} ${SIZE}`, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircularProgressCircle, { + className: classes.circle, + style: circleStyle, + ownerState, + cx: SIZE, + cy: SIZE, + r: (SIZE - thickness) / 2, + fill: "none", + strokeWidth: thickness + }) + }) + }); +}); +function getIconButtonUtilityClass(slot) { + return generateUtilityClass("MuiIconButton", slot); +} +const iconButtonClasses = generateUtilityClasses("MuiIconButton", ["root", "disabled", "colorInherit", "colorPrimary", "colorSecondary", "colorError", "colorInfo", "colorSuccess", "colorWarning", "edgeStart", "edgeEnd", "sizeSmall", "sizeMedium", "sizeLarge", "loading", "loadingIndicator", "loadingWrapper"]); +const useUtilityClasses$t = (ownerState) => { + const { + classes, + disabled, + color: color2, + edge, + size, + loading + } = ownerState; + const slots = { + root: ["root", loading && "loading", disabled && "disabled", color2 !== "default" && `color${capitalize(color2)}`, edge && `edge${capitalize(edge)}`, `size${capitalize(size)}`], + loadingIndicator: ["loadingIndicator"], + loadingWrapper: ["loadingWrapper"] + }; + return composeClasses(slots, getIconButtonUtilityClass, classes); +}; +const IconButtonRoot = styled(ButtonBase, { + name: "MuiIconButton", + slot: "Root", + overridesResolver: (props, styles2) => { + const { + ownerState + } = props; + return [styles2.root, ownerState.loading && styles2.loading, ownerState.color !== "default" && styles2[`color${capitalize(ownerState.color)}`], ownerState.edge && styles2[`edge${capitalize(ownerState.edge)}`], styles2[`size${capitalize(ownerState.size)}`]]; + } +})(memoTheme(({ + theme +}) => ({ + textAlign: "center", + flex: "0 0 auto", + fontSize: theme.typography.pxToRem(24), + padding: 8, + borderRadius: "50%", + color: (theme.vars || theme).palette.action.active, + transition: theme.transitions.create("background-color", { + duration: theme.transitions.duration.shortest + }), + variants: [{ + props: (props) => !props.disableRipple, + style: { + "--IconButton-hoverBg": theme.vars ? `rgba(${theme.vars.palette.action.activeChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha(theme.palette.action.active, theme.palette.action.hoverOpacity), + "&:hover": { + backgroundColor: "var(--IconButton-hoverBg)", + // Reset on touch devices, it doesn't add specificity + "@media (hover: none)": { + backgroundColor: "transparent" + } + } + } + }, { + props: { + edge: "start" + }, + style: { + marginLeft: -12 + } + }, { + props: { + edge: "start", + size: "small" + }, + style: { + marginLeft: -3 + } + }, { + props: { + edge: "end" + }, + style: { + marginRight: -12 + } + }, { + props: { + edge: "end", + size: "small" + }, + style: { + marginRight: -3 + } + }] +})), memoTheme(({ + theme +}) => ({ + variants: [{ + props: { + color: "inherit" + }, + style: { + color: "inherit" + } + }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()).map(([color2]) => ({ + props: { + color: color2 + }, + style: { + color: (theme.vars || theme).palette[color2].main + } + })), ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()).map(([color2]) => ({ + props: { + color: color2 + }, + style: { + "--IconButton-hoverBg": theme.vars ? `rgba(${(theme.vars || theme).palette[color2].mainChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha((theme.vars || theme).palette[color2].main, theme.palette.action.hoverOpacity) + } + })), { + props: { + size: "small" + }, + style: { + padding: 5, + fontSize: theme.typography.pxToRem(18) + } + }, { + props: { + size: "large" + }, + style: { + padding: 12, + fontSize: theme.typography.pxToRem(28) + } + }], + [`&.${iconButtonClasses.disabled}`]: { + backgroundColor: "transparent", + color: (theme.vars || theme).palette.action.disabled + }, + [`&.${iconButtonClasses.loading}`]: { + color: "transparent" + } +}))); +const IconButtonLoadingIndicator = styled("span", { + name: "MuiIconButton", + slot: "LoadingIndicator", + overridesResolver: (props, styles2) => styles2.loadingIndicator +})(({ + theme +}) => ({ + display: "none", + position: "absolute", + visibility: "visible", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + color: (theme.vars || theme).palette.action.disabled, + variants: [{ + props: { + loading: true + }, + style: { + display: "flex" + } + }] +})); +const IconButton = /* @__PURE__ */ reactExports.forwardRef(function IconButton2(inProps, ref) { + const props = useDefaultProps({ + props: inProps, + name: "MuiIconButton" + }); + const { + edge = false, + children, + className, + color: color2 = "default", + disabled = false, + disableFocusRipple = false, + size = "medium", + id: idProp, + loading = null, + loadingIndicator: loadingIndicatorProp, + ...other + } = props; + const loadingId = useId(idProp); + const loadingIndicator = loadingIndicatorProp ?? /* @__PURE__ */ jsxRuntimeExports.jsx(CircularProgress, { + "aria-labelledby": loadingId, + color: "inherit", + size: 16 + }); + const ownerState = { + ...props, + edge, + color: color2, + disabled, + disableFocusRipple, + loading, + loadingIndicator, + size + }; + const classes = useUtilityClasses$t(ownerState); + return /* @__PURE__ */ jsxRuntimeExports.jsxs(IconButtonRoot, { + id: loading ? loadingId : idProp, + className: clsx(classes.root, className), + centerRipple: true, + focusRipple: !disableFocusRipple, + disabled: disabled || loading, + ref, + ...other, + ownerState, + children: [typeof loading === "boolean" && // use plain HTML span to minimize the runtime overhead + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { + className: classes.loadingWrapper, + style: { + display: "contents" + }, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconButtonLoadingIndicator, { + className: classes.loadingIndicator, + ownerState, + children: loading && loadingIndicator + }) + }), children] + }); +}); +function getTypographyUtilityClass(slot) { + return generateUtilityClass("MuiTypography", slot); +} +const typographyClasses = generateUtilityClasses("MuiTypography", ["root", "h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "inherit", "button", "caption", "overline", "alignLeft", "alignRight", "alignCenter", "alignJustify", "noWrap", "gutterBottom", "paragraph"]); +const v6Colors = { + primary: true, + secondary: true, + error: true, + info: true, + success: true, + warning: true, + textPrimary: true, + textSecondary: true, + textDisabled: true +}; +const extendSxProp = internal_createExtendSxProp(); +const useUtilityClasses$s = (ownerState) => { + const { + align, + gutterBottom, + noWrap, + paragraph: paragraph2, + variant, + classes + } = ownerState; + const slots = { + root: ["root", variant, ownerState.align !== "inherit" && `align${capitalize(align)}`, gutterBottom && "gutterBottom", noWrap && "noWrap", paragraph2 && "paragraph"] + }; + return composeClasses(slots, getTypographyUtilityClass, classes); +}; +const TypographyRoot = styled("span", { + name: "MuiTypography", + slot: "Root", + overridesResolver: (props, styles2) => { + const { + ownerState + } = props; + return [styles2.root, ownerState.variant && styles2[ownerState.variant], ownerState.align !== "inherit" && styles2[`align${capitalize(ownerState.align)}`], ownerState.noWrap && styles2.noWrap, ownerState.gutterBottom && styles2.gutterBottom, ownerState.paragraph && styles2.paragraph]; + } +})(memoTheme(({ + theme +}) => { + var _a; + return { + margin: 0, + variants: [{ + props: { + variant: "inherit" + }, + style: { + // Some elements, like