From f941496c98e8c89e8ee0bc5a6055c87c8957a881 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 5 Sep 2023 16:10:22 -0400 Subject: [PATCH 01/33] chore(striker-ui): install formik, yup --- striker-ui/package-lock.json | 156 +++++++++++++++++++++++++++++++++-- striker-ui/package.json | 4 +- 2 files changed, 150 insertions(+), 10 deletions(-) diff --git a/striker-ui/package-lock.json b/striker-ui/package-lock.json index 8459bbca..0036bbaa 100644 --- a/striker-ui/package-lock.json +++ b/striker-ui/package-lock.json @@ -22,13 +22,15 @@ "@novnc/novnc": "^1.2.0", "axios": "^0.24.0", "format-data-size": "^0.1.0", + "formik": "^2.4.3", "netmask": "^2.0.2", "next": "^12.1.0", "pretty-bytes": "^5.6.0", "react": "17.0.2", "react-dom": "17.0.2", "swr": "^1.2.2", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "yup": "^1.2.0" }, "devDependencies": { "@commitlint/cli": "^17.7.0", @@ -2504,6 +2506,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3410,6 +3420,29 @@ "format-data-size": "dist/cli.js" } }, + "node_modules/formik": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.3.tgz", + "integrity": "sha512-2Dy79Szw3zlXmZiokUdKsn+n1ow4G8hRrC/n92cOWHNTWXCRpQXlyvz6HcjW7aSQZrldytvDOavYjhfmDnUq8Q==", + "funding": [ + { + "type": "individual", + "url": "https://opencollective.com/formik" + } + ], + "dependencies": { + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/fs-extra": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", @@ -4469,8 +4502,12 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "node_modules/lodash.camelcase": { "version": "4.3.0", @@ -5238,6 +5275,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -5311,6 +5353,11 @@ "react": "17.0.2" } }, + "node_modules/react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -6141,6 +6188,11 @@ "readable-stream": "3" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -6166,6 +6218,11 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -6190,8 +6247,7 @@ "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -6473,6 +6529,28 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yup": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.2.0.tgz", + "integrity": "sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { @@ -8220,6 +8298,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -8922,6 +9005,20 @@ "resolved": "https://registry.npmjs.org/format-data-size/-/format-data-size-0.1.0.tgz", "integrity": "sha512-iataqDS6c73/MpJal7+GCtXiTbZEOn8HwfHesLvHOzu9MQwQ6LNOLbK/oli9qZmrap7TPGlaJnYMCxNR9Fh4iA==" }, + "formik": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.3.tgz", + "integrity": "sha512-2Dy79Szw3zlXmZiokUdKsn+n1ow4G8hRrC/n92cOWHNTWXCRpQXlyvz6HcjW7aSQZrldytvDOavYjhfmDnUq8Q==", + "requires": { + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^2.0.0" + } + }, "fs-extra": { "version": "11.1.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz", @@ -9721,8 +9818,12 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, "lodash.camelcase": { "version": "4.3.0", @@ -10280,6 +10381,11 @@ } } }, + "property-expr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", + "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==" + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -10323,6 +10429,11 @@ "scheduler": "^0.20.2" } }, + "react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -10940,6 +11051,11 @@ "readable-stream": "3" } }, + "tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -10959,6 +11075,11 @@ "is-number": "^7.0.0" } }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -10980,8 +11101,7 @@ "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "tsutils": { "version": "3.21.0", @@ -11193,6 +11313,24 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "yup": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.2.0.tgz", + "integrity": "sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ==", + "requires": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + }, + "dependencies": { + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + } + } } } } diff --git a/striker-ui/package.json b/striker-ui/package.json index e91de57a..d85c7f69 100644 --- a/striker-ui/package.json +++ b/striker-ui/package.json @@ -27,13 +27,15 @@ "@novnc/novnc": "^1.2.0", "axios": "^0.24.0", "format-data-size": "^0.1.0", + "formik": "^2.4.3", "netmask": "^2.0.2", "next": "^12.1.0", "pretty-bytes": "^5.6.0", "react": "17.0.2", "react-dom": "17.0.2", "swr": "^1.2.2", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "yup": "^1.2.0" }, "devDependencies": { "@commitlint/cli": "^17.7.0", From 782bbd2bfafd81ec146dc727d90ced10bb2afdcf Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 12 Sep 2023 01:27:53 -0400 Subject: [PATCH 02/33] fix(striker-ui): add UncontrolledInput --- striker-ui/components/UncontrolledInput.tsx | 106 ++++++++++++++++++++ striker-ui/lib/consts/INPUT_TYPES.ts | 1 + striker-ui/types/UncontrolledInput.d.ts | 43 ++++++++ 3 files changed, 150 insertions(+) create mode 100644 striker-ui/components/UncontrolledInput.tsx create mode 100644 striker-ui/types/UncontrolledInput.d.ts diff --git a/striker-ui/components/UncontrolledInput.tsx b/striker-ui/components/UncontrolledInput.tsx new file mode 100644 index 00000000..befa9315 --- /dev/null +++ b/striker-ui/components/UncontrolledInput.tsx @@ -0,0 +1,106 @@ +import { + ForwardedRef, + ReactElement, + cloneElement, + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useMemo, + useState, +} from 'react'; + +import INPUT_TYPES from '../lib/consts/INPUT_TYPES'; +import MAP_TO_VALUE_CONVERTER from '../lib/consts/MAP_TO_VALUE_CONVERTER'; + +const UncontrolledInput = forwardRef( + ( + props: UncontrolledInputProps, + ref: ForwardedRef>, + ) => { + const { + input, + onChange = ({ handlers: { base, origin } }, ...args) => { + base?.call(null, ...args); + origin?.call(null, ...args); + }, + onMount, + onUnmount, + } = props; + const { props: inputProps } = input; + + const { valueKey, valueType } = useMemo(() => { + const { type } = inputProps; + + let vkey: 'checked' | 'value' = 'value'; + let vtype: keyof MapToInputType = 'string'; + + if (type === INPUT_TYPES.checkbox) { + vkey = 'checked'; + vtype = 'boolean'; + } + + return { + valueKey: vkey, + valueType: vtype, + }; + }, [inputProps]); + + const { + onChange: inputOnChange, + [valueKey]: inputValue, + ...restInputProps + } = inputProps; + + const [value, setValue] = useState(inputValue); + + const baseChangeEventHandler = useCallback( + ({ target: { [valueKey]: changed } }) => { + const converted = MAP_TO_VALUE_CONVERTER[valueType]( + changed, + ) as MapToInputType[ValueType]; + + setValue(converted); + }, + [valueKey, valueType], + ); + + const changeEventHandler = useCallback( + (...args) => + onChange?.call( + null, + { handlers: { base: baseChangeEventHandler, origin: inputOnChange } }, + ...args, + ), + [baseChangeEventHandler, inputOnChange, onChange], + ); + + // Handle mount/unmount events; these only happen once hence no deps + useEffect(() => { + onMount?.call(null); + + return onUnmount; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useImperativeHandle( + ref, + () => ({ + get: () => value, + set: setValue, + }), + [value], + ); + + return cloneElement(input, { + ...restInputProps, + onChange: changeEventHandler, + [valueKey]: value, + }); + }, +); + +UncontrolledInput.displayName = 'UncontrolledInput'; + +export default UncontrolledInput; diff --git a/striker-ui/lib/consts/INPUT_TYPES.ts b/striker-ui/lib/consts/INPUT_TYPES.ts index 5ce15917..1f1ab20a 100644 --- a/striker-ui/lib/consts/INPUT_TYPES.ts +++ b/striker-ui/lib/consts/INPUT_TYPES.ts @@ -1,4 +1,5 @@ const INPUT_TYPES = { + checkbox: 'checkbox', number: 'number', password: 'password', text: 'text', diff --git a/striker-ui/types/UncontrolledInput.d.ts b/striker-ui/types/UncontrolledInput.d.ts new file mode 100644 index 00000000..7c27404e --- /dev/null +++ b/striker-ui/types/UncontrolledInput.d.ts @@ -0,0 +1,43 @@ +type MuiInputBaseProps = import('@mui/material').InputBaseProps; + +type ReactChangeEventHandler = + import('react').ChangeEventHandler; + +type MuiInputBasePropsBlurEventHandler = Exclude< + MuiInputBaseProps['onBlur'], + undefined +>; + +type MuiInputBasePropsFocusEventHandler = Exclude< + MuiInputBaseProps['onFocus'], + undefined +>; + +type UncontrolledInputEventHandler = ( + toolbox: { handlers: { base?: HandlerType; origin?: HandlerType } }, + ...rest: Parameters +) => ReturnType; + +type UncontrolledInputComponentMountEventHandler = () => void; + +type UncontrolledInputComponentUnmountEventHandler = () => void; + +type UncontrolledInputOptionalProps = { + onBlur?: UncontrolledInputEventHandler; + onChange?: UncontrolledInputEventHandler; + onFocus?: UncontrolledInputEventHandler; + onMount?: UncontrolledInputComponentMountEventHandler; + onUnmount?: UncontrolledInputComponentUnmountEventHandler; +}; + +type UncontrolledInputProps = + UncontrolledInputOptionalProps & { + input: InputElement; + }; + +type UncontrolledInputForwardedRefContent< + ValueType extends keyof MapToInputType, +> = { + get: () => MapToInputType[ValueType]; + set: (value: MapToInputType[ValueType]) => void; +}; From 3431cac5ddd8ff9780db526b7d1512aae947ace6 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 12 Sep 2023 01:48:54 -0400 Subject: [PATCH 03/33] chore(striker-ui): install lodash --- striker-ui/package-lock.json | 14 ++++++++++++++ striker-ui/package.json | 2 ++ 2 files changed, 16 insertions(+) diff --git a/striker-ui/package-lock.json b/striker-ui/package-lock.json index 0036bbaa..da71ae6b 100644 --- a/striker-ui/package-lock.json +++ b/striker-ui/package-lock.json @@ -23,6 +23,7 @@ "axios": "^0.24.0", "format-data-size": "^0.1.0", "formik": "^2.4.3", + "lodash": "^4.17.21", "netmask": "^2.0.2", "next": "^12.1.0", "pretty-bytes": "^5.6.0", @@ -35,6 +36,7 @@ "devDependencies": { "@commitlint/cli": "^17.7.0", "@commitlint/config-conventional": "^12.1.4", + "@types/lodash": "^4.14.198", "@types/netmask": "^1.0.30", "@types/node": "^15.12.2", "@types/novnc-core": "^0.1.3", @@ -1552,6 +1554,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.198", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.198.tgz", + "integrity": "sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==", + "dev": true + }, "node_modules/@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", @@ -7598,6 +7606,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.198", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.198.tgz", + "integrity": "sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==", + "dev": true + }, "@types/minimist": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", diff --git a/striker-ui/package.json b/striker-ui/package.json index d85c7f69..79ea8e2d 100644 --- a/striker-ui/package.json +++ b/striker-ui/package.json @@ -28,6 +28,7 @@ "axios": "^0.24.0", "format-data-size": "^0.1.0", "formik": "^2.4.3", + "lodash": "^4.17.21", "netmask": "^2.0.2", "next": "^12.1.0", "pretty-bytes": "^5.6.0", @@ -40,6 +41,7 @@ "devDependencies": { "@commitlint/cli": "^17.7.0", "@commitlint/config-conventional": "^12.1.4", + "@types/lodash": "^4.14.198", "@types/netmask": "^1.0.30", "@types/node": "^15.12.2", "@types/novnc-core": "^0.1.3", From f064623dd3cbd0f1a7cf56076c8969e28df4df8f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 13 Sep 2023 17:17:18 -0400 Subject: [PATCH 04/33] fix(striker-ui): adjust combine scroll box branching in ConfirmDialog --- striker-ui/components/ConfirmDialog.tsx | 34 +++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/striker-ui/components/ConfirmDialog.tsx b/striker-ui/components/ConfirmDialog.tsx index 319bf313..d7f4506e 100644 --- a/striker-ui/components/ConfirmDialog.tsx +++ b/striker-ui/components/ConfirmDialog.tsx @@ -203,29 +203,25 @@ const ConfirmDialog = forwardRef< ), [titleText], ); - const combinedScrollBoxSx = useMemo(() => { - let result: SxProps | undefined; + const combinedScrollBoxSx = useMemo | undefined>(() => { + if (!isScrollContent) return scrollBoxSx; - if (isScrollContent) { - let overflowX: 'hidden' | undefined; - let paddingTop: string | undefined; + let overflowX: 'hidden' | undefined; + let paddingTop: string | undefined; - if (isFormContent) { - overflowX = 'hidden'; - paddingTop = '.6em'; - } - - result = { - maxHeight: '60vh', - overflowX, - overflowY: 'scroll', - paddingRight: '.4em', - paddingTop, - ...scrollBoxSx, - }; + if (isFormContent) { + overflowX = 'hidden'; + paddingTop = '.6em'; } - return result; + return { + maxHeight: '60vh', + overflowX, + overflowY: 'scroll', + paddingRight: '.4em', + paddingTop, + ...scrollBoxSx, + }; }, [isFormContent, isScrollContent, scrollBoxSx]); const contentAreaElement = useMemo( From e2c264a40fafc3c5194a9b5c06ba08ff69251a5e Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 13 Sep 2023 18:49:52 -0400 Subject: [PATCH 05/33] fix(striker-ui): add colour presets in ContainedButton --- striker-ui/components/ConfirmDialog.tsx | 27 +-------- striker-ui/components/ContainedButton.tsx | 74 ++++++++++++++++------- striker-ui/types/ContainedButton.d.ts | 9 ++- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/striker-ui/components/ConfirmDialog.tsx b/striker-ui/components/ConfirmDialog.tsx index d7f4506e..fbd15fe4 100644 --- a/striker-ui/components/ConfirmDialog.tsx +++ b/striker-ui/components/ConfirmDialog.tsx @@ -10,22 +10,12 @@ import { useState, } from 'react'; -import { BLUE, RED, TEXT } from '../lib/consts/DEFAULT_THEME'; - import ContainedButton from './ContainedButton'; import FlexBox from './FlexBox'; import { Panel, PanelHeader } from './Panels'; import Spinner from './Spinner'; import { BodyText, HeaderText } from './Text'; -const MAP_TO_COLOUR: Record< - Exclude, - string -> = { - blue: BLUE, - red: RED, -}; - const ConfirmDialog = forwardRef< ConfirmDialogForwardedRefContent, ConfirmDialogProps @@ -53,7 +43,7 @@ const ConfirmDialog = forwardRef< openInitially = false, preActionArea, proceedButtonProps = {}, - proceedColour: proceedColourKey = 'blue', + proceedColour = 'blue', scrollContent: isScrollContent = false, scrollBoxProps: { sx: scrollBoxSx, ...restScrollBoxProps } = {}, titleText, @@ -63,7 +53,6 @@ const ConfirmDialog = forwardRef< const { sx: paperSx, ...restPaperProps } = paperProps; const { disabled: proceedButtonDisabled = isDisableProceed, - sx: proceedButtonSx, ...restProceedButtonProps } = proceedButtonProps; @@ -75,10 +64,6 @@ const ConfirmDialog = forwardRef< () => (ref ? isOpen : baseOpen), [baseOpen, isOpen, ref], ); - const proceedColour = useMemo( - () => MAP_TO_COLOUR[proceedColourKey], - [proceedColourKey], - ); const { contentContainerComponent, contentContainerSubmitEventHandler, @@ -146,18 +131,11 @@ const ConfirmDialog = forwardRef< const proceedButtonElement = useMemo( () => ( {actionProceedText} @@ -166,7 +144,6 @@ const ConfirmDialog = forwardRef< actionProceedText, proceedButtonClickEventHandler, proceedButtonDisabled, - proceedButtonSx, proceedButtonType, proceedColour, restProceedButtonProps, diff --git a/striker-ui/components/ContainedButton.tsx b/striker-ui/components/ContainedButton.tsx index 090bdf03..8f0e6a2e 100644 --- a/striker-ui/components/ContainedButton.tsx +++ b/striker-ui/components/ContainedButton.tsx @@ -1,34 +1,62 @@ import { - Button as MUIButton, + Button as MuiButton, buttonClasses as muiButtonClasses, - SxProps, - Theme, + styled, } from '@mui/material'; -import { FC, useMemo } from 'react'; +import { FC } from 'react'; -import { BLACK, DISABLED, GREY } from '../lib/consts/DEFAULT_THEME'; +import { + BLACK, + BLUE, + DISABLED, + GREY, + RED, + TEXT, +} from '../lib/consts/DEFAULT_THEME'; + +const MAP_TO_COLOUR: Record = { + blue: BLUE, + normal: GREY, + red: RED, +}; -const ContainedButton: FC = ({ sx, ...restProps }) => { - const combinedSx = useMemo>( - () => ({ - backgroundColor: GREY, - color: BLACK, - textTransform: 'none', +const BaseStyle = styled(MuiButton)({ + backgroundColor: GREY, + color: BLACK, + textTransform: 'none', - '&:hover': { - backgroundColor: `${GREY}F0`, - }, + '&:hover': { + backgroundColor: `${GREY}F0`, + }, - [`&.${muiButtonClasses.disabled}`]: { - backgroundColor: DISABLED, - }, + [`&.${muiButtonClasses.disabled}`]: { + backgroundColor: DISABLED, + }, +}); - ...sx, - }), - [sx], - ); +const Base: FC = (props) => ( + +); - return ; -}; +const ContainedButton = styled(Base)((props) => { + const { background = 'normal' } = props; + + let bg: string | undefined; + let color: string | undefined; + + if (background !== 'normal') { + bg = MAP_TO_COLOUR[background]; + color = TEXT; + } + + return { + backgroundColor: bg, + color, + + '&:hover': { + backgroundColor: `${bg}F0`, + }, + }; +}); export default ContainedButton; diff --git a/striker-ui/types/ContainedButton.d.ts b/striker-ui/types/ContainedButton.d.ts index 4f115135..6208da60 100644 --- a/striker-ui/types/ContainedButton.d.ts +++ b/striker-ui/types/ContainedButton.d.ts @@ -1 +1,8 @@ -type ContainedButtonProps = import('@mui/material').ButtonProps; +type ContainedButtonBackground = 'blue' | 'normal' | 'red'; + +type ContainedButtonOptionalProps = { + background?: ContainedButtonBackground; +}; + +type ContainedButtonProps = import('@mui/material').ButtonProps & + ContainedButtonOptionalProps; From bbbdb42d59a7f4c3dfb5ae16dc0bf1dceba95e69 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 14 Sep 2023 20:03:49 -0400 Subject: [PATCH 06/33] fix(striker-ui): add string wrapper function --- striker-ui/lib/sxstring.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 striker-ui/lib/sxstring.ts diff --git a/striker-ui/lib/sxstring.ts b/striker-ui/lib/sxstring.ts new file mode 100644 index 00000000..6b512d16 --- /dev/null +++ b/striker-ui/lib/sxstring.ts @@ -0,0 +1,14 @@ +import { ReactNode, createElement } from 'react'; + +/** + * "jsx"/"tsx" + "string"; wraps input with wrapper if input is a string. + */ +const sxstring = ( + children: ReactNode, + wrapper: CreatableComponent, +): ReactNode => + typeof children === 'string' + ? createElement(wrapper, null, children) + : children; + +export default sxstring; From c03ad02738cfa5232bde020706bc722efe71123c Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 14 Sep 2023 20:10:46 -0400 Subject: [PATCH 07/33] fix(striker-ui): move type to extendable event handler --- striker-ui/types/ExtendableEventHandler.d.ts | 4 ++++ striker-ui/types/UncontrolledInput.d.ts | 11 +++-------- 2 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 striker-ui/types/ExtendableEventHandler.d.ts diff --git a/striker-ui/types/ExtendableEventHandler.d.ts b/striker-ui/types/ExtendableEventHandler.d.ts new file mode 100644 index 00000000..55df3d3b --- /dev/null +++ b/striker-ui/types/ExtendableEventHandler.d.ts @@ -0,0 +1,4 @@ +type ExtendableEventHandler = ( + toolbox: { handlers: { base?: T; origin?: T } }, + ...rest: Parameters +) => ReturnType; diff --git a/striker-ui/types/UncontrolledInput.d.ts b/striker-ui/types/UncontrolledInput.d.ts index 7c27404e..7461a4a9 100644 --- a/striker-ui/types/UncontrolledInput.d.ts +++ b/striker-ui/types/UncontrolledInput.d.ts @@ -13,19 +13,14 @@ type MuiInputBasePropsFocusEventHandler = Exclude< undefined >; -type UncontrolledInputEventHandler = ( - toolbox: { handlers: { base?: HandlerType; origin?: HandlerType } }, - ...rest: Parameters -) => ReturnType; - type UncontrolledInputComponentMountEventHandler = () => void; type UncontrolledInputComponentUnmountEventHandler = () => void; type UncontrolledInputOptionalProps = { - onBlur?: UncontrolledInputEventHandler; - onChange?: UncontrolledInputEventHandler; - onFocus?: UncontrolledInputEventHandler; + onBlur?: ExtendableEventHandler; + onChange?: ExtendableEventHandler; + onFocus?: ExtendableEventHandler; onMount?: UncontrolledInputComponentMountEventHandler; onUnmount?: UncontrolledInputComponentUnmountEventHandler; }; From a8f517026424a3d46291c6185a35988b68dc9d5e Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 14 Sep 2023 23:49:11 -0400 Subject: [PATCH 08/33] fix(striker-ui): decompose confirm dialog --- striker-ui/components/ConfirmDialog.tsx | 285 +++++------------- striker-ui/components/Dialog/Dialog.tsx | 86 ++++++ .../components/Dialog/DialogActionArea.tsx | 112 +++++++ striker-ui/components/Dialog/DialogHeader.tsx | 42 +++ .../components/Dialog/DialogScrollBox.tsx | 9 + striker-ui/components/Dialog/index.tsx | 6 + striker-ui/components/FormDialog.tsx | 127 ++++---- striker-ui/types/ConfirmDialog.d.ts | 18 +- striker-ui/types/Dialog.d.ts | 43 +++ 9 files changed, 450 insertions(+), 278 deletions(-) create mode 100644 striker-ui/components/Dialog/Dialog.tsx create mode 100644 striker-ui/components/Dialog/DialogActionArea.tsx create mode 100644 striker-ui/components/Dialog/DialogHeader.tsx create mode 100644 striker-ui/components/Dialog/DialogScrollBox.tsx create mode 100644 striker-ui/components/Dialog/index.tsx create mode 100644 striker-ui/types/Dialog.d.ts diff --git a/striker-ui/components/ConfirmDialog.tsx b/striker-ui/components/ConfirmDialog.tsx index fbd15fe4..e4700fec 100644 --- a/striker-ui/components/ConfirmDialog.tsx +++ b/striker-ui/components/ConfirmDialog.tsx @@ -1,256 +1,115 @@ -import { Box, Dialog as MUIDialog, SxProps, Theme } from '@mui/material'; +import { Box as MuiBox } from '@mui/material'; import { - ButtonHTMLAttributes, - ElementType, - FormEventHandler, + ForwardRefExoticComponent, + PropsWithChildren, + RefAttributes, + createElement, forwardRef, - MouseEventHandler, useImperativeHandle, useMemo, - useState, + useRef, } from 'react'; -import ContainedButton from './ContainedButton'; +import { + Dialog, + DialogActionArea, + DialogHeader, + DialogScrollBox, +} from './Dialog'; import FlexBox from './FlexBox'; -import { Panel, PanelHeader } from './Panels'; -import Spinner from './Spinner'; -import { BodyText, HeaderText } from './Text'; +import sxstring from '../lib/sxstring'; +import { BodyText } from './Text'; -const ConfirmDialog = forwardRef< - ConfirmDialogForwardedRefContent, - ConfirmDialogProps ->( +const ConfirmDialog: ForwardRefExoticComponent< + PropsWithChildren & + RefAttributes +> = forwardRef( ( { actionCancelText = 'Cancel', actionProceedText, - contentContainerProps = {}, - closeOnProceed: isCloseOnProceed = false, - content, - dialogProps: { - open: baseOpen = false, - PaperProps: paperProps = {}, - ...restDialogProps - } = {}, - disableProceed: isDisableProceed, - formContent: isFormContent, - loading: isLoading = false, - loadingAction: isLoadingAction = false, + children, + closeOnProceed = false, + contentContainerProps, + dialogProps, + disableProceed, + loading, + loadingAction = false, onActionAppend, onCancelAppend, onProceedAppend, - onSubmitAppend, - openInitially = false, + openInitially, preActionArea, - proceedButtonProps = {}, + proceedButtonProps, proceedColour = 'blue', - scrollContent: isScrollContent = false, - scrollBoxProps: { sx: scrollBoxSx, ...restScrollBoxProps } = {}, + scrollContent = false, + scrollBoxProps, + showClose, titleText, + // Dependents + content = children, }, ref, ) => { - const { sx: paperSx, ...restPaperProps } = paperProps; - const { - disabled: proceedButtonDisabled = isDisableProceed, - ...restProceedButtonProps - } = proceedButtonProps; - - const [isOpen, setIsOpen] = useState(openInitially); - - // TODO: using base open is depreciated; use internal state once all - // dependent components finish the migrate. - const open = useMemo( - () => (ref ? isOpen : baseOpen), - [baseOpen, isOpen, ref], - ); - const { - contentContainerComponent, - contentContainerSubmitEventHandler, - proceedButtonClickEventHandler, - proceedButtonType, - } = useMemo(() => { - let ccComponent: ElementType | undefined; - let ccSubmitEventHandler: FormEventHandler | undefined; - let pbClickEventHandler: - | MouseEventHandler - | undefined = (...args) => { - if (isCloseOnProceed) { - setIsOpen(false); - } - - onActionAppend?.call(null, ...args); - onProceedAppend?.call(null, ...args); - }; - let pbType: ButtonHTMLAttributes['type'] | undefined; - - if (isFormContent) { - ccComponent = 'form'; - ccSubmitEventHandler = (event, ...restArgs) => { - event.preventDefault(); - - if (isCloseOnProceed) { - setIsOpen(false); - } + const dialogRef = useRef(null); - onSubmitAppend?.call(null, event, ...restArgs); - }; - pbClickEventHandler = undefined; - pbType = 'submit'; - } - - return { - contentContainerComponent: ccComponent, - contentContainerSubmitEventHandler: ccSubmitEventHandler, - proceedButtonClickEventHandler: pbClickEventHandler, - proceedButtonType: pbType, - }; - }, [ - isCloseOnProceed, - isFormContent, - onActionAppend, - onProceedAppend, - onSubmitAppend, - ]); - - const cancelButtonElement = useMemo( - () => ( - { - setIsOpen(false); - - onActionAppend?.call(null, ...args); - onCancelAppend?.call(null, ...args); - }} - > - {actionCancelText} - - ), - [actionCancelText, onActionAppend, onCancelAppend], - ); - const proceedButtonElement = useMemo( - () => ( - - {actionProceedText} - - ), - [ - actionProceedText, - proceedButtonClickEventHandler, - proceedButtonDisabled, - proceedButtonType, - proceedColour, - restProceedButtonProps, - ], - ); - - const actionAreaElement = useMemo( - () => - isLoadingAction ? ( - - ) : ( - - {cancelButtonElement} - {proceedButtonElement} - - ), - [cancelButtonElement, isLoadingAction, proceedButtonElement], - ); const contentElement = useMemo( - () => - typeof content === 'string' ? : content, + () => sxstring(content, BodyText), [content], ); - const headerElement = useMemo( - () => - typeof titleText === 'string' ? ( - {titleText} - ) : ( - titleText - ), - [titleText], - ); - const combinedScrollBoxSx = useMemo | undefined>(() => { - if (!isScrollContent) return scrollBoxSx; - - let overflowX: 'hidden' | undefined; - let paddingTop: string | undefined; - - if (isFormContent) { - overflowX = 'hidden'; - paddingTop = '.6em'; - } - - return { - maxHeight: '60vh', - overflowX, - overflowY: 'scroll', - paddingRight: '.4em', - paddingTop, - ...scrollBoxSx, - }; - }, [isFormContent, isScrollContent, scrollBoxSx]); - const contentAreaElement = useMemo( + const bodyElement = useMemo( () => - isLoading ? ( - - ) : ( - <> - - {contentElement} - - {preActionArea} - {actionAreaElement} - + createElement( + scrollContent ? DialogScrollBox : MuiBox, + scrollBoxProps, + contentElement, ), - [ - actionAreaElement, - combinedScrollBoxSx, - contentElement, - isLoading, - preActionArea, - restScrollBoxProps, - ], + [contentElement, scrollBoxProps, scrollContent], ); useImperativeHandle( ref, () => ({ - setOpen: (value) => setIsOpen(value), + setOpen: (open) => dialogRef.current?.setOpen(open), }), [], ); return ( - - {headerElement} - - {contentAreaElement} + {titleText} + + {bodyElement} + {preActionArea} + { + onActionAppend?.call(null, ...args); + onCancelAppend?.call(null, ...args); + }, + }} + closeOnProceed={closeOnProceed} + loading={loadingAction} + proceedProps={{ + background: proceedColour, + children: actionProceedText, + disabled: disableProceed, + onClick: (...args) => { + onActionAppend?.call(null, ...args); + onProceedAppend?.call(null, ...args); + }, + ...proceedButtonProps, + }} + /> - + ); }, ); diff --git a/striker-ui/components/Dialog/Dialog.tsx b/striker-ui/components/Dialog/Dialog.tsx new file mode 100644 index 00000000..285a7d90 --- /dev/null +++ b/striker-ui/components/Dialog/Dialog.tsx @@ -0,0 +1,86 @@ +import { Dialog as MuiDialog, SxProps, Theme } from '@mui/material'; +import { + ForwardRefExoticComponent, + PropsWithChildren, + ReactNode, + RefAttributes, + createContext, + forwardRef, + useImperativeHandle, + useMemo, + useState, +} from 'react'; + +import { Panel } from '../Panels'; +import Spinner from '../Spinner'; + +const DialogContext = createContext( + undefined, +); + +const Dialog: ForwardRefExoticComponent< + PropsWithChildren & RefAttributes +> = forwardRef((props, ref) => { + const { + children: externalChildren, + dialogProps = {}, + loading, + openInitially = false, + } = props; + + const { + open: externalOpen = false, + PaperProps: paperProps = {}, + ...restDialogProps + } = dialogProps; + + const { sx: externalPaperSx, ...restPaperProps } = paperProps; + + const [controlOpen, setControlOpen] = useState(openInitially); + + const open = useMemo( + () => (ref ? controlOpen : externalOpen), + [controlOpen, externalOpen, ref], + ); + + const children = useMemo( + () => (loading ? : externalChildren), + [externalChildren, loading], + ); + + const paperSx = useMemo>( + () => ({ + overflow: 'visible', + ...externalPaperSx, + }), + [externalPaperSx], + ); + + useImperativeHandle( + ref, + () => ({ + open, + setOpen: setControlOpen, + }), + [open], + ); + + return ( + + + {children} + + + ); +}); + +Dialog.displayName = 'Dialog'; + +export { DialogContext }; + +export default Dialog; diff --git a/striker-ui/components/Dialog/DialogActionArea.tsx b/striker-ui/components/Dialog/DialogActionArea.tsx new file mode 100644 index 00000000..cb80f541 --- /dev/null +++ b/striker-ui/components/Dialog/DialogActionArea.tsx @@ -0,0 +1,112 @@ +import { styled } from '@mui/material'; +import { FC, useCallback, useContext, useMemo } from 'react'; + +import ContainedButton from '../ContainedButton'; +import { DialogContext } from './Dialog'; +import FlexBox from '../FlexBox'; +import Spinner from '../Spinner'; + +const FlexEndBox = styled(FlexBox)({ + justifyContent: 'flex-end', + width: '100%', +}); + +const handleAction: ExtendableEventHandler = ( + { handlers: { base, origin } }, + ...args +) => { + base?.call(null, ...args); + origin?.call(null, ...args); +}; + +const DialogActionArea: FC = (props) => { + const { + cancelProps, + closeOnProceed, + loading = false, + onCancel = handleAction, + onProceed = handleAction, + proceedColour, + proceedProps, + // Dependents + cancelChildren = cancelProps?.children, + proceedChildren = proceedProps?.children, + } = props; + + const dialogContext = useContext(DialogContext); + + const cancelHandler = useCallback( + (...args) => + onCancel( + { + handlers: { + base: () => { + dialogContext?.setOpen(false); + }, + origin: cancelProps?.onClick, + }, + }, + ...args, + ), + [cancelProps?.onClick, dialogContext, onCancel], + ); + + const proceedHandler = useCallback( + (...args) => + onProceed( + { + handlers: { + base: () => { + if (closeOnProceed) { + dialogContext?.setOpen(false); + } + }, + origin: proceedProps?.onClick, + }, + }, + ...args, + ), + [closeOnProceed, dialogContext, onProceed, proceedProps?.onClick], + ); + + const cancelButton = useMemo( + () => ( + + {cancelChildren} + + ), + [cancelChildren, cancelHandler, cancelProps], + ); + + const proceedButton = useMemo( + () => ( + + {proceedChildren} + + ), + [proceedChildren, proceedColour, proceedHandler, proceedProps], + ); + + const actions = useMemo( + () => ( + + {cancelButton} + {proceedButton} + + ), + [cancelButton, proceedButton], + ); + + const actionArea = useMemo( + () => (loading ? : actions), + [actions, loading], + ); + + return actionArea; +}; + +export default DialogActionArea; diff --git a/striker-ui/components/Dialog/DialogHeader.tsx b/striker-ui/components/Dialog/DialogHeader.tsx new file mode 100644 index 00000000..7fdb1541 --- /dev/null +++ b/striker-ui/components/Dialog/DialogHeader.tsx @@ -0,0 +1,42 @@ +import { FC, ReactNode, useContext, useMemo } from 'react'; + +import { DialogContext } from './Dialog'; +import IconButton from '../IconButton'; +import { PanelHeader } from '../Panels'; +import sxstring from '../../lib/sxstring'; +import { HeaderText } from '../Text'; + +const DialogHeader: FC = (props) => { + const { children, showClose } = props; + + const dialogContext = useContext(DialogContext); + + const title = useMemo( + () => sxstring(children, HeaderText), + [children], + ); + + const close = useMemo( + () => + showClose && ( + { + dialogContext?.setOpen(false); + }} + size="small" + variant="redcontained" + /> + ), + [dialogContext, showClose], + ); + + return ( + + {title} + {close} + + ); +}; + +export default DialogHeader; diff --git a/striker-ui/components/Dialog/DialogScrollBox.tsx b/striker-ui/components/Dialog/DialogScrollBox.tsx new file mode 100644 index 00000000..5566dbfa --- /dev/null +++ b/striker-ui/components/Dialog/DialogScrollBox.tsx @@ -0,0 +1,9 @@ +import { Box as MuiBox, styled } from '@mui/material'; + +const DialogScrollBox = styled(MuiBox)({ + maxHeight: '60vh', + overflowY: 'scroll', + paddingRight: '.4em', +}); + +export default DialogScrollBox; diff --git a/striker-ui/components/Dialog/index.tsx b/striker-ui/components/Dialog/index.tsx new file mode 100644 index 00000000..26339e3f --- /dev/null +++ b/striker-ui/components/Dialog/index.tsx @@ -0,0 +1,6 @@ +import Dialog from './Dialog'; +import DialogActionArea from './DialogActionArea'; +import DialogHeader from './DialogHeader'; +import DialogScrollBox from './DialogScrollBox'; + +export { Dialog, DialogActionArea, DialogHeader, DialogScrollBox }; diff --git a/striker-ui/components/FormDialog.tsx b/striker-ui/components/FormDialog.tsx index 1babad4a..422bb4bb 100644 --- a/striker-ui/components/FormDialog.tsx +++ b/striker-ui/components/FormDialog.tsx @@ -1,67 +1,84 @@ -import { forwardRef, useMemo } from 'react'; +import { BoxProps as MuiBoxProps } from '@mui/material'; +import { + ForwardRefExoticComponent, + PropsWithChildren, + RefAttributes, + forwardRef, + useMemo, +} from 'react'; import ConfirmDialog from './ConfirmDialog'; -import IconButton from './IconButton'; -import { HeaderText } from './Text'; +import { FlexBoxProps } from './FlexBox'; -const FormDialog = forwardRef< - ConfirmDialogForwardedRefContent, - ConfirmDialogProps & { showClose?: boolean } ->((props, ref) => { - const { scrollContent, showClose, titleText, ...restProps } = props; +const FormDialog: ForwardRefExoticComponent< + PropsWithChildren & + RefAttributes +> = forwardRef( + (props, ref) => { + const { + children, + contentContainerProps, + dialogProps, + onSubmitAppend, + proceedButtonProps, + scrollBoxProps, + scrollContent, + ...restProps + } = props; - const scrollBoxPaddingRight = useMemo( - () => (scrollContent ? '.5em' : undefined), - [scrollContent], - ); + const formBodyProps = useMemo( + () => ({ + ...contentContainerProps, + component: 'form', + onSubmit: (...args) => { + const [event] = args; - const titleElement = useMemo(() => { - const title = - typeof titleText === 'string' ? ( - {titleText} - ) : ( - titleText - ); + event.preventDefault(); - return showClose ? ( - <> - {title} - { - if (ref && 'current' in ref) { - ref.current?.setOpen?.call(null, false); - } - }} - variant="redcontained" - /> - - ) : ( - title + onSubmitAppend?.call(null, ...args); + }, + }), + [contentContainerProps, onSubmitAppend], ); - }, [ref, showClose, titleText]); - return ( - - ); -}); + const formScrollBoxProps = useMemo( + () => ({ + ...scrollBoxProps, + sx: scrollContent + ? { + overflowX: 'hidden', + paddingTop: '.6em', + ...scrollBoxProps?.sx, + } + : scrollBoxProps?.sx, + }), + [scrollBoxProps, scrollContent], + ); -FormDialog.defaultProps = { - showClose: false, -}; + return ( + + {children} + + ); + }, +); FormDialog.displayName = 'FormDialog'; diff --git a/striker-ui/types/ConfirmDialog.d.ts b/striker-ui/types/ConfirmDialog.d.ts index f7e55de3..67c688ab 100644 --- a/striker-ui/types/ConfirmDialog.d.ts +++ b/striker-ui/types/ConfirmDialog.d.ts @@ -4,17 +4,14 @@ type DivFormEventHandlerParameters = Parameters; type ConfirmDialogOptionalProps = { actionCancelText?: string; closeOnProceed?: boolean; + content?: import('react').ReactNode; contentContainerProps?: import('../components/FlexBox').FlexBoxProps; - dialogProps?: Partial; disableProceed?: boolean; - formContent?: boolean; - loading?: boolean; loadingAction?: boolean; onActionAppend?: ContainedButtonProps['onClick']; - onProceedAppend?: ContainedButtonProps['onClick']; onCancelAppend?: ContainedButtonProps['onClick']; + onProceedAppend?: ContainedButtonProps['onClick']; onSubmitAppend?: DivFormEventHandler; - openInitially?: boolean; preActionArea?: import('react').ReactNode; proceedButtonProps?: ContainedButtonProps; proceedColour?: 'blue' | 'red'; @@ -22,11 +19,12 @@ type ConfirmDialogOptionalProps = { scrollBoxProps?: import('@mui/material').BoxProps; }; -type ConfirmDialogProps = ConfirmDialogOptionalProps & { - actionProceedText: string; - content: import('react').ReactNode; - titleText: import('react').ReactNode; -}; +type ConfirmDialogProps = DialogProps & + DialogHeaderProps & + ConfirmDialogOptionalProps & { + actionProceedText: string; + titleText: import('react').ReactNode; + }; type ConfirmDialogForwardedRefContent = { setOpen?: (value: boolean) => void; diff --git a/striker-ui/types/Dialog.d.ts b/striker-ui/types/Dialog.d.ts new file mode 100644 index 00000000..adbf6d98 --- /dev/null +++ b/striker-ui/types/Dialog.d.ts @@ -0,0 +1,43 @@ +type DialogContextContent = { + open: boolean; + setOpen: (open: boolean) => void; +}; + +type DialogOptionalProps = { + dialogProps?: Partial; + loading?: boolean; + openInitially?: boolean; +}; + +type DialogProps = DialogOptionalProps; + +type DialogForwardedRefContent = DialogContextContent; + +/** DialogHeader */ + +type DialogHeaderOptionalProps = { + showClose?: boolean; +}; + +type DialogHeaderProps = DialogHeaderOptionalProps; + +/** DialogActionArea */ + +type ButtonClickEventHandler = Exclude< + ContainedButtonProps['onClick'], + undefined +>; + +type DialogActionAreaOptionalProps = { + cancelChildren?: ContainedButtonProps['children']; + cancelProps?: Partial; + closeOnProceed?: boolean; + loading?: boolean; + onCancel?: ExtendableEventHandler; + onProceed?: ExtendableEventHandler; + proceedChildren?: ContainedButtonProps['children']; + proceedColour?: ContainedButtonProps['background']; + proceedProps?: Partial; +}; + +type DialogActionAreaProps = DialogActionAreaOptionalProps; From 414987ef3ec2925bacca26dc168a4737c74ae3bd Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 15 Sep 2023 00:27:29 -0400 Subject: [PATCH 09/33] fix(striker-ui): decompose dialog scroll box --- striker-ui/components/Dialog/DialogScrollBox.tsx | 8 ++++---- striker-ui/components/ScrollBox.tsx | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 striker-ui/components/ScrollBox.tsx diff --git a/striker-ui/components/Dialog/DialogScrollBox.tsx b/striker-ui/components/Dialog/DialogScrollBox.tsx index 5566dbfa..fa884121 100644 --- a/striker-ui/components/Dialog/DialogScrollBox.tsx +++ b/striker-ui/components/Dialog/DialogScrollBox.tsx @@ -1,9 +1,9 @@ -import { Box as MuiBox, styled } from '@mui/material'; +import { styled } from '@mui/material'; -const DialogScrollBox = styled(MuiBox)({ +import ScrollBox from '../ScrollBox'; + +const DialogScrollBox = styled(ScrollBox)({ maxHeight: '60vh', - overflowY: 'scroll', - paddingRight: '.4em', }); export default DialogScrollBox; diff --git a/striker-ui/components/ScrollBox.tsx b/striker-ui/components/ScrollBox.tsx new file mode 100644 index 00000000..92da81e8 --- /dev/null +++ b/striker-ui/components/ScrollBox.tsx @@ -0,0 +1,8 @@ +import { Box as MuiBox, styled } from '@mui/material'; + +const ScrollBox = styled(MuiBox)({ + overflowY: 'scroll', + paddingRight: '.4em', +}); + +export default ScrollBox; From e8fe92321e80fd902b9b1b5676642b4446ac3898 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 15 Sep 2023 13:41:24 -0400 Subject: [PATCH 10/33] fix(striker-ui): break dialog action group --- striker-ui/components/ActionGroup.tsx | 33 +++++++++ ...ogActionArea.tsx => DialogActionGroup.tsx} | 68 ++++++++----------- striker-ui/components/Dialog/index.tsx | 9 ++- striker-ui/types/ActionGroup.d.ts | 5 ++ striker-ui/types/Dialog.d.ts | 6 +- 5 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 striker-ui/components/ActionGroup.tsx rename striker-ui/components/Dialog/{DialogActionArea.tsx => DialogActionGroup.tsx} (61%) create mode 100644 striker-ui/types/ActionGroup.d.ts diff --git a/striker-ui/components/ActionGroup.tsx b/striker-ui/components/ActionGroup.tsx new file mode 100644 index 00000000..f31e1b4a --- /dev/null +++ b/striker-ui/components/ActionGroup.tsx @@ -0,0 +1,33 @@ +import { styled } from '@mui/material'; +import { FC, ReactElement, useMemo } from 'react'; +import { v4 as uuidv4 } from 'uuid'; + +import ContainedButton from './ContainedButton'; +import FlexBox from './FlexBox'; + +const FlexEndBox = styled(FlexBox)({ + justifyContent: 'flex-end', + width: '100%', +}); + +const ActionGroup: FC = (props) => { + const { actions = [] } = props; + + const elements = useMemo( + () => + actions.map((actionProps) => ( + + {actionProps.children} + + )), + [actions], + ); + + return ( + + {elements} + + ); +}; + +export default ActionGroup; diff --git a/striker-ui/components/Dialog/DialogActionArea.tsx b/striker-ui/components/Dialog/DialogActionGroup.tsx similarity index 61% rename from striker-ui/components/Dialog/DialogActionArea.tsx rename to striker-ui/components/Dialog/DialogActionGroup.tsx index cb80f541..32aa6b57 100644 --- a/striker-ui/components/Dialog/DialogActionArea.tsx +++ b/striker-ui/components/Dialog/DialogActionGroup.tsx @@ -1,16 +1,9 @@ -import { styled } from '@mui/material'; import { FC, useCallback, useContext, useMemo } from 'react'; -import ContainedButton from '../ContainedButton'; +import ActionGroup from '../ActionGroup'; import { DialogContext } from './Dialog'; -import FlexBox from '../FlexBox'; import Spinner from '../Spinner'; -const FlexEndBox = styled(FlexBox)({ - justifyContent: 'flex-end', - width: '100%', -}); - const handleAction: ExtendableEventHandler = ( { handlers: { base, origin } }, ...args @@ -19,7 +12,7 @@ const handleAction: ExtendableEventHandler = ( origin?.call(null, ...args); }; -const DialogActionArea: FC = (props) => { +const DialogActionGroup: FC = (props) => { const { cancelProps, closeOnProceed, @@ -69,44 +62,41 @@ const DialogActionArea: FC = (props) => { [closeOnProceed, dialogContext, onProceed, proceedProps?.onClick], ); - const cancelButton = useMemo( - () => ( - - {cancelChildren} - - ), - [cancelChildren, cancelHandler, cancelProps], - ); - - const proceedButton = useMemo( - () => ( - - {proceedChildren} - - ), - [proceedChildren, proceedColour, proceedHandler, proceedProps], - ); - const actions = useMemo( () => ( - - {cancelButton} - {proceedButton} - + ), - [cancelButton, proceedButton], + [ + cancelChildren, + cancelHandler, + cancelProps, + proceedChildren, + proceedColour, + proceedHandler, + proceedProps, + ], ); - const actionArea = useMemo( + const result = useMemo( () => (loading ? : actions), [actions, loading], ); - return actionArea; + return result; }; -export default DialogActionArea; +export default DialogActionGroup; diff --git a/striker-ui/components/Dialog/index.tsx b/striker-ui/components/Dialog/index.tsx index 26339e3f..f9d4d9da 100644 --- a/striker-ui/components/Dialog/index.tsx +++ b/striker-ui/components/Dialog/index.tsx @@ -1,6 +1,11 @@ import Dialog from './Dialog'; -import DialogActionArea from './DialogActionArea'; +import DialogActionGroup from './DialogActionGroup'; import DialogHeader from './DialogHeader'; import DialogScrollBox from './DialogScrollBox'; -export { Dialog, DialogActionArea, DialogHeader, DialogScrollBox }; +export { + Dialog, + DialogActionGroup as DialogActionArea, + DialogHeader, + DialogScrollBox, +}; diff --git a/striker-ui/types/ActionGroup.d.ts b/striker-ui/types/ActionGroup.d.ts new file mode 100644 index 00000000..9251ffd1 --- /dev/null +++ b/striker-ui/types/ActionGroup.d.ts @@ -0,0 +1,5 @@ +type ActionGroupOptionalProps = { + actions?: ContainedButtonProps[]; +}; + +type ActionGroupProps = ActionGroupOptionalProps; diff --git a/striker-ui/types/Dialog.d.ts b/striker-ui/types/Dialog.d.ts index adbf6d98..dd5758ee 100644 --- a/striker-ui/types/Dialog.d.ts +++ b/striker-ui/types/Dialog.d.ts @@ -21,14 +21,14 @@ type DialogHeaderOptionalProps = { type DialogHeaderProps = DialogHeaderOptionalProps; -/** DialogActionArea */ +/** DialogActionGroup */ type ButtonClickEventHandler = Exclude< ContainedButtonProps['onClick'], undefined >; -type DialogActionAreaOptionalProps = { +type DialogActionGroupOptionalProps = { cancelChildren?: ContainedButtonProps['children']; cancelProps?: Partial; closeOnProceed?: boolean; @@ -40,4 +40,4 @@ type DialogActionAreaOptionalProps = { proceedProps?: Partial; }; -type DialogActionAreaProps = DialogActionAreaOptionalProps; +type DialogActionGroupProps = DialogActionGroupOptionalProps; From c05932c261097734c6b62d698eca6781c2f33229 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 15 Sep 2023 17:42:09 -0400 Subject: [PATCH 11/33] fix(striker-ui): prioritize parent open state in Dialog --- striker-ui/components/Dialog/Dialog.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/striker-ui/components/Dialog/Dialog.tsx b/striker-ui/components/Dialog/Dialog.tsx index 285a7d90..b080dea9 100644 --- a/striker-ui/components/Dialog/Dialog.tsx +++ b/striker-ui/components/Dialog/Dialog.tsx @@ -29,7 +29,9 @@ const Dialog: ForwardRefExoticComponent< } = props; const { - open: externalOpen = false, + // Do not initialize the external open state because we need it to + // determine whether the dialog is controlled or uncontrolled. + open: externalOpen, PaperProps: paperProps = {}, ...restDialogProps } = dialogProps; @@ -39,8 +41,8 @@ const Dialog: ForwardRefExoticComponent< const [controlOpen, setControlOpen] = useState(openInitially); const open = useMemo( - () => (ref ? controlOpen : externalOpen), - [controlOpen, externalOpen, ref], + () => externalOpen ?? controlOpen, + [controlOpen, externalOpen], ); const children = useMemo( From 94db04ede766e595d4734ae5970483b858e2bbcc Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 15 Sep 2023 19:43:19 -0400 Subject: [PATCH 12/33] fix(striker-ui): add dialog with header, add wide option to dialog --- striker-ui/components/ConfirmDialog.tsx | 16 +++---- striker-ui/components/Dialog/Dialog.tsx | 4 +- .../components/Dialog/DialogWithHeader.tsx | 43 +++++++++++++++++++ striker-ui/components/Dialog/index.tsx | 2 + striker-ui/components/FormDialog.tsx | 12 +----- striker-ui/types/ConfirmDialog.d.ts | 3 +- striker-ui/types/Dialog.d.ts | 24 +++++++---- 7 files changed, 74 insertions(+), 30 deletions(-) create mode 100644 striker-ui/components/Dialog/DialogWithHeader.tsx diff --git a/striker-ui/components/ConfirmDialog.tsx b/striker-ui/components/ConfirmDialog.tsx index e4700fec..38f7ea20 100644 --- a/striker-ui/components/ConfirmDialog.tsx +++ b/striker-ui/components/ConfirmDialog.tsx @@ -10,12 +10,7 @@ import { useRef, } from 'react'; -import { - Dialog, - DialogActionArea, - DialogHeader, - DialogScrollBox, -} from './Dialog'; +import { DialogActionArea, DialogScrollBox, DialogWithHeader } from './Dialog'; import FlexBox from './FlexBox'; import sxstring from '../lib/sxstring'; import { BodyText } from './Text'; @@ -46,6 +41,7 @@ const ConfirmDialog: ForwardRefExoticComponent< scrollBoxProps, showClose, titleText, + wide, // Dependents content = children, }, @@ -77,13 +73,15 @@ const ConfirmDialog: ForwardRefExoticComponent< ); return ( - - {titleText} {bodyElement} {preActionArea} @@ -109,7 +107,7 @@ const ConfirmDialog: ForwardRefExoticComponent< }} /> - + ); }, ); diff --git a/striker-ui/components/Dialog/Dialog.tsx b/striker-ui/components/Dialog/Dialog.tsx index b080dea9..977bc696 100644 --- a/striker-ui/components/Dialog/Dialog.tsx +++ b/striker-ui/components/Dialog/Dialog.tsx @@ -26,6 +26,7 @@ const Dialog: ForwardRefExoticComponent< dialogProps = {}, loading, openInitially = false, + wide, } = props; const { @@ -52,10 +53,11 @@ const Dialog: ForwardRefExoticComponent< const paperSx = useMemo>( () => ({ + minWidth: wide ? { xs: 'calc(100%)', md: '50em' } : null, overflow: 'visible', ...externalPaperSx, }), - [externalPaperSx], + [externalPaperSx, wide], ); useImperativeHandle( diff --git a/striker-ui/components/Dialog/DialogWithHeader.tsx b/striker-ui/components/Dialog/DialogWithHeader.tsx new file mode 100644 index 00000000..76aa5b74 --- /dev/null +++ b/striker-ui/components/Dialog/DialogWithHeader.tsx @@ -0,0 +1,43 @@ +import { + ForwardRefExoticComponent, + PropsWithChildren, + RefAttributes, + forwardRef, +} from 'react'; + +import Dialog from './Dialog'; +import DialogHeader from './DialogHeader'; + +const DialogWithHeader: ForwardRefExoticComponent< + PropsWithChildren & + RefAttributes +> = forwardRef( + (props, ref) => { + const { + children, + dialogProps, + header, + loading, + openInitially, + showClose, + wide, + } = props; + + return ( + + {header} + {children} + + ); + }, +); + +DialogWithHeader.displayName = 'DialogWithHeader'; + +export default DialogWithHeader; diff --git a/striker-ui/components/Dialog/index.tsx b/striker-ui/components/Dialog/index.tsx index f9d4d9da..043b46b1 100644 --- a/striker-ui/components/Dialog/index.tsx +++ b/striker-ui/components/Dialog/index.tsx @@ -2,10 +2,12 @@ import Dialog from './Dialog'; import DialogActionGroup from './DialogActionGroup'; import DialogHeader from './DialogHeader'; import DialogScrollBox from './DialogScrollBox'; +import DialogWithHeader from './DialogWithHeader'; export { Dialog, DialogActionGroup as DialogActionArea, DialogHeader, DialogScrollBox, + DialogWithHeader, }; diff --git a/striker-ui/components/FormDialog.tsx b/striker-ui/components/FormDialog.tsx index 422bb4bb..44e1e6d6 100644 --- a/striker-ui/components/FormDialog.tsx +++ b/striker-ui/components/FormDialog.tsx @@ -57,20 +57,12 @@ const FormDialog: ForwardRefExoticComponent< return ( diff --git a/striker-ui/types/ConfirmDialog.d.ts b/striker-ui/types/ConfirmDialog.d.ts index 67c688ab..b064b354 100644 --- a/striker-ui/types/ConfirmDialog.d.ts +++ b/striker-ui/types/ConfirmDialog.d.ts @@ -19,8 +19,7 @@ type ConfirmDialogOptionalProps = { scrollBoxProps?: import('@mui/material').BoxProps; }; -type ConfirmDialogProps = DialogProps & - DialogHeaderProps & +type ConfirmDialogProps = Omit & ConfirmDialogOptionalProps & { actionProceedText: string; titleText: import('react').ReactNode; diff --git a/striker-ui/types/Dialog.d.ts b/striker-ui/types/Dialog.d.ts index dd5758ee..ea6ef127 100644 --- a/striker-ui/types/Dialog.d.ts +++ b/striker-ui/types/Dialog.d.ts @@ -7,20 +7,13 @@ type DialogOptionalProps = { dialogProps?: Partial; loading?: boolean; openInitially?: boolean; + wide?: boolean; }; type DialogProps = DialogOptionalProps; type DialogForwardedRefContent = DialogContextContent; -/** DialogHeader */ - -type DialogHeaderOptionalProps = { - showClose?: boolean; -}; - -type DialogHeaderProps = DialogHeaderOptionalProps; - /** DialogActionGroup */ type ButtonClickEventHandler = Exclude< @@ -41,3 +34,18 @@ type DialogActionGroupOptionalProps = { }; type DialogActionGroupProps = DialogActionGroupOptionalProps; + +/** DialogHeader */ + +type DialogHeaderOptionalProps = { + showClose?: boolean; +}; + +type DialogHeaderProps = DialogHeaderOptionalProps; + +/** DialogWithHeader */ + +type DialogWithHeaderProps = DialogProps & + DialogHeaderProps & { + header: import('react').ReactNode; + }; From cfa6fd9539dac508819e7e7338fb21c2d605462a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 15 Sep 2023 23:14:25 -0400 Subject: [PATCH 13/33] fix(striker-ui): add tree type --- striker-ui/types/Tree.d.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 striker-ui/types/Tree.d.ts diff --git a/striker-ui/types/Tree.d.ts b/striker-ui/types/Tree.d.ts new file mode 100644 index 00000000..4723374e --- /dev/null +++ b/striker-ui/types/Tree.d.ts @@ -0,0 +1,3 @@ +type Tree = { + [k: string]: Tree | T; +}; From b88f90c3155d04152b2cf6b928881126741ea61e Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 15 Sep 2023 23:15:37 -0400 Subject: [PATCH 14/33] fix(striker-ui): allow pass messages from parent(s) in MessageGroup --- striker-ui/components/MessageGroup.tsx | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/striker-ui/components/MessageGroup.tsx b/striker-ui/components/MessageGroup.tsx index 1bfda7ad..64af15d7 100644 --- a/striker-ui/components/MessageGroup.tsx +++ b/striker-ui/components/MessageGroup.tsx @@ -16,6 +16,7 @@ type Messages = { type MessageGroupOptionalProps = { count?: number; defaultMessageType?: MessageBoxProps['type']; + messages?: Messages; onSet?: (length: number) => void; usePlaceholder?: boolean; }; @@ -29,11 +30,12 @@ type MessageGroupForwardedRefContent = { }; const MESSAGE_GROUP_DEFAULT_PROPS: Required< - Omit + Omit > & - Pick = { + Pick = { count: 0, defaultMessageType: 'info', + messages: undefined, onSet: undefined, usePlaceholder: true, }; @@ -46,13 +48,22 @@ const MessageGroup = forwardRef< { count = MESSAGE_GROUP_DEFAULT_PROPS.count, defaultMessageType = MESSAGE_GROUP_DEFAULT_PROPS.defaultMessageType, + messages: externalMessages, onSet, usePlaceholder: isUsePlaceholder = MESSAGE_GROUP_DEFAULT_PROPS.usePlaceholder, }, ref, ) => { - const [messages, setMessages] = useState({}); + const [internalMessages, setInternalMessages] = useState({}); + + const messages = useMemo( + () => ({ + ...externalMessages, + ...internalMessages, + }), + [externalMessages, internalMessages], + ); const exists = useCallback( (key: string) => messages[key] !== undefined, @@ -62,7 +73,7 @@ const MessageGroup = forwardRef< (key: string, message?: Message) => { let length = 0; - setMessages((previous) => { + setInternalMessages((previous) => { const { [key]: unused, ...rest } = previous; const result: Messages = rest; @@ -90,7 +101,7 @@ const MessageGroup = forwardRef< } : undefined; - setMessages((previous) => { + setInternalMessages((previous) => { const result: Messages = {}; Object.keys(previous).forEach((key: string) => { @@ -159,6 +170,6 @@ const MessageGroup = forwardRef< MessageGroup.defaultProps = MESSAGE_GROUP_DEFAULT_PROPS; MessageGroup.displayName = 'MessageGroup'; -export type { MessageGroupForwardedRefContent }; +export type { MessageGroupForwardedRefContent, Messages }; export default MessageGroup; From c1d69900704789d9b16341d9892fcc9586363fd0 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 15 Sep 2023 23:17:00 -0400 Subject: [PATCH 15/33] fix(striker-ui): add /api/anvil response type --- striker-ui/types/APIAnvil.d.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/striker-ui/types/APIAnvil.d.ts b/striker-ui/types/APIAnvil.d.ts index 37c40f77..fa6e0003 100644 --- a/striker-ui/types/APIAnvil.d.ts +++ b/striker-ui/types/APIAnvil.d.ts @@ -86,3 +86,24 @@ type AnvilListItem = { type AnvilList = { anvils: AnvilListItem[]; }; + +type APIAnvilOverviewArray = Array<{ + anvilName: string; + anvilUUID: string; + hosts: Array<{ hostName: string; hostUUID: string }>; +}>; + +type APIAnvilOverview = { + hosts: { + [uuid: string]: { + name: string; + uuid: string; + }; + }; + name: string; + uuid: string; +}; + +type APIAnvilOverviewList = { + [uuid: string]: APIAnvilOverview; +}; From a7d97c88fdb1bbb89a9154fc401d5b1678c323a6 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 15 Sep 2023 23:17:46 -0400 Subject: [PATCH 16/33] fix(striker-ui): add /api/file response types --- striker-ui/types/APIFile.d.ts | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 striker-ui/types/APIFile.d.ts diff --git a/striker-ui/types/APIFile.d.ts b/striker-ui/types/APIFile.d.ts new file mode 100644 index 00000000..66ef5b3b --- /dev/null +++ b/striker-ui/types/APIFile.d.ts @@ -0,0 +1,37 @@ +type APIFileOverview = { + checksum: string; + name: string; + size: string; + type: FileType; + uuid: string; +}; + +type APIFileDetail = APIFileOverview & { + anvils: { + [uuid: string]: { + description: string; + locationUuids: string[]; + name: string; + uuid: string; + }; + }; + hosts: { + [uuid: string]: { + locationUuids: string[]; + name: string; + uuid: string; + }; + }; + locations: { + [uuid: string]: { + active: boolean; + anvilUuid: string; + hostUuid: string; + uuid: string; + }; + }; +}; + +type APIFileOverviewList = { + [uuid: string]: APIFileOverview; +}; From da491ab8147a0d010a9c214a048163f6c1b9ad2e Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 19 Sep 2023 18:41:38 -0400 Subject: [PATCH 17/33] fix(striker-ui): add fetch hook --- striker-ui/hooks/useFetch.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 striker-ui/hooks/useFetch.tsx diff --git a/striker-ui/hooks/useFetch.tsx b/striker-ui/hooks/useFetch.tsx new file mode 100644 index 00000000..bbe77894 --- /dev/null +++ b/striker-ui/hooks/useFetch.tsx @@ -0,0 +1,29 @@ +import useSWR, { BareFetcher, SWRConfiguration } from 'swr'; + +import API_BASE_URL from '../lib/consts/API_BASE_URL'; + +import fetchJSON from '../lib/fetchers/fetchJSON'; + +type FetchHookResponse = { + data?: D; + error?: E; + loading: boolean; +}; + +const useFetch = ( + url: string, + options: SWRConfiguration & { + fetcher?: BareFetcher; + baseUrl?: string; + } = {}, +): FetchHookResponse => { + const { fetcher = fetchJSON, baseUrl = API_BASE_URL, ...config } = options; + + const { data, error } = useSWR(`${baseUrl}${url}`, fetcher, config); + + const loading = !error && !data; + + return { data, error, loading }; +}; + +export default useFetch; From 41055cc6267d5ca4e22ee04c66143759370825cf Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 20 Sep 2023 00:06:40 -0400 Subject: [PATCH 18/33] fix(striker-ui): relocate Message(s) types --- striker-ui/components/MessageGroup.tsx | 2 +- striker-ui/types/Message.d.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 striker-ui/types/Message.d.ts diff --git a/striker-ui/components/MessageGroup.tsx b/striker-ui/components/MessageGroup.tsx index 64af15d7..3bc45707 100644 --- a/striker-ui/components/MessageGroup.tsx +++ b/striker-ui/components/MessageGroup.tsx @@ -170,6 +170,6 @@ const MessageGroup = forwardRef< MessageGroup.defaultProps = MESSAGE_GROUP_DEFAULT_PROPS; MessageGroup.displayName = 'MessageGroup'; -export type { MessageGroupForwardedRefContent, Messages }; +export type { MessageGroupForwardedRefContent }; export default MessageGroup; diff --git a/striker-ui/types/Message.d.ts b/striker-ui/types/Message.d.ts new file mode 100644 index 00000000..d873dd8e --- /dev/null +++ b/striker-ui/types/Message.d.ts @@ -0,0 +1,5 @@ +type Message = import('../components/MessageBox').Message; + +type Messages = { + [messageKey: string]: Message; +}; From 990dfbb375f3056b6e49345102ce941c8314901f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 20 Sep 2023 00:08:12 -0400 Subject: [PATCH 19/33] fix(striker-ui): add formik errors converter --- .../lib/convertFormikErrorsToMessages.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 striker-ui/lib/convertFormikErrorsToMessages.ts diff --git a/striker-ui/lib/convertFormikErrorsToMessages.ts b/striker-ui/lib/convertFormikErrorsToMessages.ts new file mode 100644 index 00000000..fdae0b56 --- /dev/null +++ b/striker-ui/lib/convertFormikErrorsToMessages.ts @@ -0,0 +1,26 @@ +const convertFormikErrorsToMessages = ( + errors: Tree, + { + build = (mkey, err) => ({ children: err, type: 'warning' }), + chain = '', + }: { + build?: (msgkey: keyof Tree, error: Leaf) => Messages[keyof Messages]; + chain?: keyof Tree; + } = {}, +): Messages => + Object.entries(errors).reduce((previous, [key, value]) => { + const extended = String(chain).length ? [chain, key].join('.') : key; + + if (typeof value === 'object') { + return { + ...previous, + ...convertFormikErrorsToMessages(value, { chain: extended }), + }; + } + + previous[extended] = build(extended, value); + + return previous; + }, {}); + +export default convertFormikErrorsToMessages; From 2e2450b8b9fc07723913e3aaf05a070bdb97a03c Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 20 Sep 2023 00:08:39 -0400 Subject: [PATCH 20/33] fix(striker-ui): add yup dynamic object builder --- striker-ui/lib/buildYupDynamicObject.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 striker-ui/lib/buildYupDynamicObject.ts diff --git a/striker-ui/lib/buildYupDynamicObject.ts b/striker-ui/lib/buildYupDynamicObject.ts new file mode 100644 index 00000000..be5b74f8 --- /dev/null +++ b/striker-ui/lib/buildYupDynamicObject.ts @@ -0,0 +1,14 @@ +const buildYupDynamicObject = ( + obj: Record | undefined, + schema: S, +): Record | undefined => + obj && + Object.keys(obj).reduce>( + (previous, key) => ({ + ...previous, + [key]: schema, + }), + {}, + ); + +export default buildYupDynamicObject; From 13994880ac86040d2b1d2df6e8be65b49bebc90d Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 20 Sep 2023 01:52:52 -0400 Subject: [PATCH 21/33] fix(striker-ui): add file related schemas --- striker-ui/components/Files/schema.ts | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 striker-ui/components/Files/schema.ts diff --git a/striker-ui/components/Files/schema.ts b/striker-ui/components/Files/schema.ts new file mode 100644 index 00000000..23631fbb --- /dev/null +++ b/striker-ui/components/Files/schema.ts @@ -0,0 +1,29 @@ +import * as yup from 'yup'; + +import buildYupDynamicObject from '../../lib/buildYupDynamicObject'; + +const fileLocationSchema = yup.object({ active: yup.boolean().required() }); + +const fileLocationAnvilSchema = yup.lazy((anvils) => + yup.object(buildYupDynamicObject(anvils, fileLocationSchema)), +); + +const fileLocationDrHostSchema = yup.lazy((drHosts) => + yup.object(buildYupDynamicObject(drHosts, fileLocationSchema)), +); + +const fileSchema = yup.object({ + locations: yup.object({ + anvils: fileLocationAnvilSchema, + drHosts: fileLocationDrHostSchema, + }), + name: yup.string().required(), + type: yup.string().oneOf(['iso', 'other', 'script']), + uuid: yup.string().uuid().required(), +}); + +const fileListSchema = yup.lazy((files) => + yup.object(buildYupDynamicObject(files, fileSchema)), +); + +export default fileListSchema; From 4823b7aeb56bfcb80259224ab4fa8524f7bbad1f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 20 Sep 2023 16:54:05 -0400 Subject: [PATCH 22/33] fix(striker-ui): use normal colour on close dialog button --- striker-ui/components/Dialog/DialogHeader.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/striker-ui/components/Dialog/DialogHeader.tsx b/striker-ui/components/Dialog/DialogHeader.tsx index 7fdb1541..1f343112 100644 --- a/striker-ui/components/Dialog/DialogHeader.tsx +++ b/striker-ui/components/Dialog/DialogHeader.tsx @@ -25,7 +25,6 @@ const DialogHeader: FC = (props) => { dialogContext?.setOpen(false); }} size="small" - variant="redcontained" /> ), [dialogContext, showClose], From 37a096563b74fc963bfd92b303387af2d0903d59 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 21 Sep 2023 00:38:41 -0400 Subject: [PATCH 23/33] fix(striker-ui-api): add info to /api/anvil --- .../lib/request_handlers/anvil/getAnvil.ts | 23 ++++++++++++++++--- striker-ui-api/src/types/ApiAn.d.ts | 2 ++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/striker-ui-api/src/lib/request_handlers/anvil/getAnvil.ts b/striker-ui-api/src/lib/request_handlers/anvil/getAnvil.ts index 0e952642..945b31f3 100644 --- a/striker-ui-api/src/lib/request_handlers/anvil/getAnvil.ts +++ b/striker-ui-api/src/lib/request_handlers/anvil/getAnvil.ts @@ -12,8 +12,10 @@ export const getAnvil: RequestHandler = buildGetRequestHandler( SELECT anv.anvil_name, anv.anvil_uuid, + anv.anvil_description, hos.host_name, - hos.host_uuid + hos.host_uuid, + hos.host_type FROM anvils AS anv JOIN hosts AS hos ON hos.host_uuid IN ( @@ -31,10 +33,21 @@ export const getAnvil: RequestHandler = buildGetRequestHandler( let rowStage: AnvilOverview | undefined; results = queryStdout.reduce( - (reducedRows, [anvilName, anvilUUID, hostName, hostUUID]) => { + ( + reducedRows, + [ + anvilName, + anvilUUID, + anvilDescription, + hostName, + hostUUID, + hostType, + ], + ) => { if (!rowStage || anvilUUID !== rowStage.anvilUUID) { { rowStage = { + anvilDescription, anvilName, anvilUUID, hosts: [], @@ -44,7 +57,11 @@ export const getAnvil: RequestHandler = buildGetRequestHandler( } } - rowStage.hosts.push({ hostName, hostUUID }); + rowStage.hosts.push({ + hostName, + hostType, + hostUUID, + }); return reducedRows; }, diff --git a/striker-ui-api/src/types/ApiAn.d.ts b/striker-ui-api/src/types/ApiAn.d.ts index 25b74c2b..2885af63 100644 --- a/striker-ui-api/src/types/ApiAn.d.ts +++ b/striker-ui-api/src/types/ApiAn.d.ts @@ -112,10 +112,12 @@ type AnvilDetailStoreSummary = { }; type AnvilOverview = { + anvilDescription: string; anvilName: string; anvilUUID: string; hosts: Array<{ hostName: string; + hostType: string; hostUUID: string; }>; }; From d8e214715a31318fa448b5855fe1898461878ea5 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 21 Sep 2023 01:19:16 -0400 Subject: [PATCH 24/33] fix(striker-ui-api): add host type to file detail --- .../src/lib/request_handlers/file/buildQueryFileDetail.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/striker-ui-api/src/lib/request_handlers/file/buildQueryFileDetail.ts b/striker-ui-api/src/lib/request_handlers/file/buildQueryFileDetail.ts index a6000745..e69bc04f 100644 --- a/striker-ui-api/src/lib/request_handlers/file/buildQueryFileDetail.ts +++ b/striker-ui-api/src/lib/request_handlers/file/buildQueryFileDetail.ts @@ -32,7 +32,8 @@ export const buildQueryFileDetail = ({ anv.anvil_name, anv.anvil_description, hos.host_uuid, - hos.host_name + hos.host_name, + hos.host_type FROM files AS fil JOIN file_locations AS fil_loc ON fil.file_uuid = fil_loc.file_location_file_uuid From 34fc75b3d734931eb7b4bae61961d746bf64c34b Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 21 Sep 2023 01:42:37 -0400 Subject: [PATCH 25/33] fix(striker-ui): add new file management components --- striker-ui/components/Files/AddFileForm.tsx | 223 ++++++++++ striker-ui/components/Files/EditFileForm.tsx | 154 +++++++ .../components/Files/FileInputGroup.tsx | 217 ++++++++++ .../components/Files/ManageFilePanel.tsx | 383 ++++++++++++++++++ .../components/Files/UploadFileProgress.tsx | 42 ++ striker-ui/types/APIAnvil.d.ts | 9 +- striker-ui/types/APIFile.d.ts | 11 + striker-ui/types/ManageFile.d.ts | 62 +++ 8 files changed, 1100 insertions(+), 1 deletion(-) create mode 100644 striker-ui/components/Files/AddFileForm.tsx create mode 100644 striker-ui/components/Files/EditFileForm.tsx create mode 100644 striker-ui/components/Files/FileInputGroup.tsx create mode 100644 striker-ui/components/Files/ManageFilePanel.tsx create mode 100644 striker-ui/components/Files/UploadFileProgress.tsx create mode 100644 striker-ui/types/ManageFile.d.ts diff --git a/striker-ui/components/Files/AddFileForm.tsx b/striker-ui/components/Files/AddFileForm.tsx new file mode 100644 index 00000000..22c9eb35 --- /dev/null +++ b/striker-ui/components/Files/AddFileForm.tsx @@ -0,0 +1,223 @@ +import { useFormik } from 'formik'; +import { + ChangeEventHandler, + FC, + ReactElement, + useCallback, + useMemo, + useRef, +} from 'react'; +import { v4 as uuidv4 } from 'uuid'; + +import ActionGroup from '../ActionGroup'; +import api from '../../lib/api'; +import ContainedButton from '../ContainedButton'; +import convertFormikErrorsToMessages from '../../lib/convertFormikErrorsToMessages'; +import FileInputGroup from './FileInputGroup'; +import FlexBox from '../FlexBox'; +import handleAPIError from '../../lib/handleAPIError'; +import MessageBox from '../MessageBox'; +import MessageGroup from '../MessageGroup'; +import fileListSchema from './schema'; +import UploadFileProgress from './UploadFileProgress'; +import useProtectedState from '../../hooks/useProtectedState'; + +const REQUEST_INCOMPLETE_UPLOAD_LIMIT = 99; + +const setUploadProgress: ( + previous: UploadFiles | undefined, + uuid: keyof UploadFiles, + progress: UploadFiles[string]['progress'], +) => UploadFiles | undefined = (previous, uuid, progress) => { + if (!previous) return previous; + + previous[uuid].progress = progress; + + return { ...previous }; +}; + +const AddFileForm: FC = (props) => { + const { anvils, drHosts } = props; + + const filePickerRef = useRef(null); + + const [uploads, setUploads] = useProtectedState( + undefined, + ); + + const formik = useFormik({ + initialValues: {}, + onSubmit: (values) => { + const files = Object.values(values); + + setUploads( + files.reduce((previous, { file, name, uuid }) => { + if (!file) return previous; + + previous[uuid] = { name, progress: 0, uuid }; + + return previous; + }, {}), + ); + + files.forEach(({ file, name, uuid }) => { + if (!file) return; + + const data = new FormData(); + + data.append('file', new File([file], name, { ...file })); + + api + .post('/file', data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + onUploadProgress: ( + (fileUuid: string) => + ({ loaded, total }) => { + setUploads((previous) => + setUploadProgress( + previous, + fileUuid, + Math.round( + (loaded / total) * REQUEST_INCOMPLETE_UPLOAD_LIMIT, + ), + ), + ); + } + )(uuid), + }) + .then( + ((fileUuid: string) => () => { + setUploads((previous) => + setUploadProgress(previous, fileUuid, 100), + ); + })(uuid), + ) + .catch((error) => { + handleAPIError(error); + }); + }); + }, + validationSchema: fileListSchema, + }); + + const formikErrors = useMemo( + () => convertFormikErrorsToMessages(formik.errors), + [formik.errors], + ); + + const disableProceed = useMemo( + () => + !formik.dirty || + !formik.isValid || + formik.isValidating || + formik.isSubmitting, + [formik.dirty, formik.isSubmitting, formik.isValid, formik.isValidating], + ); + + const handleSelectFiles = useCallback>( + (event) => { + const { + target: { files }, + } = event; + + if (!files) return; + + const values = Array.from(files).reduce( + (previous, file) => { + const fileUuid = uuidv4(); + + previous[fileUuid] = { + file, + name: file.name, + uuid: fileUuid, + }; + + return previous; + }, + {}, + ); + + formik.setValues(values); + }, + [formik], + ); + + const fileInputs = useMemo( + () => + formik.values && + Object.values(formik.values).map((file) => { + const { uuid: fileUuid } = file; + + return ( + + ); + }), + [anvils, drHosts, formik], + ); + + return ( + + + Uploaded files will be listed automatically, but it may take a while for + larger files to finish uploading and appear on the list. + + {uploads ? ( + <> + + This dialog can be closed after all uploads complete. Closing before + completion will stop the upload. + + + + ) : ( + { + event.preventDefault(); + + formik.submitForm(); + }} + > + + { + filePickerRef.current?.click(); + }} + > + Browse + + {fileInputs} + + + + )} + + ); +}; + +export default AddFileForm; diff --git a/striker-ui/components/Files/EditFileForm.tsx b/striker-ui/components/Files/EditFileForm.tsx new file mode 100644 index 00000000..4f62720c --- /dev/null +++ b/striker-ui/components/Files/EditFileForm.tsx @@ -0,0 +1,154 @@ +import { useFormik } from 'formik'; +import { FC, useMemo } from 'react'; + +import ActionGroup from '../ActionGroup'; +import api from '../../lib/api'; +import convertFormikErrorsToMessages from '../../lib/convertFormikErrorsToMessages'; +import FileInputGroup from './FileInputGroup'; +import FlexBox from '../FlexBox'; +import MessageGroup from '../MessageGroup'; +import fileListSchema from './schema'; + +const toEditFileRequestBody = ( + file: FileFormikFile, + pfile: APIFileDetail, +): APIEditFileRequestBody | undefined => { + const { locations, name: fileName, type: fileType, uuid: fileUUID } = file; + + if (!locations || !fileType) return undefined; + + const fileLocations: APIEditFileRequestBody['fileLocations'] = []; + + Object.entries(locations.anvils).reduce< + APIEditFileRequestBody['fileLocations'] + >((previous, [anvilUuid, { active: isFileLocationActive }]) => { + const { + anvils: { + [anvilUuid]: { locationUuids }, + }, + } = pfile; + + const current = locationUuids.map< + APIEditFileRequestBody['fileLocations'][number] + >((fileLocationUUID) => ({ + fileLocationUUID, + isFileLocationActive, + })); + + previous.push(...current); + + return previous; + }, fileLocations); + + Object.entries(locations.drHosts).reduce< + APIEditFileRequestBody['fileLocations'] + >((previous, [drHostUuid, { active: isFileLocationActive }]) => { + const { + hosts: { + [drHostUuid]: { locationUuids }, + }, + } = pfile; + + const current = locationUuids.map< + APIEditFileRequestBody['fileLocations'][number] + >((fileLocationUUID) => ({ + fileLocationUUID, + isFileLocationActive, + })); + + previous.push(...current); + + return previous; + }, fileLocations); + + return { fileLocations, fileName, fileType, fileUUID }; +}; + +const EditFileForm: FC = (props) => { + const { anvils, drHosts, previous: file } = props; + + const formikInitialValues = useMemo(() => { + const { locations, name, type, uuid } = file; + + return { + [uuid]: { + locations: Object.values(locations).reduce( + (previous, { active, anvilUuid, hostUuid }) => { + let category: keyof FileFormikLocations = 'anvils'; + let id = anvilUuid; + + if (hostUuid in drHosts) { + category = 'drHosts'; + id = hostUuid; + } + + previous[category][id] = { active }; + + return previous; + }, + { anvils: {}, drHosts: {} }, + ), + name, + type, + uuid, + }, + }; + }, [drHosts, file]); + + const formik = useFormik({ + initialValues: formikInitialValues, + onSubmit: (values) => { + const body = toEditFileRequestBody(values[file.uuid], file); + + api.put(`/file/${file.uuid}`, body); + }, + validationSchema: fileListSchema, + }); + + const formikErrors = useMemo( + () => convertFormikErrorsToMessages(formik.errors), + [formik.errors], + ); + + const disableProceed = useMemo( + () => + !formik.dirty || + !formik.isValid || + formik.isValidating || + formik.isSubmitting, + [formik.dirty, formik.isSubmitting, formik.isValid, formik.isValidating], + ); + + return ( + { + event.preventDefault(); + + formik.submitForm(); + }} + > + + + + + ); +}; + +export default EditFileForm; diff --git a/striker-ui/components/Files/FileInputGroup.tsx b/striker-ui/components/Files/FileInputGroup.tsx new file mode 100644 index 00000000..a50a598c --- /dev/null +++ b/striker-ui/components/Files/FileInputGroup.tsx @@ -0,0 +1,217 @@ +import { FormGroup } from '@mui/material'; +import { cloneDeep, debounce } from 'lodash'; +import { FC, useCallback, useMemo } from 'react'; + +import { UPLOAD_FILE_TYPES_ARRAY } from '../../lib/consts/UPLOAD_FILE_TYPES'; + +import FlexBox from '../FlexBox'; +import List from '../List'; +import OutlinedInputWithLabel from '../OutlinedInputWithLabel'; +import { ExpandablePanel } from '../Panels'; +import SelectWithLabel from '../SelectWithLabel'; +import { BodyText } from '../Text'; +import UncontrolledInput from '../UncontrolledInput'; + +const FileInputGroup: FC = (props) => { + const { + anvils, + drHosts, + fileUuid: fuuid, + formik, + showSyncInputGroup, + showTypeInput, + } = props; + + const { handleBlur, handleChange } = formik; + + const debounceChangeEventHandler = useMemo( + () => debounce(handleChange, 500), + [handleChange], + ); + + const { nameChain, locationsChain, typeChain } = useMemo( + () => ({ + nameChain: `${fuuid}.name`, + locationsChain: `${fuuid}.locations`, + typeChain: `${fuuid}.type`, + }), + [fuuid], + ); + + const handleCheckAllLocations = useCallback( + (type: keyof FileFormikLocations, checked: boolean) => { + formik.setValues((previous) => { + const current = cloneDeep(previous); + const locations = current[fuuid].locations?.[type]; + + if (!locations) return previous; + + Object.keys(locations).forEach((key) => { + locations[key].active = checked; + }); + + return current; + }); + }, + [formik, fuuid], + ); + + const getAllLocationsCheckboxProps = useCallback( + (type: keyof FileFormikLocations): CheckboxProps => { + const locations = formik.values[fuuid].locations?.[type]; + + if (!locations) return {}; + + return { + checked: Object.values(locations).every(({ active }) => active), + onChange: (event, checked) => { + handleCheckAllLocations(type, checked); + }, + }; + }, + [formik.values, fuuid, handleCheckAllLocations], + ); + + const getLocationCheckboxProps = useCallback( + (type: keyof FileFormikLocations, uuid: string): CheckboxProps => { + const gridChain = `${locationsChain}.${type}.${uuid}`; + const activeChain = `${gridChain}.active`; + + return { + id: activeChain, + name: activeChain, + checked: formik.values[fuuid].locations?.[type][uuid].active, + onBlur: handleBlur, + onChange: handleChange, + }; + }, + [formik.values, fuuid, handleBlur, handleChange, locationsChain], + ); + + const enableCheckAllLocations = useCallback( + (type: keyof FileFormikLocations) => { + const locations = formik.values[fuuid].locations?.[type]; + + return locations && Object.keys(locations).length > 1; + }, + [formik.values, fuuid], + ); + + const nameInput = useMemo( + () => ( + + } + /> + ), + [debounceChangeEventHandler, formik.values, fuuid, handleBlur, nameChain], + ); + + const syncNodeInputGroup = useMemo( + () => + showSyncInputGroup && ( + + getAllLocationsCheckboxProps('anvils')} + getListItemCheckboxProps={(uuid) => + getLocationCheckboxProps('anvils', uuid) + } + renderListItem={(anvilUuid, { description, name }) => ( + + {name}: {description} + + )} + /> + + ), + [ + anvils, + enableCheckAllLocations, + getAllLocationsCheckboxProps, + getLocationCheckboxProps, + showSyncInputGroup, + ], + ); + + const syncDrHostInputGroup = useMemo( + () => + showSyncInputGroup && ( + + getAllLocationsCheckboxProps('drHosts')} + getListItemCheckboxProps={(uuid) => + getLocationCheckboxProps('drHosts', uuid) + } + renderListItem={(anvilUuid, { hostName }) => ( + {hostName} + )} + /> + + ), + [ + drHosts, + enableCheckAllLocations, + getAllLocationsCheckboxProps, + getLocationCheckboxProps, + showSyncInputGroup, + ], + ); + + const typeInput = useMemo( + () => + showTypeInput && ( + ({ + displayValue, + value, + }), + )} + value={formik.values[fuuid].type} + /> + ), + [formik.values, fuuid, handleBlur, handleChange, showTypeInput, typeChain], + ); + + return ( + :not(:first-child)': { marginTop: '1em' } }}> + + {nameInput} + {typeInput} + + {syncNodeInputGroup} + {syncDrHostInputGroup} + + ); +}; + +export default FileInputGroup; diff --git a/striker-ui/components/Files/ManageFilePanel.tsx b/striker-ui/components/Files/ManageFilePanel.tsx new file mode 100644 index 00000000..c0c485e2 --- /dev/null +++ b/striker-ui/components/Files/ManageFilePanel.tsx @@ -0,0 +1,383 @@ +import { dSizeStr } from 'format-data-size'; +import { FC, useCallback, useMemo, useRef, useState } from 'react'; + +import API_BASE_URL from '../../lib/consts/API_BASE_URL'; +import { UPLOAD_FILE_TYPES } from '../../lib/consts/UPLOAD_FILE_TYPES'; + +import AddFileForm from './AddFileForm'; +import api from '../../lib/api'; +import ConfirmDialog from '../ConfirmDialog'; +import { DialogWithHeader } from '../Dialog'; +import Divider from '../Divider'; +import EditFileForm from './EditFileForm'; +import FlexBox from '../FlexBox'; +import handleAPIError from '../../lib/handleAPIError'; +import List from '../List'; +import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup'; +import { Panel, PanelHeader } from '../Panels'; +import periodicFetch from '../../lib/fetchers/periodicFetch'; +import Spinner from '../Spinner'; +import { BodyText, HeaderText, MonoText } from '../Text'; +import useChecklist from '../../hooks/useChecklist'; +import useConfirmDialogProps from '../../hooks/useConfirmDialogProps'; +import useFetch from '../../hooks/useFetch'; +import useProtectedState from '../../hooks/useProtectedState'; + +const toAnvilOverviewHostList = ( + data: APIAnvilOverviewArray[number]['hosts'], +) => + data.reduce( + (previous, { hostName: name, hostType: type, hostUUID: uuid }) => { + previous[uuid] = { name, type, uuid }; + + return previous; + }, + {}, + ); + +const toAnvilOverviewList = (data: APIAnvilOverviewArray) => + data.reduce( + ( + previous, + { + anvilDescription: description, + anvilName: name, + anvilUUID: uuid, + hosts, + }, + ) => { + previous[uuid] = { + description, + hosts: toAnvilOverviewHostList(hosts), + name, + uuid, + }; + + return previous; + }, + {}, + ); + +const toFileOverviewList = (rows: string[][]) => + rows.reduce((previous, row) => { + const [uuid, name, size, type, checksum] = row; + + previous[uuid] = { + checksum, + name, + size, + type: type as FileType, + uuid, + }; + + return previous; + }, {}); + +const toFileDetail = (rows: string[][]) => { + const { 0: first } = rows; + + if (!first) return undefined; + + const [uuid, name, size, type, checksum] = first; + + return rows.reduce( + (previous, row) => { + const { + 5: locationUuid, + 6: locationActive, + 7: anvilUuid, + 8: anvilName, + 9: anvilDescription, + 10: hostUuid, + 11: hostName, + 12: hostType, + } = row; + + if (!previous.anvils[anvilUuid]) { + previous.anvils[anvilUuid] = { + description: anvilDescription, + locationUuids: [], + name: anvilName, + uuid: anvilUuid, + }; + } + + if (!previous.hosts[hostUuid]) { + previous.hosts[hostUuid] = { + locationUuids: [], + name: hostName, + type: hostType, + uuid: hostUuid, + }; + } + + if (hostType === 'dr') { + previous.hosts[hostUuid].locationUuids.push(locationUuid); + } else { + previous.anvils[anvilUuid].locationUuids.push(locationUuid); + } + + const active = Number(locationActive) === 1; + + previous.locations[locationUuid] = { + anvilUuid, + active, + hostUuid, + uuid: locationUuid, + }; + + return previous; + }, + { + anvils: {}, + checksum, + hosts: {}, + locations: {}, + name, + size, + type: type as FileType, + uuid, + }, + ); +}; + +const ManageFilePanel: FC = () => { + const addFormDialogRef = useRef(null); + const confirmDialogRef = useRef({}); + const editFormDialogRef = useRef(null); + const messageGroupRef = useRef({}); + + const [confirmDialogProps, setConfirmDialogProps] = useConfirmDialogProps(); + const [edit, setEdit] = useState(false); + const [file, setFile] = useProtectedState( + undefined, + ); + const [loadingFile, setLoadingFile] = useProtectedState(false); + + const { data: rows, isLoading: loadingFiles } = periodicFetch( + `${API_BASE_URL}/file`, + ); + + const files = useMemo( + () => (rows ? toFileOverviewList(rows) : undefined), + [rows], + ); + + const { + buildDeleteDialogProps, + checks, + getCheck, + hasAllChecks, + hasChecks, + multipleItems, + setAllChecks, + setCheck, + } = useChecklist({ + list: files, + }); + + const setApiMessage = useCallback( + (message: Message) => + messageGroupRef.current.setMessage?.call(null, 'api', message), + [], + ); + + const getFileDetail = useCallback( + (fileUuid: string) => { + setLoadingFile(true); + + api + .get(`file/${fileUuid}`) + .then(({ data }) => { + setFile(toFileDetail(data)); + }) + .catch((error) => { + const emsg = handleAPIError(error); + + emsg.children = <>Failed to get file detail. {emsg.children}; + + setApiMessage(emsg); + }) + .finally(() => { + setLoadingFile(false); + }); + }, + [setApiMessage, setFile, setLoadingFile], + ); + + const { data: rawAnvils, loading: loadingAnvils } = + useFetch('/anvil', { + onError: (error) => { + setApiMessage({ + children: <>Failed to get node list. {error}, + type: 'warning', + }); + }, + }); + + const anvils = useMemo( + () => rawAnvils && toAnvilOverviewList(rawAnvils), + [rawAnvils], + ); + + const { data: drHosts, loading: loadingDrHosts } = + useFetch('/host?types=dr', { + onError: (error) => { + setApiMessage({ + children: <>Failed to get DR host list. {error}, + type: 'warning', + }); + }, + }); + + const list = useMemo( + () => ( + ({ + checked: hasAllChecks, + onChange: (event, checked) => { + setAllChecks(checked); + }, + })} + getListItemCheckboxProps={(uuid) => ({ + checked: getCheck(uuid), + onChange: (event, checked) => { + setCheck(uuid, checked); + }, + })} + header + listEmpty="No file(s) found." + listItems={files} + onAdd={() => { + addFormDialogRef.current?.setOpen(true); + }} + onDelete={() => { + setConfirmDialogProps( + buildDeleteDialogProps({ + onProceedAppend: () => { + checks.forEach((fileUuid) => api.delete(`/file/${fileUuid}`)); + }, + getConfirmDialogTitle: (count) => + `Delete the following ${count} file(s)?`, + renderEntry: ({ key }) => ( + {files?.[key].name} + ), + }), + ); + + confirmDialogRef.current.setOpen?.call(null, true); + }} + onEdit={() => { + setEdit((previous) => !previous); + }} + onItemClick={(value, uuid) => { + editFormDialogRef.current?.setOpen(true); + getFileDetail(uuid); + }} + renderListItem={(uuid, { checksum, name, size, type }) => ( + + + + {name} + + {UPLOAD_FILE_TYPES.get(type)?.[1]} + + {dSizeStr(size, { toUnit: 'ibyte' })} + + {checksum} + + )} + /> + ), + [ + buildDeleteDialogProps, + checks, + edit, + files, + getCheck, + getFileDetail, + hasAllChecks, + hasChecks, + multipleItems, + setAllChecks, + setCheck, + setConfirmDialogProps, + ], + ); + + const panelContent = useMemo( + () => (loadingFiles ? : list), + [loadingFiles, list], + ); + + const messageArea = useMemo( + () => ( + + ), + [], + ); + + const loadingAddForm = useMemo( + () => loadingFiles || loadingAnvils || loadingDrHosts, + [loadingAnvils, loadingDrHosts, loadingFiles], + ); + + const loadingEditForm = useMemo( + () => loadingFiles || loadingAnvils || loadingDrHosts || loadingFile, + [loadingAnvils, loadingDrHosts, loadingFile, loadingFiles], + ); + + const addForm = useMemo( + () => + anvils && drHosts && , + [anvils, drHosts], + ); + + const editForm = useMemo( + () => + anvils && + drHosts && + file && ( + + ), + [anvils, drHosts, file], + ); + + return ( + <> + + + Files + + {messageArea} + {panelContent} + + + {addForm} + + + {editForm} + + + + ); +}; + +export default ManageFilePanel; diff --git a/striker-ui/components/Files/UploadFileProgress.tsx b/striker-ui/components/Files/UploadFileProgress.tsx new file mode 100644 index 00000000..f0ffe33a --- /dev/null +++ b/striker-ui/components/Files/UploadFileProgress.tsx @@ -0,0 +1,42 @@ +import { Box as MuiBox } from '@mui/material'; +import { FC } from 'react'; + +import { ProgressBar } from '../Bars'; +import FlexBox from '../FlexBox'; +import { BodyText } from '../Text'; + +const UploadFileProgress: FC = (props) => { + const { uploads } = props; + + return ( + + {Object.values(uploads).map(({ name, progress, uuid }) => ( + :first-child': { + minWidth: 100, + overflow: 'hidden', + overflowWrap: 'normal', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + width: { xs: '100%', md: 200 }, + wordBreak: 'keep-all', + }, + + '& > :last-child': { flexGrow: 1 }, + }} + > + {name} + + + ))} + + ); +}; + +export default UploadFileProgress; diff --git a/striker-ui/types/APIAnvil.d.ts b/striker-ui/types/APIAnvil.d.ts index fa6e0003..216b035d 100644 --- a/striker-ui/types/APIAnvil.d.ts +++ b/striker-ui/types/APIAnvil.d.ts @@ -88,15 +88,22 @@ type AnvilList = { }; type APIAnvilOverviewArray = Array<{ + anvilDescription: string; anvilName: string; anvilUUID: string; - hosts: Array<{ hostName: string; hostUUID: string }>; + hosts: Array<{ + hostName: string; + hostType: string; + hostUUID: string; + }>; }>; type APIAnvilOverview = { + description: string; hosts: { [uuid: string]: { name: string; + type: string; uuid: string; }; }; diff --git a/striker-ui/types/APIFile.d.ts b/striker-ui/types/APIFile.d.ts index 66ef5b3b..3e0e4a8d 100644 --- a/striker-ui/types/APIFile.d.ts +++ b/striker-ui/types/APIFile.d.ts @@ -19,6 +19,7 @@ type APIFileDetail = APIFileOverview & { [uuid: string]: { locationUuids: string[]; name: string; + type: string; uuid: string; }; }; @@ -35,3 +36,13 @@ type APIFileDetail = APIFileOverview & { type APIFileOverviewList = { [uuid: string]: APIFileOverview; }; + +type APIEditFileRequestBody = { + fileName: string; + fileType: FileType; + fileUUID: string; + fileLocations: Array<{ + fileLocationUUID: string; + isFileLocationActive: boolean; + }>; +}; diff --git a/striker-ui/types/ManageFile.d.ts b/striker-ui/types/ManageFile.d.ts new file mode 100644 index 00000000..0ae41f74 --- /dev/null +++ b/striker-ui/types/ManageFile.d.ts @@ -0,0 +1,62 @@ +type FileFormikLocations = { + anvils: { + [anvilUuid: string]: { + active: boolean; + }; + }; + drHosts: { + [hostUuid: string]: { + active: boolean; + }; + }; +}; + +type FileFormikFile = { + file?: File; + locations?: FileFormikLocations; + name: string; + type?: FileType; + uuid: string; +}; + +type FileFormikValues = { + [fileUuid: string]: FileFormikFile; +}; + +/** ---------- Component types ---------- */ + +/** FileInputGroup */ + +type FileInputGroupOptionalProps = { + showSyncInputGroup?: boolean; + showTypeInput?: boolean; +}; + +type FileInputGroupProps = FileInputGroupOptionalProps & { + anvils: APIAnvilOverviewList; + drHosts: APIHostOverviewList; + fileUuid: string; + formik: ReturnType>; +}; + +/** AddFileForm */ + +type UploadFiles = { + [fileUuid: string]: Pick & { + progress: number; + }; +}; + +type AddFileFormProps = Pick; + +/** EditFileForm */ + +type EditFileFormProps = Pick & { + previous: APIFileDetail; +}; + +/** UploadFileProgress */ + +type UploadFileProgressProps = { + uploads: UploadFiles; +}; From 689734b29338a61c69fc872d9c1975338accf41e Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 21 Sep 2023 01:50:38 -0400 Subject: [PATCH 26/33] fix(striker-ui): close on proceed when confirm in manage files --- striker-ui/components/Files/ManageFilePanel.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/striker-ui/components/Files/ManageFilePanel.tsx b/striker-ui/components/Files/ManageFilePanel.tsx index c0c485e2..b5c78483 100644 --- a/striker-ui/components/Files/ManageFilePanel.tsx +++ b/striker-ui/components/Files/ManageFilePanel.tsx @@ -375,7 +375,12 @@ const ManageFilePanel: FC = () => { > {editForm} - + ); }; From f0189b4949c46751c188bae8c2632a2457ed5f02 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 21 Sep 2023 02:26:40 -0400 Subject: [PATCH 27/33] fix(striker-ui): move loading spinner from Dialog actions to ActionGroup --- striker-ui/components/ActionGroup.tsx | 7 +++++-- striker-ui/components/Dialog/DialogActionGroup.tsx | 10 +++------- striker-ui/types/ActionGroup.d.ts | 1 + 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/striker-ui/components/ActionGroup.tsx b/striker-ui/components/ActionGroup.tsx index f31e1b4a..ec177c5a 100644 --- a/striker-ui/components/ActionGroup.tsx +++ b/striker-ui/components/ActionGroup.tsx @@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid'; import ContainedButton from './ContainedButton'; import FlexBox from './FlexBox'; +import Spinner from './Spinner'; const FlexEndBox = styled(FlexBox)({ justifyContent: 'flex-end', @@ -11,7 +12,7 @@ const FlexEndBox = styled(FlexBox)({ }); const ActionGroup: FC = (props) => { - const { actions = [] } = props; + const { actions = [], loading } = props; const elements = useMemo( () => @@ -23,7 +24,9 @@ const ActionGroup: FC = (props) => { [actions], ); - return ( + return loading ? ( + + ) : ( {elements} diff --git a/striker-ui/components/Dialog/DialogActionGroup.tsx b/striker-ui/components/Dialog/DialogActionGroup.tsx index 32aa6b57..e213529f 100644 --- a/striker-ui/components/Dialog/DialogActionGroup.tsx +++ b/striker-ui/components/Dialog/DialogActionGroup.tsx @@ -2,7 +2,6 @@ import { FC, useCallback, useContext, useMemo } from 'react'; import ActionGroup from '../ActionGroup'; import { DialogContext } from './Dialog'; -import Spinner from '../Spinner'; const handleAction: ExtendableEventHandler = ( { handlers: { base, origin } }, @@ -78,12 +77,14 @@ const DialogActionGroup: FC = (props) => { onClick: proceedHandler, }, ]} + loading={loading} /> ), [ cancelChildren, cancelHandler, cancelProps, + loading, proceedChildren, proceedColour, proceedHandler, @@ -91,12 +92,7 @@ const DialogActionGroup: FC = (props) => { ], ); - const result = useMemo( - () => (loading ? : actions), - [actions, loading], - ); - - return result; + return actions; }; export default DialogActionGroup; diff --git a/striker-ui/types/ActionGroup.d.ts b/striker-ui/types/ActionGroup.d.ts index 9251ffd1..7c6d8c39 100644 --- a/striker-ui/types/ActionGroup.d.ts +++ b/striker-ui/types/ActionGroup.d.ts @@ -1,5 +1,6 @@ type ActionGroupOptionalProps = { actions?: ContainedButtonProps[]; + loading?: boolean; }; type ActionGroupProps = ActionGroupOptionalProps; From 7d0dab6bb27ebfb87119975c2f1ed48674c21ae9 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 21 Sep 2023 02:50:17 -0400 Subject: [PATCH 28/33] fix(striker-ui): show request error, show submitting in edit file form --- striker-ui/components/Files/EditFileForm.tsx | 31 ++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/striker-ui/components/Files/EditFileForm.tsx b/striker-ui/components/Files/EditFileForm.tsx index 4f62720c..3dfb6334 100644 --- a/striker-ui/components/Files/EditFileForm.tsx +++ b/striker-ui/components/Files/EditFileForm.tsx @@ -1,12 +1,13 @@ import { useFormik } from 'formik'; -import { FC, useMemo } from 'react'; +import { FC, useCallback, useMemo, useRef } from 'react'; import ActionGroup from '../ActionGroup'; import api from '../../lib/api'; import convertFormikErrorsToMessages from '../../lib/convertFormikErrorsToMessages'; import FileInputGroup from './FileInputGroup'; import FlexBox from '../FlexBox'; -import MessageGroup from '../MessageGroup'; +import handleAPIError from '../../lib/handleAPIError'; +import MessageGroup, { MessageGroupForwardedRefContent } from '../MessageGroup'; import fileListSchema from './schema'; const toEditFileRequestBody = ( @@ -67,6 +68,14 @@ const toEditFileRequestBody = ( const EditFileForm: FC = (props) => { const { anvils, drHosts, previous: file } = props; + const messageGroupRef = useRef({}); + + const setApiMessage = useCallback( + (message?: Message) => + messageGroupRef.current.setMessage?.call(null, 'api', message), + [], + ); + const formikInitialValues = useMemo(() => { const { locations, name, type, uuid } = file; @@ -97,10 +106,21 @@ const EditFileForm: FC = (props) => { const formik = useFormik({ initialValues: formikInitialValues, - onSubmit: (values) => { + onSubmit: (values, { setSubmitting }) => { const body = toEditFileRequestBody(values[file.uuid], file); - api.put(`/file/${file.uuid}`, body); + api + .put(`/file/${file.uuid}`, body) + .catch((error) => { + const emsg = handleAPIError(error); + + emsg.children = <>Failed to modify file. {emsg.children}; + + setApiMessage(emsg); + }) + .finally(() => { + setSubmitting(false); + }); }, validationSchema: fileListSchema, }); @@ -136,8 +156,9 @@ const EditFileForm: FC = (props) => { showSyncInputGroup showTypeInput /> - + Date: Thu, 21 Sep 2023 02:50:46 -0400 Subject: [PATCH 29/33] fix(striker-ui): replace file manager --- striker-ui/pages/file-manager/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/striker-ui/pages/file-manager/index.tsx b/striker-ui/pages/file-manager/index.tsx index 49ce9a42..a83ba1b1 100644 --- a/striker-ui/pages/file-manager/index.tsx +++ b/striker-ui/pages/file-manager/index.tsx @@ -1,7 +1,7 @@ import Head from 'next/head'; -import Files from '../../components/Files'; import Header from '../../components/Header'; +import ManageFilePanel from '../../components/Files/ManageFilePanel'; const FileManager = (): JSX.Element => ( <> @@ -9,7 +9,7 @@ const FileManager = (): JSX.Element => ( File Manager
- + ); From 45ea4217245b331a23a3aaaa95c26a0569932317 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 21 Sep 2023 03:21:19 -0400 Subject: [PATCH 30/33] fix(striker-ui): prepare handle error in add file form --- striker-ui/components/Files/AddFileForm.tsx | 74 ++++++++++++--------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/striker-ui/components/Files/AddFileForm.tsx b/striker-ui/components/Files/AddFileForm.tsx index 22c9eb35..5ca20a94 100644 --- a/striker-ui/components/Files/AddFileForm.tsx +++ b/striker-ui/components/Files/AddFileForm.tsx @@ -60,43 +60,53 @@ const AddFileForm: FC = (props) => { }, {}), ); - files.forEach(({ file, name, uuid }) => { - if (!file) return; + const promises = files.reduce[]>( + (chain, { file, name, uuid }) => { + if (!file) return chain; - const data = new FormData(); + const data = new FormData(); - data.append('file', new File([file], name, { ...file })); + data.append('file', new File([file], name, { ...file })); - api - .post('/file', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - onUploadProgress: ( - (fileUuid: string) => - ({ loaded, total }) => { - setUploads((previous) => - setUploadProgress( - previous, - fileUuid, - Math.round( - (loaded / total) * REQUEST_INCOMPLETE_UPLOAD_LIMIT, + const promise = api + .post('/file', data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + onUploadProgress: ( + (fileUuid: string) => + ({ loaded, total }) => { + setUploads((previous) => + setUploadProgress( + previous, + fileUuid, + Math.round( + (loaded / total) * REQUEST_INCOMPLETE_UPLOAD_LIMIT, + ), ), - ), + ); + } + )(uuid), + }) + .then( + ((fileUuid: string) => () => { + setUploads((previous) => + setUploadProgress(previous, fileUuid, 100), ); - } - )(uuid), - }) - .then( - ((fileUuid: string) => () => { - setUploads((previous) => - setUploadProgress(previous, fileUuid, 100), - ); - })(uuid), - ) - .catch((error) => { - handleAPIError(error); - }); + })(uuid), + ); + + chain.push(promise); + + return chain; + }, + [], + ); + + Promise.all(promises).catch((error) => { + const emsg = handleAPIError(error); + + emsg.children = <>Failed to add file. {emsg.children}; }); }, validationSchema: fileListSchema, From 8b98c5fe6c35857d451b5c19b0ddc4bbfa2542fd Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 21 Sep 2023 03:38:47 -0400 Subject: [PATCH 31/33] build(striker-ui-api): rebuild --- striker-ui-api/out/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/striker-ui-api/out/index.js b/striker-ui-api/out/index.js index 8873f7c6..d0b08292 100644 --- a/striker-ui-api/out/index.js +++ b/striker-ui-api/out/index.js @@ -1,2 +1,2 @@ /*! For license information please see index.js.LICENSE.txt */ -(()=>{var e={9078:(e,t,n)=>{"use strict";var r=n(159),a=n(983);function o(e){if(!(this instanceof o))return new o(e);this.headers=e.headers,this.negotiator=new r(e)}function i(e){return-1===e.indexOf("/")?a.lookup(e):e}function s(e){return"string"==typeof e}e.exports=o,o.prototype.type=o.prototype.types=function(e){var t=e;if(t&&!Array.isArray(t)){t=new Array(arguments.length);for(var n=0;n{"use strict";function t(e,n,r){for(var a=0;a0&&Array.isArray(o)?t(o,n,r-1):n.push(o)}return n}function n(e,t){for(var r=0;r{"use strict";var r=n(412)("body-parser"),a=Object.create(null);function o(e){return function(){return function(e){var t=a[e];if(void 0!==t)return t;switch(e){case"json":t=n(6035);break;case"raw":t=n(187);break;case"text":t=n(6560);break;case"urlencoded":t=n(4861)}return a[e]=t}(e)}}t=e.exports=r.function((function(e){var n=Object.create(e||null,{type:{configurable:!0,enumerable:!0,value:void 0,writable:!0}}),r=t.urlencoded(n),a=t.json(n);return function(e,t,n){a(e,t,(function(a){if(a)return n(a);r(e,t,n)}))}}),"bodyParser: use individual json/urlencoded middlewares"),Object.defineProperty(t,"json",{configurable:!0,enumerable:!0,get:o("json")}),Object.defineProperty(t,"raw",{configurable:!0,enumerable:!0,get:o("raw")}),Object.defineProperty(t,"text",{configurable:!0,enumerable:!0,get:o("text")}),Object.defineProperty(t,"urlencoded",{configurable:!0,enumerable:!0,get:o("urlencoded")})},3211:(e,t,n)=>{"use strict";var r=n(9009),a=n(6149),o=n(1045),i=n(4914),s=n(338),c=n(8170),u=n(9796);e.exports=function(e,t,n,p,l,d){var f,m,v=d;e._body=!0;var h=null!==v.encoding?v.encoding:null,b=v.verify;try{m=function(e,t,n){var a,o=(e.headers["content-encoding"]||"identity").toLowerCase(),i=e.headers["content-length"];if(t('content-encoding "%s"',o),!1===n&&"identity"!==o)throw r(415,"content encoding unsupported",{encoding:o,type:"encoding.unsupported"});switch(o){case"deflate":a=u.createInflate(),t("inflate body"),e.pipe(a);break;case"gzip":a=u.createGunzip(),t("gunzip body"),e.pipe(a);break;case"identity":(a=e).length=i;break;default:throw r(415,'unsupported content encoding "'+o+'"',{encoding:o,type:"encoding.unsupported"})}return a}(e,l,v.inflate),f=m.length,m.length=void 0}catch(e){return n(e)}if(v.length=f,v.encoding=b?null:h,null===v.encoding&&null!==h&&!i.encodingExists(h))return n(r(415,'unsupported charset "'+h.toUpperCase()+'"',{charset:h.toLowerCase(),type:"charset.unsupported"}));l("read body"),o(m,v,(function(o,u){var d;if(o)return d="encoding.unsupported"===o.type?r(415,'unsupported charset "'+h.toUpperCase()+'"',{charset:h.toLowerCase(),type:"charset.unsupported"}):r(400,o),m!==e&&(c(e),a(m,!0)),void function(e,t){s.isFinished(e)?t():(s(e,t),e.resume())}(e,(function(){n(r(400,d))}));if(b)try{l("verify body"),b(e,t,u,h)}catch(e){return void n(r(403,e,{body:u,type:e.type||"entity.verify.failed"}))}var f=u;try{l("parse body"),f="string"!=typeof u&&null!==h?i.decode(u,h):u,e.body=p(f)}catch(e){return void n(r(400,e,{body:f,type:e.type||"entity.parse.failed"}))}n()}))}},6035:(e,t,n)=>{"use strict";var r=n(9830),a=n(7811),o=n(9009),i=n(5158)("body-parser:json"),s=n(3211),c=n(273);e.exports=function(e){var t=e||{},n="number"!=typeof t.limit?r.parse(t.limit||"100kb"):t.limit,l=!1!==t.inflate,d=t.reviver,f=!1!==t.strict,m=t.type||"application/json",v=t.verify||!1;if(!1!==v&&"function"!=typeof v)throw new TypeError("option verify must be function");var h="function"!=typeof m?function(e){return function(t){return Boolean(c(t,e))}}(m):m;function b(e){if(0===e.length)return{};if(f){var t=(n=e,(r=u.exec(n))?r[1]:void 0);if("{"!==t&&"["!==t)throw i("strict violation"),function(e,t){var n=e.indexOf(t),r=-1!==n?e.substring(0,n)+"#":"";try{throw JSON.parse(r),new SyntaxError("strict violation")}catch(e){return p(e,{message:e.message.replace("#",t),stack:e.stack})}}(e,t)}var n,r;try{return i("parse json"),JSON.parse(e,d)}catch(e){throw p(e,{message:e.message,stack:e.stack})}}return function(e,t,r){if(e._body)return i("body already parsed"),void r();if(e.body=e.body||{},!c.hasBody(e))return i("skip empty body"),void r();if(i("content-type %j",e.headers["content-type"]),!h(e))return i("skip parsing"),void r();var u=function(e){try{return(a.parse(e).parameters.charset||"").toLowerCase()}catch(e){return}}(e)||"utf-8";if("utf-"!==u.slice(0,4))return i("invalid charset"),void r(o(415,'unsupported charset "'+u.toUpperCase()+'"',{charset:u,type:"charset.unsupported"}));s(e,t,r,b,i,{encoding:u,inflate:l,limit:n,verify:v})}};var u=/^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/;function p(e,t){for(var n=Object.getOwnPropertyNames(e),r=0;r{"use strict";var r=n(9830),a=n(5158)("body-parser:raw"),o=n(3211),i=n(273);e.exports=function(e){var t=e||{},n=!1!==t.inflate,s="number"!=typeof t.limit?r.parse(t.limit||"100kb"):t.limit,c=t.type||"application/octet-stream",u=t.verify||!1;if(!1!==u&&"function"!=typeof u)throw new TypeError("option verify must be function");var p="function"!=typeof c?function(e){return function(t){return Boolean(i(t,e))}}(c):c;function l(e){return e}return function(e,t,r){return e._body?(a("body already parsed"),void r()):(e.body=e.body||{},i.hasBody(e)?(a("content-type %j",e.headers["content-type"]),p(e)?void o(e,t,r,l,a,{encoding:null,inflate:n,limit:s,verify:u}):(a("skip parsing"),void r())):(a("skip empty body"),void r()))}}},6560:(e,t,n)=>{"use strict";var r=n(9830),a=n(7811),o=n(5158)("body-parser:text"),i=n(3211),s=n(273);e.exports=function(e){var t=e||{},n=t.defaultCharset||"utf-8",c=!1!==t.inflate,u="number"!=typeof t.limit?r.parse(t.limit||"100kb"):t.limit,p=t.type||"text/plain",l=t.verify||!1;if(!1!==l&&"function"!=typeof l)throw new TypeError("option verify must be function");var d="function"!=typeof p?function(e){return function(t){return Boolean(s(t,e))}}(p):p;function f(e){return e}return function(e,t,r){if(e._body)return o("body already parsed"),void r();if(e.body=e.body||{},!s.hasBody(e))return o("skip empty body"),void r();if(o("content-type %j",e.headers["content-type"]),!d(e))return o("skip parsing"),void r();var p=function(e){try{return(a.parse(e).parameters.charset||"").toLowerCase()}catch(e){return}}(e)||n;i(e,t,r,f,o,{encoding:p,inflate:c,limit:u,verify:l})}}},4861:(e,t,n)=>{"use strict";var r=n(9830),a=n(7811),o=n(9009),i=n(5158)("body-parser:urlencoded"),s=n(412)("body-parser"),c=n(3211),u=n(273);e.exports=function(e){var t=e||{};void 0===t.extended&&s("undefined extended: provide extended option");var n=!1!==t.extended,p=!1!==t.inflate,f="number"!=typeof t.limit?r.parse(t.limit||"100kb"):t.limit,m=t.type||"application/x-www-form-urlencoded",v=t.verify||!1;if(!1!==v&&"function"!=typeof v)throw new TypeError("option verify must be function");var h=n?function(e){var t=void 0!==e.parameterLimit?e.parameterLimit:1e3,n=d("qs");if(isNaN(t)||t<1)throw new TypeError("option parameterLimit must be a positive number");return isFinite(t)&&(t|=0),function(e){var r=l(e,t);if(void 0===r)throw i("too many parameters"),o(413,"too many parameters",{type:"parameters.too.many"});var a=Math.max(100,r);return i("parse extended urlencoding"),n(e,{allowPrototypes:!0,arrayLimit:a,depth:1/0,parameterLimit:t})}}(t):function(e){var t=void 0!==e.parameterLimit?e.parameterLimit:1e3,n=d("querystring");if(isNaN(t)||t<1)throw new TypeError("option parameterLimit must be a positive number");return isFinite(t)&&(t|=0),function(e){if(void 0===l(e,t))throw i("too many parameters"),o(413,"too many parameters",{type:"parameters.too.many"});return i("parse urlencoding"),n(e,void 0,void 0,{maxKeys:t})}}(t),b="function"!=typeof m?function(e){return function(t){return Boolean(u(t,e))}}(m):m;function g(e){return e.length?h(e):{}}return function(e,t,n){if(e._body)return i("body already parsed"),void n();if(e.body=e.body||{},!u.hasBody(e))return i("skip empty body"),void n();if(i("content-type %j",e.headers["content-type"]),!b(e))return i("skip parsing"),void n();var r=function(e){try{return(a.parse(e).parameters.charset||"").toLowerCase()}catch(e){return}}(e)||"utf-8";if("utf-8"!==r)return i("invalid charset"),void n(o(415,'unsupported charset "'+r.toUpperCase()+'"',{charset:r,type:"charset.unsupported"}));c(e,t,n,g,i,{debug:i,encoding:r,inflate:p,limit:f,verify:v})}};var p=Object.create(null);function l(e,t){for(var n=0,r=0;-1!==(r=e.indexOf("&",r));)if(r++,++n===t)return;return n}function d(e){var t=p[e];if(void 0!==t)return t.parse;switch(e){case"qs":t=n(129);break;case"querystring":t=n(3477)}return p[e]=t,t.parse}},6744:(e,t,n)=>{"use strict";const r=n(3349),a=n(7529),o=n(8050),i=n(4339),s=(e,t={})=>{let n=[];if(Array.isArray(e))for(let r of e){let e=s.create(r,t);Array.isArray(e)?n.push(...e):n.push(e)}else n=[].concat(s.create(e,t));return t&&!0===t.expand&&!0===t.nodupes&&(n=[...new Set(n)]),n};s.parse=(e,t={})=>i(e,t),s.stringify=(e,t={})=>r("string"==typeof e?s.parse(e,t):e,t),s.compile=(e,t={})=>("string"==typeof e&&(e=s.parse(e,t)),a(e,t)),s.expand=(e,t={})=>{"string"==typeof e&&(e=s.parse(e,t));let n=o(e,t);return!0===t.noempty&&(n=n.filter(Boolean)),!0===t.nodupes&&(n=[...new Set(n)]),n},s.create=(e,t={})=>""===e||e.length<3?[e]:!0!==t.expand?s.compile(e,t):s.expand(e,t),e.exports=s},7529:(e,t,n)=>{"use strict";const r=n(2664),a=n(3083);e.exports=(e,t={})=>{let n=(e,o={})=>{let i=a.isInvalidBrace(o),s=!0===e.invalid&&!0===t.escapeInvalid,c=!0===i||!0===s,u=!0===t.escapeInvalid?"\\":"",p="";if(!0===e.isOpen)return u+e.value;if(!0===e.isClose)return u+e.value;if("open"===e.type)return c?u+e.value:"(";if("close"===e.type)return c?u+e.value:")";if("comma"===e.type)return"comma"===e.prev.type?"":c?e.value:"|";if(e.value)return e.value;if(e.nodes&&e.ranges>0){let n=a.reduce(e.nodes),o=r(...n,{...t,wrap:!1,toRegex:!0});if(0!==o.length)return n.length>1&&o.length>1?`(${o})`:o}if(e.nodes)for(let t of e.nodes)p+=n(t,e);return p};return n(e)}},6611:e=>{"use strict";e.exports={MAX_LENGTH:65536,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:"\n",CHAR_NO_BREAK_SPACE:" ",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:"\t",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\ufeff"}},8050:(e,t,n)=>{"use strict";const r=n(2664),a=n(3349),o=n(3083),i=(e="",t="",n=!1)=>{let r=[];if(e=[].concat(e),!(t=[].concat(t)).length)return e;if(!e.length)return n?o.flatten(t).map((e=>`{${e}}`)):t;for(let a of e)if(Array.isArray(a))for(let e of a)r.push(i(e,t,n));else for(let e of t)!0===n&&"string"==typeof e&&(e=`{${e}}`),r.push(Array.isArray(e)?i(a,e,n):a+e);return o.flatten(r)};e.exports=(e,t={})=>{let n=void 0===t.rangeLimit?1e3:t.rangeLimit,s=(e,c={})=>{e.queue=[];let u=c,p=c.queue;for(;"brace"!==u.type&&"root"!==u.type&&u.parent;)u=u.parent,p=u.queue;if(e.invalid||e.dollar)return void p.push(i(p.pop(),a(e,t)));if("brace"===e.type&&!0!==e.invalid&&2===e.nodes.length)return void p.push(i(p.pop(),["{}"]));if(e.nodes&&e.ranges>0){let s=o.reduce(e.nodes);if(o.exceedsLimit(...s,t.step,n))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let c=r(...s,t);return 0===c.length&&(c=a(e,t)),p.push(i(p.pop(),c)),void(e.nodes=[])}let l=o.encloseBrace(e),d=e.queue,f=e;for(;"brace"!==f.type&&"root"!==f.type&&f.parent;)f=f.parent,d=f.queue;for(let t=0;t{"use strict";const r=n(3349),{MAX_LENGTH:a,CHAR_BACKSLASH:o,CHAR_BACKTICK:i,CHAR_COMMA:s,CHAR_DOT:c,CHAR_LEFT_PARENTHESES:u,CHAR_RIGHT_PARENTHESES:p,CHAR_LEFT_CURLY_BRACE:l,CHAR_RIGHT_CURLY_BRACE:d,CHAR_LEFT_SQUARE_BRACKET:f,CHAR_RIGHT_SQUARE_BRACKET:m,CHAR_DOUBLE_QUOTE:v,CHAR_SINGLE_QUOTE:h,CHAR_NO_BREAK_SPACE:b,CHAR_ZERO_WIDTH_NOBREAK_SPACE:g}=n(6611);e.exports=(e,t={})=>{if("string"!=typeof e)throw new TypeError("Expected a string");let n=t||{},y="number"==typeof n.maxLength?Math.min(a,n.maxLength):a;if(e.length>y)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${y})`);let x,w={type:"root",input:e,nodes:[]},_=[w],S=w,k=w,E=0,A=e.length,j=0,O=0;const C=()=>e[j++],R=e=>{if("text"===e.type&&"dot"===k.type&&(k.type="text"),!k||"text"!==k.type||"text"!==e.type)return S.nodes.push(e),e.parent=S,e.prev=k,k=e,e;k.value+=e.value};for(R({type:"bos"});j0){if(S.ranges>0){S.ranges=0;let e=S.nodes.shift();S.nodes=[e,{type:"text",value:r(S)}]}R({type:"comma",value:x}),S.commas++}else if(x===c&&O>0&&0===S.commas){let e=S.nodes;if(0===O||0===e.length){R({type:"text",value:x});continue}if("dot"===k.type){if(S.range=[],k.value+=x,k.type="range",3!==S.nodes.length&&5!==S.nodes.length){S.invalid=!0,S.ranges=0,k.type="text";continue}S.ranges++,S.args=[];continue}if("range"===k.type){e.pop();let t=e[e.length-1];t.value+=k.value+x,k=t,S.ranges--;continue}R({type:"dot",value:x})}else R({type:"text",value:x});else{if("brace"!==S.type){R({type:"text",value:x});continue}let e="close";S=_.pop(),S.close=!0,R({type:e,value:x}),O--,S=_[_.length-1]}else{O++;let e=k.value&&"$"===k.value.slice(-1)||!0===S.dollar;S=R({type:"brace",open:!0,close:!1,dollar:e,depth:O,commas:0,ranges:0,nodes:[]}),_.push(S),R({type:"open",value:x})}else{let e,n=x;for(!0!==t.keepQuotes&&(x="");j{e.nodes||("open"===e.type&&(e.isOpen=!0),"close"===e.type&&(e.isClose=!0),e.nodes||(e.type="text"),e.invalid=!0)}));let e=_[_.length-1],t=e.nodes.indexOf(S);e.nodes.splice(t,1,...S.nodes)}}while(_.length>0);return R({type:"eos"}),w}},3349:(e,t,n)=>{"use strict";const r=n(3083);e.exports=(e,t={})=>{let n=(e,a={})=>{let o=t.escapeInvalid&&r.isInvalidBrace(a),i=!0===e.invalid&&!0===t.escapeInvalid,s="";if(e.value)return(o||i)&&r.isOpenOrClose(e)?"\\"+e.value:e.value;if(e.value)return e.value;if(e.nodes)for(let t of e.nodes)s+=n(t);return s};return n(e)}},3083:(e,t)=>{"use strict";t.isInteger=e=>"number"==typeof e?Number.isInteger(e):"string"==typeof e&&""!==e.trim()&&Number.isInteger(Number(e)),t.find=(e,t)=>e.nodes.find((e=>e.type===t)),t.exceedsLimit=(e,n,r=1,a)=>!1!==a&&!(!t.isInteger(e)||!t.isInteger(n))&&(Number(n)-Number(e))/Number(r)>=a,t.escapeNode=(e,t=0,n)=>{let r=e.nodes[t];r&&(n&&r.type===n||"open"===r.type||"close"===r.type)&&!0!==r.escaped&&(r.value="\\"+r.value,r.escaped=!0)},t.encloseBrace=e=>"brace"===e.type&&e.commas>>0+e.ranges>>0==0&&(e.invalid=!0,!0),t.isInvalidBrace=e=>!("brace"!==e.type||!0!==e.invalid&&!e.dollar&&(e.commas>>0+e.ranges>>0!=0&&!0===e.open&&!0===e.close||(e.invalid=!0,0))),t.isOpenOrClose=e=>"open"===e.type||"close"===e.type||!0===e.open||!0===e.close,t.reduce=e=>e.reduce(((e,t)=>("text"===t.type&&e.push(t.value),"range"===t.type&&(t.type="text"),e)),[]),t.flatten=(...e)=>{const t=[],n=e=>{for(let r=0;r{"use strict";const{parseContentType:r}=n(1510),a=[n(7626),n(4403)].filter((function(e){return"function"==typeof e.detect}));e.exports=e=>{if("object"==typeof e&&null!==e||(e={}),"object"!=typeof e.headers||null===e.headers||"string"!=typeof e.headers["content-type"])throw new Error("Missing Content-Type");return function(e){const t=e.headers,n=r(t["content-type"]);if(!n)throw new Error("Malformed content type");for(const r of a){if(!r.detect(n))continue;const a={limits:e.limits,headers:t,conType:n,highWaterMark:void 0,fileHwm:void 0,defCharset:void 0,defParamCharset:void 0,preservePath:!1};return e.highWaterMark&&(a.highWaterMark=e.highWaterMark),e.fileHwm&&(a.fileHwm=e.fileHwm),a.defCharset=e.defCharset,a.defParamCharset=e.defParamCharset,a.preservePath=e.preservePath,new r(a)}throw new Error(`Unsupported content type: ${t["content-type"]}`)}(e)}},7626:(e,t,n)=>{"use strict";const{Readable:r,Writable:a}=n(2781),o=n(1301),{basename:i,convertToUTF8:s,getDecoder:c,parseContentType:u,parseDisposition:p}=n(1510),l=Buffer.from("\r\n"),d=Buffer.from("\r"),f=Buffer.from("-");function m(){}const v=16384;class h{constructor(e){this.header=Object.create(null),this.pairCount=0,this.byteCount=0,this.state=0,this.name="",this.value="",this.crlf=0,this.cb=e}reset(){this.header=Object.create(null),this.pairCount=0,this.byteCount=0,this.state=0,this.name="",this.value="",this.crlf=0}push(e,t,n){let r=t;for(;t{if(this._read(),0==--t._fileEndsLeft&&t._finalcb){const e=t._finalcb;t._finalcb=null,process.nextTick(e)}}))}_read(e){const t=this._readcb;t&&(this._readcb=null,t())}}const g={push:(e,t)=>{},destroy:()=>{}};function y(e,t){return e}function x(e,t,n){if(n)return t(n);t(n=w(e))}function w(e){if(e._hparser)return new Error("Malformed part header");const t=e._fileStream;return t&&(e._fileStream=null,t.destroy(new Error("Unexpected end of file"))),e._complete?void 0:new Error("Unexpected end of form")}const _=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],S=[0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1];e.exports=class extends a{constructor(e){if(super({autoDestroy:!0,emitClose:!0,highWaterMark:"number"==typeof e.highWaterMark?e.highWaterMark:void 0}),!e.conType.params||"string"!=typeof e.conType.params.boundary)throw new Error("Multipart: Boundary not found");const t=e.conType.params.boundary,n="string"==typeof e.defParamCharset&&e.defParamCharset?c(e.defParamCharset):y,r=e.defCharset||"utf8",a=e.preservePath,v={autoDestroy:!0,emitClose:!0,highWaterMark:"number"==typeof e.fileHwm?e.fileHwm:void 0},x=e.limits,w=x&&"number"==typeof x.fieldSize?x.fieldSize:1048576,_=x&&"number"==typeof x.fileSize?x.fileSize:1/0,S=x&&"number"==typeof x.files?x.files:1/0,k=x&&"number"==typeof x.fields?x.fields:1/0,E=x&&"number"==typeof x.parts?x.parts:1/0;let A=-1,j=0,O=0,C=!1;this._fileEndsLeft=0,this._fileStream=void 0,this._complete=!1;let R,T,I,P,U,N=0,D=0,L=!1,M=!1,F=!1;this._hparser=null;const q=new h((e=>{let t;if(this._hparser=null,C=!1,P="text/plain",T=r,I="7bit",U=void 0,L=!1,!e["content-disposition"])return void(C=!0);const o=p(e["content-disposition"][0],n);if(o&&"form-data"===o.type){if(o.params&&(o.params.name&&(U=o.params.name),o.params["filename*"]?t=o.params["filename*"]:o.params.filename&&(t=o.params.filename),void 0===t||a||(t=i(t))),e["content-type"]){const t=u(e["content-type"][0]);t&&(P=`${t.type}/${t.subtype}`,t.params&&"string"==typeof t.params.charset&&(T=t.params.charset.toLowerCase()))}if(e["content-transfer-encoding"]&&(I=e["content-transfer-encoding"][0].toLowerCase()),"application/octet-stream"===P||void 0!==t){if(O===S)return M||(M=!0,this.emit("filesLimit")),void(C=!0);if(++O,0===this.listenerCount("file"))return void(C=!0);N=0,this._fileStream=new b(v,this),++this._fileEndsLeft,this.emit("file",U,this._fileStream,{filename:t,encoding:I,mimeType:P})}else{if(j===k)return F||(F=!0,this.emit("fieldsLimit")),void(C=!0);if(++j,0===this.listenerCount("field"))return void(C=!0);R=[],D=0}}else C=!0}));let B=0;const H=(e,t,n,r,a)=>{e:for(;t;){if(null!==this._hparser){const e=this._hparser.push(t,n,r);if(-1===e){this._hparser=null,q.reset(),this.emit("error",new Error("Malformed part header"));break}n=e}if(n===r)break;if(0!==B){if(1===B){switch(t[n]){case 45:B=2,++n;break;case 13:B=3,++n;break;default:B=0}if(n===r)return}if(2===B){if(B=0,45===t[n])return this._complete=!0,void(this._bparser=g);const e=this._writecb;this._writecb=m,H(!1,f,0,1,!1),this._writecb=e}else if(3===B){if(B=0,10===t[n]){if(++n,A>=E)break;if(this._hparser=q,n===r)break;continue e}{const e=this._writecb;this._writecb=m,H(!1,d,0,1,!1),this._writecb=e}}}if(!C)if(this._fileStream){let e;const o=Math.min(r-n,_-N);a?e=t.slice(n,n+o):(e=Buffer.allocUnsafe(o),t.copy(e,0,n,n+o)),N+=e.length,N===_?(e.length>0&&this._fileStream.push(e),this._fileStream.emit("limit"),this._fileStream.truncated=!0,C=!0):this._fileStream.push(e)||(this._writecb&&(this._fileStream._readcb=this._writecb),this._writecb=null)}else if(void 0!==R){let e;const o=Math.min(r-n,w-D);a?e=t.slice(n,n+o):(e=Buffer.allocUnsafe(o),t.copy(e,0,n,n+o)),D+=o,R.push(e),D===w&&(C=!0,L=!0)}break}if(e){if(B=1,this._fileStream)this._fileStream.push(null),this._fileStream=null;else if(void 0!==R){let e;switch(R.length){case 0:e="";break;case 1:e=s(R[0],T,0);break;default:e=s(Buffer.concat(R,D),T,0)}R=void 0,D=0,this.emit("field",U,e,{nameTruncated:!1,valueTruncated:L,encoding:I,mimeType:P})}++A===E&&this.emit("partsLimit")}};this._bparser=new o(`\r\n--${t}`,H),this._writecb=null,this._finalcb=null,this.write(l)}static detect(e){return"multipart"===e.type&&"form-data"===e.subtype}_write(e,t,n){this._writecb=n,this._bparser.push(e,0),this._writecb&&function(e,t){const n=e._writecb;e._writecb=null,n&&n()}(this)}_destroy(e,t){this._hparser=null,this._bparser=g,e||(e=w(this));const n=this._fileStream;n&&(this._fileStream=null,n.destroy(e)),t(e)}_final(e){if(this._bparser.destroy(),!this._complete)return e(new Error("Unexpected end of form"));this._fileEndsLeft?this._finalcb=x.bind(null,this,e):x(this,e)}}},4403:(e,t,n)=>{"use strict";const{Writable:r}=n(2781),{getDecoder:a}=n(1510);function o(e,t,n,r){if(n>=r)return r;if(-1===e._byte){const a=c[t[n++]];if(-1===a)return-1;if(a>=8&&(e._encode=2),ne.fieldNameSizeLimit){for(e._keyTrunc||e._lastPose.fieldSizeLimit){for(e._valTrunc||e._lastPos=this.fieldsLimit)return n();let r=0;const a=e.length;if(this._lastPos=0,-2!==this._byte){if(r=o(this,e,r,a),-1===r)return n(new Error("Malformed urlencoded form"));if(r>=a)return n();this._inKey?++this._bytesKey:++this._bytesVal}e:for(;r0&&this.emit("field",this._key,"",{nameTruncated:this._keyTrunc,valueTruncated:!1,encoding:this.charset,mimeType:"text/plain"}),this._key="",this._val="",this._keyTrunc=!1,this._valTrunc=!1,this._bytesKey=0,this._bytesVal=0,++this._fields>=this.fieldsLimit)return this.emit("fieldsLimit"),n();continue;case 43:this._lastPos=a)return n();++this._bytesKey,r=i(this,e,r,a);continue}++r,++this._bytesKey,r=i(this,e,r,a)}this._lastPos0||this._bytesVal>0)&&this.emit("field",this._key,this._val,{nameTruncated:this._keyTrunc,valueTruncated:this._valTrunc,encoding:this.charset,mimeType:"text/plain"}),this._key="",this._val="",this._keyTrunc=!1,this._valTrunc=!1,this._bytesKey=0,this._bytesVal=0,++this._fields>=this.fieldsLimit)return this.emit("fieldsLimit"),n();continue e;case 43:this._lastPos=a)return n();++this._bytesVal,r=s(this,e,r,a);continue}++r,++this._bytesVal,r=s(this,e,r,a)}this._lastPos0||this._bytesVal>0)&&(this._inKey?this._key=this._decoder(this._key,this._encode):this._val=this._decoder(this._val,this._encode),this.emit("field",this._key,this._val,{nameTruncated:this._keyTrunc,valueTruncated:this._valTrunc,encoding:this.charset,mimeType:"text/plain"})),e()}}},1510:function(e){"use strict";function t(e,t,n){for(;t=128?r=2:0===r&&(r=1);continue}return}break}}if(m+=e.slice(d,t),m=o(m,f,r),void 0===m)return}else{if(++t===e.length)return;if(34===e.charCodeAt(t)){d=++t;let n=!1;for(;t{if(0===e.length)return"";if("string"==typeof e){if(t<2)return e;e=Buffer.from(e,"latin1")}return e.utf8Slice(0,e.length)},latin1:(e,t)=>0===e.length?"":"string"==typeof e?e:e.latin1Slice(0,e.length),utf16le:(e,t)=>0===e.length?"":("string"==typeof e&&(e=Buffer.from(e,"latin1")),e.ucs2Slice(0,e.length)),base64:(e,t)=>0===e.length?"":("string"==typeof e&&(e=Buffer.from(e,"latin1")),e.base64Slice(0,e.length)),other:(e,t)=>{if(0===e.length)return"";"string"==typeof e&&(e=Buffer.from(e,"latin1"));try{return new TextDecoder(this).decode(e)}catch{}}};function o(e,t,n){const a=r(t);if(a)return a(e,n)}const i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],s=[0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],c=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,0,0,0,0,1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],u=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,0,0,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],p=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1];e.exports={basename:function(e){if("string"!=typeof e)return"";for(let t=e.length-1;t>=0;--t)switch(e.charCodeAt(t)){case 47:case 92:return".."===(e=e.slice(t+1))||"."===e?"":e}return".."===e||"."===e?"":e},convertToUTF8:o,getDecoder:r,parseContentType:function(e){if(0===e.length)return;const n=Object.create(null);let r=0;for(;r{"use strict";e.exports=function(e,t){return"string"==typeof e?i(e):"number"==typeof e?o(e,t):null},e.exports.format=o,e.exports.parse=i;var t=/\B(?=(\d{3})+(?!\d))/g,n=/(?:\.0*|(\.[^0]+)0+)$/,r={b:1,kb:1024,mb:1<<20,gb:1<<30,tb:Math.pow(1024,4),pb:Math.pow(1024,5)},a=/^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i;function o(e,a){if(!Number.isFinite(e))return null;var o=Math.abs(e),i=a&&a.thousandsSeparator||"",s=a&&a.unitSeparator||"",c=a&&void 0!==a.decimalPlaces?a.decimalPlaces:2,u=Boolean(a&&a.fixedDecimals),p=a&&a.unit||"";p&&r[p.toLowerCase()]||(p=o>=r.pb?"PB":o>=r.tb?"TB":o>=r.gb?"GB":o>=r.mb?"MB":o>=r.kb?"KB":"B");var l=(e/r[p.toLowerCase()]).toFixed(c);return u||(l=l.replace(n,"$1")),i&&(l=l.split(".").map((function(e,n){return 0===n?e.replace(t,i):e})).join(".")),l+s+p}function i(e){if("number"==typeof e&&!isNaN(e))return e;if("string"!=typeof e)return null;var t,n=a.exec(e),o="b";return n?(t=parseFloat(n[1]),o=n[4].toLowerCase()):(t=parseInt(e,10),o="b"),isNaN(t)?null:Math.floor(r[o]*t)}},1924:(e,t,n)=>{"use strict";var r=n(210),a=n(5559),o=a(r("String.prototype.indexOf"));e.exports=function(e,t){var n=r(e,!!t);return"function"==typeof n&&o(e,".prototype.")>-1?a(n):n}},5559:(e,t,n)=>{"use strict";var r=n(8612),a=n(210),o=a("%Function.prototype.apply%"),i=a("%Function.prototype.call%"),s=a("%Reflect.apply%",!0)||r.call(i,o),c=a("%Object.getOwnPropertyDescriptor%",!0),u=a("%Object.defineProperty%",!0),p=a("%Math.max%");if(u)try{u({},"a",{value:1})}catch(e){u=null}e.exports=function(e){var t=s(r,i,arguments);if(c&&u){var n=c(t,"length");n.configurable&&u(t,"length",{value:1+p(0,e.length-(arguments.length-1))})}return t};var l=function(){return s(r,o,arguments)};u?u(e.exports,"apply",{value:l}):e.exports.apply=l},7389:(e,t,n)=>{"use strict";e.exports=function(e,t){var n=t||{},a=n.type||"attachment",o=function(e,t){if(void 0!==e){var n={};if("string"!=typeof e)throw new TypeError("filename must be a string");if(void 0===t&&(t=!0),"string"!=typeof t&&"boolean"!=typeof t)throw new TypeError("fallback must be a string or boolean");if("string"==typeof t&&c.test(t))throw new TypeError("fallback must be ISO-8859-1 string");var a=r(e),o=d.test(a),s="string"!=typeof t?t&&b(a):r(t),u="string"==typeof s&&s!==a;return(u||!o||i.test(a))&&(n["filename*"]=a),(o||u)&&(n.filename=u?s:a),n}}(e,n.fallback);return function(e){var t=e.parameters,n=e.type;if(!n||"string"!=typeof n||!f.test(n))throw new TypeError("invalid type");var r=String(n).toLowerCase();if(t&&"object"==typeof t)for(var a,o=Object.keys(t).sort(),i=0;i?@[\\\]{}\x7f]/g,i=/%[0-9A-Fa-f]{2}/,s=/%([0-9A-Fa-f]{2})/g,c=/[^\x20-\x7e\xa0-\xff]/g,u=/\\([\u0000-\u007f])/g,p=/([\\"])/g,l=/;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g,d=/^[\x20-\x7e\x80-\xff]+$/,f=/^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/,m=/^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/,v=/^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/;function h(e){var t=m.exec(e);if(!t)throw new TypeError("invalid extended field value");var n,r=t[1].toLowerCase(),o=t[2].replace(s,g);switch(r){case"iso-8859-1":n=b(o);break;case"utf-8":n=a.from(o,"binary").toString("utf8");break;default:throw new TypeError("unsupported charset in extended field")}return n}function b(e){return String(e).replace(c,"?")}function g(e,t){return String.fromCharCode(parseInt(t,16))}function y(e){return"%"+String(e).charCodeAt(0).toString(16).toUpperCase()}function x(e){return'"'+String(e).replace(p,"\\$1")+'"'}function w(e){var t=String(e);return"UTF-8''"+encodeURIComponent(t).replace(o,y)}function _(e,t){this.type=e,this.parameters=t}},7296:(e,t,n)=>{var r=n(4300),a=r.Buffer;function o(e,t){for(var n in e)t[n]=e[n]}function i(e,t,n){return a(e,t,n)}a.from&&a.alloc&&a.allocUnsafe&&a.allocUnsafeSlow?e.exports=r:(o(r,t),t.Buffer=i),i.prototype=Object.create(a.prototype),o(a,i),i.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return a(e,t,n)},i.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var r=a(e);return void 0!==t?"string"==typeof n?r.fill(t,n):r.fill(t):r.fill(0),r},i.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return a(e)},i.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r.SlowBuffer(e)}},7811:(e,t)=>{"use strict";var n=/; *([!#$%&'*+.^_`|~0-9A-Za-z-]+) *= *("(?:[\u000b\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\u000b\u0020-\u00ff])*"|[!#$%&'*+.^_`|~0-9A-Za-z-]+) */g,r=/^[\u000b\u0020-\u007e\u0080-\u00ff]+$/,a=/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/,o=/\\([\u000b\u0020-\u00ff])/g,i=/([\\"])/g,s=/^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;function c(e){var t=String(e);if(a.test(t))return t;if(t.length>0&&!r.test(t))throw new TypeError("invalid parameter value");return'"'+t.replace(i,"\\$1")+'"'}function u(e){this.parameters=Object.create(null),this.type=e}t.format=function(e){if(!e||"object"!=typeof e)throw new TypeError("argument obj is required");var t=e.parameters,n=e.type;if(!n||!s.test(n))throw new TypeError("invalid type");var r=n;if(t&&"object"==typeof t)for(var o,i=Object.keys(t).sort(),u=0;u{var r=n(6113);function a(e){return r.createHash("sha1").update(e).digest("hex")}t.sign=function(e,t){if("string"!=typeof e)throw new TypeError("Cookie value must be provided as a string.");if("string"!=typeof t)throw new TypeError("Secret string must be provided.");return e+"."+r.createHmac("sha256",t).update(e).digest("base64").replace(/\=+$/,"")},t.unsign=function(e,n){if("string"!=typeof e)throw new TypeError("Signed cookie string must be provided.");if("string"!=typeof n)throw new TypeError("Secret string must be provided.");var r=e.slice(0,e.lastIndexOf("."));return a(t.sign(r,n))==a(e)&&r}},6489:(e,t)=>{"use strict";t.parse=function(e,t){if("string"!=typeof e)throw new TypeError("argument str must be a string");for(var n={},r=(t||{}).decode||a,o=0;o{var r=n(614),a=n(6330),o=TypeError;e.exports=function(e){if(r(e))return e;throw o(a(e)+" is not a function")}},9483:(e,t,n)=>{var r=n(4411),a=n(6330),o=TypeError;e.exports=function(e){if(r(e))return e;throw o(a(e)+" is not a constructor")}},6077:(e,t,n)=>{var r=n(614),a=String,o=TypeError;e.exports=function(e){if("object"==typeof e||r(e))return e;throw o("Can't set "+a(e)+" as a prototype")}},1223:(e,t,n)=>{var r=n(5112),a=n(30),o=n(3070).f,i=r("unscopables"),s=Array.prototype;null==s[i]&&o(s,i,{configurable:!0,value:a(null)}),e.exports=function(e){s[i][e]=!0}},1530:(e,t,n)=>{"use strict";var r=n(8710).charAt;e.exports=function(e,t,n){return t+(n?r(e,t).length:1)}},5787:(e,t,n)=>{var r=n(7976),a=TypeError;e.exports=function(e,t){if(r(t,e))return e;throw a("Incorrect invocation")}},9670:(e,t,n)=>{var r=n(111),a=String,o=TypeError;e.exports=function(e){if(r(e))return e;throw o(a(e)+" is not an object")}},8533:(e,t,n)=>{"use strict";var r=n(2092).forEach,a=n(9341)("forEach");e.exports=a?[].forEach:function(e){return r(this,e,arguments.length>1?arguments[1]:void 0)}},8457:(e,t,n)=>{"use strict";var r=n(9974),a=n(6916),o=n(7908),i=n(3411),s=n(7659),c=n(4411),u=n(6244),p=n(6135),l=n(4121),d=n(1246),f=Array;e.exports=function(e){var t=o(e),n=c(this),m=arguments.length,v=m>1?arguments[1]:void 0,h=void 0!==v;h&&(v=r(v,m>2?arguments[2]:void 0));var b,g,y,x,w,_,S=d(t),k=0;if(!S||this===f&&s(S))for(b=u(t),g=n?new this(b):f(b);b>k;k++)_=h?v(t[k],k):t[k],p(g,k,_);else for(w=(x=l(t,S)).next,g=n?new this:[];!(y=a(w,x)).done;k++)_=h?i(x,v,[y.value,k],!0):y.value,p(g,k,_);return g.length=k,g}},1318:(e,t,n)=>{var r=n(5656),a=n(1400),o=n(6244),i=function(e){return function(t,n,i){var s,c=r(t),u=o(c),p=a(i,u);if(e&&n!=n){for(;u>p;)if((s=c[p++])!=s)return!0}else for(;u>p;p++)if((e||p in c)&&c[p]===n)return e||p||0;return!e&&-1}};e.exports={includes:i(!0),indexOf:i(!1)}},2092:(e,t,n)=>{var r=n(9974),a=n(1702),o=n(8361),i=n(7908),s=n(6244),c=n(5417),u=a([].push),p=function(e){var t=1==e,n=2==e,a=3==e,p=4==e,l=6==e,d=7==e,f=5==e||l;return function(m,v,h,b){for(var g,y,x=i(m),w=o(x),_=r(v,h),S=s(w),k=0,E=b||c,A=t?E(m,S):n||d?E(m,0):void 0;S>k;k++)if((f||k in w)&&(y=_(g=w[k],k,x),e))if(t)A[k]=y;else if(y)switch(e){case 3:return!0;case 5:return g;case 6:return k;case 2:u(A,g)}else switch(e){case 4:return!1;case 7:u(A,g)}return l?-1:a||p?p:A}};e.exports={forEach:p(0),map:p(1),filter:p(2),some:p(3),every:p(4),find:p(5),findIndex:p(6),filterReject:p(7)}},1194:(e,t,n)=>{var r=n(7293),a=n(5112),o=n(7392),i=a("species");e.exports=function(e){return o>=51||!r((function(){var t=[];return(t.constructor={})[i]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},9341:(e,t,n)=>{"use strict";var r=n(7293);e.exports=function(e,t){var n=[][e];return!!n&&r((function(){n.call(null,t||function(){return 1},1)}))}},3671:(e,t,n)=>{var r=n(9662),a=n(7908),o=n(8361),i=n(6244),s=TypeError,c=function(e){return function(t,n,c,u){r(n);var p=a(t),l=o(p),d=i(p),f=e?d-1:0,m=e?-1:1;if(c<2)for(;;){if(f in l){u=l[f],f+=m;break}if(f+=m,e?f<0:d<=f)throw s("Reduce of empty array with no initial value")}for(;e?f>=0:d>f;f+=m)f in l&&(u=n(u,l[f],f,p));return u}};e.exports={left:c(!1),right:c(!0)}},1589:(e,t,n)=>{var r=n(1400),a=n(6244),o=n(6135),i=Array,s=Math.max;e.exports=function(e,t,n){for(var c=a(e),u=r(t,c),p=r(void 0===n?c:n,c),l=i(s(p-u,0)),d=0;u{var r=n(1702);e.exports=r([].slice)},4362:(e,t,n)=>{var r=n(1589),a=Math.floor,o=function(e,t){var n=e.length,c=a(n/2);return n<8?i(e,t):s(e,o(r(e,0,c),t),o(r(e,c),t),t)},i=function(e,t){for(var n,r,a=e.length,o=1;o0;)e[r]=e[--r];r!==o++&&(e[r]=n)}return e},s=function(e,t,n,r){for(var a=t.length,o=n.length,i=0,s=0;i{var r=n(3157),a=n(4411),o=n(111),i=n(5112)("species"),s=Array;e.exports=function(e){var t;return r(e)&&(t=e.constructor,(a(t)&&(t===s||r(t.prototype))||o(t)&&null===(t=t[i]))&&(t=void 0)),void 0===t?s:t}},5417:(e,t,n)=>{var r=n(7475);e.exports=function(e,t){return new(r(e))(0===t?0:t)}},3411:(e,t,n)=>{var r=n(9670),a=n(9212);e.exports=function(e,t,n,o){try{return o?t(r(n)[0],n[1]):t(n)}catch(t){a(e,"throw",t)}}},7072:(e,t,n)=>{var r=n(5112)("iterator"),a=!1;try{var o=0,i={next:function(){return{done:!!o++}},return:function(){a=!0}};i[r]=function(){return this},Array.from(i,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!a)return!1;var n=!1;try{var o={};o[r]=function(){return{next:function(){return{done:n=!0}}}},e(o)}catch(e){}return n}},4326:(e,t,n)=>{var r=n(1702),a=r({}.toString),o=r("".slice);e.exports=function(e){return o(a(e),8,-1)}},648:(e,t,n)=>{var r=n(1694),a=n(614),o=n(4326),i=n(5112)("toStringTag"),s=Object,c="Arguments"==o(function(){return arguments}());e.exports=r?o:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=s(e),i))?n:c?o(t):"Object"==(r=o(t))&&a(t.callee)?"Arguments":r}},9920:(e,t,n)=>{var r=n(2597),a=n(3887),o=n(1236),i=n(3070);e.exports=function(e,t,n){for(var s=a(t),c=i.f,u=o.f,p=0;p{var r=n(5112)("match");e.exports=function(e){var t=/./;try{"/./"[e](t)}catch(n){try{return t[r]=!1,"/./"[e](t)}catch(e){}}return!1}},8544:(e,t,n)=>{var r=n(7293);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},6178:e=>{e.exports=function(e,t){return{value:e,done:t}}},8880:(e,t,n)=>{var r=n(9781),a=n(3070),o=n(9114);e.exports=r?function(e,t,n){return a.f(e,t,o(1,n))}:function(e,t,n){return e[t]=n,e}},9114:e=>{e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},6135:(e,t,n)=>{"use strict";var r=n(4948),a=n(3070),o=n(9114);e.exports=function(e,t,n){var i=r(t);i in e?a.f(e,i,o(0,n)):e[i]=n}},7045:(e,t,n)=>{var r=n(6339),a=n(3070);e.exports=function(e,t,n){return n.get&&r(n.get,t,{getter:!0}),n.set&&r(n.set,t,{setter:!0}),a.f(e,t,n)}},8052:(e,t,n)=>{var r=n(614),a=n(3070),o=n(6339),i=n(3072);e.exports=function(e,t,n,s){s||(s={});var c=s.enumerable,u=void 0!==s.name?s.name:t;if(r(n)&&o(n,u,s),s.global)c?e[t]=n:i(t,n);else{try{s.unsafe?e[t]&&(c=!0):delete e[t]}catch(e){}c?e[t]=n:a.f(e,t,{value:n,enumerable:!1,configurable:!s.nonConfigurable,writable:!s.nonWritable})}return e}},3072:(e,t,n)=>{var r=n(7854),a=Object.defineProperty;e.exports=function(e,t){try{a(r,e,{value:t,configurable:!0,writable:!0})}catch(n){r[e]=t}return t}},5117:(e,t,n)=>{"use strict";var r=n(6330),a=TypeError;e.exports=function(e,t){if(!delete e[t])throw a("Cannot delete property "+r(t)+" of "+r(e))}},9781:(e,t,n)=>{var r=n(7293);e.exports=!r((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},4154:e=>{var t="object"==typeof document&&document.all,n=void 0===t&&void 0!==t;e.exports={all:t,IS_HTMLDDA:n}},317:(e,t,n)=>{var r=n(7854),a=n(111),o=r.document,i=a(o)&&a(o.createElement);e.exports=function(e){return i?o.createElement(e):{}}},7207:e=>{var t=TypeError;e.exports=function(e){if(e>9007199254740991)throw t("Maximum allowed index exceeded");return e}},8324:e=>{e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},8509:(e,t,n)=>{var r=n(317)("span").classList,a=r&&r.constructor&&r.constructor.prototype;e.exports=a===Object.prototype?void 0:a},8886:(e,t,n)=>{var r=n(8113).match(/firefox\/(\d+)/i);e.exports=!!r&&+r[1]},7871:(e,t,n)=>{var r=n(3823),a=n(5268);e.exports=!r&&!a&&"object"==typeof window&&"object"==typeof document},3823:e=>{e.exports="object"==typeof Deno&&Deno&&"object"==typeof Deno.version},256:(e,t,n)=>{var r=n(8113);e.exports=/MSIE|Trident/.test(r)},1528:(e,t,n)=>{var r=n(8113);e.exports=/ipad|iphone|ipod/i.test(r)&&"undefined"!=typeof Pebble},6833:(e,t,n)=>{var r=n(8113);e.exports=/(?:ipad|iphone|ipod).*applewebkit/i.test(r)},5268:(e,t,n)=>{var r=n(4326);e.exports="undefined"!=typeof process&&"process"==r(process)},1036:(e,t,n)=>{var r=n(8113);e.exports=/web0s(?!.*chrome)/i.test(r)},8113:e=>{e.exports="undefined"!=typeof navigator&&String(navigator.userAgent)||""},7392:(e,t,n)=>{var r,a,o=n(7854),i=n(8113),s=o.process,c=o.Deno,u=s&&s.versions||c&&c.version,p=u&&u.v8;p&&(a=(r=p.split("."))[0]>0&&r[0]<4?1:+(r[0]+r[1])),!a&&i&&(!(r=i.match(/Edge\/(\d+)/))||r[1]>=74)&&(r=i.match(/Chrome\/(\d+)/))&&(a=+r[1]),e.exports=a},8008:(e,t,n)=>{var r=n(8113).match(/AppleWebKit\/(\d+)\./);e.exports=!!r&&+r[1]},748:e=>{e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},2109:(e,t,n)=>{var r=n(7854),a=n(1236).f,o=n(8880),i=n(8052),s=n(3072),c=n(9920),u=n(4705);e.exports=function(e,t){var n,p,l,d,f,m=e.target,v=e.global,h=e.stat;if(n=v?r:h?r[m]||s(m,{}):(r[m]||{}).prototype)for(p in t){if(d=t[p],l=e.dontCallGetSet?(f=a(n,p))&&f.value:n[p],!u(v?p:m+(h?".":"#")+p,e.forced)&&void 0!==l){if(typeof d==typeof l)continue;c(d,l)}(e.sham||l&&l.sham)&&o(d,"sham",!0),i(n,p,d,e)}}},7293:e=>{e.exports=function(e){try{return!!e()}catch(e){return!0}}},7007:(e,t,n)=>{"use strict";n(4916);var r=n(1470),a=n(8052),o=n(2261),i=n(7293),s=n(5112),c=n(8880),u=s("species"),p=RegExp.prototype;e.exports=function(e,t,n,l){var d=s(e),f=!i((function(){var t={};return t[d]=function(){return 7},7!=""[e](t)})),m=f&&!i((function(){var t=!1,n=/a/;return"split"===e&&((n={}).constructor={},n.constructor[u]=function(){return n},n.flags="",n[d]=/./[d]),n.exec=function(){return t=!0,null},n[d](""),!t}));if(!f||!m||n){var v=r(/./[d]),h=t(d,""[e],(function(e,t,n,a,i){var s=r(e),c=t.exec;return c===o||c===p.exec?f&&!i?{done:!0,value:v(t,n,a)}:{done:!0,value:s(n,t,a)}:{done:!1}}));a(String.prototype,e,h[0]),a(p,d,h[1])}l&&c(p[d],"sham",!0)}},2104:(e,t,n)=>{var r=n(4374),a=Function.prototype,o=a.apply,i=a.call;e.exports="object"==typeof Reflect&&Reflect.apply||(r?i.bind(o):function(){return i.apply(o,arguments)})},9974:(e,t,n)=>{var r=n(1470),a=n(9662),o=n(4374),i=r(r.bind);e.exports=function(e,t){return a(e),void 0===t?e:o?i(e,t):function(){return e.apply(t,arguments)}}},4374:(e,t,n)=>{var r=n(7293);e.exports=!r((function(){var e=function(){}.bind();return"function"!=typeof e||e.hasOwnProperty("prototype")}))},7065:(e,t,n)=>{"use strict";var r=n(1702),a=n(9662),o=n(111),i=n(2597),s=n(206),c=n(4374),u=Function,p=r([].concat),l=r([].join),d={},f=function(e,t,n){if(!i(d,t)){for(var r=[],a=0;a{var r=n(4374),a=Function.prototype.call;e.exports=r?a.bind(a):function(){return a.apply(a,arguments)}},6530:(e,t,n)=>{var r=n(9781),a=n(2597),o=Function.prototype,i=r&&Object.getOwnPropertyDescriptor,s=a(o,"name"),c=s&&"something"===function(){}.name,u=s&&(!r||r&&i(o,"name").configurable);e.exports={EXISTS:s,PROPER:c,CONFIGURABLE:u}},5668:(e,t,n)=>{var r=n(1702),a=n(9662);e.exports=function(e,t,n){try{return r(a(Object.getOwnPropertyDescriptor(e,t)[n]))}catch(e){}}},1470:(e,t,n)=>{var r=n(4326),a=n(1702);e.exports=function(e){if("Function"===r(e))return a(e)}},1702:(e,t,n)=>{var r=n(4374),a=Function.prototype,o=a.call,i=r&&a.bind.bind(o,o);e.exports=r?i:function(e){return function(){return o.apply(e,arguments)}}},5005:(e,t,n)=>{var r=n(7854),a=n(614),o=function(e){return a(e)?e:void 0};e.exports=function(e,t){return arguments.length<2?o(r[e]):r[e]&&r[e][t]}},1246:(e,t,n)=>{var r=n(648),a=n(8173),o=n(8554),i=n(7497),s=n(5112)("iterator");e.exports=function(e){if(!o(e))return a(e,s)||a(e,"@@iterator")||i[r(e)]}},4121:(e,t,n)=>{var r=n(6916),a=n(9662),o=n(9670),i=n(6330),s=n(1246),c=TypeError;e.exports=function(e,t){var n=arguments.length<2?s(e):t;if(a(n))return o(r(n,e));throw c(i(e)+" is not iterable")}},8044:(e,t,n)=>{var r=n(1702),a=n(3157),o=n(614),i=n(4326),s=n(1340),c=r([].push);e.exports=function(e){if(o(e))return e;if(a(e)){for(var t=e.length,n=[],r=0;r{var r=n(9662),a=n(8554);e.exports=function(e,t){var n=e[t];return a(n)?void 0:r(n)}},647:(e,t,n)=>{var r=n(1702),a=n(7908),o=Math.floor,i=r("".charAt),s=r("".replace),c=r("".slice),u=/\$([$&'`]|\d{1,2}|<[^>]*>)/g,p=/\$([$&'`]|\d{1,2})/g;e.exports=function(e,t,n,r,l,d){var f=n+e.length,m=r.length,v=p;return void 0!==l&&(l=a(l),v=u),s(d,v,(function(a,s){var u;switch(i(s,0)){case"$":return"$";case"&":return e;case"`":return c(t,0,n);case"'":return c(t,f);case"<":u=l[c(s,1,-1)];break;default:var p=+s;if(0===p)return a;if(p>m){var d=o(p/10);return 0===d?a:d<=m?void 0===r[d-1]?i(s,1):r[d-1]+i(s,1):a}u=r[p-1]}return void 0===u?"":u}))}},7854:e=>{var t=function(e){return e&&e.Math==Math&&e};e.exports=t("object"==typeof globalThis&&globalThis)||t("object"==typeof window&&window)||t("object"==typeof self&&self)||t("object"==typeof global&&global)||function(){return this}()||Function("return this")()},2597:(e,t,n)=>{var r=n(1702),a=n(7908),o=r({}.hasOwnProperty);e.exports=Object.hasOwn||function(e,t){return o(a(e),t)}},3501:e=>{e.exports={}},842:e=>{e.exports=function(e,t){try{1==arguments.length?console.error(e):console.error(e,t)}catch(e){}}},490:(e,t,n)=>{var r=n(5005);e.exports=r("document","documentElement")},4664:(e,t,n)=>{var r=n(9781),a=n(7293),o=n(317);e.exports=!r&&!a((function(){return 7!=Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},8361:(e,t,n)=>{var r=n(1702),a=n(7293),o=n(4326),i=Object,s=r("".split);e.exports=a((function(){return!i("z").propertyIsEnumerable(0)}))?function(e){return"String"==o(e)?s(e,""):i(e)}:i},9587:(e,t,n)=>{var r=n(614),a=n(111),o=n(7674);e.exports=function(e,t,n){var i,s;return o&&r(i=t.constructor)&&i!==n&&a(s=i.prototype)&&s!==n.prototype&&o(e,s),e}},2788:(e,t,n)=>{var r=n(1702),a=n(614),o=n(5465),i=r(Function.toString);a(o.inspectSource)||(o.inspectSource=function(e){return i(e)}),e.exports=o.inspectSource},9909:(e,t,n)=>{var r,a,o,i=n(4811),s=n(7854),c=n(111),u=n(8880),p=n(2597),l=n(5465),d=n(6200),f=n(3501),m="Object already initialized",v=s.TypeError,h=s.WeakMap;if(i||l.state){var b=l.state||(l.state=new h);b.get=b.get,b.has=b.has,b.set=b.set,r=function(e,t){if(b.has(e))throw v(m);return t.facade=e,b.set(e,t),t},a=function(e){return b.get(e)||{}},o=function(e){return b.has(e)}}else{var g=d("state");f[g]=!0,r=function(e,t){if(p(e,g))throw v(m);return t.facade=e,u(e,g,t),t},a=function(e){return p(e,g)?e[g]:{}},o=function(e){return p(e,g)}}e.exports={set:r,get:a,has:o,enforce:function(e){return o(e)?a(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!c(t)||(n=a(t)).type!==e)throw v("Incompatible receiver, "+e+" required");return n}}}},7659:(e,t,n)=>{var r=n(5112),a=n(7497),o=r("iterator"),i=Array.prototype;e.exports=function(e){return void 0!==e&&(a.Array===e||i[o]===e)}},3157:(e,t,n)=>{var r=n(4326);e.exports=Array.isArray||function(e){return"Array"==r(e)}},614:(e,t,n)=>{var r=n(4154),a=r.all;e.exports=r.IS_HTMLDDA?function(e){return"function"==typeof e||e===a}:function(e){return"function"==typeof e}},4411:(e,t,n)=>{var r=n(1702),a=n(7293),o=n(614),i=n(648),s=n(5005),c=n(2788),u=function(){},p=[],l=s("Reflect","construct"),d=/^\s*(?:class|function)\b/,f=r(d.exec),m=!d.exec(u),v=function(e){if(!o(e))return!1;try{return l(u,p,e),!0}catch(e){return!1}},h=function(e){if(!o(e))return!1;switch(i(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return m||!!f(d,c(e))}catch(e){return!0}};h.sham=!0,e.exports=!l||a((function(){var e;return v(v.call)||!v(Object)||!v((function(){e=!0}))||e}))?h:v},4705:(e,t,n)=>{var r=n(7293),a=n(614),o=/#|\.prototype\./,i=function(e,t){var n=c[s(e)];return n==p||n!=u&&(a(t)?r(t):!!t)},s=i.normalize=function(e){return String(e).replace(o,".").toLowerCase()},c=i.data={},u=i.NATIVE="N",p=i.POLYFILL="P";e.exports=i},5988:(e,t,n)=>{var r=n(111),a=Math.floor;e.exports=Number.isInteger||function(e){return!r(e)&&isFinite(e)&&a(e)===e}},8554:e=>{e.exports=function(e){return null==e}},111:(e,t,n)=>{var r=n(614),a=n(4154),o=a.all;e.exports=a.IS_HTMLDDA?function(e){return"object"==typeof e?null!==e:r(e)||e===o}:function(e){return"object"==typeof e?null!==e:r(e)}},1913:e=>{e.exports=!1},7850:(e,t,n)=>{var r=n(111),a=n(4326),o=n(5112)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[o])?!!t:"RegExp"==a(e))}},2190:(e,t,n)=>{var r=n(5005),a=n(614),o=n(7976),i=n(3307),s=Object;e.exports=i?function(e){return"symbol"==typeof e}:function(e){var t=r("Symbol");return a(t)&&o(t.prototype,s(e))}},408:(e,t,n)=>{var r=n(9974),a=n(6916),o=n(9670),i=n(6330),s=n(7659),c=n(6244),u=n(7976),p=n(4121),l=n(1246),d=n(9212),f=TypeError,m=function(e,t){this.stopped=e,this.result=t},v=m.prototype;e.exports=function(e,t,n){var h,b,g,y,x,w,_,S=n&&n.that,k=!(!n||!n.AS_ENTRIES),E=!(!n||!n.IS_RECORD),A=!(!n||!n.IS_ITERATOR),j=!(!n||!n.INTERRUPTED),O=r(t,S),C=function(e){return h&&d(h,"normal",e),new m(!0,e)},R=function(e){return k?(o(e),j?O(e[0],e[1],C):O(e[0],e[1])):j?O(e,C):O(e)};if(E)h=e.iterator;else if(A)h=e;else{if(!(b=l(e)))throw f(i(e)+" is not iterable");if(s(b)){for(g=0,y=c(e);y>g;g++)if((x=R(e[g]))&&u(v,x))return x;return new m(!1)}h=p(e,b)}for(w=E?e.next:h.next;!(_=a(w,h)).done;){try{x=R(_.value)}catch(e){d(h,"throw",e)}if("object"==typeof x&&x&&u(v,x))return x}return new m(!1)}},9212:(e,t,n)=>{var r=n(6916),a=n(9670),o=n(8173);e.exports=function(e,t,n){var i,s;a(e);try{if(!(i=o(e,"return"))){if("throw"===t)throw n;return n}i=r(i,e)}catch(e){s=!0,i=e}if("throw"===t)throw n;if(s)throw i;return a(i),n}},3061:(e,t,n)=>{"use strict";var r=n(3383).IteratorPrototype,a=n(30),o=n(9114),i=n(8003),s=n(7497),c=function(){return this};e.exports=function(e,t,n,u){var p=t+" Iterator";return e.prototype=a(r,{next:o(+!u,n)}),i(e,p,!1,!0),s[p]=c,e}},1656:(e,t,n)=>{"use strict";var r=n(2109),a=n(6916),o=n(1913),i=n(6530),s=n(614),c=n(3061),u=n(9518),p=n(7674),l=n(8003),d=n(8880),f=n(8052),m=n(5112),v=n(7497),h=n(3383),b=i.PROPER,g=i.CONFIGURABLE,y=h.IteratorPrototype,x=h.BUGGY_SAFARI_ITERATORS,w=m("iterator"),_="keys",S="values",k="entries",E=function(){return this};e.exports=function(e,t,n,i,m,h,A){c(n,t,i);var j,O,C,R=function(e){if(e===m&&N)return N;if(!x&&e in P)return P[e];switch(e){case _:case S:case k:return function(){return new n(this,e)}}return function(){return new n(this)}},T=t+" Iterator",I=!1,P=e.prototype,U=P[w]||P["@@iterator"]||m&&P[m],N=!x&&U||R(m),D="Array"==t&&P.entries||U;if(D&&(j=u(D.call(new e)))!==Object.prototype&&j.next&&(o||u(j)===y||(p?p(j,y):s(j[w])||f(j,w,E)),l(j,T,!0,!0),o&&(v[T]=E)),b&&m==S&&U&&U.name!==S&&(!o&&g?d(P,"name",S):(I=!0,N=function(){return a(U,this)})),m)if(O={values:R(S),keys:h?N:R(_),entries:R(k)},A)for(C in O)(x||I||!(C in P))&&f(P,C,O[C]);else r({target:t,proto:!0,forced:x||I},O);return o&&!A||P[w]===N||f(P,w,N,{name:m}),v[t]=N,O}},3383:(e,t,n)=>{"use strict";var r,a,o,i=n(7293),s=n(614),c=n(111),u=n(30),p=n(9518),l=n(8052),d=n(5112),f=n(1913),m=d("iterator"),v=!1;[].keys&&("next"in(o=[].keys())?(a=p(p(o)))!==Object.prototype&&(r=a):v=!0),!c(r)||i((function(){var e={};return r[m].call(e)!==e}))?r={}:f&&(r=u(r)),s(r[m])||l(r,m,(function(){return this})),e.exports={IteratorPrototype:r,BUGGY_SAFARI_ITERATORS:v}},7497:e=>{e.exports={}},6244:(e,t,n)=>{var r=n(7466);e.exports=function(e){return r(e.length)}},6339:(e,t,n)=>{var r=n(1702),a=n(7293),o=n(614),i=n(2597),s=n(9781),c=n(6530).CONFIGURABLE,u=n(2788),p=n(9909),l=p.enforce,d=p.get,f=String,m=Object.defineProperty,v=r("".slice),h=r("".replace),b=r([].join),g=s&&!a((function(){return 8!==m((function(){}),"length",{value:8}).length})),y=String(String).split("String"),x=e.exports=function(e,t,n){"Symbol("===v(f(t),0,7)&&(t="["+h(f(t),/^Symbol\(([^)]*)\)/,"$1")+"]"),n&&n.getter&&(t="get "+t),n&&n.setter&&(t="set "+t),(!i(e,"name")||c&&e.name!==t)&&(s?m(e,"name",{value:t,configurable:!0}):e.name=t),g&&n&&i(n,"arity")&&e.length!==n.arity&&m(e,"length",{value:n.arity});try{n&&i(n,"constructor")&&n.constructor?s&&m(e,"prototype",{writable:!1}):e.prototype&&(e.prototype=void 0)}catch(e){}var r=l(e);return i(r,"source")||(r.source=b(y,"string"==typeof t?t:"")),e};Function.prototype.toString=x((function(){return o(this)&&d(this).source||u(this)}),"toString")},4758:e=>{var t=Math.ceil,n=Math.floor;e.exports=Math.trunc||function(e){var r=+e;return(r>0?n:t)(r)}},5948:(e,t,n)=>{var r,a,o,i,s,c=n(7854),u=n(9974),p=n(1236).f,l=n(261).set,d=n(8572),f=n(6833),m=n(1528),v=n(1036),h=n(5268),b=c.MutationObserver||c.WebKitMutationObserver,g=c.document,y=c.process,x=c.Promise,w=p(c,"queueMicrotask"),_=w&&w.value;if(!_){var S=new d,k=function(){var e,t;for(h&&(e=y.domain)&&e.exit();t=S.get();)try{t()}catch(e){throw S.head&&r(),e}e&&e.enter()};f||h||v||!b||!g?!m&&x&&x.resolve?((i=x.resolve(void 0)).constructor=x,s=u(i.then,i),r=function(){s(k)}):h?r=function(){y.nextTick(k)}:(l=u(l,c),r=function(){l(k)}):(a=!0,o=g.createTextNode(""),new b(k).observe(o,{characterData:!0}),r=function(){o.data=a=!a}),_=function(e){S.head||r(),S.add(e)}}e.exports=_},8523:(e,t,n)=>{"use strict";var r=n(9662),a=TypeError,o=function(e){var t,n;this.promise=new e((function(e,r){if(void 0!==t||void 0!==n)throw a("Bad Promise constructor");t=e,n=r})),this.resolve=r(t),this.reject=r(n)};e.exports.f=function(e){return new o(e)}},3929:(e,t,n)=>{var r=n(7850),a=TypeError;e.exports=function(e){if(r(e))throw a("The method doesn't accept regular expressions");return e}},2814:(e,t,n)=>{var r=n(7854),a=n(7293),o=n(1702),i=n(1340),s=n(3111).trim,c=n(1361),u=o("".charAt),p=r.parseFloat,l=r.Symbol,d=l&&l.iterator,f=1/p(c+"-0")!=-1/0||d&&!a((function(){p(Object(d))}));e.exports=f?function(e){var t=s(i(e)),n=p(t);return 0===n&&"-"==u(t,0)?-0:n}:p},3009:(e,t,n)=>{var r=n(7854),a=n(7293),o=n(1702),i=n(1340),s=n(3111).trim,c=n(1361),u=r.parseInt,p=r.Symbol,l=p&&p.iterator,d=/^[+-]?0x/i,f=o(d.exec),m=8!==u(c+"08")||22!==u(c+"0x16")||l&&!a((function(){u(Object(l))}));e.exports=m?function(e,t){var n=s(i(e));return u(n,t>>>0||(f(d,n)?16:10))}:u},30:(e,t,n)=>{var r,a=n(9670),o=n(6048),i=n(748),s=n(3501),c=n(490),u=n(317),p=n(6200)("IE_PROTO"),l=function(){},d=function(e){return"
\ No newline at end of file +Anvil
\ No newline at end of file diff --git a/striker-ui/out/config.html b/striker-ui/out/config.html index 5f32e2a5..a81860af 100644 --- a/striker-ui/out/config.html +++ b/striker-ui/out/config.html @@ -1 +1 @@ -Loading...
Install target
Configure striker peers
Configure striker peers
Inbound connections

    No inbound connections found.
Peer connections

    No peer connections found.
Manage changed SSH keys
Manage changed SSH keys
The identity of the following targets have unexpectedly changed.
If you haven't rebuilt the listed targets, then you could be experiencing a
"Man In The Middle"
attack. Please verify the targets have changed for a known reason before proceeding to remove the broken keys.

Host name

IP address


    No conflicting keys found.
Manage users
Manage users

    No users found.
\ No newline at end of file +Loading...
Install target
Configure striker peers
Configure striker peers
Inbound connections

    No inbound connections found.
Peer connections

    No peer connections found.
Manage changed SSH keys
Manage changed SSH keys
The identity of the following targets have unexpectedly changed.
If you haven't rebuilt the listed targets, then you could be experiencing a
"Man In The Middle"
attack. Please verify the targets have changed for a known reason before proceeding to remove the broken keys.

Host name

IP address


    No conflicting keys found.
Manage users
Manage users

    No users found.
\ No newline at end of file diff --git a/striker-ui/out/file-manager.html b/striker-ui/out/file-manager.html index b338b766..a37506a3 100644 --- a/striker-ui/out/file-manager.html +++ b/striker-ui/out/file-manager.html @@ -1 +1 @@ -File Manager

Files

    \ No newline at end of file +File Manager

    Files

    \ No newline at end of file diff --git a/striker-ui/out/index.html b/striker-ui/out/index.html index 6c714e9b..74aa61c7 100644 --- a/striker-ui/out/index.html +++ b/striker-ui/out/index.html @@ -1 +1 @@ -Dashboard
    \ No newline at end of file +Dashboard
    \ No newline at end of file diff --git a/striker-ui/out/init.html b/striker-ui/out/init.html index d00a876b..5bed8ae1 100644 --- a/striker-ui/out/init.html +++ b/striker-ui/out/init.html @@ -1 +1 @@ -

    Loading...

    Placeholder
    Uncheck to skip domain and host name pattern validation.
    \ No newline at end of file +

    Loading...

    Placeholder
    Uncheck to skip domain and host name pattern validation.
    \ No newline at end of file diff --git a/striker-ui/out/login.html b/striker-ui/out/login.html index ddc837c2..7bbeea8d 100644 --- a/striker-ui/out/login.html +++ b/striker-ui/out/login.html @@ -1 +1 @@ -Login
    Placeholder
    \ No newline at end of file +Login
    Placeholder
    \ No newline at end of file diff --git a/striker-ui/out/manage-element.html b/striker-ui/out/manage-element.html index fa79929c..86e987b5 100644 --- a/striker-ui/out/manage-element.html +++ b/striker-ui/out/manage-element.html @@ -1 +1 @@ -Loading
    \ No newline at end of file +Loading
    \ No newline at end of file diff --git a/striker-ui/out/server.html b/striker-ui/out/server.html index 8526c361..9966448e 100644 --- a/striker-ui/out/server.html +++ b/striker-ui/out/server.html @@ -1 +1 @@ -
    \ No newline at end of file +
    \ No newline at end of file