feat: async renderScene

This commit is contained in:
Arnošt Pleskot 2023-08-08 17:30:54 +02:00
parent f2611f5d1f
commit 9e9f564ea9
No known key found for this signature in database
4 changed files with 543 additions and 547 deletions

View File

@ -82,6 +82,7 @@ import {
isElementInFrame,
} from "../frame";
import "canvas-roundrect-polyfill";
import { Unpromisify } from "../utility-types";
export const DEFAULT_SPACING = 2;
@ -577,7 +578,7 @@ export const paintBackground = async (
}
};
export const _renderScene = ({
export const _renderScene = async ({
elements,
appState,
scale,
@ -591,11 +592,14 @@ export const _renderScene = ({
rc: RoughCanvas;
canvas: HTMLCanvasElement;
renderConfig: RenderConfig;
}) =>
}): Promise<{
atLeastOneVisibleElement: boolean;
scrollBars: ScrollBars | undefined;
}> =>
// extra options passed to the renderer
{
if (canvas === null) {
return { atLeastOneVisibleElement: false };
return { atLeastOneVisibleElement: false, scrollBars: undefined };
}
const {
renderScrollbars = false,
@ -621,19 +625,16 @@ export const _renderScene = ({
context.filter = THEME_FILTER;
}
let renderOutput: {
atLeastOneVisibleElement: boolean;
scrollBars: ScrollBars | undefined;
} = {
atLeastOneVisibleElement: false,
scrollBars: undefined,
};
paintBackground(context, normalizedCanvasWidth, normalizedCanvasHeight, {
await paintBackground(
context,
normalizedCanvasWidth,
normalizedCanvasHeight,
{
isExporting,
viewBackgroundColor,
exportBackgroundImage,
}).then(() => {
},
);
// Apply zoom
context.save();
context.scale(renderConfig.zoom.value, renderConfig.zoom.value);
@ -653,18 +654,13 @@ export const _renderScene = ({
// Paint visible elements
const visibleElements = elements.filter((element) =>
isVisibleElement(
element,
normalizedCanvasWidth,
normalizedCanvasHeight,
{
isVisibleElement(element, normalizedCanvasWidth, normalizedCanvasHeight, {
zoom: renderConfig.zoom,
offsetLeft: appState.offsetLeft,
offsetTop: appState.offsetTop,
scrollX: renderConfig.scrollX,
scrollY: renderConfig.scrollY,
},
),
}),
);
if (isExporting && exportBackgroundImage) {
@ -707,9 +703,8 @@ export const _renderScene = ({
}
});
let editingLinearElement:
| NonDeleted<ExcalidrawLinearElement>
| undefined = undefined;
let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined =
undefined;
visibleElements
.filter((el) => !isEmbeddableOrFrameLabel(el))
@ -863,8 +858,7 @@ export const _renderScene = ({
// correct element from visible elements
if (
locallySelectedElements.length === 1 &&
appState.editingLinearElement?.elementId ===
locallySelectedElements[0].id
appState.editingLinearElement?.elementId === locallySelectedElements[0].id
) {
renderLinearPointHandles(
context,
@ -1213,36 +1207,30 @@ export const _renderScene = ({
context.restore();
renderOutput = {
return {
atLeastOneVisibleElement: visibleElements.length > 0,
scrollBars,
};
});
return {
atLeastOneVisibleElement: renderOutput.atLeastOneVisibleElement,
scrollBars: renderOutput.scrollBars ?? undefined,
};
};
const renderSceneThrottled = throttleRAF(
(config: {
async (config: {
elements: readonly NonDeletedExcalidrawElement[];
appState: AppState;
scale: number;
rc: RoughCanvas;
canvas: HTMLCanvasElement;
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);
},
{ trailing: true },
);
/** renderScene throttled to animation framerate */
export const renderScene = <T extends boolean = false>(
export const renderScene = async <T extends boolean = false>(
config: {
elements: readonly NonDeletedExcalidrawElement[];
appState: AppState;
@ -1250,19 +1238,25 @@ export const renderScene = <T extends boolean = false>(
rc: RoughCanvas;
canvas: HTMLCanvasElement;
renderConfig: RenderConfig;
callback?: (data: ReturnType<typeof _renderScene>) => void;
callback?: (data: Unpromisify<ReturnType<typeof _renderScene>>) => void;
},
/** Whether to throttle rendering. Defaults to false.
* When throttling, no value is returned. Use the callback instead. */
throttle?: T,
): T extends true ? void : ReturnType<typeof _renderScene> => {
): Promise<
T extends true ? void : Unpromisify<ReturnType<typeof _renderScene>>
> => {
if (throttle) {
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);
return ret as T extends true ? void : ReturnType<typeof _renderScene>;
return ret as T extends true
? void
: Unpromisify<ReturnType<typeof _renderScene>>;
};
const renderTransformHandles = (

View File

@ -54,7 +54,7 @@ export const exportToCanvas = async (
const onlyExportingSingleFrame = isOnlyExportingSingleFrame(elements);
renderScene({
await renderScene({
elements,
appState,
scale,

View File

@ -27,7 +27,7 @@ export type RenderConfig = {
/** when exporting the behavior is slightly different (e.g. we can't use
CSS filters), and we disable render optimizations for best output */
isExporting: boolean;
exportBackgroundImage?: string;
exportBackgroundImage?: string | null;
selectionColor?: string;
};

View File

@ -54,3 +54,5 @@ export type Assert<T extends true> = T;
export type NestedKeyOf<T, K = keyof T> = K extends keyof T & (string | number)
? `${K}` | (T[K] extends object ? `${K}.${NestedKeyOf<T[K]>}` : never)
: never;
export type Unpromisify<T> = T extends Promise<infer U> ? U : T;