perf(view): reduce shell rerenders
This commit is contained in:
76
view/App.tsx
76
view/App.tsx
@@ -10,9 +10,10 @@ import { LayersSheet } from "./LayersSheet";
|
||||
import { ShortcutsDisplay } from "./ShortcutsDisplay";
|
||||
import { ToolOverlay } from "./ToolOverlay";
|
||||
import { resolveTransformTargetBounds, selectedTransformTarget } from "@editor/transform-targets";
|
||||
import type { AppState } from "@editor/state";
|
||||
import type { ToolId } from "@editor/tools";
|
||||
import { handleDeleteSelectionKey, handleHistoryKey, handleToolKey, keybindEventFromKeyboardEvent } from "@input/index";
|
||||
import { useAppState } from "./useAppState";
|
||||
import { shallowEqual, useAppState } from "./useAppState";
|
||||
import { downloadArtboardPng } from "./exportArtboardPng";
|
||||
import { useImageImport } from "./useImageImport";
|
||||
import { useViewportActivityIsland } from "./useViewportActivityIsland";
|
||||
@@ -23,14 +24,15 @@ export type AppProps = {
|
||||
};
|
||||
|
||||
export function App({ app }: AppProps) {
|
||||
const state = useAppState(app.store);
|
||||
const viewportActivityIsland = useViewportActivityIsland(state.editor.viewport);
|
||||
const shellState = useAppState(app.store, selectAppShellState, shallowEqual);
|
||||
const { document, selection, viewport, tools, transformSession, maskEdit } = shellState;
|
||||
const viewportActivityIsland = useViewportActivityIsland(viewport);
|
||||
const imageImport = useImageImport(app.store);
|
||||
const [layersOpen, setLayersOpen] = useState(false);
|
||||
const [generateOpen, setGenerateOpen] = useState(false);
|
||||
const previousGenerateTool = useRef<ToolId>("select");
|
||||
const transformTarget = state.editor.transformSession?.target ?? selectedTransformTarget(state.document, state.editor.selection);
|
||||
const activeArtboard = state.document.artboards.find((artboard) => artboard.id === state.editor.selection.artboardId) ?? state.document.artboards[0];
|
||||
const transformTarget = transformSession?.target ?? selectedTransformTarget(document, selection);
|
||||
const activeArtboard = document.artboards.find((artboard) => artboard.id === selection.artboardId) ?? document.artboards[0];
|
||||
|
||||
const openGenerate = useCallback(() => {
|
||||
const activeTool = app.store.getState().editor.tools.activeTool;
|
||||
@@ -108,8 +110,8 @@ export function App({ app }: AppProps) {
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [app.store, closeGenerate, toggleGenerate]);
|
||||
const transformBounds = transformTarget ? resolveTransformTargetBounds(state.document, transformTarget) : undefined;
|
||||
const brushHint = brushUnavailableHint(state.document, state.editor);
|
||||
const transformBounds = transformTarget ? resolveTransformTargetBounds(document, transformTarget) : undefined;
|
||||
const brushHint = brushUnavailableHint(document, { selection, tools, maskEdit });
|
||||
|
||||
return (
|
||||
<main className="relative h-full overflow-hidden bg-[radial-gradient(circle_at_20%_18%,rgba(148,163,184,0.18),transparent_34%),radial-gradient(circle_at_82%_22%,rgba(71,85,105,0.22),transparent_36%),radial-gradient(circle_at_48%_88%,rgba(30,41,59,0.28),transparent_40%),linear-gradient(135deg,#020617_0%,#0f172a_46%,#111827_100%)] text-foreground">
|
||||
@@ -122,7 +124,7 @@ export function App({ app }: AppProps) {
|
||||
<button type="button" className={topBarButtonClass(generateOpen)} aria-pressed={generateOpen} onClick={toggleGenerate}>
|
||||
<Sparkle size={24} weight={generateOpen ? "fill" : "regular"} />
|
||||
</button>
|
||||
<button type="button" className={topBarButtonClass()} disabled={!activeArtboard} onClick={() => activeArtboard && void downloadArtboardPng(activeArtboard, state.document.assets)}>
|
||||
<button type="button" className={topBarButtonClass()} disabled={!activeArtboard} onClick={() => activeArtboard && void downloadArtboardPng(activeArtboard, document.assets)}>
|
||||
<DownloadSimple size={24} />
|
||||
</button>
|
||||
<button type="button" className={topBarButtonClass(layersOpen)} aria-pressed={layersOpen} onClick={() => { closeGenerate(); setLayersOpen((open) => !open); }}>
|
||||
@@ -132,41 +134,37 @@ export function App({ app }: AppProps) {
|
||||
</header>
|
||||
<div className="absolute left-3 top-1/2 z-10 -translate-y-1/2">
|
||||
<ToolOverlay
|
||||
activeTool={state.editor.tools.activeTool}
|
||||
interactionMode={state.editor.tools.interactionMode}
|
||||
activeTool={tools.activeTool}
|
||||
interactionMode={tools.interactionMode}
|
||||
dispatch={app.store.dispatch}
|
||||
/>
|
||||
</div>
|
||||
<GenerateSheet
|
||||
document={state.document}
|
||||
selection={state.editor.selection}
|
||||
viewport={state.editor.viewport}
|
||||
settings={state.editor.tools.generate}
|
||||
settings={tools.generate}
|
||||
open={generateOpen}
|
||||
onOpenChange={(open) => open ? openGenerate() : closeGenerate()}
|
||||
dispatch={app.store.dispatch}
|
||||
/>
|
||||
<LayersSheet
|
||||
document={state.document}
|
||||
selection={state.editor.selection}
|
||||
maskEdit={state.editor.maskEdit}
|
||||
document={document}
|
||||
selection={selection}
|
||||
maskEdit={maskEdit}
|
||||
open={layersOpen}
|
||||
dispatch={app.store.dispatch}
|
||||
/>
|
||||
<div className="absolute inset-x-0 bottom-4 z-10 flex justify-center">
|
||||
<BottomControlsIsland
|
||||
document={state.document}
|
||||
selection={state.editor.selection}
|
||||
viewport={state.editor.viewport}
|
||||
visible={state.editor.tools.activeTool === "generate" || state.editor.tools.activeTool === "brush" || state.editor.tools.activeTool === "eraser" || state.editor.tools.activeTool === "chromaKey" || state.editor.tools.activeTool === "magicWand" || Boolean(transformBounds) || viewportActivityIsland.visible}
|
||||
document={document}
|
||||
selection={selection}
|
||||
viewport={viewport}
|
||||
visible={tools.activeTool === "generate" || tools.activeTool === "brush" || tools.activeTool === "eraser" || tools.activeTool === "chromaKey" || tools.activeTool === "magicWand" || Boolean(transformBounds) || viewportActivityIsland.visible}
|
||||
action={viewportActivityIsland.action}
|
||||
activeTool={state.editor.tools.activeTool}
|
||||
brushSettings={state.editor.tools.brush}
|
||||
generateSettings={state.editor.tools.generate}
|
||||
chromaKeySettings={state.editor.tools.chromaKey}
|
||||
magicWandSettings={state.editor.tools.magicWand}
|
||||
editingMask={Boolean(state.editor.maskEdit)}
|
||||
maskViewMode={state.editor.maskEdit?.viewMode ?? "composite"}
|
||||
activeTool={tools.activeTool}
|
||||
brushSettings={tools.brush}
|
||||
generateSettings={tools.generate}
|
||||
chromaKeySettings={tools.chromaKey}
|
||||
magicWandSettings={tools.magicWand}
|
||||
editingMask={Boolean(maskEdit)}
|
||||
maskViewMode={maskEdit?.viewMode ?? "composite"}
|
||||
brushHint={brushHint}
|
||||
transformBounds={viewportActivityIsland.visible ? undefined : transformBounds}
|
||||
transformTarget={viewportActivityIsland.visible ? undefined : transformTarget}
|
||||
@@ -186,4 +184,24 @@ function topBarButtonClass(active = false) {
|
||||
return active ? `${base} bg-white text-black hover:bg-white hover:text-black` : `${base} text-white/75 hover:bg-white/10 hover:text-white`;
|
||||
}
|
||||
|
||||
type AppShellState = {
|
||||
document: AppState["document"];
|
||||
selection: AppState["editor"]["selection"];
|
||||
viewport: AppState["editor"]["viewport"];
|
||||
tools: AppState["editor"]["tools"];
|
||||
transformSession: AppState["editor"]["transformSession"];
|
||||
maskEdit: AppState["editor"]["maskEdit"];
|
||||
};
|
||||
|
||||
function selectAppShellState(state: AppState): AppShellState {
|
||||
return {
|
||||
document: state.document,
|
||||
selection: state.editor.selection,
|
||||
viewport: state.editor.viewport,
|
||||
tools: state.editor.tools,
|
||||
transformSession: state.editor.transformSession,
|
||||
maskEdit: state.editor.maskEdit,
|
||||
};
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
Reference in New Issue
Block a user