mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-02-18 13:29:36 +01:00
fix: font not rendered correctly on init (#8002)
This commit is contained in:
parent
301e83805d
commit
273ba803d9
@ -714,10 +714,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
id: this.id,
|
||||
};
|
||||
|
||||
this.fonts = new Fonts({
|
||||
scene: this.scene,
|
||||
onSceneUpdated: this.onSceneUpdated,
|
||||
});
|
||||
this.fonts = new Fonts({ scene: this.scene });
|
||||
this.history = new History();
|
||||
|
||||
this.actionManager.registerAll(actions);
|
||||
@ -940,7 +937,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
|
||||
if (updated) {
|
||||
this.scene.informMutation();
|
||||
this.scene.triggerUpdate();
|
||||
}
|
||||
|
||||
// GC
|
||||
@ -1452,10 +1449,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||
const { renderTopRightUI, renderCustomStats } = this.props;
|
||||
|
||||
const versionNonce = this.scene.getVersionNonce();
|
||||
const sceneNonce = this.scene.getSceneNonce();
|
||||
const { elementsMap, visibleElements } =
|
||||
this.renderer.getRenderableElements({
|
||||
versionNonce,
|
||||
sceneNonce,
|
||||
zoom: this.state.zoom,
|
||||
offsetLeft: this.state.offsetLeft,
|
||||
offsetTop: this.state.offsetTop,
|
||||
@ -1673,7 +1670,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elementsMap={elementsMap}
|
||||
allElementsMap={allElementsMap}
|
||||
visibleElements={visibleElements}
|
||||
versionNonce={versionNonce}
|
||||
sceneNonce={sceneNonce}
|
||||
selectionNonce={
|
||||
this.state.selectionElement?.versionNonce
|
||||
}
|
||||
@ -1695,7 +1692,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
elementsMap={elementsMap}
|
||||
visibleElements={visibleElements}
|
||||
selectedElements={selectedElements}
|
||||
versionNonce={versionNonce}
|
||||
sceneNonce={sceneNonce}
|
||||
selectionNonce={
|
||||
this.state.selectionElement?.versionNonce
|
||||
}
|
||||
@ -1819,7 +1816,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
);
|
||||
}
|
||||
this.magicGenerations.set(frameElement.id, data);
|
||||
this.onSceneUpdated();
|
||||
this.triggerRender();
|
||||
};
|
||||
|
||||
private getTextFromElements(elements: readonly ExcalidrawElement[]) {
|
||||
@ -2444,7 +2441,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.history.record(increment.elementsChange, increment.appStateChange);
|
||||
});
|
||||
|
||||
this.scene.addCallback(this.onSceneUpdated);
|
||||
this.scene.onUpdate(this.triggerRender);
|
||||
this.addEventListeners();
|
||||
|
||||
if (this.props.autoFocus && this.excalidrawContainerRef.current) {
|
||||
@ -2489,6 +2486,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
public componentWillUnmount() {
|
||||
this.renderer.destroy();
|
||||
this.scene = new Scene();
|
||||
this.fonts = new Fonts({ scene: this.scene });
|
||||
this.renderer = new Renderer(this.scene);
|
||||
this.files = {};
|
||||
this.imageCache.clear();
|
||||
@ -3670,7 +3668,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
ShapeCache.delete(element);
|
||||
}
|
||||
});
|
||||
this.scene.informMutation();
|
||||
this.scene.triggerUpdate();
|
||||
|
||||
this.addNewImagesToImageCache();
|
||||
},
|
||||
@ -3730,7 +3728,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
},
|
||||
);
|
||||
|
||||
private onSceneUpdated = () => {
|
||||
private triggerRender = () => {
|
||||
this.setState({});
|
||||
};
|
||||
|
||||
@ -5577,7 +5575,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
|
||||
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.informMutation();
|
||||
this.scene.triggerUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8564,7 +8562,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
private restoreReadyToEraseElements = () => {
|
||||
this.elementsPendingErasure = new Set();
|
||||
this.onSceneUpdated();
|
||||
this.triggerRender();
|
||||
};
|
||||
|
||||
private eraseElements = () => {
|
||||
@ -8978,7 +8976,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
files,
|
||||
);
|
||||
if (updatedFiles.size) {
|
||||
this.scene.informMutation();
|
||||
this.scene.triggerUpdate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -444,7 +444,7 @@ const LayerUI = ({
|
||||
);
|
||||
ShapeCache.delete(element);
|
||||
}
|
||||
Scene.getScene(selectedElements[0])?.informMutation();
|
||||
Scene.getScene(selectedElements[0])?.triggerUpdate();
|
||||
} else if (colorPickerType === "elementBackground") {
|
||||
setAppState({
|
||||
currentItemBackgroundColor: color,
|
||||
|
@ -19,7 +19,7 @@ type InteractiveCanvasProps = {
|
||||
elementsMap: RenderableElementsMap;
|
||||
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||
selectedElements: readonly NonDeletedExcalidrawElement[];
|
||||
versionNonce: number | undefined;
|
||||
sceneNonce: number | undefined;
|
||||
selectionNonce: number | undefined;
|
||||
scale: number;
|
||||
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
|
||||
if (
|
||||
prevProps.selectionNonce !== nextProps.selectionNonce ||
|
||||
prevProps.versionNonce !== nextProps.versionNonce ||
|
||||
prevProps.sceneNonce !== nextProps.sceneNonce ||
|
||||
prevProps.scale !== nextProps.scale ||
|
||||
// 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)
|
||||
prevProps.elementsMap !== nextProps.elementsMap ||
|
||||
prevProps.visibleElements !== nextProps.visibleElements ||
|
||||
|
@ -19,7 +19,7 @@ type StaticCanvasProps = {
|
||||
elementsMap: RenderableElementsMap;
|
||||
allElementsMap: NonDeletedSceneElementsMap;
|
||||
visibleElements: readonly NonDeletedExcalidrawElement[];
|
||||
versionNonce: number | undefined;
|
||||
sceneNonce: number | undefined;
|
||||
selectionNonce: number | undefined;
|
||||
scale: number;
|
||||
appState: StaticCanvasAppState;
|
||||
@ -112,10 +112,10 @@ const areEqual = (
|
||||
nextProps: StaticCanvasProps,
|
||||
) => {
|
||||
if (
|
||||
prevProps.versionNonce !== nextProps.versionNonce ||
|
||||
prevProps.sceneNonce !== nextProps.sceneNonce ||
|
||||
prevProps.scale !== nextProps.scale ||
|
||||
// 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)
|
||||
prevProps.elementsMap !== nextProps.elementsMap ||
|
||||
prevProps.visibleElements !== nextProps.visibleElements
|
||||
|
@ -98,7 +98,7 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||
element.updated = getUpdatedTimestamp();
|
||||
|
||||
if (informMutation) {
|
||||
Scene.getScene(element)?.informMutation();
|
||||
Scene.getScene(element)?.triggerUpdate();
|
||||
}
|
||||
|
||||
return element;
|
||||
@ -107,6 +107,8 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
||||
export const newElementWith = <TElement extends ExcalidrawElement>(
|
||||
element: TElement,
|
||||
updates: ElementUpdate<TElement>,
|
||||
/** pass `true` to always regenerate */
|
||||
force = false,
|
||||
): TElement => {
|
||||
let didChange = false;
|
||||
for (const key in updates) {
|
||||
@ -123,7 +125,7 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
|
||||
}
|
||||
}
|
||||
|
||||
if (!didChange) {
|
||||
if (!didChange && !force) {
|
||||
return element;
|
||||
}
|
||||
|
||||
|
@ -876,7 +876,7 @@ export const resizeMultipleElements = (
|
||||
}
|
||||
}
|
||||
|
||||
Scene.getScene(elementsAndUpdates[0].element)?.informMutation();
|
||||
Scene.getScene(elementsAndUpdates[0].element)?.triggerUpdate();
|
||||
};
|
||||
|
||||
const rotateMultipleElements = (
|
||||
@ -938,7 +938,7 @@ const rotateMultipleElements = (
|
||||
}
|
||||
});
|
||||
|
||||
Scene.getScene(elements[0])?.informMutation();
|
||||
Scene.getScene(elements[0])?.triggerUpdate();
|
||||
};
|
||||
|
||||
export const getResizeOffsetXY = (
|
||||
|
@ -644,7 +644,7 @@ export const textWysiwyg = ({
|
||||
};
|
||||
|
||||
// handle updates of textElement properties of editing element
|
||||
const unbindUpdate = Scene.getScene(element)!.addCallback(() => {
|
||||
const unbindUpdate = Scene.getScene(element)!.onUpdate(() => {
|
||||
updateWysiwygStyle();
|
||||
const isColorPickerActive = !!document.activeElement?.closest(
|
||||
".color-picker-content",
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { isTextElement, refreshTextDimensions } from "../element";
|
||||
import { isTextElement } from "../element";
|
||||
import { newElementWith } from "../element/mutateElement";
|
||||
import { getContainerElement } from "../element/textElement";
|
||||
import { isBoundToContainer } from "../element/typeChecks";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawTextElement,
|
||||
@ -12,17 +10,9 @@ import { ShapeCache } from "./ShapeCache";
|
||||
|
||||
export class Fonts {
|
||||
private scene: Scene;
|
||||
private onSceneUpdated: () => void;
|
||||
|
||||
constructor({
|
||||
scene,
|
||||
onSceneUpdated,
|
||||
}: {
|
||||
scene: Scene;
|
||||
onSceneUpdated: () => void;
|
||||
}) {
|
||||
constructor({ scene }: { scene: Scene }) {
|
||||
this.scene = scene;
|
||||
this.onSceneUpdated = onSceneUpdated;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
this.scene.mapElements((element) => {
|
||||
if (isTextElement(element) && !isBoundToContainer(element)) {
|
||||
ShapeCache.delete(element);
|
||||
if (isTextElement(element)) {
|
||||
didUpdate = true;
|
||||
return newElementWith(element, {
|
||||
...refreshTextDimensions(
|
||||
element,
|
||||
getContainerElement(element, this.scene.getNonDeletedElementsMap()),
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
),
|
||||
});
|
||||
ShapeCache.delete(element);
|
||||
return newElementWith(element, {}, true);
|
||||
}
|
||||
return element;
|
||||
});
|
||||
|
||||
if (didUpdate) {
|
||||
this.onSceneUpdated();
|
||||
this.scene.triggerUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -107,9 +107,8 @@ export class Renderer {
|
||||
width,
|
||||
editingElement,
|
||||
pendingImageElementId,
|
||||
// unused but serves we cache on it to invalidate elements if they
|
||||
// get mutated
|
||||
versionNonce: _versionNonce,
|
||||
// cache-invalidation nonce
|
||||
sceneNonce: _sceneNonce,
|
||||
}: {
|
||||
zoom: AppState["zoom"];
|
||||
offsetLeft: AppState["offsetLeft"];
|
||||
@ -120,7 +119,7 @@ export class Renderer {
|
||||
width: AppState["width"];
|
||||
editingElement: AppState["editingElement"];
|
||||
pendingImageElementId: AppState["pendingImageElementId"];
|
||||
versionNonce: ReturnType<InstanceType<typeof Scene>["getVersionNonce"]>;
|
||||
sceneNonce: ReturnType<InstanceType<typeof Scene>["getSceneNonce"]>;
|
||||
}) => {
|
||||
const elements = this.scene.getNonDeletedElements();
|
||||
|
||||
|
@ -138,7 +138,17 @@ class Scene {
|
||||
elements: null,
|
||||
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() {
|
||||
return this.nonDeletedElementsMap;
|
||||
@ -214,10 +224,6 @@ class Scene {
|
||||
return (this.elementsMap.get(id) as T | undefined) || null;
|
||||
}
|
||||
|
||||
getVersionNonce() {
|
||||
return this.versionNonce;
|
||||
}
|
||||
|
||||
getNonDeletedElement(
|
||||
id: ExcalidrawElement["id"],
|
||||
): NonDeleted<ExcalidrawElement> | null {
|
||||
@ -286,18 +292,18 @@ class Scene {
|
||||
this.frames = nextFrameLikes;
|
||||
this.nonDeletedFramesLikes = getNonDeletedElements(this.frames).elements;
|
||||
|
||||
this.informMutation();
|
||||
this.triggerUpdate();
|
||||
}
|
||||
|
||||
informMutation() {
|
||||
this.versionNonce = randomInteger();
|
||||
triggerUpdate() {
|
||||
this.sceneNonce = randomInteger();
|
||||
|
||||
for (const callback of Array.from(this.callbacks)) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
addCallback(cb: SceneStateCallback): SceneStateCallbackRemover {
|
||||
onUpdate(cb: SceneStateCallback): SceneStateCallbackRemover {
|
||||
if (this.callbacks.has(cb)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user