diff --git a/package.json b/package.json index 6f96a109f..b5d02e532 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ "@mui/icons-material": "^5.6.2", "@mui/material": "^5.6.3", "@reduxjs/toolkit": "^1.8.1", + "@rjsf/core": "^5.0.0-beta.10", + "@rjsf/mui": "^5.0.0-beta.10", + "@rjsf/utils": "^5.0.0-beta.10", + "@rjsf/validator-ajv6": "^5.0.0-beta.10", "@sentry/react": "^7.5.0", "@sentry/tracing": "^7.5.0", "@testing-library/jest-dom": "^5.16.4", diff --git a/src/App.jsx b/src/App.jsx index 6cfb88d47..af64e68b6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -22,6 +22,7 @@ import Header from "./features/navigation/Header"; import Healthcheck from "./features/healthcheck/Healthcheck"; import Home from "./features/home/Main"; import Packages from "./features/packages/Packages"; +import Package from "./features/package/Package"; import Startup from "./features/startup/Startup"; import Setup from "./features/setup/Setup"; @@ -43,6 +44,7 @@ import { selectPassed } from "./features/healthcheck/healthcheckSlice"; import { selectCanClaim } from "./features/tabGovernor/tabGovernorSlice"; + function App({ adb, handleAdbConnectClick, @@ -122,6 +124,12 @@ function App({ render={isConnected && adb} /> + : null} + path="package/:repo/:packageSlug" + render={isConnected && adb} + /> + : null} path="startup" diff --git a/src/app/store.js b/src/app/store.js index 340ecbe73..e50bb1de6 100644 --- a/src/app/store.js +++ b/src/app/store.js @@ -5,6 +5,7 @@ import deviceReducer from "../features/device/deviceSlice"; import donateReducer from "../features/donate/donateSlice"; import healthcheckReducer from "../features/healthcheck/healthcheckSlice"; import packagesReducer from "../features/packages/packagesSlice"; +import packageReducer from "../features/package/packageSlice"; import rootReducer from "../features/root/rootSlice"; import settingsReducer from "../features/settings/settingsSlice"; import startupReducer from "../features/startup/startupSlice"; @@ -17,6 +18,7 @@ export const store = configureStore({ donate: donateReducer, healthcheck: healthcheckReducer, packages: packagesReducer, + package: packageReducer, root: rootReducer, settings: settingsReducer, startup: startupReducer, diff --git a/src/features/package/Package.jsx b/src/features/package/Package.jsx new file mode 100644 index 000000000..e30313343 --- /dev/null +++ b/src/features/package/Package.jsx @@ -0,0 +1,136 @@ +import PropTypes from "prop-types"; +import React, { + useCallback, + useEffect, +} from "react"; +import { + useDispatch, + useSelector, +} from "react-redux"; +import { useTranslation } from "react-i18next"; +import { useParams } from "react-router-dom"; + +import validator from "@rjsf/validator-ajv6"; +import Form from "@rjsf/mui"; + +import Alert from "@mui/material/Alert"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Paper from "@mui/material/Paper"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; + +import { + fetchPackage, + fetchConfig, + reset, + selectConfig, + selectDescription, + selectError, + selectFetched, + selectInstalled, + selectLoading, + selectName, + selectSchema, + selectWriting, + writeConfig, +} from "./packageSlice"; + +import { selectPassed } from "../healthcheck/healthcheckSlice"; +import Spinner from "../loading/Spinner"; + +export default function Package({ adb }) { + const { t } = useTranslation("package"); + const dispatch = useDispatch(); + + let { packageSlug } = useParams(); + + const healthchecksPassed = useSelector(selectPassed); + + const packageName = useSelector(selectName); + const description = useSelector(selectDescription); + const installed = useSelector(selectInstalled); + + const fetched = useSelector(selectFetched); + const config = useSelector(selectConfig); + const schema = useSelector(selectSchema); + + const loading = useSelector(selectLoading); + const writing = useSelector(selectWriting); + const error = useSelector(selectError); + + /** + * Fetch package details if healthchecks passed and dtails are not yet + * set for the selected package. + */ + useEffect(() => { + if (!fetched && healthchecksPassed) { + dispatch(fetchPackage({ + adb, + name: packageSlug, + })); + } + }, [adb, dispatch, fetched, healthchecksPassed, packageSlug]); + + useEffect(() => { + if(packageName !== packageSlug) { + dispatch(reset()); + } + }, [dispatch, packageName, packageSlug]); + + // Fetch config and schema if package is installed + useEffect(() => { + if(installed) { + dispatch(fetchConfig(adb)); + } + }, [adb, dispatch, installed]); + + const saveConfig = useCallback(({ formData }) => { + dispatch(writeConfig({ + adb, + config: formData, + })); + }, [adb, dispatch]); + + return ( + + + + + {t("detailsFor", { name: packageSlug })} + + + {loading && + } + + + {description} + + + {schema && +
+ +
} + + {error && + + {error} + } +
+
+
+ ); +} + +Package.propTypes = { adb: PropTypes.shape().isRequired }; diff --git a/src/features/package/packageSlice.js b/src/features/package/packageSlice.js new file mode 100644 index 000000000..88d893861 --- /dev/null +++ b/src/features/package/packageSlice.js @@ -0,0 +1,102 @@ +import { + createAsyncThunk, + createSlice, +} from "@reduxjs/toolkit"; + +const initialState = { + loading: true, + fetched: false, + + name: null, + description: null, + installed: false, + + schema: null, + config: null, + + writing: false, + error: null, +}; + +export const fetchPackage = createAsyncThunk( + "package/fetchPackage", + async ({ + adb, + name, + }) => { + return adb.getPackageDetails(name); + } +); + +export const fetchConfig = createAsyncThunk( + "package/fetchConfig", + async (adb, thunk) => { + const name = thunk.getState().package.name; + return adb.getPackageConfig(name); + } +); + +export const writeConfig = createAsyncThunk( + "package/writeConfig", + async ({ + adb, + config, + }, thunk) => { + const name = thunk.getState().package.name; + const stringified = JSON.stringify(config, null, " "); + await adb.writePackageConfig(name, stringified); + + return config; + } +); + +export const packageSlice = createSlice({ + name: "package", + initialState, + reducers: { reset: () => initialState }, + extraReducers: (builder) => { + builder + .addCase(fetchPackage.pending, (state) => { + state.loading = true; + }) + .addCase(fetchPackage.fulfilled, (state, action) => { + state.loading = false; + state.fetched = true; + + state.name = action.payload.name; + state.description = action.payload.description; + state.installed = action.payload.installed; + }).addCase(fetchConfig.pending, (state, action) => { + state.config = null; + state.schema = null; + }).addCase(fetchConfig.fulfilled, (state, action) => { + state.config = action.payload.config; + state.schema = action.payload.schema; + }).addCase(writeConfig.pending, (state, action) => { + state.writing = true; + }).addCase(writeConfig.fulfilled, (state, action) => { + state.config = action.payload; + state.writing = false; + }).addCase(writeConfig.rejected, (state, action) => { + state.error = action.error.message; + }); + }, + +}); + +export const { reset } = packageSlice.actions; + +export const selectFetched = (state) => state.package.fetched; + +export const selectName = (state) => state.package.name; +export const selectDescription = (state) => state.package.description; +export const selectInstalled = (state) => state.package.installed; + +export const selectWriting = (state) => state.package.writing; +export const selectError = (state) => state.package.error; +export const selectLoading = (state) => state.package.loading; + +export const selectConfig = (state) => state.package.config; +export const selectSchema = (state) => state.package.schema; + +export default packageSlice.reducer; diff --git a/src/features/packages/Packages.jsx b/src/features/packages/Packages.jsx index 15fe7bb01..2255d9529 100644 --- a/src/features/packages/Packages.jsx +++ b/src/features/packages/Packages.jsx @@ -11,6 +11,8 @@ import { } from "react-redux"; import { useTranslation } from "react-i18next"; +import { Link as RouterLink } from "react-router-dom"; + import Box from "@mui/material/Box"; import DeleteIcon from "@mui/icons-material/Delete"; import DownloadIcon from "@mui/icons-material/Download"; @@ -31,6 +33,7 @@ import TableRow from "@mui/material/TableRow"; import TextField from "@mui/material/TextField"; import Typography from "@mui/material/Typography"; import Paper from "@mui/material/Paper"; +import { styled } from "@mui/material/styles"; import ReactGA from "react-ga4"; @@ -66,6 +69,16 @@ export default function Packages({ adb }) { const tableEl = useRef(); const scrollListenerId = useRef(); + const StyledRouterLink = styled(RouterLink)(() => ({ + "&": { + whiteSpace: "nowrap", + color: "#1676c7", + textDecoration: "underline", + textDecorationColor: "rgba(22, 118, 199, 0.4)", + }, + "&:hover": { textDecorationColor: "inherit" }, + })); + const dispatch = useDispatch(); const fetched = useSelector(selectFetched); @@ -172,11 +185,14 @@ export default function Packages({ adb }) { }, [dispatch]); const rows = renderRows.map((item) => { - console.log(item.details.homepage); return ( - {item.name} + + + {item.name} + + {!hasOpkgBinary && diff --git a/src/index.jsx b/src/index.jsx index 1e2a35a63..1d0bde40f 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -63,6 +63,7 @@ for(const lang of languageKeys) { healthcheck: require(`./translations/${lang}/healthcheck.json`), home: require(`./translations/${lang}/home.json`), navigation: require(`./translations/${lang}/navigation.json`), + package: require(`./translations/${lang}/package.json`), packages: require(`./translations/${lang}/packages.json`), root: require(`./translations/${lang}/root.json`), settings: require(`./translations/${lang}/settings.json`), diff --git a/src/translations/de/package.json b/src/translations/de/package.json new file mode 100644 index 000000000..28976f3d8 --- /dev/null +++ b/src/translations/de/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for:" +} \ No newline at end of file diff --git a/src/translations/el/package.json b/src/translations/el/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/el/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/en/package.json b/src/translations/en/package.json new file mode 100644 index 000000000..1c49510b4 --- /dev/null +++ b/src/translations/en/package.json @@ -0,0 +1,5 @@ +{ + "detailsFor": "Details for {{name}}", + "submit": "Save settings", + "loading": "Loading package details..." +} \ No newline at end of file diff --git a/src/translations/es/package.json b/src/translations/es/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/es/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/fr/package.json b/src/translations/fr/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/fr/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/it/package.json b/src/translations/it/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/it/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/nl/package.json b/src/translations/nl/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/nl/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/pt-BR/package.json b/src/translations/pt-BR/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/pt-BR/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/pt-PT/package.json b/src/translations/pt-PT/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/pt-PT/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/ru/package.json b/src/translations/ru/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/ru/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/sk/package.json b/src/translations/sk/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/sk/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/sv/package.json b/src/translations/sv/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/sv/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/tr/package.json b/src/translations/tr/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/tr/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/uk/package.json b/src/translations/uk/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/uk/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/vi/package.json b/src/translations/vi/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/vi/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/zh-CN/package.json b/src/translations/zh-CN/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/zh-CN/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/zh-TW/package.json b/src/translations/zh-TW/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/zh-TW/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/translations/zh/package.json b/src/translations/zh/package.json new file mode 100644 index 000000000..4aa0cf705 --- /dev/null +++ b/src/translations/zh/package.json @@ -0,0 +1,3 @@ +{ + "detailsFor": "Details for: {{name}}" +} \ No newline at end of file diff --git a/src/utils/AdbWrapper.js b/src/utils/AdbWrapper.js index 0a4201852..fdad5a133 100644 --- a/src/utils/AdbWrapper.js +++ b/src/utils/AdbWrapper.js @@ -36,6 +36,9 @@ export default class AdbWrapper { opkgConfigUrl: "http://repo.fpv.wtf/pigeon/wtfos-opkg-config_armv7-3.2.ipk", healthchecksUrl: "https://github.com/fpv-wtf/wtfos-healthchecks/releases/latest/download/healthchecks.tar.gz", healthchesksPath: "/tmp/healthchecks", + packageConfigPath: "/opt/etc/package-config", + packageConfigFile: "config.json", + packageConfigSchema: "schema.json", }; } @@ -223,8 +226,14 @@ export default class AdbWrapper { return packages; } - async getPackages() { + async getPackageDetails(name) { + const packages = await this.getPackages(); + const pkg = packages.find((pkg) => pkg.name === name); + + return pkg; + } + async getPackages() { let output = await this.executeCommand([ this.wtfos.bin.opkg, "list-installed", @@ -393,6 +402,72 @@ export default class AdbWrapper { return output.stdout; } + async getPackageConfig(name) { + const configPath = `${this.wtfos.packageConfigPath}/${name}/${this.wtfos.packageConfigFile}`; + const schemaPath = `${this.wtfos.packageConfigPath}/${name}/${this.wtfos.packageConfigSchema}`; + + try { + const config = await this.pullJsonFile(configPath); + const schema = await this.pullJsonFile(schemaPath); + + return { + config, + schema, + }; + } catch(e) { + console.log("Failed fetching package config", e); + } + + return { + config: null, + schema: null, + }; + } + + // Returns a JSON object from the contents of a file at the given path + async pullJsonFile(path) { + const data = await this.pullFile(path); + const string = new TextDecoder().decode(data); + const json = JSON.parse(string); + + return json; + } + + async pullFile(path) { + const sync = await this.adb.sync(); + const stream = await sync.read(path); + const reader = stream.getReader(); + + let data = new Buffer.from([]); + let notDone = true; + while(notDone) { + let { + done, + value, + } = await reader.read(); + + if(value) { + data = Buffer.concat([data, value]); + } + + notDone = !done; + } + + return data; + } + + async pushFile(path, blob) { + const stream = new WrapReadableStream(blob.stream()); + const sync = await this.adb.sync(); + await stream.pipeTo(sync.write(path)); + } + + async writePackageConfig(packageName, content) { + const path = `${this.wtfos.packageConfigPath}/${packageName}/${this.wtfos.packageConfigFile}`; + const blob = new Blob([content]); + await this.pushFile(path, blob); + } + async getProductInfo() { let output = await this.executeCommand([ "unrd product_type", @@ -588,11 +663,7 @@ export default class AdbWrapper { const buffer = Buffer.from(`GET ${this.wtfos.healthchecksUrl}?cachebust=${Math.random()}`); const response = await proxy.proxyRequest(buffer); const blob = await response.blob(); - const file = new File([blob], "healthchecks.tar.gz"); - - const stream = new WrapReadableStream(file.stream()); - const sync = await this.adb.sync(); - await stream.pipeTo(sync.write("/tmp/healthchecks.tar.gz")); + await this.pushFile("/tmp/healthchecks.tar.gz", blob); } catch(e) { statusCallback("ERROR: Failed fetching Healthchecks"); return; diff --git a/yarn.lock b/yarn.lock index b48887f6c..dd6ae2d84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1672,6 +1672,41 @@ redux-thunk "^2.4.1" reselect "^4.1.5" +"@rjsf/core@^5.0.0-beta.10": + version "5.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@rjsf/core/-/core-5.0.0-beta.10.tgz#ff8f5f7da2f8509ae4d169746efd5cc7633e78cf" + integrity sha512-L/ZqK3/3uXhRLcvfOX3uL3adiJ0i7deV/egUkwnJQvqv1bIn58vY4wfiJwqTc8seiAHtyPCvehWZtLthyVP9Rw== + dependencies: + lodash "^4.17.15" + lodash-es "^4.17.15" + nanoid "^3.3.4" + prop-types "^15.7.2" + +"@rjsf/mui@^5.0.0-beta.10": + version "5.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@rjsf/mui/-/mui-5.0.0-beta.10.tgz#df1e1bbca83acafe45cc0610987ed4c839a54c20" + integrity sha512-FtbGizbfrnxQc+yKbHUbJmbFWqDLldEquvCnnv6KVe/beoxhP2+/vsBAPP628TjkFn8Qe596us9NFfVK5tWtVQ== + +"@rjsf/utils@^5.0.0-beta.10": + version "5.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@rjsf/utils/-/utils-5.0.0-beta.10.tgz#61a32e1b085f756d00e35b72bfb84de2a0cbd1c9" + integrity sha512-cXXD9KHILJKk80ma69RN/53hxyPNxpJvyxEVXN1C9xg/VVhjrDTevTgvsfsVshqc8xTevbxVt6P8pm8ITG5QPw== + dependencies: + json-schema-merge-allof "^0.8.1" + jsonpointer "^5.0.1" + lodash "^4.17.15" + lodash-es "^4.17.15" + react-is "^18.2.0" + +"@rjsf/validator-ajv6@^5.0.0-beta.10": + version "5.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@rjsf/validator-ajv6/-/validator-ajv6-5.0.0-beta.10.tgz#dfb50f7da21f4486fda620fb868f7f026d844eec" + integrity sha512-Uw7F8JNFK/sU84gv4gRdS1NYOoeK2s9Mue10vne+F1sVHWDD6T4UL4oT7f1AWo2I8pQQ+r2spWQK5cflRfCZ9w== + dependencies: + ajv "^6.7.0" + lodash "^4.17.15" + lodash-es "^4.17.15" + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -2697,7 +2732,7 @@ ajv-keywords@^5.0.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.7.0: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3845,6 +3880,25 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" +compute-gcd@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/compute-gcd/-/compute-gcd-1.2.1.tgz#34d639f3825625e1357ce81f0e456a6249d8c77f" + integrity sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg== + dependencies: + validate.io-array "^1.0.3" + validate.io-function "^1.0.2" + validate.io-integer-array "^1.0.0" + +compute-lcm@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/compute-lcm/-/compute-lcm-1.1.2.tgz#9107c66b9dca28cefb22b4ab4545caac4034af23" + integrity sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ== + dependencies: + compute-gcd "^1.2.1" + validate.io-array "^1.0.3" + validate.io-function "^1.0.2" + validate.io-integer-array "^1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -7031,6 +7085,22 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-compare@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/json-schema-compare/-/json-schema-compare-0.2.2.tgz#dd601508335a90c7f4cfadb6b2e397225c908e56" + integrity sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ== + dependencies: + lodash "^4.17.4" + +json-schema-merge-allof@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz#ed2828cdd958616ff74f932830a26291789eaaf2" + integrity sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w== + dependencies: + compute-lcm "^1.1.2" + json-schema-compare "^0.2.2" + lodash "^4.17.20" + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -7082,6 +7152,11 @@ jsonpointer@^5.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072" integrity sha512-PNYZIdMjVIvVgDSYKTT63Y+KZ6IZvGRNNWcxwD+GNnUz1MKPfv30J8ueCjdwcN0nDx2SlshgyB7Oy0epAzVRRg== +jsonpointer@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" + integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== + "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz#6ab1e52c71dfc0c0707008a91729a9491fe9f76c" @@ -7221,6 +7296,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -7549,6 +7629,11 @@ nanoid@^3.3.1: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -8873,6 +8958,11 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.0.0.tgz#026f6c4a27dbe33bf4a35655b9e1327c4e55e3f5" integrity sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw== +react-is@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-redux@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.1.tgz#2bc029f5ada9b443107914c373a2750f6bc0f40c" @@ -10486,6 +10576,36 @@ v8flags@^2.1.1: dependencies: user-home "^1.1.1" +validate.io-array@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/validate.io-array/-/validate.io-array-1.0.6.tgz#5b5a2cafd8f8b85abb2f886ba153f2d93a27774d" + integrity sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg== + +validate.io-function@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/validate.io-function/-/validate.io-function-1.0.2.tgz#343a19802ed3b1968269c780e558e93411c0bad7" + integrity sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ== + +validate.io-integer-array@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz#2cabde033293a6bcbe063feafe91eaf46b13a089" + integrity sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA== + dependencies: + validate.io-array "^1.0.3" + validate.io-integer "^1.0.4" + +validate.io-integer@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/validate.io-integer/-/validate.io-integer-1.0.5.tgz#168496480b95be2247ec443f2233de4f89878068" + integrity sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ== + dependencies: + validate.io-number "^1.0.3" + +validate.io-number@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/validate.io-number/-/validate.io-number-1.0.3.tgz#f63ffeda248bf28a67a8d48e0e3b461a1665baf8" + integrity sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg== + validator@^13.7.0: version "13.7.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"