refactor: rewrite the UI of the code scanner. (#4677)

* feat: workbench block

* feat: mobilePage

* fix: update WorkbenchAction

* feat: support qrcode embed in markdown and scan in mobile

* fix: fix markdown button be covered problem

* fix: fix unit test error

* fix: fix unit test errors

* refactor: use react router in qrcode scanner

* feat: markdown add loading

* fix: fix blank content in print page

* refactor: change plugin dependencies to devDependencies

* feat: add some padding in markdown editor

* chore: improve some code

* feat: improve code

* fix: add QRCodeScanner

* fix: iconColor

* fix: Improve code

* feat: Improve code

* fix: version

* chore: improve some code

* chore: improve some code

* fix: i18n

* refactor: refact the UI of code scanner

* feat: add some locale

* feat: add qrbox

* feat: add image upload and fix qrbox

* feat: fix full screen problem

* fix: fix not full screen problem

* fix: add some error handling

* feat: add album locale

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
Sheldon Guo 2024-06-30 10:09:46 +08:00 committed by GitHub
parent 86a56ef7cc
commit ba1cdce6e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 373 additions and 96 deletions

View File

@ -2,9 +2,7 @@
"version": "1.2.11-alpha",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": [
"--ignore-engines"
],
"npmClientArgs": ["--ignore-engines"],
"command": {
"version": {
"forcePublish": true,

View File

@ -12,6 +12,8 @@
"@nocobase/test": "1.x"
},
"devDependencies": {
"@ant-design/icons": "^5.x",
"antd": "^5.x",
"html5-qrcode": "^2.3.8",
"react-router-dom": "^6.x"
},

View File

@ -37,7 +37,7 @@ const InternalIcons = () => {
<DndContext>
<Space wrap>
{fieldSchema.mapProperties((s, key) => (
<RecursionField name={key} schema={s} />
<RecursionField name={key} schema={s} key={key} />
))}
</Space>
</DndContext>

View File

@ -11,7 +11,6 @@ import {
ButtonEditor,
ISchema,
SchemaSettings,
useActionContext,
useSchemaInitializer,
useSchemaInitializerItem,
} from '@nocobase/client';
@ -45,14 +44,6 @@ export function WorkbenchScanActionSchemaInitializerItem(props) {
// 调用插入功能
const { insert } = useSchemaInitializer();
const { t } = useTranslation();
const useCancelAction = () => {
const { setVisible } = useActionContext();
return {
run() {
setVisible(false);
},
};
};
return (
<ModalActionSchemaInitializerItem
@ -96,33 +87,8 @@ export function WorkbenchScanActionSchemaInitializerItem(props) {
properties: {
modal: {
type: 'void',
'x-component': 'Action.Modal',
title: t('Scan QR code', { ns: 'block-workbench' }),
'x-decorator': 'FormV2',
properties: {
scanner: {
'x-component': 'QRCodeScanner',
'x-component-props': {
fps: 10,
qrbox: 250,
disableFlip: false,
},
},
footer: {
type: 'void',
'x-component': 'Action.Modal.Footer',
properties: {
close: {
title: 'Close',
'x-component': 'Action',
'x-component-props': {
type: 'default',
useAction: useCancelAction,
},
},
},
},
},
title: t('Scan QR code', { ns: 'block-workbench' }),
},
},
} as ISchema);

View File

@ -0,0 +1,29 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React from 'react';
export function ScanBox({ style = {} }: { style: React.CSSProperties }) {
const commonStyle: React.CSSProperties = {
position: 'absolute',
backgroundColor: 'rgb(255, 255, 255)',
};
return (
<div id="qr-scan-box" style={{ boxSizing: 'border-box', inset: '0px', ...style }}>
<div style={{ width: '40px', height: '5px', top: '-5px', left: '0px', ...commonStyle }}></div>
<div style={{ width: '40px', height: '5px', top: '-5px', right: '0px', ...commonStyle }}></div>
<div style={{ width: '40px', height: '5px', bottom: '-5px', left: '0px', ...commonStyle }}></div>
<div style={{ width: '40px', height: '5px', bottom: '-5px', right: '0px', ...commonStyle }}></div>
<div style={{ width: '5px', height: '45px', top: '-5px', left: '-5px', ...commonStyle }}></div>
<div style={{ width: '5px', height: '45px', bottom: '-5px', left: '-5px', ...commonStyle }}></div>
<div style={{ width: '5px', height: '45px', top: '-5px', right: '-5px', ...commonStyle }}></div>
<div style={{ width: '5px', height: '45px', bottom: '-5px', right: '-5px', ...commonStyle }}></div>
</div>
);
}

View File

@ -6,51 +6,177 @@
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { LeftOutlined, FileImageOutlined } from '@ant-design/icons';
import { Html5Qrcode } from 'html5-qrcode';
import React, { useState, useEffect, useRef } from 'react';
import { useActionContext } from '@nocobase/client';
import { useTranslation } from 'react-i18next';
import { ScanBox } from './ScanBox';
import { useScanner } from './useScanner';
import { Html5QrcodeScanner } from 'html5-qrcode';
import { useNavigate } from 'react-router-dom';
import React, { useEffect } from 'react';
const qrcodeEleId = 'qrcode';
export const QRCodeScannerInner = (props) => {
const containerRef = useRef<HTMLDivElement>();
const imgUploaderRef = useRef<HTMLInputElement>();
const { t } = useTranslation('block-workbench');
const [originVideoSize, setOriginVideoSize] = useState({ width: 0, height: 0 });
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
const qrcodeRegionId = 'html5qr-code-full-region';
const { startScanFile } = useScanner({
onScannerSizeChanged: setOriginVideoSize,
elementId: qrcodeEleId,
});
// Creates the configuration object for Html5QrcodeScanner.
const createConfig = (props) => {
const config: any = {};
if (props.fps) {
config.fps = props.fps;
const getBoxStyle = (): React.CSSProperties => {
const size = Math.floor(Math.min(vw, vh) * 0.6);
return {
left: `${(vw - size) / 2}px`,
top: `${(vh - size) / 2}px`,
position: 'fixed',
width: `${size}px`,
height: `${size}px`,
};
};
const onImgBtnClick = () => {
if (imgUploaderRef.current) imgUploaderRef.current.click();
};
const onImgUploaded = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files.length > 0) {
const file = files[0];
if (file.size < 1000000) startScanFile(file);
else alert(t('The image size is too large. Please compress it to below 1MB before uploading'));
}
if (props.qrbox) {
config.qrbox = props.qrbox;
};
useEffect(() => {
document.documentElement.style.overscrollBehavior = 'none';
return () => {
document.documentElement.style.overscrollBehavior = 'default';
};
}, []);
useEffect(() => {
const { width, height } = originVideoSize;
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
if (width > 0 && height > 0 && height < vh) {
const zoomRatio = vh / height;
const zoomedWidth = Math.floor(zoomRatio * width);
const video = document.getElementsByTagName('video')[0];
video.style.height = `${vh}px`;
video.style.width = `${zoomedWidth}px`;
containerRef.current.style.left = `${(vw - zoomedWidth) / 2}px`;
containerRef.current.style.position = `absolute`;
}
if (props.aspectRatio) {
config.aspectRatio = props.aspectRatio;
}
if (props.disableFlip !== undefined) {
config.disableFlip = props.disableFlip;
}
return config;
}, [originVideoSize]);
const ToolBar = () => {
return (
<div style={{ position: 'absolute', bottom: '20px', left: '20px', padding: '10px 60px' }}>
<div
style={{
color: 'white',
width: '40px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<FileImageOutlined style={imageBtnStyle} onClick={onImgBtnClick} />
{t('Album')}
<input
ref={imgUploaderRef}
type="file"
accept="image/*"
onChange={onImgUploaded}
style={{ visibility: 'hidden' }}
/>
</div>
</div>
);
};
const imageBtnStyle: React.CSSProperties = {
zIndex: '1003',
fontSize: '1.8em',
fontWeight: 'bold',
};
return (
<>
<div ref={containerRef} id={qrcodeEleId} style={{ position: 'absolute' }} />
<ScanBox style={{ ...getBoxStyle() }} />
<ToolBar />
</>
);
};
export const QRCodeScanner = (props) => {
const navigate = useNavigate();
const { visible, setVisible } = useActionContext();
const [cameraAvaliable, setCameraAvaliable] = useState(false);
const { t } = useTranslation('block-workbench');
useEffect(() => {
// when component mounts
const config = createConfig(props);
const verbose = props.verbose === true;
// Suceess callback is required.
const qrCodeSuccessCallback = (decodedText, decodedResult) => {
navigate(decodedText);
const getCameras = async () => {
try {
const res = await Html5Qrcode.getCameras();
if (res.length === 0) alert(t('No camera device detected'));
else setCameraAvaliable(true);
} catch (error) {
const errMsgMap = {
NotFoundError: t('No camera device detected'),
NotAllowedError: t('You have not granted permission to use the camera'),
};
const html5QrcodeScanner = new Html5QrcodeScanner(qrcodeRegionId, config, verbose);
html5QrcodeScanner.render(qrCodeSuccessCallback, props.qrCodeErrorCallback);
console.log(error);
const msg = errMsgMap[error.name];
alert(msg ?? error);
setCameraAvaliable(false);
setVisible(false);
}
};
if (visible && !cameraAvaliable) getCameras();
}, [visible, cameraAvaliable, setVisible, t]);
// cleanup function when component will unmount
return () => {
html5QrcodeScanner.clear().catch((error) => {
console.error('Failed to clear html5QrcodeScanner. ', error);
});
const style: React.CSSProperties = {
position: 'fixed',
width: '100%',
height: '100%',
background: 'black',
zIndex: '1001',
top: 0,
left: 0,
overflow: 'hidden',
};
const backIconStyle: React.CSSProperties = {
position: 'absolute',
top: '22px',
left: '20px',
zIndex: '1003',
color: 'white',
fontSize: '1.8em',
fontWeight: 'bold',
};
}, [navigate, props]);
return <div id={qrcodeRegionId} />;
const titleStyle: React.CSSProperties = {
position: 'absolute',
color: 'white',
fontSize: '1.5em',
left: 0,
right: 0,
top: '20px',
zIndex: '1002',
marginLeft: 'auto',
marginRight: 'auto',
textAlign: 'center',
};
return visible && cameraAvaliable ? (
<div style={style}>
<QRCodeScannerInner />
<LeftOutlined style={backIconStyle} onClick={() => setVisible(false)} />
<div style={titleStyle}>{t('Scan QR code')}</div>
</div>
) : null;
};

View File

@ -0,0 +1,77 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Html5Qrcode, Html5QrcodeScannerState } from 'html5-qrcode';
import { useState, useCallback, useMemo, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
export function useScanner({ onScannerSizeChanged, elementId }) {
const [scanner, setScanner] = useState<Html5Qrcode>();
const navigate = useNavigate();
const { t } = useTranslation('block-workbench');
const viewPoint = useMemo(() => {
const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
return { width: vw, height: vh };
}, []);
const startScanCamera = useCallback(
async (scanner: Html5Qrcode) => {
return scanner.start(
{
facingMode: 'environment',
},
{
fps: 10,
qrbox(width, height) {
const minEdge = Math.min(width, height);
onScannerSizeChanged({ width, height });
return { width: viewPoint.width, height: viewPoint.height };
},
},
(text) => {
navigate(text);
},
undefined,
);
},
[navigate, onScannerSizeChanged, viewPoint],
);
const stopScanner = useCallback(async (scanner: Html5Qrcode) => {
const state = scanner.getState();
if ([Html5QrcodeScannerState.SCANNING, Html5QrcodeScannerState.PAUSED].includes(state)) {
return scanner.stop();
} else return;
}, []);
const startScanFile = useCallback(
async (file: File) => {
await stopScanner(scanner);
try {
const { decodedText } = await scanner.scanFileV2(file, false);
navigate(decodedText);
} catch (error) {
alert(t('QR code recognition failed, please scan again'));
startScanCamera(scanner);
}
},
[scanner, stopScanner, startScanCamera, t, navigate],
);
useEffect(() => {
const scanner = new Html5Qrcode(elementId);
setScanner(scanner);
startScanCamera(scanner);
return () => {
stopScanner(scanner);
};
}, [elementId, startScanCamera, stopScanner]);
return { startScanCamera, startScanFile };
}

View File

@ -1,4 +1,10 @@
{
"Workbench": "工作台",
"Scan QR code": "扫描二维码"
"Scan QR code": "扫描二维码",
"Album": "相册",
"No camera device detected": "未检测到摄像头设备",
"You have not granted permission to use the camera": "您未授权使用摄像头",
"Unknown error": "未知错误",
"The image size is too large. Please compress it to below 1MB before uploading": "图片尺寸过大请压缩到1MB以下上传",
"QR code recognition failed, please scan again": "二维码识别失败,请重新扫描"
}

111
yarn.lock
View File

@ -217,6 +217,11 @@
resolved "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.3.1.tgz#4b2f65a17d4d32b526baa6414aca2117382bf8da"
integrity sha512-4QBZg8ccyC6LPIRii7A0bZUk3+lEDCLnhB+FVsflGdcWPPmV+j3fire4AwwoqHV/BibgvBmR9ZIo4s867smv+g==
"@ant-design/icons-svg@^4.4.0":
version "4.4.2"
resolved "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz#ed2be7fb4d82ac7e1d45a54a5b06d6cecf8be6f6"
integrity sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==
"@ant-design/icons@5.x", "@ant-design/icons@^5.0.0", "@ant-design/icons@^5.1.3", "@ant-design/icons@^5.1.4", "@ant-design/icons@^5.2.5", "@ant-design/icons@^5.2.6":
version "5.2.6"
resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.2.6.tgz#2d4a9a37f531eb2a20cebec01d6fb69cf593900d"
@ -228,6 +233,17 @@
classnames "^2.2.6"
rc-util "^5.31.1"
"@ant-design/icons@^5.3.7":
version "5.3.7"
resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.3.7.tgz#d9f3654bf7934ee5faba43f91b5a187f5309ec68"
integrity sha512-bCPXTAg66f5bdccM4TT21SQBDO1Ek2gho9h3nO9DAKXJP4sq+5VBjrQMSxMVXSB3HyEz+cUbHQ5+6ogxCOpaew==
dependencies:
"@ant-design/colors" "^7.0.0"
"@ant-design/icons-svg" "^4.4.0"
"@babel/runtime" "^7.11.2"
classnames "^2.2.6"
rc-util "^5.31.1"
"@ant-design/plots@^2.1.4":
version "2.1.14"
resolved "https://registry.npmmirror.com/@ant-design/plots/-/plots-2.1.14.tgz#967a6fbf3e86ad216026fdbb853af1391549c795"
@ -5172,11 +5188,6 @@
resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.14.0.tgz#9bc39a5a3a71b81bdb310eba6def5bc3966695b7"
integrity sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==
"@remix-run/router@1.16.1":
version "1.16.1"
resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.16.1.tgz#73db3c48b975eeb06d0006481bde4f5f2d17d1cd"
integrity sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==
"@restart/hooks@^0.4.7":
version "0.4.15"
resolved "https://registry.npmmirror.com/@restart/hooks/-/hooks-0.4.15.tgz#70990085c9b79d1f3e140db824b29218bdc2b5bb"
@ -6788,13 +6799,20 @@
"@types/prop-types" "*"
"@types/react" "*"
"@types/react-dom@17.x", "@types/react-dom@^17.0.0", "@types/react-dom@^18.0.0":
"@types/react-dom@17.x", "@types/react-dom@^17.0.0":
version "17.0.25"
resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-17.0.25.tgz#e0e5b3571e1069625b3a3da2b279379aa33a0cb5"
integrity sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==
dependencies:
"@types/react" "^17"
"@types/react-dom@^18.0.0":
version "18.3.0"
resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0"
integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==
dependencies:
"@types/react" "*"
"@types/react-redux@^7.1.20":
version "7.1.33"
resolved "https://registry.npmmirror.com/@types/react-redux/-/react-redux-7.1.33.tgz#53c5564f03f1ded90904e3c90f77e4bd4dc20b15"
@ -6805,7 +6823,7 @@
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@17.x", "@types/react@>=16.9.11", "@types/react@^17", "@types/react@^17.0.0", "@types/react@^18.0.0":
"@types/react@*", "@types/react@16 || 17 || 18", "@types/react@17.x", "@types/react@>=16.9.11", "@types/react@^17", "@types/react@^17.0.0":
version "17.0.73"
resolved "https://registry.npmmirror.com/@types/react/-/react-17.0.73.tgz#23a663c803b18d8b7f4f2bb9b467f2f3fd70787a"
integrity sha512-6AcjgPIVsXTIsFDgsGW0iQhvg0xb2vt2qAWgXyncnVNRaW9ZXTTwAh7RQoh7PzK1AhjPoGDvUBkdAREih9n5oQ==
@ -6814,6 +6832,14 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^18.0.0":
version "18.3.3"
resolved "https://registry.npmmirror.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f"
integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/readdir-glob@*":
version "1.1.5"
resolved "https://registry.npmmirror.com/@types/readdir-glob/-/readdir-glob-1.1.5.tgz#21a4a98898fc606cb568ad815f2a0eedc24d412a"
@ -7046,7 +7072,17 @@
semver "^7.5.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/parser@^5.62.0", "@typescript-eslint/parser@^6.2.0":
"@typescript-eslint/parser@^5.62.0":
version "5.62.0"
resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7"
integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==
dependencies:
"@typescript-eslint/scope-manager" "5.62.0"
"@typescript-eslint/types" "5.62.0"
"@typescript-eslint/typescript-estree" "5.62.0"
debug "^4.3.4"
"@typescript-eslint/parser@^6.2.0":
version "6.21.0"
resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b"
integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==
@ -8125,7 +8161,7 @@ antd-token-previewer@^2.0.0-alpha.6:
use-debouncy "~4.3.0"
vanilla-jsoneditor "^0.16.1"
antd@5.12.8, antd@5.x, antd@^5.12.8, antd@^5.5.1:
antd@5.x, antd@^5.12.8, antd@^5.5.1, antd@^5.x:
version "5.12.8"
resolved "https://registry.npmmirror.com/antd/-/antd-5.12.8.tgz#94fea825e9bae535af7adf70417fa1f0dadc4c98"
integrity sha512-R2CRcB+aaVZurb3J0IKpBRWq5kW4CLcSqDF58/QBsqYdzK7XjSvM8+eF3rWVRUDbSJfGmyW7I80ywNRYpW1+vA==
@ -14908,7 +14944,7 @@ highlight.js@^10.1.0, highlight.js@^10.2.0:
resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
history@5.3.0:
history@5.3.0, history@^5.2.0:
version "5.3.0"
resolved "https://registry.npmmirror.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b"
integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==
@ -19337,7 +19373,7 @@ number-is-nan@^1.0.0:
resolved "https://registry.npmmirror.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
nwsapi@2.2.7, nwsapi@^2.2.0:
nwsapi@^2.2.0:
version "2.2.7"
resolved "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30"
integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==
@ -21871,7 +21907,15 @@ react-copy-to-clipboard@^5.1.0:
copy-to-clipboard "^3.3.1"
prop-types "^15.8.1"
react-dom@18.1.0, react-dom@18.x, react-dom@^18.0.0, react-dom@^18.2.0:
react-dom@18.1.0:
version "18.1.0"
resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.1.0.tgz#7f6dd84b706408adde05e1df575b3a024d7e8a2f"
integrity sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.22.0"
react-dom@18.x, react-dom@^18.0.0, react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@ -22063,7 +22107,15 @@ react-refresh@0.14.0, react-refresh@^0.14.0:
resolved "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
react-router-dom@6.3.0, react-router-dom@6.x, react-router-dom@^6.11.2, react-router-dom@^6.x:
react-router-dom@6.3.0:
version "6.3.0"
resolved "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d"
integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==
dependencies:
history "^5.2.0"
react-router "6.3.0"
react-router-dom@6.x, react-router-dom@^6.11.2, react-router-dom@^6.x:
version "6.21.0"
resolved "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.21.0.tgz#aa4c6bc046a8e8723095bc09b3c0ab2254532712"
integrity sha512-1dUdVj3cwc1npzJaf23gulB562ESNvxf7E4x8upNJycqyUm5BRRZ6dd3LrlzhtLaMrwOCO8R0zoiYxdaJx4LlQ==
@ -22071,12 +22123,19 @@ react-router-dom@6.3.0, react-router-dom@6.x, react-router-dom@^6.11.2, react-ro
"@remix-run/router" "1.14.0"
react-router "6.21.0"
react-router@6.21.0, react-router@6.3.0, react-router@^6.11.2:
version "6.23.1"
resolved "https://registry.npmmirror.com/react-router/-/react-router-6.23.1.tgz#d08cbdbd9d6aedc13eea6e94bc6d9b29cb1c4be9"
integrity sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==
react-router@6.21.0:
version "6.21.0"
resolved "https://registry.npmmirror.com/react-router/-/react-router-6.21.0.tgz#6fe3e59877aca3dccceec1801d26991ddf42d12b"
integrity sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==
dependencies:
"@remix-run/router" "1.16.1"
"@remix-run/router" "1.14.0"
react-router@6.3.0:
version "6.3.0"
resolved "https://registry.npmmirror.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557"
integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==
dependencies:
history "^5.2.0"
react-side-effect@^2.1.0:
version "2.1.2"
@ -22095,7 +22154,14 @@ react-to-print@^2.14.7:
resolved "https://registry.npmmirror.com/react-to-print/-/react-to-print-2.14.15.tgz#edb4ce8a92205cf37fd8c3d57de829034aa5c911"
integrity sha512-SKnwOzU2cJ8eaAkoJO7+gNhvfEDmm+Y34IdcHsjtHioUevUPhprqbVtvNJlZ2JkGJ8ExK2QNWM9pXECTDR5D8w==
react@18.1.0, react@18.x, react@^18.0.0, react@^18.2.0:
react@18.1.0:
version "18.1.0"
resolved "https://registry.npmmirror.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==
dependencies:
loose-envify "^1.1.0"
react@18.x, react@^18.0.0, react@^18.2.0:
version "18.2.0"
resolved "https://registry.npmmirror.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@ -23095,6 +23161,13 @@ saxes@^5.0.1:
dependencies:
xmlchars "^2.2.0"
scheduler@^0.22.0:
version "0.22.0"
resolved "https://registry.npmmirror.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8"
integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==
dependencies:
loose-envify "^1.1.0"
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"