fix(ui): bbox not centered on very first app startup

This commit is contained in:
psychedelicious
2024-09-23 19:55:33 +10:00
committed by Kent Keirsey
parent dfac0292f4
commit 1d61a587ee
2 changed files with 42 additions and 9 deletions

View File

@@ -98,7 +98,6 @@ export class CanvasStageModule extends CanvasModuleBase {
this.konva.stage.container(this.container);
this.setResizeObserver();
this.fitStageToContainer();
this.fitLayersToStage();
this.konva.stage.on('wheel', this.onStageMouseWheel);
this.konva.stage.on('dragmove', this.onStageDragMove);

View File

@@ -1,26 +1,60 @@
import { useAppDispatch } from 'app/store/storeHooks';
import { buildUseBoolean } from 'common/hooks/useBoolean';
import { useCanvasManagerSafe } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
import { useCallback, useMemo } from 'react';
import { useCallback } from 'react';
import type { ImageDTO } from 'services/api/types';
/**
* There's a race condition that causes the canvas to not fit to layers on the very first app startup.
*
* The canvas stage uses a resize observer to fit the stage to the container, and on the first resize event, it also
* fits the layers to the stage. Subsequent resize events only fit the stage to the container, they do not fit layers
* to the stage.
*
* On the very first app startup (new user or after they reset all web UI state), the resizable panels library needs
* to do one extra resize as it initializes and figures out its target size. At this time, the canvas stage has already
* done its one-time fit layers to stage, so the canvas stage does not fit layers to the stage again.
*
* For the end user, this means that the bbox is not centered in the canvas stage on the very first app startup. On
* all subsequent app startups, the bbox is centered in the canvas stage.
*
* We can hack around this, thanks to the fact that the image viewer is always opened on the first app startup. By the
* time the user closes it, the resizable panels library has already done its one extra resize and the DOM layout has
* stablized. So we can track the first time the image viewer is closed and fit the layers to the stage at that time,
* ensuring that the bbox is centered in the canvas stage on that first app startup.
*
* TODO(psyche): Figure out a better way to do handle this...
*/
let didCloseImageViewer = false;
const [useImageViewerState, $imageViewerState] = buildUseBoolean(true);
export const useImageViewer = () => {
const dispatch = useAppDispatch();
const canvasManager = useCanvasManagerSafe();
const imageViewerState = useImageViewerState();
const isOpen = useMemo(() => imageViewerState.isTrue, [imageViewerState]);
const open = useMemo(() => imageViewerState.setTrue, [imageViewerState]);
const close = useMemo(() => imageViewerState.setFalse, [imageViewerState]);
const toggle = useMemo(() => imageViewerState.toggle, [imageViewerState]);
const close = useCallback(() => {
if (!didCloseImageViewer && canvasManager) {
didCloseImageViewer = true;
canvasManager.stage.fitLayersToStage();
}
imageViewerState.setFalse();
}, [canvasManager, imageViewerState]);
const openImageInViewer = useCallback(
(imageDTO: ImageDTO) => {
dispatch(imageToCompareChanged(null));
dispatch(imageSelected(imageDTO));
open();
imageViewerState.setTrue();
},
[dispatch, open]
[dispatch, imageViewerState]
);
return { isOpen, open, close, toggle, $state: $imageViewerState, openImageInViewer };
return {
isOpen: imageViewerState.isTrue,
open: imageViewerState.setTrue,
close,
toggle: imageViewerState.toggle,
$state: $imageViewerState,
openImageInViewer,
};
};