Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94005b790a | ||
|
|
cc4c51996c | ||
|
|
79257a1923 | ||
|
|
dc66261c19 | ||
|
|
273ba803d9 | ||
|
|
301e83805d | ||
|
|
ed5ce8d3de |
@@ -126,6 +126,38 @@ polyfill();
|
|||||||
|
|
||||||
window.EXCALIDRAW_THROTTLE_RENDER = true;
|
window.EXCALIDRAW_THROTTLE_RENDER = true;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface BeforeInstallPromptEventChoiceResult {
|
||||||
|
outcome: "accepted" | "dismissed";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BeforeInstallPromptEvent extends Event {
|
||||||
|
prompt(): Promise<void>;
|
||||||
|
userChoice: Promise<BeforeInstallPromptEventChoiceResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WindowEventMap {
|
||||||
|
beforeinstallprompt: BeforeInstallPromptEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pwaEvent: BeforeInstallPromptEvent | null = null;
|
||||||
|
|
||||||
|
// Adding a listener outside of the component as it may (?) need to be
|
||||||
|
// subscribed early to catch the event.
|
||||||
|
//
|
||||||
|
// Also note that it will fire only if certain heuristics are met (user has
|
||||||
|
// used the app for some time, etc.)
|
||||||
|
window.addEventListener(
|
||||||
|
"beforeinstallprompt",
|
||||||
|
(event: BeforeInstallPromptEvent) => {
|
||||||
|
// prevent Chrome <= 67 from automatically showing the prompt
|
||||||
|
event.preventDefault();
|
||||||
|
// cache for later use
|
||||||
|
pwaEvent = event;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let isSelfEmbedding = false;
|
let isSelfEmbedding = false;
|
||||||
|
|
||||||
if (window.self !== window.top) {
|
if (window.self !== window.top) {
|
||||||
@@ -1100,6 +1132,21 @@ const ExcalidrawWrapper = () => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t("labels.installPWA"),
|
||||||
|
category: DEFAULT_CATEGORIES.app,
|
||||||
|
predicate: () => !!pwaEvent,
|
||||||
|
perform: () => {
|
||||||
|
if (pwaEvent) {
|
||||||
|
pwaEvent.prompt();
|
||||||
|
pwaEvent.userChoice.then(() => {
|
||||||
|
// event cannot be reused, but we'll hopefully
|
||||||
|
// grab new one as the event should be fired again
|
||||||
|
pwaEvent = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Excalidraw>
|
</Excalidraw>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "excalidraw-monorepo",
|
"name": "excalidraw-monorepo",
|
||||||
|
"packageManager": "yarn@1.22.22",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"excalidraw-app",
|
"excalidraw-app",
|
||||||
"packages/excalidraw",
|
"packages/excalidraw",
|
||||||
|
|||||||
@@ -714,10 +714,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.fonts = new Fonts({
|
this.fonts = new Fonts({ scene: this.scene });
|
||||||
scene: this.scene,
|
|
||||||
onSceneUpdated: this.onSceneUpdated,
|
|
||||||
});
|
|
||||||
this.history = new History();
|
this.history = new History();
|
||||||
|
|
||||||
this.actionManager.registerAll(actions);
|
this.actionManager.registerAll(actions);
|
||||||
@@ -940,7 +937,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
this.scene.informMutation();
|
this.scene.triggerUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// GC
|
// GC
|
||||||
@@ -1452,10 +1449,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||||
const { renderTopRightUI, renderCustomStats } = this.props;
|
const { renderTopRightUI, renderCustomStats } = this.props;
|
||||||
|
|
||||||
const versionNonce = this.scene.getVersionNonce();
|
const sceneNonce = this.scene.getSceneNonce();
|
||||||
const { elementsMap, visibleElements } =
|
const { elementsMap, visibleElements } =
|
||||||
this.renderer.getRenderableElements({
|
this.renderer.getRenderableElements({
|
||||||
versionNonce,
|
sceneNonce,
|
||||||
zoom: this.state.zoom,
|
zoom: this.state.zoom,
|
||||||
offsetLeft: this.state.offsetLeft,
|
offsetLeft: this.state.offsetLeft,
|
||||||
offsetTop: this.state.offsetTop,
|
offsetTop: this.state.offsetTop,
|
||||||
@@ -1673,7 +1670,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementsMap={elementsMap}
|
elementsMap={elementsMap}
|
||||||
allElementsMap={allElementsMap}
|
allElementsMap={allElementsMap}
|
||||||
visibleElements={visibleElements}
|
visibleElements={visibleElements}
|
||||||
versionNonce={versionNonce}
|
sceneNonce={sceneNonce}
|
||||||
selectionNonce={
|
selectionNonce={
|
||||||
this.state.selectionElement?.versionNonce
|
this.state.selectionElement?.versionNonce
|
||||||
}
|
}
|
||||||
@@ -1695,7 +1692,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementsMap={elementsMap}
|
elementsMap={elementsMap}
|
||||||
visibleElements={visibleElements}
|
visibleElements={visibleElements}
|
||||||
selectedElements={selectedElements}
|
selectedElements={selectedElements}
|
||||||
versionNonce={versionNonce}
|
sceneNonce={sceneNonce}
|
||||||
selectionNonce={
|
selectionNonce={
|
||||||
this.state.selectionElement?.versionNonce
|
this.state.selectionElement?.versionNonce
|
||||||
}
|
}
|
||||||
@@ -1819,7 +1816,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.magicGenerations.set(frameElement.id, data);
|
this.magicGenerations.set(frameElement.id, data);
|
||||||
this.onSceneUpdated();
|
this.triggerRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
private getTextFromElements(elements: readonly ExcalidrawElement[]) {
|
private getTextFromElements(elements: readonly ExcalidrawElement[]) {
|
||||||
@@ -2444,7 +2441,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.history.record(increment.elementsChange, increment.appStateChange);
|
this.history.record(increment.elementsChange, increment.appStateChange);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scene.addCallback(this.onSceneUpdated);
|
this.scene.onUpdate(this.triggerRender);
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
|
|
||||||
if (this.props.autoFocus && this.excalidrawContainerRef.current) {
|
if (this.props.autoFocus && this.excalidrawContainerRef.current) {
|
||||||
@@ -2489,6 +2486,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
this.renderer.destroy();
|
this.renderer.destroy();
|
||||||
this.scene = new Scene();
|
this.scene = new Scene();
|
||||||
|
this.fonts = new Fonts({ scene: this.scene });
|
||||||
this.renderer = new Renderer(this.scene);
|
this.renderer = new Renderer(this.scene);
|
||||||
this.files = {};
|
this.files = {};
|
||||||
this.imageCache.clear();
|
this.imageCache.clear();
|
||||||
@@ -3670,7 +3668,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
ShapeCache.delete(element);
|
ShapeCache.delete(element);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.scene.informMutation();
|
this.scene.triggerUpdate();
|
||||||
|
|
||||||
this.addNewImagesToImageCache();
|
this.addNewImagesToImageCache();
|
||||||
},
|
},
|
||||||
@@ -3730,7 +3728,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
private onSceneUpdated = () => {
|
private triggerRender = () => {
|
||||||
this.setState({});
|
this.setState({});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5577,7 +5575,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.elementsPendingErasure = new Set(this.elementsPendingErasure);
|
this.elementsPendingErasure = new Set(this.elementsPendingErasure);
|
||||||
this.onSceneUpdated();
|
this.triggerRender();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8069,7 +8067,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.scene.informMutation();
|
this.scene.triggerUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8564,7 +8562,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
private restoreReadyToEraseElements = () => {
|
private restoreReadyToEraseElements = () => {
|
||||||
this.elementsPendingErasure = new Set();
|
this.elementsPendingErasure = new Set();
|
||||||
this.onSceneUpdated();
|
this.triggerRender();
|
||||||
};
|
};
|
||||||
|
|
||||||
private eraseElements = () => {
|
private eraseElements = () => {
|
||||||
@@ -8978,7 +8976,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
files,
|
files,
|
||||||
);
|
);
|
||||||
if (updatedFiles.size) {
|
if (updatedFiles.size) {
|
||||||
this.scene.informMutation();
|
this.scene.triggerUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ function CommandPaletteInner({
|
|||||||
...command,
|
...command,
|
||||||
icon: command.icon || boltIcon,
|
icon: command.icon || boltIcon,
|
||||||
order: command.order ?? getCategoryOrder(command.category),
|
order: command.order ?? getCategoryOrder(command.category),
|
||||||
haystack: `${deburr(command.label)} ${
|
haystack: `${deburr(command.label.toLocaleLowerCase())} ${
|
||||||
command.keywords?.join(" ") || ""
|
command.keywords?.join(" ") || ""
|
||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
@@ -777,7 +777,9 @@ function CommandPaletteInner({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _query = deburr(commandSearch.replace(/[<>-_| ]/g, ""));
|
const _query = deburr(
|
||||||
|
commandSearch.toLocaleLowerCase().replace(/[<>_| -]/g, ""),
|
||||||
|
);
|
||||||
matchingCommands = fuzzy
|
matchingCommands = fuzzy
|
||||||
.filter(_query, matchingCommands, {
|
.filter(_query, matchingCommands, {
|
||||||
extract: (command) => command.haystack,
|
extract: (command) => command.haystack,
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ const LayerUI = ({
|
|||||||
);
|
);
|
||||||
ShapeCache.delete(element);
|
ShapeCache.delete(element);
|
||||||
}
|
}
|
||||||
Scene.getScene(selectedElements[0])?.informMutation();
|
Scene.getScene(selectedElements[0])?.triggerUpdate();
|
||||||
} else if (colorPickerType === "elementBackground") {
|
} else if (colorPickerType === "elementBackground") {
|
||||||
setAppState({
|
setAppState({
|
||||||
currentItemBackgroundColor: color,
|
currentItemBackgroundColor: color,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type InteractiveCanvasProps = {
|
|||||||
elementsMap: RenderableElementsMap;
|
elementsMap: RenderableElementsMap;
|
||||||
visibleElements: readonly NonDeletedExcalidrawElement[];
|
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||||
selectedElements: readonly NonDeletedExcalidrawElement[];
|
selectedElements: readonly NonDeletedExcalidrawElement[];
|
||||||
versionNonce: number | undefined;
|
sceneNonce: number | undefined;
|
||||||
selectionNonce: number | undefined;
|
selectionNonce: number | undefined;
|
||||||
scale: number;
|
scale: number;
|
||||||
appState: InteractiveCanvasAppState;
|
appState: InteractiveCanvasAppState;
|
||||||
@@ -206,10 +206,10 @@ const areEqual = (
|
|||||||
// This could be further optimised if needed, as we don't have to render interactive canvas on each scene mutation
|
// This could be further optimised if needed, as we don't have to render interactive canvas on each scene mutation
|
||||||
if (
|
if (
|
||||||
prevProps.selectionNonce !== nextProps.selectionNonce ||
|
prevProps.selectionNonce !== nextProps.selectionNonce ||
|
||||||
prevProps.versionNonce !== nextProps.versionNonce ||
|
prevProps.sceneNonce !== nextProps.sceneNonce ||
|
||||||
prevProps.scale !== nextProps.scale ||
|
prevProps.scale !== nextProps.scale ||
|
||||||
// we need to memoize on elementsMap because they may have renewed
|
// we need to memoize on elementsMap because they may have renewed
|
||||||
// even if versionNonce didn't change (e.g. we filter elements out based
|
// even if sceneNonce didn't change (e.g. we filter elements out based
|
||||||
// on appState)
|
// on appState)
|
||||||
prevProps.elementsMap !== nextProps.elementsMap ||
|
prevProps.elementsMap !== nextProps.elementsMap ||
|
||||||
prevProps.visibleElements !== nextProps.visibleElements ||
|
prevProps.visibleElements !== nextProps.visibleElements ||
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type StaticCanvasProps = {
|
|||||||
elementsMap: RenderableElementsMap;
|
elementsMap: RenderableElementsMap;
|
||||||
allElementsMap: NonDeletedSceneElementsMap;
|
allElementsMap: NonDeletedSceneElementsMap;
|
||||||
visibleElements: readonly NonDeletedExcalidrawElement[];
|
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||||
versionNonce: number | undefined;
|
sceneNonce: number | undefined;
|
||||||
selectionNonce: number | undefined;
|
selectionNonce: number | undefined;
|
||||||
scale: number;
|
scale: number;
|
||||||
appState: StaticCanvasAppState;
|
appState: StaticCanvasAppState;
|
||||||
@@ -112,10 +112,10 @@ const areEqual = (
|
|||||||
nextProps: StaticCanvasProps,
|
nextProps: StaticCanvasProps,
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
prevProps.versionNonce !== nextProps.versionNonce ||
|
prevProps.sceneNonce !== nextProps.sceneNonce ||
|
||||||
prevProps.scale !== nextProps.scale ||
|
prevProps.scale !== nextProps.scale ||
|
||||||
// we need to memoize on elementsMap because they may have renewed
|
// we need to memoize on elementsMap because they may have renewed
|
||||||
// even if versionNonce didn't change (e.g. we filter elements out based
|
// even if sceneNonce didn't change (e.g. we filter elements out based
|
||||||
// on appState)
|
// on appState)
|
||||||
prevProps.elementsMap !== nextProps.elementsMap ||
|
prevProps.elementsMap !== nextProps.elementsMap ||
|
||||||
prevProps.visibleElements !== nextProps.visibleElements
|
prevProps.visibleElements !== nextProps.visibleElements
|
||||||
|
|||||||
@@ -698,14 +698,18 @@ export const BringForwardIcon = createIcon(arrownNarrowUpJSX, tablerIconProps);
|
|||||||
|
|
||||||
export const SendBackwardIcon = createIcon(arrownNarrowUpJSX, {
|
export const SendBackwardIcon = createIcon(arrownNarrowUpJSX, {
|
||||||
...tablerIconProps,
|
...tablerIconProps,
|
||||||
transform: "rotate(180)",
|
style: {
|
||||||
|
transform: "rotate(180deg)",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BringToFrontIcon = createIcon(arrowBarToTopJSX, tablerIconProps);
|
export const BringToFrontIcon = createIcon(arrowBarToTopJSX, tablerIconProps);
|
||||||
|
|
||||||
export const SendToBackIcon = createIcon(arrowBarToTopJSX, {
|
export const SendToBackIcon = createIcon(arrowBarToTopJSX, {
|
||||||
...tablerIconProps,
|
...tablerIconProps,
|
||||||
transform: "rotate(180)",
|
style: {
|
||||||
|
transform: "rotate(180deg)",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
element.updated = getUpdatedTimestamp();
|
element.updated = getUpdatedTimestamp();
|
||||||
|
|
||||||
if (informMutation) {
|
if (informMutation) {
|
||||||
Scene.getScene(element)?.informMutation();
|
Scene.getScene(element)?.triggerUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
@@ -107,6 +107,8 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
export const newElementWith = <TElement extends ExcalidrawElement>(
|
export const newElementWith = <TElement extends ExcalidrawElement>(
|
||||||
element: TElement,
|
element: TElement,
|
||||||
updates: ElementUpdate<TElement>,
|
updates: ElementUpdate<TElement>,
|
||||||
|
/** pass `true` to always regenerate */
|
||||||
|
force = false,
|
||||||
): TElement => {
|
): TElement => {
|
||||||
let didChange = false;
|
let didChange = false;
|
||||||
for (const key in updates) {
|
for (const key in updates) {
|
||||||
@@ -123,7 +125,7 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didChange) {
|
if (!didChange && !force) {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -876,7 +876,7 @@ export const resizeMultipleElements = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scene.getScene(elementsAndUpdates[0].element)?.informMutation();
|
Scene.getScene(elementsAndUpdates[0].element)?.triggerUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
const rotateMultipleElements = (
|
const rotateMultipleElements = (
|
||||||
@@ -938,7 +938,7 @@ const rotateMultipleElements = (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Scene.getScene(elements[0])?.informMutation();
|
Scene.getScene(elements[0])?.triggerUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getResizeOffsetXY = (
|
export const getResizeOffsetXY = (
|
||||||
|
|||||||
@@ -226,6 +226,8 @@ export const textWysiwyg = ({
|
|||||||
if (!container) {
|
if (!container) {
|
||||||
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
||||||
textElementWidth = Math.min(textElementWidth, maxWidth);
|
textElementWidth = Math.min(textElementWidth, maxWidth);
|
||||||
|
} else {
|
||||||
|
textElementWidth += 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure text editor height doesn't go beyond viewport
|
// Make sure text editor height doesn't go beyond viewport
|
||||||
@@ -644,7 +646,7 @@ export const textWysiwyg = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// handle updates of textElement properties of editing element
|
// handle updates of textElement properties of editing element
|
||||||
const unbindUpdate = Scene.getScene(element)!.addCallback(() => {
|
const unbindUpdate = Scene.getScene(element)!.onUpdate(() => {
|
||||||
updateWysiwygStyle();
|
updateWysiwygStyle();
|
||||||
const isColorPickerActive = !!document.activeElement?.closest(
|
const isColorPickerActive = !!document.activeElement?.closest(
|
||||||
".color-picker-content",
|
".color-picker-content",
|
||||||
|
|||||||
@@ -148,7 +148,8 @@
|
|||||||
"discordChat": "Discord chat",
|
"discordChat": "Discord chat",
|
||||||
"zoomToFitViewport": "Zoom to fit in viewport",
|
"zoomToFitViewport": "Zoom to fit in viewport",
|
||||||
"zoomToFitSelection": "Zoom to fit selection",
|
"zoomToFitSelection": "Zoom to fit selection",
|
||||||
"zoomToFit": "Zoom to fit all elements"
|
"zoomToFit": "Zoom to fit all elements",
|
||||||
|
"installPWA": "Install Excalidraw locally (PWA)"
|
||||||
},
|
},
|
||||||
"library": {
|
"library": {
|
||||||
"noItems": "No items added yet...",
|
"noItems": "No items added yet...",
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { isTextElement, refreshTextDimensions } from "../element";
|
import { isTextElement } from "../element";
|
||||||
import { newElementWith } from "../element/mutateElement";
|
import { newElementWith } from "../element/mutateElement";
|
||||||
import { getContainerElement } from "../element/textElement";
|
|
||||||
import { isBoundToContainer } from "../element/typeChecks";
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
@@ -12,17 +10,9 @@ import { ShapeCache } from "./ShapeCache";
|
|||||||
|
|
||||||
export class Fonts {
|
export class Fonts {
|
||||||
private scene: Scene;
|
private scene: Scene;
|
||||||
private onSceneUpdated: () => void;
|
|
||||||
|
|
||||||
constructor({
|
constructor({ scene }: { scene: Scene }) {
|
||||||
scene,
|
|
||||||
onSceneUpdated,
|
|
||||||
}: {
|
|
||||||
scene: Scene;
|
|
||||||
onSceneUpdated: () => void;
|
|
||||||
}) {
|
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.onSceneUpdated = onSceneUpdated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's ok to track fonts across multiple instances only once, so let's use
|
// it's ok to track fonts across multiple instances only once, so let's use
|
||||||
@@ -57,22 +47,16 @@ export class Fonts {
|
|||||||
let didUpdate = false;
|
let didUpdate = false;
|
||||||
|
|
||||||
this.scene.mapElements((element) => {
|
this.scene.mapElements((element) => {
|
||||||
if (isTextElement(element) && !isBoundToContainer(element)) {
|
if (isTextElement(element)) {
|
||||||
ShapeCache.delete(element);
|
|
||||||
didUpdate = true;
|
didUpdate = true;
|
||||||
return newElementWith(element, {
|
ShapeCache.delete(element);
|
||||||
...refreshTextDimensions(
|
return newElementWith(element, {}, true);
|
||||||
element,
|
|
||||||
getContainerElement(element, this.scene.getNonDeletedElementsMap()),
|
|
||||||
this.scene.getNonDeletedElementsMap(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return element;
|
return element;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (didUpdate) {
|
if (didUpdate) {
|
||||||
this.onSceneUpdated();
|
this.scene.triggerUpdate();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -107,9 +107,8 @@ export class Renderer {
|
|||||||
width,
|
width,
|
||||||
editingElement,
|
editingElement,
|
||||||
pendingImageElementId,
|
pendingImageElementId,
|
||||||
// unused but serves we cache on it to invalidate elements if they
|
// cache-invalidation nonce
|
||||||
// get mutated
|
sceneNonce: _sceneNonce,
|
||||||
versionNonce: _versionNonce,
|
|
||||||
}: {
|
}: {
|
||||||
zoom: AppState["zoom"];
|
zoom: AppState["zoom"];
|
||||||
offsetLeft: AppState["offsetLeft"];
|
offsetLeft: AppState["offsetLeft"];
|
||||||
@@ -120,7 +119,7 @@ export class Renderer {
|
|||||||
width: AppState["width"];
|
width: AppState["width"];
|
||||||
editingElement: AppState["editingElement"];
|
editingElement: AppState["editingElement"];
|
||||||
pendingImageElementId: AppState["pendingImageElementId"];
|
pendingImageElementId: AppState["pendingImageElementId"];
|
||||||
versionNonce: ReturnType<InstanceType<typeof Scene>["getVersionNonce"]>;
|
sceneNonce: ReturnType<InstanceType<typeof Scene>["getSceneNonce"]>;
|
||||||
}) => {
|
}) => {
|
||||||
const elements = this.scene.getNonDeletedElements();
|
const elements = this.scene.getNonDeletedElements();
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,17 @@ class Scene {
|
|||||||
elements: null,
|
elements: null,
|
||||||
cache: new Map(),
|
cache: new Map(),
|
||||||
};
|
};
|
||||||
private versionNonce: number | undefined;
|
/**
|
||||||
|
* Random integer regenerated each scene update.
|
||||||
|
*
|
||||||
|
* Does not relate to elements versions, it's only a renderer
|
||||||
|
* cache-invalidation nonce at the moment.
|
||||||
|
*/
|
||||||
|
private sceneNonce: number | undefined;
|
||||||
|
|
||||||
|
getSceneNonce() {
|
||||||
|
return this.sceneNonce;
|
||||||
|
}
|
||||||
|
|
||||||
getNonDeletedElementsMap() {
|
getNonDeletedElementsMap() {
|
||||||
return this.nonDeletedElementsMap;
|
return this.nonDeletedElementsMap;
|
||||||
@@ -214,10 +224,6 @@ class Scene {
|
|||||||
return (this.elementsMap.get(id) as T | undefined) || null;
|
return (this.elementsMap.get(id) as T | undefined) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getVersionNonce() {
|
|
||||||
return this.versionNonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
getNonDeletedElement(
|
getNonDeletedElement(
|
||||||
id: ExcalidrawElement["id"],
|
id: ExcalidrawElement["id"],
|
||||||
): NonDeleted<ExcalidrawElement> | null {
|
): NonDeleted<ExcalidrawElement> | null {
|
||||||
@@ -286,18 +292,18 @@ class Scene {
|
|||||||
this.frames = nextFrameLikes;
|
this.frames = nextFrameLikes;
|
||||||
this.nonDeletedFramesLikes = getNonDeletedElements(this.frames).elements;
|
this.nonDeletedFramesLikes = getNonDeletedElements(this.frames).elements;
|
||||||
|
|
||||||
this.informMutation();
|
this.triggerUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
informMutation() {
|
triggerUpdate() {
|
||||||
this.versionNonce = randomInteger();
|
this.sceneNonce = randomInteger();
|
||||||
|
|
||||||
for (const callback of Array.from(this.callbacks)) {
|
for (const callback of Array.from(this.callbacks)) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addCallback(cb: SceneStateCallback): SceneStateCallbackRemover {
|
onUpdate(cb: SceneStateCallback): SceneStateCallbackRemover {
|
||||||
if (this.callbacks.has(cb)) {
|
if (this.callbacks.has(cb)) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ export const exportToSvg = async (
|
|||||||
assetPath =
|
assetPath =
|
||||||
window.EXCALIDRAW_ASSET_PATH ||
|
window.EXCALIDRAW_ASSET_PATH ||
|
||||||
`https://unpkg.com/${import.meta.env.VITE_PKG_NAME}@${
|
`https://unpkg.com/${import.meta.env.VITE_PKG_NAME}@${
|
||||||
import.meta.env.PKG_VERSION
|
import.meta.env.VITE_PKG_VERSION
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
if (assetPath?.startsWith("/")) {
|
if (assetPath?.startsWith("/")) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ exports[`Test Linear Elements > Test bound text element > should match styles fo
|
|||||||
class="excalidraw-wysiwyg"
|
class="excalidraw-wysiwyg"
|
||||||
data-type="wysiwyg"
|
data-type="wysiwyg"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
style="position: absolute; display: inline-block; min-height: 1em; backface-visibility: hidden; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10px; height: 25px; left: 35px; top: 7.5px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(30, 30, 30); opacity: 1; filter: var(--theme-filter); max-height: 992.5px; font: Emoji 20px 20px; line-height: 1.25; font-family: Virgil, Segoe UI Emoji;"
|
style="position: absolute; display: inline-block; min-height: 1em; backface-visibility: hidden; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10.5px; height: 25px; left: 35px; top: 7.5px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(30, 30, 30); opacity: 1; filter: var(--theme-filter); max-height: 992.5px; font: Emoji 20px 20px; line-height: 1.25; font-family: Virgil, Segoe UI Emoji;"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
wrap="off"
|
wrap="off"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user