Browse Source

Merge branch 'excalidraw:master' into add-kannada-translation

pull/10339/head
Owais-cmd 1 month ago committed by GitHub
parent
commit
8c59643dd9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 12
      excalidraw-app/components/ExcalidrawPlusPromoBanner.tsx
  2. 10
      packages/excalidraw/components/App.tsx
  3. 96
      packages/excalidraw/components/canvases/InteractiveCanvas.tsx
  4. 84
      packages/excalidraw/renderer/animation.ts
  5. 32
      packages/excalidraw/renderer/interactiveScene.ts
  6. 2
      packages/excalidraw/scene/Renderer.ts
  7. 7
      packages/excalidraw/scene/types.ts

12
excalidraw-app/components/ExcalidrawPlusPromoBanner.tsx

@ -5,13 +5,13 @@ export const ExcalidrawPlusPromoBanner = ({ @@ -5,13 +5,13 @@ export const ExcalidrawPlusPromoBanner = ({
}) => {
return (
<a
href={`${
href={
isSignedIn
? import.meta.env.VITE_APP_PLUS_LP
: import.meta.env.VITE_APP_PLUS_APP
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=${
isSignedIn ? "signedInBanner" : "guestBanner"
}#excalidraw-redirect`}
? import.meta.env.VITE_APP_PLUS_APP
: `${
import.meta.env.VITE_APP_PLUS_LP
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=guestBanner#excalidraw-redirect`
}
target="_blank"
rel="noopener"
className="plus-banner"

10
packages/excalidraw/components/App.tsx

@ -1813,6 +1813,7 @@ class App extends React.Component<AppProps, AppState> { @@ -1813,6 +1813,7 @@ class App extends React.Component<AppProps, AppState> {
/>
)}
<InteractiveCanvas
app={this}
containerRef={this.excalidrawContainerRef}
canvas={this.interactiveCanvas}
elementsMap={elementsMap}
@ -8846,6 +8847,15 @@ class App extends React.Component<AppProps, AppState> { @@ -8846,6 +8847,15 @@ class App extends React.Component<AppProps, AppState> {
}));
this.scene.replaceAllElements(elementsWithIndices);
selectedElements.forEach((element) => {
if (
isBindableElement(element) &&
element.boundElements?.some((other) => other.type === "arrow")
) {
updateBoundElements(element, this.scene);
}
});
this.maybeCacheVisibleGaps(event, selectedElements, true);
this.maybeCacheReferenceSnapPoints(event, selectedElements, true);
});

96
packages/excalidraw/components/canvases/InteractiveCanvas.tsx

@ -6,6 +6,15 @@ import { @@ -6,6 +6,15 @@ import {
sceneCoordsToViewportCoords,
type EditorInterface,
} from "@excalidraw/common";
import { AnimationController } from "@excalidraw/excalidraw/renderer/animation";
import type {
InteractiveCanvasRenderConfig,
InteractiveSceneRenderAnimationState,
InteractiveSceneRenderConfig,
RenderableElementsMap,
RenderInteractiveSceneCallback,
} from "@excalidraw/excalidraw/scene/types";
import type {
NonDeletedExcalidrawElement,
@ -13,15 +22,13 @@ import type { @@ -13,15 +22,13 @@ import type {
} from "@excalidraw/element/types";
import { t } from "../../i18n";
import { isRenderThrottlingEnabled } from "../../reactUtils";
import { renderInteractiveScene } from "../../renderer/interactiveScene";
import type {
InteractiveCanvasRenderConfig,
RenderableElementsMap,
RenderInteractiveSceneCallback,
} from "../../scene/types";
import type { AppState, InteractiveCanvasAppState } from "../../types";
AppClassProperties,
AppState,
InteractiveCanvasAppState,
} from "../../types";
import type { DOMAttributes } from "react";
type InteractiveCanvasProps = {
@ -37,6 +44,7 @@ type InteractiveCanvasProps = { @@ -37,6 +44,7 @@ type InteractiveCanvasProps = {
appState: InteractiveCanvasAppState;
renderScrollbars: boolean;
editorInterface: EditorInterface;
app: AppClassProperties;
renderInteractiveSceneCallback: (
data: RenderInteractiveSceneCallback,
) => void;
@ -71,8 +79,11 @@ type InteractiveCanvasProps = { @@ -71,8 +79,11 @@ type InteractiveCanvasProps = {
>;
};
export const INTERACTIVE_SCENE_ANIMATION_KEY = "animateInteractiveScene";
const InteractiveCanvas = (props: InteractiveCanvasProps) => {
const isComponentMounted = useRef(false);
const rendererParams = useRef(null as InteractiveSceneRenderConfig | null);
useEffect(() => {
if (!isComponentMounted.current) {
@ -129,29 +140,58 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => { @@ -129,29 +140,58 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
)) ||
"#6965db";
renderInteractiveScene(
{
canvas: props.canvas,
elementsMap: props.elementsMap,
visibleElements: props.visibleElements,
selectedElements: props.selectedElements,
allElementsMap: props.allElementsMap,
scale: window.devicePixelRatio,
appState: props.appState,
renderConfig: {
remotePointerViewportCoords,
remotePointerButton,
remoteSelectedElementIds,
remotePointerUsernames,
remotePointerUserStates,
selectionColor,
renderScrollbars: props.renderScrollbars,
},
editorInterface: props.editorInterface,
callback: props.renderInteractiveSceneCallback,
rendererParams.current = {
app: props.app,
canvas: props.canvas,
elementsMap: props.elementsMap,
visibleElements: props.visibleElements,
selectedElements: props.selectedElements,
allElementsMap: props.allElementsMap,
scale: window.devicePixelRatio,
appState: props.appState,
renderConfig: {
remotePointerViewportCoords,
remotePointerButton,
remoteSelectedElementIds,
remotePointerUsernames,
remotePointerUserStates,
selectionColor,
renderScrollbars: props.renderScrollbars,
},
editorInterface: props.editorInterface,
callback: props.renderInteractiveSceneCallback,
animationState: {
bindingHighlight: undefined,
},
isRenderThrottlingEnabled(),
);
deltaTime: 0,
};
if (!AnimationController.running(INTERACTIVE_SCENE_ANIMATION_KEY)) {
AnimationController.start<InteractiveSceneRenderAnimationState>(
INTERACTIVE_SCENE_ANIMATION_KEY,
({ deltaTime, state }) => {
const nextAnimationState = renderInteractiveScene({
...rendererParams.current!,
deltaTime,
animationState: state,
}).animationState;
if (nextAnimationState) {
for (const key in nextAnimationState) {
if (
nextAnimationState[
key as keyof InteractiveSceneRenderAnimationState
] !== undefined
) {
return nextAnimationState;
}
}
}
return undefined;
},
);
}
});
return (

84
packages/excalidraw/renderer/animation.ts

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
import { isRenderThrottlingEnabled } from "../reactUtils";
export type Animation<R extends object> = (params: {
deltaTime: number;
state?: R;
}) => R | null | undefined;
export class AnimationController {
private static isRunning = false;
private static animations = new Map<
string,
{
animation: Animation<any>;
lastTime: number;
state: any;
}
>();
static start<R extends object>(key: string, animation: Animation<R>) {
const initialState = animation({
deltaTime: 0,
state: undefined,
});
if (initialState) {
AnimationController.animations.set(key, {
animation,
lastTime: 0,
state: initialState,
});
if (!AnimationController.isRunning) {
AnimationController.isRunning = true;
if (isRenderThrottlingEnabled()) {
requestAnimationFrame(AnimationController.tick);
} else {
setTimeout(AnimationController.tick, 0);
}
}
}
}
private static tick() {
if (AnimationController.animations.size > 0) {
for (const [key, animation] of AnimationController.animations) {
const now = performance.now();
const deltaTime =
animation.lastTime === 0 ? 0 : now - animation.lastTime;
const state = animation.animation({
deltaTime,
state: animation.state,
});
if (!state) {
AnimationController.animations.delete(key);
if (AnimationController.animations.size === 0) {
AnimationController.isRunning = false;
return;
}
} else {
animation.lastTime = now;
animation.state = state;
}
}
if (isRenderThrottlingEnabled()) {
requestAnimationFrame(AnimationController.tick);
} else {
setTimeout(AnimationController.tick, 0);
}
}
}
static running(key: string) {
return AnimationController.animations.has(key);
}
static cancel(key: string) {
AnimationController.animations.delete(key);
}
}

32
packages/excalidraw/renderer/interactiveScene.ts

@ -13,7 +13,6 @@ import { @@ -13,7 +13,6 @@ import {
FRAME_STYLE,
invariant,
THEME,
throttleRAF,
} from "@excalidraw/common";
import { FIXED_BINDING_DISTANCE, maxBindingGap } from "@excalidraw/element";
@ -735,7 +734,14 @@ const _renderInteractiveScene = ({ @@ -735,7 +734,14 @@ const _renderInteractiveScene = ({
appState,
renderConfig,
editorInterface,
}: InteractiveSceneRenderConfig) => {
animationState,
deltaTime,
}: InteractiveSceneRenderConfig): {
scrollBars?: ReturnType<typeof getScrollBars>;
atLeastOneVisibleElement: boolean;
elementsMap: RenderableElementsMap;
animationState?: typeof animationState;
} => {
if (canvas === null) {
return { atLeastOneVisibleElement: false, elementsMap };
}
@ -745,6 +751,8 @@ const _renderInteractiveScene = ({ @@ -745,6 +751,8 @@ const _renderInteractiveScene = ({
scale,
);
const nextAnimationState = animationState;
const context = bootstrapCanvas({
canvas,
scale,
@ -1198,34 +1206,20 @@ const _renderInteractiveScene = ({ @@ -1198,34 +1206,20 @@ const _renderInteractiveScene = ({
scrollBars,
atLeastOneVisibleElement: visibleElements.length > 0,
elementsMap,
animationState: nextAnimationState,
};
};
/** throttled to animation framerate */
export const renderInteractiveSceneThrottled = throttleRAF(
(config: InteractiveSceneRenderConfig) => {
const ret = _renderInteractiveScene(config);
config.callback?.(ret);
},
{ trailing: true },
);
/**
* Interactive scene is the ui-canvas where we render bounding boxes, selections
* and other ui stuff.
*/
export const renderInteractiveScene = <
U extends typeof _renderInteractiveScene,
T extends boolean = false,
>(
renderConfig: InteractiveSceneRenderConfig,
throttle?: T,
): T extends true ? void : ReturnType<U> => {
if (throttle) {
renderInteractiveSceneThrottled(renderConfig);
return undefined as T extends true ? void : ReturnType<U>;
}
): ReturnType<U> => {
const ret = _renderInteractiveScene(renderConfig);
renderConfig.callback(ret);
return ret as T extends true ? void : ReturnType<U>;
return ret as ReturnType<U>;
};

2
packages/excalidraw/scene/Renderer.ts

@ -10,7 +10,6 @@ import type { @@ -10,7 +10,6 @@ import type {
import type { Scene } from "@excalidraw/element";
import { renderInteractiveSceneThrottled } from "../renderer/interactiveScene";
import { renderStaticSceneThrottled } from "../renderer/staticScene";
import type { RenderableElementsMap } from "./types";
@ -150,7 +149,6 @@ export class Renderer { @@ -150,7 +149,6 @@ export class Renderer {
// NOTE Doesn't destroy everything (scene, rc, etc.) because it may not be
// safe to break TS contract here (for upstream cases)
public destroy() {
renderInteractiveSceneThrottled.cancel();
renderStaticSceneThrottled.cancel();
this.getRenderableElements.clear();
}

7
packages/excalidraw/scene/types.ts

@ -87,7 +87,12 @@ export type StaticSceneRenderConfig = { @@ -87,7 +87,12 @@ export type StaticSceneRenderConfig = {
renderConfig: StaticCanvasRenderConfig;
};
export type InteractiveSceneRenderAnimationState = {
bindingHighlight: { runtime: number } | undefined;
};
export type InteractiveSceneRenderConfig = {
app: AppClassProperties;
canvas: HTMLCanvasElement | null;
elementsMap: RenderableElementsMap;
visibleElements: readonly NonDeletedExcalidrawElement[];
@ -98,6 +103,8 @@ export type InteractiveSceneRenderConfig = { @@ -98,6 +103,8 @@ export type InteractiveSceneRenderConfig = {
renderConfig: InteractiveCanvasRenderConfig;
editorInterface: EditorInterface;
callback: (data: RenderInteractiveSceneCallback) => void;
animationState?: InteractiveSceneRenderAnimationState;
deltaTime: number;
};
export type NewElementSceneRenderConfig = {

Loading…
Cancel
Save