feat: async renderScene
This commit is contained in:
parent
f2611f5d1f
commit
9e9f564ea9
@ -82,6 +82,7 @@ import {
|
|||||||
isElementInFrame,
|
isElementInFrame,
|
||||||
} from "../frame";
|
} from "../frame";
|
||||||
import "canvas-roundrect-polyfill";
|
import "canvas-roundrect-polyfill";
|
||||||
|
import { Unpromisify } from "../utility-types";
|
||||||
|
|
||||||
export const DEFAULT_SPACING = 2;
|
export const DEFAULT_SPACING = 2;
|
||||||
|
|
||||||
@ -577,7 +578,7 @@ export const paintBackground = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const _renderScene = ({
|
export const _renderScene = async ({
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
scale,
|
scale,
|
||||||
@ -591,11 +592,14 @@ export const _renderScene = ({
|
|||||||
rc: RoughCanvas;
|
rc: RoughCanvas;
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
renderConfig: RenderConfig;
|
renderConfig: RenderConfig;
|
||||||
}) =>
|
}): Promise<{
|
||||||
|
atLeastOneVisibleElement: boolean;
|
||||||
|
scrollBars: ScrollBars | undefined;
|
||||||
|
}> =>
|
||||||
// extra options passed to the renderer
|
// extra options passed to the renderer
|
||||||
{
|
{
|
||||||
if (canvas === null) {
|
if (canvas === null) {
|
||||||
return { atLeastOneVisibleElement: false };
|
return { atLeastOneVisibleElement: false, scrollBars: undefined };
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
renderScrollbars = false,
|
renderScrollbars = false,
|
||||||
@ -621,19 +625,16 @@ export const _renderScene = ({
|
|||||||
context.filter = THEME_FILTER;
|
context.filter = THEME_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
let renderOutput: {
|
await paintBackground(
|
||||||
atLeastOneVisibleElement: boolean;
|
context,
|
||||||
scrollBars: ScrollBars | undefined;
|
normalizedCanvasWidth,
|
||||||
} = {
|
normalizedCanvasHeight,
|
||||||
atLeastOneVisibleElement: false,
|
{
|
||||||
scrollBars: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
paintBackground(context, normalizedCanvasWidth, normalizedCanvasHeight, {
|
|
||||||
isExporting,
|
isExporting,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
exportBackgroundImage,
|
exportBackgroundImage,
|
||||||
}).then(() => {
|
},
|
||||||
|
);
|
||||||
// Apply zoom
|
// Apply zoom
|
||||||
context.save();
|
context.save();
|
||||||
context.scale(renderConfig.zoom.value, renderConfig.zoom.value);
|
context.scale(renderConfig.zoom.value, renderConfig.zoom.value);
|
||||||
@ -653,18 +654,13 @@ export const _renderScene = ({
|
|||||||
|
|
||||||
// Paint visible elements
|
// Paint visible elements
|
||||||
const visibleElements = elements.filter((element) =>
|
const visibleElements = elements.filter((element) =>
|
||||||
isVisibleElement(
|
isVisibleElement(element, normalizedCanvasWidth, normalizedCanvasHeight, {
|
||||||
element,
|
|
||||||
normalizedCanvasWidth,
|
|
||||||
normalizedCanvasHeight,
|
|
||||||
{
|
|
||||||
zoom: renderConfig.zoom,
|
zoom: renderConfig.zoom,
|
||||||
offsetLeft: appState.offsetLeft,
|
offsetLeft: appState.offsetLeft,
|
||||||
offsetTop: appState.offsetTop,
|
offsetTop: appState.offsetTop,
|
||||||
scrollX: renderConfig.scrollX,
|
scrollX: renderConfig.scrollX,
|
||||||
scrollY: renderConfig.scrollY,
|
scrollY: renderConfig.scrollY,
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isExporting && exportBackgroundImage) {
|
if (isExporting && exportBackgroundImage) {
|
||||||
@ -707,9 +703,8 @@ export const _renderScene = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let editingLinearElement:
|
let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined =
|
||||||
| NonDeleted<ExcalidrawLinearElement>
|
undefined;
|
||||||
| undefined = undefined;
|
|
||||||
|
|
||||||
visibleElements
|
visibleElements
|
||||||
.filter((el) => !isEmbeddableOrFrameLabel(el))
|
.filter((el) => !isEmbeddableOrFrameLabel(el))
|
||||||
@ -863,8 +858,7 @@ export const _renderScene = ({
|
|||||||
// correct element from visible elements
|
// correct element from visible elements
|
||||||
if (
|
if (
|
||||||
locallySelectedElements.length === 1 &&
|
locallySelectedElements.length === 1 &&
|
||||||
appState.editingLinearElement?.elementId ===
|
appState.editingLinearElement?.elementId === locallySelectedElements[0].id
|
||||||
locallySelectedElements[0].id
|
|
||||||
) {
|
) {
|
||||||
renderLinearPointHandles(
|
renderLinearPointHandles(
|
||||||
context,
|
context,
|
||||||
@ -1213,36 +1207,30 @@ export const _renderScene = ({
|
|||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
|
|
||||||
renderOutput = {
|
return {
|
||||||
atLeastOneVisibleElement: visibleElements.length > 0,
|
atLeastOneVisibleElement: visibleElements.length > 0,
|
||||||
scrollBars,
|
scrollBars,
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
atLeastOneVisibleElement: renderOutput.atLeastOneVisibleElement,
|
|
||||||
scrollBars: renderOutput.scrollBars ?? undefined,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSceneThrottled = throttleRAF(
|
const renderSceneThrottled = throttleRAF(
|
||||||
(config: {
|
async (config: {
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
scale: number;
|
scale: number;
|
||||||
rc: RoughCanvas;
|
rc: RoughCanvas;
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
renderConfig: RenderConfig;
|
renderConfig: RenderConfig;
|
||||||
callback?: (data: ReturnType<typeof _renderScene>) => void;
|
callback?: (data: Unpromisify<ReturnType<typeof _renderScene>>) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const ret = _renderScene(config);
|
const ret = await _renderScene(config);
|
||||||
config.callback?.(ret);
|
config.callback?.(ret);
|
||||||
},
|
},
|
||||||
{ trailing: true },
|
{ trailing: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
/** renderScene throttled to animation framerate */
|
/** renderScene throttled to animation framerate */
|
||||||
export const renderScene = <T extends boolean = false>(
|
export const renderScene = async <T extends boolean = false>(
|
||||||
config: {
|
config: {
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
@ -1250,19 +1238,25 @@ export const renderScene = <T extends boolean = false>(
|
|||||||
rc: RoughCanvas;
|
rc: RoughCanvas;
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
renderConfig: RenderConfig;
|
renderConfig: RenderConfig;
|
||||||
callback?: (data: ReturnType<typeof _renderScene>) => void;
|
callback?: (data: Unpromisify<ReturnType<typeof _renderScene>>) => void;
|
||||||
},
|
},
|
||||||
/** Whether to throttle rendering. Defaults to false.
|
/** Whether to throttle rendering. Defaults to false.
|
||||||
* When throttling, no value is returned. Use the callback instead. */
|
* When throttling, no value is returned. Use the callback instead. */
|
||||||
throttle?: T,
|
throttle?: T,
|
||||||
): T extends true ? void : ReturnType<typeof _renderScene> => {
|
): Promise<
|
||||||
|
T extends true ? void : Unpromisify<ReturnType<typeof _renderScene>>
|
||||||
|
> => {
|
||||||
if (throttle) {
|
if (throttle) {
|
||||||
renderSceneThrottled(config);
|
renderSceneThrottled(config);
|
||||||
return undefined as T extends true ? void : ReturnType<typeof _renderScene>;
|
return undefined as T extends true
|
||||||
|
? void
|
||||||
|
: Unpromisify<ReturnType<typeof _renderScene>>;
|
||||||
}
|
}
|
||||||
const ret = _renderScene(config);
|
const ret = await _renderScene(config);
|
||||||
config.callback?.(ret);
|
config.callback?.(ret);
|
||||||
return ret as T extends true ? void : ReturnType<typeof _renderScene>;
|
return ret as T extends true
|
||||||
|
? void
|
||||||
|
: Unpromisify<ReturnType<typeof _renderScene>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTransformHandles = (
|
const renderTransformHandles = (
|
||||||
|
@ -54,7 +54,7 @@ export const exportToCanvas = async (
|
|||||||
|
|
||||||
const onlyExportingSingleFrame = isOnlyExportingSingleFrame(elements);
|
const onlyExportingSingleFrame = isOnlyExportingSingleFrame(elements);
|
||||||
|
|
||||||
renderScene({
|
await renderScene({
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
scale,
|
scale,
|
||||||
|
@ -27,7 +27,7 @@ export type RenderConfig = {
|
|||||||
/** when exporting the behavior is slightly different (e.g. we can't use
|
/** when exporting the behavior is slightly different (e.g. we can't use
|
||||||
CSS filters), and we disable render optimizations for best output */
|
CSS filters), and we disable render optimizations for best output */
|
||||||
isExporting: boolean;
|
isExporting: boolean;
|
||||||
exportBackgroundImage?: string;
|
exportBackgroundImage?: string | null;
|
||||||
selectionColor?: string;
|
selectionColor?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,3 +54,5 @@ export type Assert<T extends true> = T;
|
|||||||
export type NestedKeyOf<T, K = keyof T> = K extends keyof T & (string | number)
|
export type NestedKeyOf<T, K = keyof T> = K extends keyof T & (string | number)
|
||||||
? `${K}` | (T[K] extends object ? `${K}.${NestedKeyOf<T[K]>}` : never)
|
? `${K}` | (T[K] extends object ? `${K}.${NestedKeyOf<T[K]>}` : never)
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
|
export type Unpromisify<T> = T extends Promise<infer U> ? U : T;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user