mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
POC versioning slider
This commit is contained in:
parent
725c25c966
commit
59a0653fd4
5 changed files with 155 additions and 21 deletions
|
@ -16,6 +16,7 @@ import type {
|
||||||
FileId,
|
FileId,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
|
SceneElementsMap,
|
||||||
} from "../packages/excalidraw/element/types";
|
} from "../packages/excalidraw/element/types";
|
||||||
import { useCallbackRefState } from "../packages/excalidraw/hooks/useCallbackRefState";
|
import { useCallbackRefState } from "../packages/excalidraw/hooks/useCallbackRefState";
|
||||||
import { t } from "../packages/excalidraw/i18n";
|
import { t } from "../packages/excalidraw/i18n";
|
||||||
|
@ -134,6 +135,10 @@ import DebugCanvas, {
|
||||||
import { AIComponents } from "./components/AI";
|
import { AIComponents } from "./components/AI";
|
||||||
import { ExcalidrawPlusIframeExport } from "./ExcalidrawPlusIframeExport";
|
import { ExcalidrawPlusIframeExport } from "./ExcalidrawPlusIframeExport";
|
||||||
import { isElementLink } from "../packages/excalidraw/element/elementLink";
|
import { isElementLink } from "../packages/excalidraw/element/elementLink";
|
||||||
|
import type { ElementsChange } from "../packages/excalidraw/change";
|
||||||
|
|
||||||
|
import Slider from "rc-slider";
|
||||||
|
import "rc-slider/assets/index.css";
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
|
@ -365,16 +370,26 @@ const ExcalidrawWrapper = () => {
|
||||||
const [, setShareDialogState] = useAtom(shareDialogStateAtom);
|
const [, setShareDialogState] = useAtom(shareDialogStateAtom);
|
||||||
const [collabAPI] = useAtom(collabAPIAtom);
|
const [collabAPI] = useAtom(collabAPIAtom);
|
||||||
const [syncAPI] = useAtom(syncAPIAtom);
|
const [syncAPI] = useAtom(syncAPIAtom);
|
||||||
|
const [nextVersion, setNextVersion] = useState(-1);
|
||||||
|
const currentVersion = useRef(-1);
|
||||||
|
const [acknowledgedChanges, setAcknowledgedChanges] = useState<
|
||||||
|
ElementsChange[]
|
||||||
|
>([]);
|
||||||
const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => {
|
const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => {
|
||||||
return isCollaborationLink(window.location.href);
|
return isCollaborationLink(window.location.href);
|
||||||
});
|
});
|
||||||
const collabError = useAtomValue(collabErrorIndicatorAtom);
|
const collabError = useAtomValue(collabErrorIndicatorAtom);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setAcknowledgedChanges([...(syncAPI?.acknowledgedChanges ?? [])]);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
syncAPI?.reconnect();
|
syncAPI?.reconnect();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
syncAPI?.disconnect();
|
syncAPI?.disconnect();
|
||||||
|
clearInterval(interval);
|
||||||
};
|
};
|
||||||
}, [syncAPI]);
|
}, [syncAPI]);
|
||||||
|
|
||||||
|
@ -807,6 +822,44 @@ const ExcalidrawWrapper = () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debouncedTimeTravel = debounce((value: number) => {
|
||||||
|
let elements = new Map(
|
||||||
|
excalidrawAPI?.getSceneElements().map((x) => [x.id, x]),
|
||||||
|
);
|
||||||
|
|
||||||
|
let changes: ElementsChange[] = [];
|
||||||
|
|
||||||
|
const goingLeft =
|
||||||
|
currentVersion.current === -1 || value - currentVersion.current <= 0;
|
||||||
|
|
||||||
|
if (goingLeft) {
|
||||||
|
changes = acknowledgedChanges
|
||||||
|
.slice(value)
|
||||||
|
.reverse()
|
||||||
|
.map((x) => x.inverse());
|
||||||
|
} else {
|
||||||
|
changes = acknowledgedChanges.slice(currentVersion.current, value) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const change of changes) {
|
||||||
|
[elements] = change.applyTo(
|
||||||
|
elements as SceneElementsMap,
|
||||||
|
excalidrawAPI?.store.snapshot.elements!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
excalidrawAPI?.updateScene({
|
||||||
|
appState: {
|
||||||
|
...excalidrawAPI?.getAppState(),
|
||||||
|
viewModeEnabled: value !== acknowledgedChanges.length,
|
||||||
|
},
|
||||||
|
elements: Array.from(elements.values()),
|
||||||
|
storeAction: StoreAction.UPDATE,
|
||||||
|
});
|
||||||
|
|
||||||
|
currentVersion.current = value;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{ height: "100%" }}
|
style={{ height: "100%" }}
|
||||||
|
@ -814,6 +867,23 @@ const ExcalidrawWrapper = () => {
|
||||||
"is-collaborating": isCollaborating,
|
"is-collaborating": isCollaborating,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
<Slider
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
bottom: "25px",
|
||||||
|
zIndex: 999,
|
||||||
|
width: "60%",
|
||||||
|
left: "25%",
|
||||||
|
}}
|
||||||
|
step={1}
|
||||||
|
min={0}
|
||||||
|
max={acknowledgedChanges.length}
|
||||||
|
value={nextVersion === -1 ? acknowledgedChanges.length : nextVersion}
|
||||||
|
onChange={(value) => {
|
||||||
|
setNextVersion(value as number);
|
||||||
|
debouncedTimeTravel(value as number);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
excalidrawAPI={excalidrawRefCallback}
|
excalidrawAPI={excalidrawRefCallback}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"i18next-browser-languagedetector": "6.1.4",
|
"i18next-browser-languagedetector": "6.1.4",
|
||||||
"idb-keyval": "6.0.3",
|
"idb-keyval": "6.0.3",
|
||||||
"jotai": "2.11.0",
|
"jotai": "2.11.0",
|
||||||
|
"rc-slider": "11.1.7",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"socket.io-client": "4.7.2",
|
"socket.io-client": "4.7.2",
|
||||||
|
|
|
@ -19,8 +19,8 @@ export class DurableChangesRepository implements ChangesRepository {
|
||||||
);`);
|
);`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveAll = (changes: Array<CLIENT_CHANGE>) =>
|
public saveAll = (changes: Array<CLIENT_CHANGE>) => {
|
||||||
this.storage.transactionSync(() => {
|
return this.storage.transactionSync(() => {
|
||||||
const prevVersion = this.getLastVersion();
|
const prevVersion = this.getLastVersion();
|
||||||
const nextVersion = prevVersion + changes.length;
|
const nextVersion = prevVersion + changes.length;
|
||||||
|
|
||||||
|
@ -45,14 +45,16 @@ export class DurableChangesRepository implements ChangesRepository {
|
||||||
|
|
||||||
return this.getSinceVersion(prevVersion);
|
return this.getSinceVersion(prevVersion);
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
public getSinceVersion = (version: number): Array<SERVER_CHANGE> =>
|
public getSinceVersion = (version: number): Array<SERVER_CHANGE> => {
|
||||||
this.storage.sql
|
return this.storage.sql
|
||||||
.exec<SERVER_CHANGE>(
|
.exec<SERVER_CHANGE>(
|
||||||
`SELECT id, payload, version FROM changes WHERE version > (?) ORDER BY version ASC;`,
|
`SELECT id, payload, version FROM changes WHERE version > (?) ORDER BY version ASC;`,
|
||||||
version,
|
version,
|
||||||
)
|
)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
};
|
||||||
|
|
||||||
public getLastVersion = (): number => {
|
public getLastVersion = (): number => {
|
||||||
const result = this.storage.sql
|
const result = this.storage.sql
|
||||||
|
|
|
@ -13,7 +13,7 @@ export class ExcalidrawSyncClient {
|
||||||
: "https://excalidraw-sync.marcel-529.workers.dev";
|
: "https://excalidraw-sync.marcel-529.workers.dev";
|
||||||
|
|
||||||
private static readonly ROOM_ID = import.meta.env.DEV
|
private static readonly ROOM_ID = import.meta.env.DEV
|
||||||
? "test_room_dev"
|
? "test_room_x"
|
||||||
: "test_room_prod";
|
: "test_room_prod";
|
||||||
|
|
||||||
private static readonly RECONNECT_INTERVAL = 10_000;
|
private static readonly RECONNECT_INTERVAL = 10_000;
|
||||||
|
@ -22,9 +22,14 @@ export class ExcalidrawSyncClient {
|
||||||
|
|
||||||
private readonly api: ExcalidrawImperativeAPI;
|
private readonly api: ExcalidrawImperativeAPI;
|
||||||
private readonly roomId: string;
|
private readonly roomId: string;
|
||||||
private readonly queuedChanges: Map<string, CLIENT_CHANGE> = new Map();
|
private readonly queuedChanges: Map<
|
||||||
|
string,
|
||||||
|
{ queuedAt: number; change: CLIENT_CHANGE }
|
||||||
|
> = new Map();
|
||||||
|
public readonly acknowledgedChanges: Array<ElementsChange> = [];
|
||||||
|
|
||||||
private get localChanges() {
|
private get localChanges() {
|
||||||
return Array.from(this.queuedChanges.values());
|
return Array.from(this.queuedChanges.values()).map(({ change }) => change);
|
||||||
}
|
}
|
||||||
|
|
||||||
private server: WebSocket | null = null;
|
private server: WebSocket | null = null;
|
||||||
|
@ -45,6 +50,7 @@ export class ExcalidrawSyncClient {
|
||||||
this.lastAcknowledgedVersion = 0;
|
this.lastAcknowledgedVersion = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: throttle does not work that well here (after some period it tries to reconnect too often)
|
||||||
public reconnect = throttle(
|
public reconnect = throttle(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -58,7 +64,7 @@ export class ExcalidrawSyncClient {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.trace("Reconnecting to the sync server...");
|
console.info("Reconnecting to the sync server...");
|
||||||
|
|
||||||
const isConnecting = {
|
const isConnecting = {
|
||||||
done: () => {},
|
done: () => {},
|
||||||
|
@ -109,6 +115,7 @@ export class ExcalidrawSyncClient {
|
||||||
this.server?.removeEventListener("close", this.onClose);
|
this.server?.removeEventListener("close", this.onClose);
|
||||||
this.server?.removeEventListener("error", this.onError);
|
this.server?.removeEventListener("error", this.onError);
|
||||||
this.server?.removeEventListener("open", this.onOpen);
|
this.server?.removeEventListener("open", this.onOpen);
|
||||||
|
this.server?.close();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.isConnecting?.done(error);
|
this.isConnecting?.done(error);
|
||||||
|
@ -143,15 +150,19 @@ export class ExcalidrawSyncClient {
|
||||||
this.pull();
|
this.pull();
|
||||||
};
|
};
|
||||||
|
|
||||||
private onClose = () =>
|
private onClose = (event: CloseEvent) => {
|
||||||
|
console.log("close", event);
|
||||||
this.disconnect(
|
this.disconnect(
|
||||||
new Error(`Received "closed" event on the sync connection`),
|
new Error(`Received "${event.type}" event on the sync connection`),
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private onError = (event: Event) =>
|
private onError = (event: Event) => {
|
||||||
|
console.log("error", event);
|
||||||
this.disconnect(
|
this.disconnect(
|
||||||
new Error(`Received "${event.type}" on the sync connection`),
|
new Error(`Received "${event.type}" on the sync connection`),
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: could be an array buffer
|
// TODO: could be an array buffer
|
||||||
private onMessage = (event: MessageEvent) => {
|
private onMessage = (event: MessageEvent) => {
|
||||||
|
@ -193,7 +204,10 @@ export class ExcalidrawSyncClient {
|
||||||
if (type === "durable") {
|
if (type === "durable") {
|
||||||
// TODO: persist in idb (with insertion order)
|
// TODO: persist in idb (with insertion order)
|
||||||
for (const change of changes) {
|
for (const change of changes) {
|
||||||
this.queuedChanges.set(change.id, change);
|
this.queuedChanges.set(change.id, {
|
||||||
|
queuedAt: Date.now(),
|
||||||
|
change,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// batch all queued changes
|
// batch all queued changes
|
||||||
|
@ -226,21 +240,20 @@ export class ExcalidrawSyncClient {
|
||||||
this.api.getSceneElementsIncludingDeleted().map((el) => [el.id, el]),
|
this.api.getSceneElementsIncludingDeleted().map((el) => [el.id, el]),
|
||||||
) as SceneElementsMap;
|
) as SceneElementsMap;
|
||||||
|
|
||||||
console.log("remote changes", remoteChanges);
|
|
||||||
console.log("local changes", this.localChanges);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// apply remote changes
|
// apply remote changes
|
||||||
for (const remoteChange of remoteChanges) {
|
for (const remoteChange of remoteChanges) {
|
||||||
if (this.queuedChanges.has(remoteChange.id)) {
|
if (this.queuedChanges.has(remoteChange.id)) {
|
||||||
|
const { change, queuedAt } = this.queuedChanges.get(remoteChange.id)!;
|
||||||
|
this.acknowledgedChanges.push(change);
|
||||||
|
console.info(
|
||||||
|
`Acknowledged change "${remoteChange.id}" after ${
|
||||||
|
Date.now() - queuedAt
|
||||||
|
}ms`,
|
||||||
|
);
|
||||||
// local change acknowledge by the server, safe to remove
|
// local change acknowledge by the server, safe to remove
|
||||||
this.queuedChanges.delete(remoteChange.id);
|
this.queuedChanges.delete(remoteChange.id);
|
||||||
} else {
|
} else {
|
||||||
[elements] = ElementsChange.load(remoteChange.payload).applyTo(
|
|
||||||
elements,
|
|
||||||
this.api.store.snapshot.elements,
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: we might not need to be that strict here
|
// TODO: we might not need to be that strict here
|
||||||
if (this.lastAcknowledgedVersion + 1 !== remoteChange.version) {
|
if (this.lastAcknowledgedVersion + 1 !== remoteChange.version) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -249,11 +262,25 @@ export class ExcalidrawSyncClient {
|
||||||
}", but received "${remoteChange.version}"`,
|
}", but received "${remoteChange.version}"`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const change = ElementsChange.load(remoteChange.payload);
|
||||||
|
[elements] = change.applyTo(
|
||||||
|
elements,
|
||||||
|
this.api.store.snapshot.elements,
|
||||||
|
);
|
||||||
|
this.acknowledgedChanges.push(change);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastAcknowledgedVersion = remoteChange.version;
|
this.lastAcknowledgedVersion = remoteChange.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.debug(`${now()} remote changes`, remoteChanges);
|
||||||
|
console.debug(`${now()} local changes`, this.localChanges);
|
||||||
|
console.debug(
|
||||||
|
`${now()} acknowledged changes`,
|
||||||
|
this.acknowledgedChanges.slice(-remoteChanges.length),
|
||||||
|
);
|
||||||
|
|
||||||
// apply local changes
|
// apply local changes
|
||||||
// TODO: only necessary when remote changes modified same element properties!
|
// TODO: only necessary when remote changes modified same element properties!
|
||||||
for (const localChange of this.localChanges) {
|
for (const localChange of this.localChanges) {
|
||||||
|
@ -298,3 +325,8 @@ export class ExcalidrawSyncClient {
|
||||||
this.server?.send(JSON.stringify(message));
|
this.server?.send(JSON.stringify(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const now = () => {
|
||||||
|
const date = new Date();
|
||||||
|
return `[${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}]`;
|
||||||
|
};
|
||||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -1412,6 +1412,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||||
|
|
||||||
|
"@babel/runtime@^7.10.1", "@babel/runtime@^7.18.3":
|
||||||
|
version "7.26.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
|
||||||
|
integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.6", "@babel/runtime@^7.16.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.6", "@babel/runtime@^7.16.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||||
version "7.24.6"
|
version "7.24.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.6.tgz#5b76eb89ad45e2e4a0a8db54c456251469a3358e"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.6.tgz#5b76eb89ad45e2e4a0a8db54c456251469a3358e"
|
||||||
|
@ -4829,6 +4836,11 @@ ci-info@^3.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
||||||
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
|
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
|
||||||
|
|
||||||
|
classnames@^2.2.5:
|
||||||
|
version "2.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||||
|
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
|
||||||
|
|
||||||
clean-css@^5.2.2:
|
clean-css@^5.2.2:
|
||||||
version "5.3.3"
|
version "5.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd"
|
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd"
|
||||||
|
@ -9272,6 +9284,23 @@ randombytes@^2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "^5.1.0"
|
safe-buffer "^5.1.0"
|
||||||
|
|
||||||
|
rc-slider@11.1.7:
|
||||||
|
version "11.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/rc-slider/-/rc-slider-11.1.7.tgz#3de333b1ec84d53a7bda2f816bb4779423628f09"
|
||||||
|
integrity sha512-ytYbZei81TX7otdC0QvoYD72XSlxvTihNth5OeZ6PMXyEDq/vHdWFulQmfDGyXK1NwKwSlKgpvINOa88uT5g2A==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.10.1"
|
||||||
|
classnames "^2.2.5"
|
||||||
|
rc-util "^5.36.0"
|
||||||
|
|
||||||
|
rc-util@^5.36.0:
|
||||||
|
version "5.43.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.43.0.tgz#bba91fbef2c3e30ea2c236893746f3e9b05ecc4c"
|
||||||
|
integrity sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.18.3"
|
||||||
|
react-is "^18.2.0"
|
||||||
|
|
||||||
react-dom@18.2.0:
|
react-dom@18.2.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||||
|
@ -9290,7 +9319,7 @@ react-is@^17.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||||
|
|
||||||
react-is@^18.0.0:
|
react-is@^18.0.0, react-is@^18.2.0:
|
||||||
version "18.3.1"
|
version "18.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
|
||||||
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
|
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue