mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Expose store, a bit
This commit is contained in:
parent
52eaf64591
commit
245d681b7d
6 changed files with 86 additions and 51 deletions
|
@ -107,6 +107,7 @@ import Trans from "../packages/excalidraw/components/Trans";
|
||||||
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
|
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
|
||||||
import CollabError, { collabErrorIndicatorAtom } from "./collab/CollabError";
|
import CollabError, { collabErrorIndicatorAtom } from "./collab/CollabError";
|
||||||
import type { RemoteExcalidrawElement } from "../packages/excalidraw/data/reconcile";
|
import type { RemoteExcalidrawElement } from "../packages/excalidraw/data/reconcile";
|
||||||
|
import type { StoreIncrementEvent } from "../packages/excalidraw/store";
|
||||||
import {
|
import {
|
||||||
CommandPalette,
|
CommandPalette,
|
||||||
DEFAULT_CATEGORIES,
|
DEFAULT_CATEGORIES,
|
||||||
|
@ -663,6 +664,17 @@ const ExcalidrawWrapper = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onIncrement = (increment: StoreIncrementEvent) => {
|
||||||
|
// ephemerals are not part of this (which is alright)
|
||||||
|
// - wysiwyg, dragging elements / points, mouse movements, etc.
|
||||||
|
const { elementsChange } = increment;
|
||||||
|
|
||||||
|
// some appState like selections should also be transfered (we could even persist it)
|
||||||
|
if (!elementsChange.isEmpty()) {
|
||||||
|
console.log(elementsChange)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const [latestShareableLink, setLatestShareableLink] = useState<string | null>(
|
const [latestShareableLink, setLatestShareableLink] = useState<string | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
@ -795,6 +807,7 @@ const ExcalidrawWrapper = () => {
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
excalidrawAPI={excalidrawRefCallback}
|
excalidrawAPI={excalidrawRefCallback}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onIncrement={onIncrement}
|
||||||
initialData={initialStatePromiseRef.current.promise}
|
initialData={initialStatePromiseRef.current.promise}
|
||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
onPointerUpdate={collabAPI?.onPointerUpdate}
|
onPointerUpdate={collabAPI?.onPointerUpdate}
|
||||||
|
|
|
@ -806,33 +806,37 @@ type ElementPartial<T extends ExcalidrawElement = ExcalidrawElement> = Omit<
|
||||||
*/
|
*/
|
||||||
export class ElementsChange implements Change<SceneElementsMap> {
|
export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
private constructor(
|
private constructor(
|
||||||
private readonly added: Map<string, Delta<ElementPartial>>,
|
private readonly added: Record<string, Delta<ElementPartial>>,
|
||||||
private readonly removed: Map<string, Delta<ElementPartial>>,
|
private readonly removed: Record<string, Delta<ElementPartial>>,
|
||||||
private readonly updated: Map<string, Delta<ElementPartial>>,
|
private readonly updated: Record<string, Delta<ElementPartial>>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public static create(
|
public static create(
|
||||||
added: Map<string, Delta<ElementPartial>>,
|
added: Record<string, Delta<ElementPartial>>,
|
||||||
removed: Map<string, Delta<ElementPartial>>,
|
removed: Record<string, Delta<ElementPartial>>,
|
||||||
updated: Map<string, Delta<ElementPartial>>,
|
updated: Record<string, Delta<ElementPartial>>,
|
||||||
options = { shouldRedistribute: false },
|
options = { shouldRedistribute: false },
|
||||||
) {
|
) {
|
||||||
let change: ElementsChange;
|
let change: ElementsChange;
|
||||||
|
|
||||||
if (options.shouldRedistribute) {
|
if (options.shouldRedistribute) {
|
||||||
const nextAdded = new Map<string, Delta<ElementPartial>>();
|
const nextAdded: Record<string, Delta<ElementPartial>> = {};
|
||||||
const nextRemoved = new Map<string, Delta<ElementPartial>>();
|
const nextRemoved: Record<string, Delta<ElementPartial>> = {};
|
||||||
const nextUpdated = new Map<string, Delta<ElementPartial>>();
|
const nextUpdated: Record<string, Delta<ElementPartial>> = {};
|
||||||
|
|
||||||
const deltas = [...added, ...removed, ...updated];
|
const deltas = [
|
||||||
|
...Object.entries(added),
|
||||||
|
...Object.entries(removed),
|
||||||
|
...Object.entries(updated),
|
||||||
|
];
|
||||||
|
|
||||||
for (const [id, delta] of deltas) {
|
for (const [id, delta] of deltas) {
|
||||||
if (this.satisfiesAddition(delta)) {
|
if (this.satisfiesAddition(delta)) {
|
||||||
nextAdded.set(id, delta);
|
nextAdded[id] = delta;
|
||||||
} else if (this.satisfiesRemoval(delta)) {
|
} else if (this.satisfiesRemoval(delta)) {
|
||||||
nextRemoved.set(id, delta);
|
nextRemoved[id] = delta;
|
||||||
} else {
|
} else {
|
||||||
nextUpdated.set(id, delta);
|
nextUpdated[id] = delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -873,7 +877,7 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
type: "added" | "removed" | "updated",
|
type: "added" | "removed" | "updated",
|
||||||
satifies: (delta: Delta<ElementPartial>) => boolean,
|
satifies: (delta: Delta<ElementPartial>) => boolean,
|
||||||
) {
|
) {
|
||||||
for (const [id, delta] of change[type].entries()) {
|
for (const [id, delta] of Object.entries(change[type])) {
|
||||||
if (!satifies(delta)) {
|
if (!satifies(delta)) {
|
||||||
console.error(
|
console.error(
|
||||||
`Broken invariant for "${type}" delta, element "${id}", delta:`,
|
`Broken invariant for "${type}" delta, element "${id}", delta:`,
|
||||||
|
@ -900,9 +904,9 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
return ElementsChange.empty();
|
return ElementsChange.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
const added = new Map<string, Delta<ElementPartial>>();
|
const added: Record<string, Delta<ElementPartial>> = {};
|
||||||
const removed = new Map<string, Delta<ElementPartial>>();
|
const removed: Record<string, Delta<ElementPartial>> = {};
|
||||||
const updated = new Map<string, Delta<ElementPartial>>();
|
const updated: Record<string, Delta<ElementPartial>> = {};
|
||||||
|
|
||||||
// this might be needed only in same edge cases, like during collab, when `isDeleted` elements get removed or when we (un)intentionally remove the elements
|
// this might be needed only in same edge cases, like during collab, when `isDeleted` elements get removed or when we (un)intentionally remove the elements
|
||||||
for (const prevElement of prevElements.values()) {
|
for (const prevElement of prevElements.values()) {
|
||||||
|
@ -918,7 +922,7 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
ElementsChange.stripIrrelevantProps,
|
ElementsChange.stripIrrelevantProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
removed.set(prevElement.id, delta);
|
removed[prevElement.id] = delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -938,7 +942,7 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
ElementsChange.stripIrrelevantProps,
|
ElementsChange.stripIrrelevantProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
added.set(nextElement.id, delta);
|
added[nextElement.id] = delta;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -959,9 +963,9 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
) {
|
) {
|
||||||
// notice that other props could have been updated as well
|
// notice that other props could have been updated as well
|
||||||
if (prevElement.isDeleted && !nextElement.isDeleted) {
|
if (prevElement.isDeleted && !nextElement.isDeleted) {
|
||||||
added.set(nextElement.id, delta);
|
added[nextElement.id] = delta;
|
||||||
} else {
|
} else {
|
||||||
removed.set(nextElement.id, delta);
|
removed[nextElement.id] = delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
@ -969,7 +973,7 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
|
|
||||||
// making sure there are at least some changes
|
// making sure there are at least some changes
|
||||||
if (!Delta.isEmpty(delta)) {
|
if (!Delta.isEmpty(delta)) {
|
||||||
updated.set(nextElement.id, delta);
|
updated[nextElement.id] = delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -978,15 +982,23 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static empty() {
|
public static empty() {
|
||||||
return ElementsChange.create(new Map(), new Map(), new Map());
|
return ElementsChange.create({}, {}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static load(data: {
|
||||||
|
added: Record<string, Delta<ElementPartial>>;
|
||||||
|
removed: Record<string, Delta<ElementPartial>>;
|
||||||
|
updated: Record<string, Delta<ElementPartial>>;
|
||||||
|
}) {
|
||||||
|
return ElementsChange.create(data.added, data.removed, data.updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
public inverse(): ElementsChange {
|
public inverse(): ElementsChange {
|
||||||
const inverseInternal = (deltas: Map<string, Delta<ElementPartial>>) => {
|
const inverseInternal = (deltas: Record<string, Delta<ElementPartial>>) => {
|
||||||
const inversedDeltas = new Map<string, Delta<ElementPartial>>();
|
const inversedDeltas: Record<string, Delta<ElementPartial>> = {};
|
||||||
|
|
||||||
for (const [id, delta] of deltas.entries()) {
|
for (const [id, delta] of Object.entries(deltas)) {
|
||||||
inversedDeltas.set(id, Delta.create(delta.inserted, delta.deleted));
|
inversedDeltas[id] = Delta.create(delta.inserted, delta.deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
return inversedDeltas;
|
return inversedDeltas;
|
||||||
|
@ -1002,9 +1014,9 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
|
|
||||||
public isEmpty(): boolean {
|
public isEmpty(): boolean {
|
||||||
return (
|
return (
|
||||||
this.added.size === 0 &&
|
Object.keys(this.added).length === 0 &&
|
||||||
this.removed.size === 0 &&
|
Object.keys(this.removed).length === 0 &&
|
||||||
this.updated.size === 0
|
Object.keys(this.updated).length === 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1036,11 +1048,11 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyLatestChangesInternal = (
|
const applyLatestChangesInternal = (
|
||||||
deltas: Map<string, Delta<ElementPartial>>,
|
deltas: Record<string, Delta<ElementPartial>>,
|
||||||
) => {
|
) => {
|
||||||
const modifiedDeltas = new Map<string, Delta<ElementPartial>>();
|
const modifiedDeltas: Record<string, Delta<ElementPartial>> = {};
|
||||||
|
|
||||||
for (const [id, delta] of deltas.entries()) {
|
for (const [id, delta] of Object.entries(deltas)) {
|
||||||
const existingElement = elements.get(id);
|
const existingElement = elements.get(id);
|
||||||
|
|
||||||
if (existingElement) {
|
if (existingElement) {
|
||||||
|
@ -1051,9 +1063,9 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
"inserted",
|
"inserted",
|
||||||
);
|
);
|
||||||
|
|
||||||
modifiedDeltas.set(id, modifiedDelta);
|
modifiedDeltas[id] = modifiedDelta;
|
||||||
} else {
|
} else {
|
||||||
modifiedDeltas.set(id, delta);
|
modifiedDeltas[id] = delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1158,8 +1170,8 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
flags,
|
flags,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (deltas: Map<string, Delta<ElementPartial>>) =>
|
return (deltas: Record<string, Delta<ElementPartial>>) =>
|
||||||
Array.from(deltas.entries()).reduce((acc, [id, delta]) => {
|
Object.entries(deltas).reduce((acc, [id, delta]) => {
|
||||||
const element = getElement(id, delta.inserted);
|
const element = getElement(id, delta.inserted);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
|
@ -1331,17 +1343,18 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// removed delta is affecting the bindings always, as all the affected elements of the removed elements need to be unbound
|
// removed delta is affecting the bindings always, as all the affected elements of the removed elements need to be unbound
|
||||||
for (const [id] of this.removed) {
|
for (const id of Object.keys(this.removed)) {
|
||||||
ElementsChange.unbindAffected(prevElements, nextElements, id, updater);
|
ElementsChange.unbindAffected(prevElements, nextElements, id, updater);
|
||||||
}
|
}
|
||||||
|
|
||||||
// added delta is affecting the bindings always, all the affected elements of the added elements need to be rebound
|
// added delta is affecting the bindings always, all the affected elements of the added elements need to be rebound
|
||||||
for (const [id] of this.added) {
|
for (const id of Object.keys(this.added)) {
|
||||||
ElementsChange.rebindAffected(prevElements, nextElements, id, updater);
|
ElementsChange.rebindAffected(prevElements, nextElements, id, updater);
|
||||||
}
|
}
|
||||||
|
|
||||||
// updated delta is affecting the binding only in case it contains changed binding or bindable property
|
// updated delta is affecting the binding only in case it contains changed binding or bindable property
|
||||||
for (const [id] of Array.from(this.updated).filter(([_, delta]) =>
|
for (const [id] of Array.from(Object.entries(this.updated)).filter(
|
||||||
|
([_, delta]) =>
|
||||||
Object.keys({ ...delta.deleted, ...delta.inserted }).find((prop) =>
|
Object.keys({ ...delta.deleted, ...delta.inserted }).find((prop) =>
|
||||||
bindingProperties.has(prop as BindingProp | BindableProp),
|
bindingProperties.has(prop as BindingProp | BindableProp),
|
||||||
),
|
),
|
||||||
|
@ -1367,16 +1380,16 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
||||||
nextAffectedElements,
|
nextAffectedElements,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const [id, delta] of added) {
|
for (const [id, delta] of Object.entries(added)) {
|
||||||
this.added.set(id, delta);
|
this.added[id] = delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [id, delta] of removed) {
|
for (const [id, delta] of Object.entries(removed)) {
|
||||||
this.removed.set(id, delta);
|
this.removed[id] = delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [id, delta] of updated) {
|
for (const [id, delta] of Object.entries(updated)) {
|
||||||
this.updated.set(id, delta);
|
this.updated[id] = delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextAffectedElements;
|
return nextAffectedElements;
|
||||||
|
|
|
@ -739,6 +739,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
updateFrameRendering: this.updateFrameRendering,
|
updateFrameRendering: this.updateFrameRendering,
|
||||||
toggleSidebar: this.toggleSidebar,
|
toggleSidebar: this.toggleSidebar,
|
||||||
onChange: (cb) => this.onChangeEmitter.on(cb),
|
onChange: (cb) => this.onChangeEmitter.on(cb),
|
||||||
|
onIncrement: (cb) => this.store.onStoreIncrementEmitter.on(cb),
|
||||||
onPointerDown: (cb) => this.onPointerDownEmitter.on(cb),
|
onPointerDown: (cb) => this.onPointerDownEmitter.on(cb),
|
||||||
onPointerUp: (cb) => this.onPointerUpEmitter.on(cb),
|
onPointerUp: (cb) => this.onPointerUpEmitter.on(cb),
|
||||||
onScrollChange: (cb) => this.onScrollChangeEmitter.on(cb),
|
onScrollChange: (cb) => this.onScrollChangeEmitter.on(cb),
|
||||||
|
@ -2462,6 +2463,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
|
|
||||||
this.store.onStoreIncrementEmitter.on((increment) => {
|
this.store.onStoreIncrementEmitter.on((increment) => {
|
||||||
this.history.record(increment.elementsChange, increment.appStateChange);
|
this.history.record(increment.elementsChange, increment.appStateChange);
|
||||||
|
this.props.onIncrement?.(increment);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scene.onUpdate(this.triggerRender);
|
this.scene.onUpdate(this.triggerRender);
|
||||||
|
|
|
@ -22,6 +22,7 @@ polyfill();
|
||||||
const ExcalidrawBase = (props: ExcalidrawProps) => {
|
const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||||
const {
|
const {
|
||||||
onChange,
|
onChange,
|
||||||
|
onIncrement,
|
||||||
initialData,
|
initialData,
|
||||||
excalidrawAPI,
|
excalidrawAPI,
|
||||||
isCollaborating = false,
|
isCollaborating = false,
|
||||||
|
@ -111,6 +112,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||||
<InitializeApp langCode={langCode} theme={theme}>
|
<InitializeApp langCode={langCode} theme={theme}>
|
||||||
<App
|
<App
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onIncrement={onIncrement}
|
||||||
initialData={initialData}
|
initialData={initialData}
|
||||||
excalidrawAPI={excalidrawAPI}
|
excalidrawAPI={excalidrawAPI}
|
||||||
isCollaborating={isCollaborating}
|
isCollaborating={isCollaborating}
|
||||||
|
|
|
@ -75,7 +75,7 @@ export type StoreActionType = ValueOf<typeof StoreAction>;
|
||||||
/**
|
/**
|
||||||
* Represent an increment to the Store.
|
* Represent an increment to the Store.
|
||||||
*/
|
*/
|
||||||
class StoreIncrementEvent {
|
export class StoreIncrementEvent {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly elementsChange: ElementsChange,
|
public readonly elementsChange: ElementsChange,
|
||||||
public readonly appStateChange: AppStateChange,
|
public readonly appStateChange: AppStateChange,
|
||||||
|
@ -395,6 +395,7 @@ export class Snapshot {
|
||||||
!prev ||
|
!prev ||
|
||||||
!next ||
|
!next ||
|
||||||
prev.id !== next.id ||
|
prev.id !== next.id ||
|
||||||
|
prev.version !== next.version ||
|
||||||
prev.versionNonce !== next.versionNonce
|
prev.versionNonce !== next.versionNonce
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -40,7 +40,7 @@ import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||||
import type { ContextMenuItems } from "./components/ContextMenu";
|
import type { ContextMenuItems } from "./components/ContextMenu";
|
||||||
import type { SnapLine } from "./snapping";
|
import type { SnapLine } from "./snapping";
|
||||||
import type { Merge, MaybePromise, ValueOf, MakeBrand } from "./utility-types";
|
import type { Merge, MaybePromise, ValueOf, MakeBrand } from "./utility-types";
|
||||||
import type { StoreActionType } from "./store";
|
import type { StoreActionType, StoreIncrementEvent } from "./store";
|
||||||
|
|
||||||
export type SocketId = string & { _brand: "SocketId" };
|
export type SocketId = string & { _brand: "SocketId" };
|
||||||
|
|
||||||
|
@ -498,6 +498,7 @@ export interface ExcalidrawProps {
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => void;
|
) => void;
|
||||||
|
onIncrement?: (event: StoreIncrementEvent) => void;
|
||||||
initialData?:
|
initialData?:
|
||||||
| (() => MaybePromise<ExcalidrawInitialDataState | null>)
|
| (() => MaybePromise<ExcalidrawInitialDataState | null>)
|
||||||
| MaybePromise<ExcalidrawInitialDataState | null>;
|
| MaybePromise<ExcalidrawInitialDataState | null>;
|
||||||
|
@ -782,6 +783,9 @@ export interface ExcalidrawImperativeAPI {
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => void,
|
) => void,
|
||||||
) => UnsubscribeCallback;
|
) => UnsubscribeCallback;
|
||||||
|
onIncrement: (
|
||||||
|
callback: (event: StoreIncrementEvent) => void,
|
||||||
|
) => UnsubscribeCallback;
|
||||||
onPointerDown: (
|
onPointerDown: (
|
||||||
callback: (
|
callback: (
|
||||||
activeTool: AppState["activeTool"],
|
activeTool: AppState["activeTool"],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue