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"