SQL expression rewrite, Pagination, some frontend stuff

This commit is contained in:
2023-01-05 22:47:17 +01:00
parent 4bfd6754cf
commit 99bfd7e505
61 changed files with 1745 additions and 570 deletions

View File

@@ -11,7 +11,11 @@ import {LocaleContext} from "shared/locale";
import './res/adminlte.min.css';
// views
import View404 from "./views/404";
const Overview = lazy(() => import('./views/overview'));
const UserListView = lazy(() => import('./views/user-list'));
const GroupListView = lazy(() => import('./views/group-list'));
const EditGroupView = lazy(() => import('./views/group-edit'));
export default function AdminDashboard(props) {
@@ -22,14 +26,21 @@ export default function AdminDashboard(props) {
const {currentLocale, requestModules, translate: L} = useContext(LocaleContext);
const showDialog = useCallback((message, title, options=["Close"], onOption = null) => {
setDialog({ show: true, message: message, title: title, options: options, onOption: onOption });
}, []);
const hideDialog = useCallback(() => {
setDialog({show: false});
}, []);
const showDialog = useCallback((message, title, options=["Close"], onOption = null) => {
setDialog({
show: true, message:
message,
title: title,
options: options,
onOption: onOption,
onClose: hideDialog
});
}, []);
useEffect(() => {
requestModules(api, ["general", "admin"], currentLocale).then(data => {
if (!data.success) {
@@ -52,7 +63,12 @@ export default function AdminDashboard(props) {
<section className={"content"}>
<Suspense fallback={<div>{L("general.loading")}... </div>}>
<Routes>
<Route path={"/admin"} element={<Overview {...controlObj} />}/>
<Route path={"/admin/dashboard"} element={<Overview {...controlObj} />}/>
<Route path={"/admin/users"} element={<UserListView {...controlObj} />}/>
<Route path={"/admin/groups"} element={<GroupListView {...controlObj} />}/>
<Route path={"/admin/group/:groupId"} element={<EditGroupView {...controlObj} />}/>
<Route path={"*"} element={<View404 />} />
</Routes>
</Suspense>
{/*<Route exact={true} path={"/admin/users"}><UserOverview {...this.controlObj} /></Route>

View File

@@ -85,8 +85,6 @@ export default function App() {
*/
console.log(loaded, user, api.loggedIn);
if (!loaded) {
if (error) {
return <Alert severity={"error"} title={L("general.error_occurred")}>

View File

@@ -92,7 +92,9 @@ export default function Sidebar(props) {
<div className={"os-content"} style={{padding: "0px 0px", height: "100%", width: "100%"}}>
<div className="user-panel mt-3 pb-3 mb-3 d-flex">
<div className="info">
<a href="#" className="d-block">Logged in as: {api.user.name}</a>
<a href="#" className="d-block">{L("account.logged_in_as")}:&nbsp;
<Link to={"/admin/user/" + api.user.id}>{api.user.name}</Link>
</a>
</div>
</div>
<nav className={"mt-2"}>

View File

@@ -0,0 +1,5 @@
export default function View404(props) {
return <b>Not found</b>
}

View File

@@ -0,0 +1,35 @@
import {useCallback, useEffect, useState} from "react";
import {useNavigate, useParams} from "react-router-dom";
export default function EditGroupView(props) {
// const [groupId, setGroupId] = useState(props?.match?.groupId !== "new" ? parseInt(props.match.groupId) : null);
const { groupId } = useParams();
const navigate = useNavigate();
const [fetchGroup, setFetchGroup] = useState(groupId !== "new");
const [group, setGroup] = useState(null);
const onFetchGroup = useCallback((force = false) => {
if (force || fetchGroup) {
setFetchGroup(false);
props.api.getGroup(groupId).then(res => {
if (!res.success) {
props.showDialog(res.msg, "Error fetching group");
navigate("/admin/groups");
} else {
setGroup(res.group);
}
});
}
}, []);
useEffect(() => {
onFetchGroup();
}, []);
return <></>
}

View File

@@ -0,0 +1,80 @@
import {Link, useNavigate} from "react-router-dom";
import {useCallback, useContext, useEffect, useState} from "react";
import {LocaleContext} from "shared/locale";
import {DataColumn, DataTable, NumericColumn, StringColumn} from "shared/elements/data-table";
import {Button, IconButton} from "@material-ui/core";
import EditIcon from '@mui/icons-material/Edit';
import AddIcon from '@mui/icons-material/Add';
export default function GroupListView(props) {
const {translate: L, requestModules, currentLocale} = useContext(LocaleContext);
const navigate = useNavigate();
useEffect(() => {
requestModules(props.api, ["general", "account"], currentLocale).then(data => {
if (!data.success) {
alert(data.msg);
}
});
}, [currentLocale]);
const onFetchGroups = useCallback(async (page, count, orderBy, sortOrder) => {
let res = await props.api.fetchGroups(page, count, orderBy, sortOrder);
if (res.success) {
return Promise.resolve([res.groups, res.pagination]);
} else {
props.showAlert("Error fetching groups", res.msg);
return null;
}
}, []);
const actionColumn = (() => {
let column = new DataColumn(L("general.actions"), null, false);
column.renderData = (entry) => <>
<IconButton size={"small"} title={L("general.edit")} onClick={() => navigate("/admin/group/" + entry.id)}>
<EditIcon />
</IconButton>
</>
return column;
})();
const columnDefinitions = [
new NumericColumn(L("general.id"), "id"),
new StringColumn(L("group.name"), "name"),
new NumericColumn(L("group.member_count"), "memberCount"),
actionColumn,
];
return <>
<div className={"content-header"}>
<div className={"container-fluid"}>
<div className={"row mb-2"}>
<div className={"col-sm-6"}>
<h1 className={"m-0 text-dark"}>Users</h1>
</div>
<div className={"col-sm-6"}>
<ol className={"breadcrumb float-sm-right"}>
<li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li>
<li className="breadcrumb-item active">Groups</li>
</ol>
</div>
</div>
</div>
<div className={"content"}>
<div className={"container-fluid"}>
<Link to="/admin/group/new">
<Button variant={"outlined"} startIcon={<AddIcon />} size={"small"}>
{L("general.create_new")}
</Button>
</Link>
<DataTable className={"table table-striped"}
fetchData={onFetchGroups}
placeholder={"No groups to display"}
columns={columnDefinitions} />
</div>
</div>
</div>
</>
}

View File

@@ -1,9 +1,33 @@
import * as React from "react";
import {Link} from "react-router-dom";
import {format, getDaysInMonth} from "date-fns";
import {Collapse} from "react-collapse";
import {Bar} from "react-chartjs-2";
import {CircularProgress, Icon} from "@material-ui/core";
import {useCallback, useEffect, useState} from "react";
export default function Overview(props) {
const [fetchStats, setFetchStats] = useState(true);
const [stats, setStats] = useState(null);
const onFetchStats = useCallback((force = false) => {
if (force || fetchStats) {
setFetchStats(false);
props.api.getStats().then((res) => {
if (res.success) {
setStats(res.data);
} else {
props.showDialog("Error fetching stats: " + res.msg, "Error fetching stats");
}
});
}
}, [fetchStats]);
useEffect(() => {
onFetchStats();
}, []);
const today = new Date();
const numDays = getDaysInMonth(today);
@@ -46,6 +70,8 @@ export default function Overview(props) {
}
*/
console.log(stats);
return <>
<div className={"content-header"}>
<div className={"container-fluid"}>
@@ -63,57 +89,28 @@ export default function Overview(props) {
</div>
</div>
<section className={"content"}>
<div className={"container-fluid"}>
<div className={"row"}>
<div className={"col-lg-3 col-6"}>
<div className="small-box bg-info">
<div className={"inner"}>
{stats ?
<>
<h3>{stats.userCount}</h3>
<p>Users registered</p>
</> : <CircularProgress variant={"determinate"} />
}
</div>
<div className="icon">
<Icon icon={"users"} />
</div>
<Link to={"/admin/users"} className="small-box-footer">
More info <Icon icon={"arrow-circle-right"}/>
</Link>
</div>
</div>
</div>
</div>
</section>
</>
}
/*
export default class Overview extends React.Component {
constructor(props) {
super(props);
this.state = {
chartVisible : true,
statusVisible : true,
userCount: 0,
notificationCount: 0,
visitorsTotal: 0,
visitors: { },
server: { load_avg: ["Unknown"] },
errors: []
}
}
removeError(i) {
if (i >= 0 && i < this.state.errors.length) {
let errors = this.state.errors.slice();
errors.splice(i, 1);
this.setState({...this.state, errors: errors});
}
}
componentDidMount() {
this.parent.api.getStats().then((res) => {
if(!res.success) {
let errors = this.state.errors.slice();
errors.push({ message: res.msg, title: "Error fetching Stats" });
this.setState({ ...this.state, errors: errors });
} else {
this.setState({
...this.state,
userCount: res.userCount,
pageCount: res.pageCount,
visitors: res.visitors,
visitorsTotal: res.visitorsTotal,
server: res.server
});
}
});
}
render() {
}
}*/

View File

@@ -0,0 +1,90 @@
import {Link, Navigate, useNavigate} from "react-router-dom";
import {useCallback, useContext, useEffect} from "react";
import {LocaleContext} from "shared/locale";
import {DataColumn, DataTable, NumericColumn, StringColumn} from "shared/elements/data-table";
import {Button, IconButton} from "@material-ui/core";
import EditIcon from '@mui/icons-material/Edit';
import {Chip} from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
export default function UserListView(props) {
const {translate: L, requestModules, currentLocale} = useContext(LocaleContext);
const navigate = useNavigate();
useEffect(() => {
requestModules(props.api, ["general", "account"], currentLocale).then(data => {
if (!data.success) {
props.showDialog("Error fetching translations: " + data.msg);
}
});
}, [currentLocale]);
const onFetchUsers = useCallback(async (page, count, orderBy, sortOrder) => {
let res = await props.api.fetchUsers(page, count, orderBy, sortOrder);
if (res.success) {
return Promise.resolve([res.users, res.pagination]);
} else {
props.showDialog(res.msg, "Error fetching users");
return null;
}
}, []);
const groupColumn = (() => {
let column = new DataColumn(L("account.groups"), "groups");
column.renderData = (entry) => {
return Object.values(entry.groups).map(group => <Chip key={"group-" + group.id} label={group.name}/>)
}
return column;
})();
const actionColumn = (() => {
let column = new DataColumn(L("general.actions"), null, false);
column.renderData = (entry) => <>
<IconButton size={"small"} title={L("general.edit")} onClick={() => navigate("/admin/user/" + entry.id)}>
<EditIcon />
</IconButton>
</>
return column;
})();
const columnDefinitions = [
new NumericColumn(L("general.id"), "id"),
new StringColumn(L("account.username"), "name"),
new StringColumn(L("account.email"), "email"),
groupColumn,
new StringColumn(L("account.full_name"), "full_name"),
actionColumn,
];
return <>
<div className={"content-header"}>
<div className={"container-fluid"}>
<div className={"row mb-2"}>
<div className={"col-sm-6"}>
<h1 className={"m-0 text-dark"}>Users</h1>
</div>
<div className={"col-sm-6"}>
<ol className={"breadcrumb float-sm-right"}>
<li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li>
<li className="breadcrumb-item active">Users</li>
</ol>
</div>
</div>
</div>
<div className={"content"}>
<div className={"container-fluid"}>
<Link to="/admin/user/new">
<Button variant={"outlined"} startIcon={<AddIcon />} size={"small"}>
{L("general.create_new")}
</Button>
</Link>
<DataTable className={"table table-striped"}
fetchData={onFetchUsers}
placeholder={"No users to display"} columns={columnDefinitions} />
</div>
</div>
</div>
</>
}

View File

@@ -32,9 +32,12 @@
"maxParallelRequests": 1
},
"dependencies": {
"@emotion/react": "^11.10.5",
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@material-ui/lab": "^4.0.0-alpha.61",
"@mui/icons-material": "^5.11.0",
"@mui/material": "^5.11.3",
"chart.js": "^4.0.1",
"clsx": "^1.2.1",
"date-fns": "^2.29.3",
@@ -43,7 +46,8 @@
"react-chartjs-2": "^5.0.1",
"react-collapse": "^5.1.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.3"
"react-router-dom": "^6.4.3",
"sprintf-js": "^1.1.2"
},
"browserslist": {
"production": [

View File

@@ -19,7 +19,7 @@ export default class API {
let res = await response.json();
if (!res.success && res.msg === "You are not logged in.") {
document.location.reload();
this.loggedIn = false;
}
return res;
@@ -69,12 +69,16 @@ export default class API {
return this.apiCall("user/delete", { id: id });
}
async fetchUsers(pageNum = 1, count = 20) {
return this.apiCall("user/fetch", { page: pageNum, count: count });
async fetchUsers(pageNum = 1, count = 20, orderBy = 'id', sortOrder = 'asc') {
return this.apiCall("user/fetch", { page: pageNum, count: count, orderBy: orderBy, sortOrder: sortOrder });
}
async fetchGroups(pageNum = 1, count = 20) {
return this.apiCall("groups/fetch", { page: pageNum, count: count });
async fetchGroups(pageNum = 1, count = 20, orderBy = 'id', sortOrder = 'asc') {
return this.apiCall("groups/fetch", { page: pageNum, count: count, orderBy: orderBy, sortOrder: sortOrder });
}
async getGroup(id) {
return this.apiCall("groups/get", { id: id });
}
async inviteUser(username, email) {

View File

@@ -0,0 +1,20 @@
.data-table {
padding: 0;
}
.data-table td, .data-table th {
padding: 2px;
vertical-align: middle;
}
.data-table th {
background-color: #bbb;
}
.sortable {
cursor: pointer;
}
.center {
text-align: center;
}

View File

@@ -0,0 +1,323 @@
import {Table, TableBody, TableCell, TableHead, TableRow} from "@material-ui/core";
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
import ArrowDownwardIcon from "@material-ui/icons/ArrowDownward";
import React, {useCallback, useContext, useEffect, useState} from "react";
import usePagination from "../hooks/pagination";
import {parse} from "date-fns";
import "./data-table.css";
import {LocaleContext} from "../locale";
import clsx from "clsx";
import {Box} from "@mui/material";
export function DataTable(props) {
const { className, placeholder,
fetchData, onClick, onFilter,
defaultSortColumn, defaultSortOrder,
columns, ...other } = props;
const {currentLocale, requestModules, translate: L} = useContext(LocaleContext);
const [doFetchData, setFetchData] = useState(true);
const [data, setData] = useState(null);
const [sortAscending, setSortAscending] = useState(["asc","ascending"].includes(defaultSortOrder?.toLowerCase));
const [sortColumn, setSortColumn] = useState(defaultSortColumn || null);
const pagination = usePagination();
const sortable = props.hasOwnProperty("sortable") ? !!props.sortable : true;
const onFetchData = useCallback((force = false) => {
if (doFetchData || force) {
setFetchData(false);
const orderBy = columns[sortColumn]?.field || null;
const sortOrder = sortAscending ? "asc" : "desc";
fetchData(pagination.getPage(), pagination.getPageSize(), orderBy, sortOrder).then(([data, dataPagination]) => {
if (data) {
setData(data);
pagination.update(dataPagination);
}
});
}
}, [doFetchData, columns, sortColumn, sortAscending, pagination]);
// pagination changed?
useEffect(() => {
let forceFetch = (pagination.getPageSize() < pagination.getTotal());
onFetchData(forceFetch);
}, [pagination.data.pageSize, pagination.data.current]);
// sorting changed
useEffect(() => {
onFetchData(true);
}, [sortAscending, sortColumn]);
let headerRow = [];
const onChangeSort = useCallback((index, column) => {
if (sortable && column.sortable) {
if (sortColumn === index) {
setSortAscending(!sortAscending);
} else {
setSortColumn(index);
}
}
}, [onFetchData, sortColumn, sortAscending]);
for (const [index, column] of columns.entries()) {
if (!(column instanceof DataColumn)) {
throw new Error("DataTable can only have DataColumn-objects as column definition, got: " + typeof column);
}
if (sortable && column.sortable) {
headerRow.push(<TableCell key={"col-" + index} className={"sortable"}
title={L("general.sort_by") + ": " + column.label}
onClick={() => onChangeSort(index, column) }>
{sortColumn === index ? (sortAscending ? <ArrowUpwardIcon /> : <ArrowDownwardIcon />): <></>}{column.renderHead(index)}
</TableCell>);
} else {
headerRow.push(<TableCell key={"col-" + index}>
{column.renderHead(index)}
</TableCell>);
}
}
const numColumns = columns.length;
let rows = [];
if (data) {
for (const [key, entry] of Object.entries(data)) {
let row = [];
for (const [index, column] of columns.entries()) {
row.push(<TableCell key={"col-" + index}>{column.renderData(entry)}</TableCell>);
}
rows.push(<TableRow key={"row-" + key}>{ row }</TableRow>);
}
} else if (placeholder) {
rows.push(<TableRow key={"row-placeholder"}>
<TableCell colSpan={numColumns}>
{ placeholder }
</TableCell>
</TableRow>);
}
/*
let columnElements = [];
if (columns) {
for (const [key, column] of Object.entries(columns)) {
const centered = column.alignment === "center";
const sortable = doSort && (!column.hasOwnProperty("sortable") || !!column.sortable);
const label = column.label;
if (!sortable) {
columnElements.push(
<TableCell key={"column-" + key} className={clsx(centered && classes.columnCenter)}>
{ label }
</TableCell>
);
} else {
columnElements.push(
<TableCell key={"column-" + key} label={L("Sort By") + ": " + label} className={clsx(classes.clickable, centered && classes.columnCenter)}
onClick={() => (key === sortColumn ? setSortAscending(!sortAscending) : setSortColumn(key)) }>
{ key === sortColumn ?
<Grid container alignItems={"center"} spacing={1} direction={"row"} className={classes.gridSorted}>
<Grid item>{ sortAscending ? <ArrowUpwardIcon fontSize={"small"} /> : <ArrowDownwardIcon fontSize={"small"} /> }</Grid>
<Grid item>{ label }</Grid>
<Grid item/>
</Grid> :
<span><i/>{label}</span>
}
</TableCell>
);
}
}
}
const getValue = useCallback((entry, key) => {
if (typeof columns[key]?.value === 'function') {
return columns[key].value(entry);
} else {
return entry[columns[key]?.value] ?? null;
}
}, [columns]);
let numColumns = columns ? Object.keys(columns).length : 0;
const compare = (a,b,callback) => {
let definedA = a !== null && typeof a !== 'undefined';
let definedB = b !== null && typeof b !== 'undefined';
if (!definedA && !definedB) {
return 0;
} else if (!definedA) {
return 1;
} else if (!definedB) {
return -1;
} else {
return callback(a,b);
}
}
let rows = [];
const hasClickHandler = typeof onClick === 'function';
if (data !== null && columns) {
let hidden = 0;
let sortedEntries = data.slice();
if (sortColumn && columns[sortColumn]) {
let sortFunction;
if (typeof columns[sortColumn]?.compare === 'function') {
sortFunction = columns[sortColumn].compare;
} else if (columns[sortColumn]?.type === Date) {
sortFunction = (a, b) => compare(a, b, (a,b) => a.getTime() - b.getTime());
} else if (columns[sortColumn]?.type === Number) {
sortFunction = (a, b) => compare(a, b, (a,b) => a - b);
} else {
sortFunction = ((a, b) =>
compare(a, b, (a,b) => a.toString().toLowerCase().localeCompare(b.toString().toLowerCase()))
)
}
sortedEntries.sort((a, b) => {
let entryA = getValue(a, sortColumn);
let entryB = getValue(b, sortColumn);
return sortFunction(entryA, entryB);
});
if (!sortAscending) {
sortedEntries = sortedEntries.reverse();
}
}
Array.from(Array(sortedEntries.length).keys()).forEach(rowIndex => {
if (typeof props.filter === 'function' && !props.filter(sortedEntries[rowIndex])) {
hidden++;
return;
}
let rowData = [];
for (const [key, column] of Object.entries(columns)) {
let value = getValue(sortedEntries[rowIndex], key);
if (typeof column.render === 'function') {
value = column.render(sortedEntries[rowIndex], value);
}
rowData.push(<TableCell key={"column-" + key} className={clsx(column.alignment === "center" && classes.columnCenter)}>
{ value }
</TableCell>);
}
rows.push(
<TableRow key={"entry-" + rowIndex}
className={clsx(hasClickHandler && classes.clickable)}
onClick={() => hasClickHandler && onClick(sortedEntries[rowIndex])}>
{ rowData }
</TableRow>
);
});
if (hidden > 0) {
rows.push(<TableRow key={"row-hidden"}>
<TableCell colSpan={numColumns} className={classes.hidden}>
{ "(" + (hidden > 1
? sprintf(L("%d rows hidden due to filter"), hidden)
: L("1 rows hidden due to filter")) + ")"
}
</TableCell>
</TableRow>);
} else if (rows.length === 0 && placeholder) {
rows.push(<TableRow key={"row-placeholder"}>
<TableCell colSpan={numColumns} className={classes.hidden}>
{ placeholder }
</TableCell>
</TableRow>);
}
} else if (columns && data === null) {
rows.push(<TableRow key={"loading"}>
<TableCell colSpan={numColumns} className={classes.columnCenter}>
<Grid container alignItems={"center"} spacing={1} justifyContent={"center"}>
<Grid item>{L("Loading")}…</Grid>
<Grid item><CircularProgress size={15}/></Grid>
</Grid>
</TableCell>
</TableRow>)
}
*/
return <Box position={"relative"}>
<Table className={clsx("data-table", className)} size="small" {...other}>
<TableHead>
<TableRow>
{ headerRow }
</TableRow>
</TableHead>
<TableBody>
{ rows }
</TableBody>
</Table>
{pagination.renderPagination(L, rows.length)}
</Box>
}
export class DataColumn {
constructor(label, field = null, sortable = true) {
this.label = label;
this.field = field;
this.sortable = sortable;
}
compare(a, b) {
throw new Error("Not implemented: compare");
}
renderData(entry) {
return entry[this.field]
}
renderHead() {
return this.label;
}
}
export class StringColumn extends DataColumn {
constructor(label, field = null, sortable = true, caseSensitive = false) {
super(label, field, sortable);
this.caseSensitve = caseSensitive;
}
compare(a, b) {
if (this.caseSensitve) {
return a.toString().localeCompare(b.toString());
} else {
return a.toString().toLowerCase().localeCompare(b.toString().toLowerCase());
}
}
}
export class NumericColumn extends DataColumn {
constructor(label, field = null, sortable = true) {
super(label, field, sortable);
}
compare(a, b) {
return a - b;
}
}
export class DateTimeColumn extends DataColumn {
constructor(label, field = null, sortable = true, format = "YYYY-MM-dd HH:mm:ss") {
super(label, field, sortable);
this.format = format;
}
compare(a, b) {
if (typeof a === 'string') {
a = parse(a, this.format, new Date()).getTime();
}
if (typeof b === 'string') {
b = parse(b, this.format, new Date()).getTime();
}
return a - b;
}
}

View File

@@ -0,0 +1,73 @@
import React, {useState} from "react";
import {Box, MenuItem, Select, Pagination as MuiPagination} from "@mui/material";
import {sprintf} from "sprintf-js";
class Pagination {
constructor(data, setData) {
this.data = data;
this.setData = setData;
}
getPage() {
return this.data.current;
}
getPageSize() {
return this.data.pageSize;
}
setPage(page) {
this.setData({...this.data, current: page});
}
setPageSize(pageSize) {
this.setData({...this.data, pageSize: pageSize});
}
reset() {
this.setData({current: 1, pageSize: 25, total: 0});
}
getPageCount() {
if (this.data.pageSize && this.data.total) {
return Math.max(1, Math.ceil(this.data.total / this.data.pageSize));
} else {
return 1;
}
}
getTotal() {
return this.data.total;
}
update(data) {
this.setData(data);
}
renderPagination(L, numEntries, options = null) {
options = options || [10, 25, 50, 100];
return <Box>
<Select
value={this.data.pageSize}
label={L("general.entries_per_page")}
onChange={(e) => this.setPageSize(parseInt(e.target.value))}
size={"small"}
>
{options.map(size => <MenuItem key={"size-" + size} value={size}>{size}</MenuItem>)}
</Select>
<MuiPagination count={this.getPageCount()} onChange={(_, page) => this.setPage(page)} />
{sprintf(L("general.showing_x_of_y_entries"), numEntries, this.data.total)}
</Box>
}
}
export default function usePagination() {
const [pagination, setPagination] = useState({
current: 1, pageSize: 25, total: 0
});
return new Pagination(pagination, setPagination);
}

View File

@@ -1,11 +1,36 @@
import React from 'react';
import React, {useReducer} from 'react';
import {createContext, useCallback, useState} from "react";
const LocaleContext = createContext(null);
function reducer(entries, action) {
let _entries = entries;
switch (action.type) {
case 'loadModule':
if (!_entries.hasOwnProperty(action.code)) {
_entries[action.code] = {};
}
if (_entries[action.code].hasOwnProperty(action.module)) {
_entries[action.code][action.module] = {..._entries[action.code][action.module], ...action.newEntries};
} else {
_entries[action.code][action.module] = action.newEntries;
}
break;
case 'loadModules':
_entries = {...entries, [action.code]: { ...entries[action.code], ...action.modules }};
break;
default:
throw new Error();
}
return _entries;
}
function LocaleProvider(props) {
const [entries, setEntries] = useState(window.languageEntries || {});
// const [entries, setEntries] = useState(window.languageEntries || {});
const [entries, dispatch] = useReducer(reducer, window.languageEntries || {});
const [currentLocale, setCurrentLocale] = useState(window.languageCode || "en_US");
const translate = useCallback((key) => {
@@ -24,23 +49,6 @@ function LocaleProvider(props) {
return "[" + key + "]";
}, [currentLocale, entries]);
const loadModule = useCallback((code, module, newEntries) => {
let _entries = {...entries};
if (!_entries.hasOwnProperty(code)) {
_entries[code] = {};
}
if (_entries[code].hasOwnProperty(module)) {
_entries[code][module] = {..._entries[code][module], ...newEntries};
} else {
_entries[code][module] = newEntries;
}
setEntries(_entries);
}, [entries]);
const loadModules = useCallback((code, modules) => {
setEntries({...entries, [code]: { ...entries[code], ...modules }});
}, [entries]);
const hasModule = useCallback((code, module) => {
return entries.hasOwnProperty(code) && !!entries[code][module];
}, [entries]);
@@ -104,7 +112,7 @@ function LocaleProvider(props) {
if (useCache) {
if (data && data.success) {
// insert into cache
loadModules(code, data.entries);
dispatch({type: "loadModules", code: code, modules: data.entries});
data.entries = {...data.entries, ...languageEntries};
data.cached = false;
}
@@ -114,7 +122,7 @@ function LocaleProvider(props) {
} else {
return { success: true, msg: "", entries: languageEntries, code: code, cached: true };
}
}, [currentLocale, getModule, loadModules]);
}, [currentLocale, getModule, dispatch]);
const ctx = {
currentLocale: currentLocale,

View File

@@ -162,7 +162,7 @@
dependencies:
"@babel/types" "^7.18.9"
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.18.6":
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e"
integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==
@@ -518,7 +518,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
"@babel/plugin-syntax-jsx@^7.18.6":
"@babel/plugin-syntax-jsx@^7.17.12", "@babel/plugin-syntax-jsx@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0"
integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==
@@ -1035,6 +1035,13 @@
dependencies:
regenerator-runtime "^0.13.11"
"@babel/runtime@^7.18.3", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd"
integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.18.10", "@babel/template@^7.3.3":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
@@ -1185,11 +1192,119 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
"@emotion/babel-plugin@^11.10.5":
version "11.10.5"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz#65fa6e1790ddc9e23cc22658a4c5dea423c55c3c"
integrity sha512-xE7/hyLHJac7D2Ve9dKroBBZqBT7WuPQmWcq7HSGb84sUuP4mlOWoB8dvVfD9yk5DHkU1m6RW7xSoDtnQHNQeA==
dependencies:
"@babel/helper-module-imports" "^7.16.7"
"@babel/plugin-syntax-jsx" "^7.17.12"
"@babel/runtime" "^7.18.3"
"@emotion/hash" "^0.9.0"
"@emotion/memoize" "^0.8.0"
"@emotion/serialize" "^1.1.1"
babel-plugin-macros "^3.1.0"
convert-source-map "^1.5.0"
escape-string-regexp "^4.0.0"
find-root "^1.1.0"
source-map "^0.5.7"
stylis "4.1.3"
"@emotion/cache@^11.10.5":
version "11.10.5"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.10.5.tgz#c142da9351f94e47527ed458f7bbbbe40bb13c12"
integrity sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==
dependencies:
"@emotion/memoize" "^0.8.0"
"@emotion/sheet" "^1.2.1"
"@emotion/utils" "^1.2.0"
"@emotion/weak-memoize" "^0.3.0"
stylis "4.1.3"
"@emotion/hash@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
"@emotion/hash@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.0.tgz#c5153d50401ee3c027a57a177bc269b16d889cb7"
integrity sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==
"@emotion/is-prop-valid@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83"
integrity sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==
dependencies:
"@emotion/memoize" "^0.8.0"
"@emotion/memoize@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.0.tgz#f580f9beb67176fa57aae70b08ed510e1b18980f"
integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==
"@emotion/react@^11.10.5":
version "11.10.5"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.10.5.tgz#95fff612a5de1efa9c0d535384d3cfa115fe175d"
integrity sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.10.5"
"@emotion/cache" "^11.10.5"
"@emotion/serialize" "^1.1.1"
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
"@emotion/utils" "^1.2.0"
"@emotion/weak-memoize" "^0.3.0"
hoist-non-react-statics "^3.3.1"
"@emotion/serialize@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.1.tgz#0595701b1902feded8a96d293b26be3f5c1a5cf0"
integrity sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==
dependencies:
"@emotion/hash" "^0.9.0"
"@emotion/memoize" "^0.8.0"
"@emotion/unitless" "^0.8.0"
"@emotion/utils" "^1.2.0"
csstype "^3.0.2"
"@emotion/sheet@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.1.tgz#0767e0305230e894897cadb6c8df2c51e61a6c2c"
integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==
"@emotion/styled@^11.10.5":
version "11.10.5"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.10.5.tgz#1fe7bf941b0909802cb826457e362444e7e96a79"
integrity sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.10.5"
"@emotion/is-prop-valid" "^1.2.0"
"@emotion/serialize" "^1.1.1"
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.0"
"@emotion/utils" "^1.2.0"
"@emotion/unitless@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.0.tgz#a4a36e9cbdc6903737cd20d38033241e1b8833db"
integrity sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==
"@emotion/use-insertion-effect-with-fallbacks@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz#ffadaec35dbb7885bd54de3fa267ab2f860294df"
integrity sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==
"@emotion/utils@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.0.tgz#9716eaccbc6b5ded2ea5a90d65562609aab0f561"
integrity sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==
"@emotion/weak-memoize@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
"@eslint/eslintrc@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95"
@@ -1666,6 +1781,99 @@
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-2.2.0.tgz#016d855b6bc459fd908095811f6826e45dd4ba64"
integrity sha512-XrC0JzsqQSvOyM3t04FMLO6z5gCuhPE6k4FXuLK5xf52ZbdvcFe1yBmo7meCew9B8G2f0T9iu9t3kfTYRYROgA==
"@mui/base@5.0.0-alpha.112":
version "5.0.0-alpha.112"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.112.tgz#80e815430c5df0316e0a549d34628d54215e05f1"
integrity sha512-KPwb1iYPXsV/P8uu0SNQrj7v7YU6wdN4Eccc2lZQyRDW+f6PJYjHBuFUTYKc408B98Jvs1XbC/z5MN45a2DWrQ==
dependencies:
"@babel/runtime" "^7.20.7"
"@emotion/is-prop-valid" "^1.2.0"
"@mui/types" "^7.2.3"
"@mui/utils" "^5.11.2"
"@popperjs/core" "^2.11.6"
clsx "^1.2.1"
prop-types "^15.8.1"
react-is "^18.2.0"
"@mui/core-downloads-tracker@^5.11.3":
version "5.11.3"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.3.tgz#72c04a9f12b29186877992648d7cf1c22b95ba5c"
integrity sha512-Bgb6//KtxY7IC7M5Pa5RKFX1wttc213mqpKqydnz3wn4BGDXfA5c0vf5nTu5zqsJeB4T3ysAJTRJhQ/E1GsZDQ==
"@mui/icons-material@^5.11.0":
version "5.11.0"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.11.0.tgz#9ea6949278b2266d2683866069cd43009eaf6464"
integrity sha512-I2LaOKqO8a0xcLGtIozC9xoXjZAto5G5gh0FYUMAlbsIHNHIjn4Xrw9rvjY20vZonyiGrZNMAlAXYkY6JvhF6A==
dependencies:
"@babel/runtime" "^7.20.6"
"@mui/material@^5.11.3":
version "5.11.3"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.11.3.tgz#e1be6d26eecde61878280cb6792797a77696b09a"
integrity sha512-Oz+rMFiMtxzzDLUxKyyj4mSxF9ShmsBoJ9qvglXCYqklgSrEl1R/Z4hfPZ+2pWd5CriO8U/0CFHr4DksrlTiCw==
dependencies:
"@babel/runtime" "^7.20.7"
"@mui/base" "5.0.0-alpha.112"
"@mui/core-downloads-tracker" "^5.11.3"
"@mui/system" "^5.11.2"
"@mui/types" "^7.2.3"
"@mui/utils" "^5.11.2"
"@types/react-transition-group" "^4.4.5"
clsx "^1.2.1"
csstype "^3.1.1"
prop-types "^15.8.1"
react-is "^18.2.0"
react-transition-group "^4.4.5"
"@mui/private-theming@^5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.11.2.tgz#93eafb317070888a988efa8d6a9ec1f69183a606"
integrity sha512-qZwMaqRFPwlYmqwVKblKBGKtIjJRAj3nsvX93pOmatsXyorW7N/0IPE/swPgz1VwChXhHO75DwBEx8tB+aRMNg==
dependencies:
"@babel/runtime" "^7.20.7"
"@mui/utils" "^5.11.2"
prop-types "^15.8.1"
"@mui/styled-engine@^5.11.0":
version "5.11.0"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.11.0.tgz#79afb30c612c7807c4b77602cf258526d3997c7b"
integrity sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==
dependencies:
"@babel/runtime" "^7.20.6"
"@emotion/cache" "^11.10.5"
csstype "^3.1.1"
prop-types "^15.8.1"
"@mui/system@^5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.11.2.tgz#a5a5865dda0f5f360eed8cdc1ab399a493dbd361"
integrity sha512-PPkYhrcP2MkhscX6SauIl0wPgra0w1LGPtll+hIKc2Z2JbGRSrUCFif93kxejB7I1cAoCay9jWW4mnNhsOqF/g==
dependencies:
"@babel/runtime" "^7.20.7"
"@mui/private-theming" "^5.11.2"
"@mui/styled-engine" "^5.11.0"
"@mui/types" "^7.2.3"
"@mui/utils" "^5.11.2"
clsx "^1.2.1"
csstype "^3.1.1"
prop-types "^15.8.1"
"@mui/types@^7.2.3":
version "7.2.3"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.3.tgz#06faae1c0e2f3a31c86af6f28b3a4a42143670b9"
integrity sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==
"@mui/utils@^5.11.2":
version "5.11.2"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.11.2.tgz#29764311acb99425159b159b1cb382153ad9be1f"
integrity sha512-AyizuHHlGdAtH5hOOXBW3kriuIwUIKUIgg0P7LzMvzf6jPhoQbENYqY6zJqfoZ7fAWMNNYT8mgN5EftNGzwE2w==
dependencies:
"@babel/runtime" "^7.20.7"
"@types/prop-types" "^15.7.5"
"@types/react-is" "^16.7.1 || ^17.0.0"
prop-types "^15.8.1"
react-is "^18.2.0"
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
@@ -2269,6 +2477,11 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
"@popperjs/core@^2.11.6":
version "2.11.6"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
"@remix-run/router@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.0.3.tgz#953b88c20ea00d0eddaffdc1b115c08474aa295d"
@@ -2646,7 +2859,7 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e"
integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==
"@types/prop-types@*":
"@types/prop-types@*", "@types/prop-types@^15.7.5":
version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
@@ -2666,7 +2879,14 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react-transition-group@^4.2.0":
"@types/react-is@^16.7.1 || ^17.0.0":
version "17.0.3"
resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a"
integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.2.0", "@types/react-transition-group@^4.4.5":
version "4.4.5"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==
@@ -3852,7 +4072,7 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
@@ -4123,7 +4343,7 @@ csstype@^2.5.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e"
integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==
csstype@^3.0.2:
csstype@^3.0.2, csstype@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
@@ -5036,6 +5256,11 @@ find-cache-dir@^3.3.1:
make-dir "^3.0.2"
pkg-dir "^4.1.0"
find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
@@ -5365,7 +5590,7 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hoist-non-react-statics@^3.3.2:
hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -5620,6 +5845,11 @@ ini@^1.3.5:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
install@^0.13.0:
version "0.13.0"
resolved "https://registry.yarnpkg.com/install/-/install-0.13.0.tgz#6af6e9da9dd0987de2ab420f78e60d9c17260776"
integrity sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==
internal-slot@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
@@ -8238,7 +8468,7 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^18.0.0:
react-is@^18.0.0, 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==
@@ -8323,7 +8553,7 @@ react-scripts@^5.0.1:
optionalDependencies:
fsevents "^2.3.2"
react-transition-group@^4.4.0:
react-transition-group@^4.4.0, react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
@@ -8852,6 +9082,11 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
source-map@^0.7.3:
version "0.7.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
@@ -8892,6 +9127,11 @@ spdy@^4.0.2:
select-hose "^2.0.0"
spdy-transport "^3.0.0"
sprintf-js@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -9061,6 +9301,11 @@ stylehacks@^5.1.1:
browserslist "^4.21.4"
postcss-selector-parser "^6.0.4"
stylis@4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7"
integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"