fix(ui): staging area works

This commit is contained in:
psychedelicious 2024-08-08 17:24:16 +10:00
parent e41987f08c
commit 11a66d1d09
13 changed files with 227 additions and 258 deletions

View File

@ -5,9 +5,10 @@ import { parseify } from 'common/util/serialize';
import {
caImageChanged,
ipaImageChanged,
layerAddedFromImage,
layerAdded,
rgIPAdapterImageChanged,
} from 'features/controlLayers/store/canvasV2Slice';
import type { CanvasLayerState } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { isValidDrop } from 'features/dnd/util/isValidDrop';
@ -106,7 +107,13 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
activeData.payloadType === 'IMAGE_DTO' &&
activeData.payload.imageDTO
) {
dispatch(layerAddedFromImage({ imageObject: imageDTOToImageObject(activeData.payload.imageDTO) }));
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
const { x, y } = getState().canvasV2.bbox.rect;
const overrides: Partial<CanvasLayerState> = {
objects: [imageObject],
position: { x, y },
};
dispatch(layerAdded({ overrides, isSelected: true }));
return;
}

View File

@ -15,7 +15,7 @@ export const AddLayerButton = memo(() => {
dispatch(rgAdded());
}, [dispatch]);
const addRasterLayer = useCallback(() => {
dispatch(layerAdded());
dispatch(layerAdded({ isSelected: true }));
}, [dispatch]);
return (

View File

@ -1,5 +1,6 @@
import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasPreview } from 'features/controlLayers/konva/CanvasPreview';
import type { Rect } from 'features/controlLayers/store/types';
import Konva from 'konva';
import { atom } from 'nanostores';
@ -23,6 +24,7 @@ export class CanvasBbox {
static CORNER_ANCHORS: string[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
static NO_ANCHORS: string[] = [];
parent: CanvasPreview;
manager: CanvasManager;
konva: {
@ -31,8 +33,9 @@ export class CanvasBbox {
transformer: Konva.Transformer;
};
constructor(manager: CanvasManager) {
this.manager = manager;
constructor(parent: CanvasPreview) {
this.parent = parent;
this.manager = this.parent.manager;
// Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when
// transforming the bbox.
const bbox = this.manager.stateApi.getBbox();

View File

@ -2,6 +2,7 @@ import { Mutex } from 'async-mutex';
import { deepClone } from 'common/util/deepClone';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import type { CanvasStagingArea } from 'features/controlLayers/konva/CanvasStagingArea';
import { FILTER_MAP } from 'features/controlLayers/konva/filters';
import { loadImage } from 'features/controlLayers/konva/util';
import type { CanvasImageState, GetLoggingContext } from 'features/controlLayers/store/types';
@ -19,7 +20,7 @@ export class CanvasImageRenderer {
static PLACEHOLDER_TEXT_NAME = `${CanvasImageRenderer.TYPE}_placeholder-text`;
id: string;
parent: CanvasObjectRenderer;
parent: CanvasObjectRenderer | CanvasStagingArea;
manager: CanvasManager;
log: Logger;
getLoggingContext: GetLoggingContext;
@ -36,7 +37,7 @@ export class CanvasImageRenderer {
isError: boolean = false;
mutex = new Mutex();
constructor(state: CanvasImageState, parent: CanvasObjectRenderer) {
constructor(state: CanvasImageState, parent: CanvasObjectRenderer | CanvasStagingArea) {
const { id, image } = state;
const { width, height } = image;
this.id = id;
@ -97,18 +98,16 @@ export class CanvasImageRenderer {
this.onFailedToLoadImage();
return;
}
// Load the thumbnail first, but let the image load in parallel
loadImage(imageDTO.thumbnail_url)
.then((thumbnailElement) => {
this.thumbnailElement = thumbnailElement;
this.mutex.runExclusive(this.updateImageElement);
})
.catch(this.onFailedToLoadImage);
loadImage(imageDTO.image_url)
.then((imageElement) => {
this.imageElement = imageElement;
this.mutex.runExclusive(this.updateImageElement);
this.updateImageElement();
})
.catch(this.onFailedToLoadImage);
this.imageElement = await loadImage(imageDTO.image_url);
await this.updateImageElement();
} catch {
this.onFailedToLoadImage();
}
@ -123,36 +122,50 @@ export class CanvasImageRenderer {
this.konva.placeholder.group.visible(true);
};
updateImageElement = () => {
const element = this.imageElement ?? this.thumbnailElement;
updateImageElement = async () => {
const release = await this.mutex.acquire();
if (element) {
if (this.konva.image && this.konva.image.image() !== element) {
this.konva.image.setAttrs({
image: element,
});
} else {
this.konva.image = new Konva.Image({
name: CanvasImageRenderer.IMAGE_NAME,
listening: false,
image: element,
width: this.state.image.width,
height: this.state.image.height,
});
this.konva.group.add(this.konva.image);
try {
const element = this.imageElement ?? this.thumbnailElement;
const { width, height } = this.state.image;
if (element) {
if (this.konva.image) {
this.log.trace('Updating Konva image attrs');
this.konva.image.setAttrs({
image: element,
width,
height,
});
} else {
this.log.trace('Creating new Konva image');
this.konva.image = new Konva.Image({
name: CanvasImageRenderer.IMAGE_NAME,
listening: false,
image: element,
width,
height,
});
this.konva.group.add(this.konva.image);
}
if (this.state.filters.length > 0) {
this.konva.image.cache();
this.konva.image.filters(this.state.filters.map((f) => FILTER_MAP[f]));
} else {
this.konva.image.clearCache();
this.konva.image.filters([]);
}
this.konva.placeholder.rect.setAttrs({ width, height });
this.konva.placeholder.text.setAttrs({ width, height, fontSize: width / 16 });
this.isLoading = false;
this.isError = false;
this.konva.placeholder.group.visible(false);
}
if (this.state.filters.length > 0) {
this.konva.image.cache();
this.konva.image.filters(this.state.filters.map((f) => FILTER_MAP[f]));
} else {
this.konva.image.clearCache();
this.konva.image.filters([]);
}
this.isLoading = false;
this.isError = false;
this.konva.placeholder.group.visible(false);
} finally {
release();
}
};
@ -173,8 +186,6 @@ export class CanvasImageRenderer {
this.konva.image?.clearCache();
this.konva.image?.filters([]);
}
this.konva.placeholder.rect.setAttrs({ width, height });
this.konva.placeholder.text.setAttrs({ width, height, fontSize: width / 16 });
this.state = state;
return true;
}

View File

@ -7,7 +7,6 @@ import type { CanvasBrushLineRenderer } from 'features/controlLayers/konva/Canva
import type { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine';
import type { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
import { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasProgressPreview';
import type { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect';
import type { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
import { MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/controlLayers/konva/constants';
@ -19,7 +18,6 @@ import {
nanoid,
} from 'features/controlLayers/konva/util';
import type { Extents, ExtentsResult, GetBboxTask, WorkerLogMessage } from 'features/controlLayers/konva/worker';
import { $lastProgressEvent, $shouldShowStagedImage } from 'features/controlLayers/store/canvasV2Slice';
import type {
CanvasControlAdapterState,
CanvasEntityIdentifier,
@ -49,14 +47,12 @@ import type { ImageCategory, ImageDTO } from 'services/api/types';
import { assert } from 'tsafe';
import { CanvasBackground } from './CanvasBackground';
import { CanvasBbox } from './CanvasBbox';
import { CanvasControlAdapter } from './CanvasControlAdapter';
import { CanvasLayerAdapter } from './CanvasLayerAdapter';
import { CanvasMaskAdapter } from './CanvasMaskAdapter';
import { CanvasPreview } from './CanvasPreview';
import { CanvasStagingArea } from './CanvasStagingArea';
import { CanvasStateApi } from './CanvasStateApi';
import { CanvasTool } from './CanvasTool';
import { setStageEventHandlers } from './events';
// type Extents = {
@ -159,15 +155,6 @@ export class CanvasManager {
this.transformingEntity = new PubSub<CanvasEntityIdentifier | null>(null);
this.toolState = new PubSub(this.stateApi.getToolState());
this.currentFill = new PubSub(this.getCurrentFill());
this.selectedEntityIdentifier = new PubSub(
this.stateApi.getState().selectedEntityIdentifier,
(a, b) => a?.id === b?.id
);
this.selectedEntity = new PubSub(
this.getSelectedEntity(),
(a, b) => a?.state === b?.state && a?.adapter === b?.adapter
);
this._prevState = this.stateApi.getState();
@ -187,13 +174,8 @@ export class CanvasManager {
uploadImage,
};
this.preview = new CanvasPreview(
new CanvasBbox(this),
new CanvasTool(this),
new CanvasStagingArea(this),
new CanvasProgressPreview(this)
);
this.stage.add(this.preview.layer);
this.preview = new CanvasPreview(this);
this.stage.add(this.preview.getLayer());
this.background = new CanvasBackground(this);
this.stage.add(this.background.konva.layer);
@ -226,6 +208,16 @@ export class CanvasManager {
this.log.error('Worker message error');
};
this.currentFill = new PubSub(this.getCurrentFill());
this.selectedEntityIdentifier = new PubSub(
this.stateApi.getState().selectedEntityIdentifier,
(a, b) => a?.id === b?.id
);
this.selectedEntity = new PubSub(
this.getSelectedEntity(),
(a, b) => a?.state === b?.state && a?.adapter === b?.adapter
);
this.inpaintMask = new CanvasMaskAdapter(this.stateApi.getInpaintMaskState(), this);
this.stage.add(this.inpaintMask.konva.layer);
}
@ -249,10 +241,6 @@ export class CanvasManager {
this._worker.postMessage(task, [data.buffer]);
}
async renderProgressPreview() {
await this.preview.progressPreview.render(this.stateApi.$lastProgressEvent.get());
}
async renderControlAdapters() {
const { entities } = this.stateApi.getControlAdaptersState();
@ -291,7 +279,7 @@ export class CanvasManager {
this.regions.get(rg.id)?.konva.layer.zIndex(++zIndex);
}
this.inpaintMask.konva.layer.zIndex(++zIndex);
this.preview.layer.zIndex(++zIndex);
this.preview.getLayer().zIndex(++zIndex);
}
fitStageToContainer() {
@ -611,25 +599,6 @@ export class CanvasManager {
const unsubscribeRenderer = this._store.subscribe(this.render);
// When we this flag, we need to render the staging area
const unsubscribeShouldShowStagedImage = $shouldShowStagedImage.subscribe(
async (shouldShowStagedImage, prevShouldShowStagedImage) => {
if (shouldShowStagedImage !== prevShouldShowStagedImage) {
this.log.debug('Rendering staging area');
await this.preview.stagingArea.render();
}
}
);
const unsubscribeLastProgressEvent = $lastProgressEvent.subscribe(
async (lastProgressEvent, prevLastProgressEvent) => {
if (lastProgressEvent !== prevLastProgressEvent) {
this.log.debug('Rendering progress image');
await this.preview.progressPreview.render(lastProgressEvent);
}
}
);
this.log.debug('First render of konva stage');
this.preview.tool.render();
this.render();
@ -650,8 +619,6 @@ export class CanvasManager {
this.preview.destroy();
unsubscribeRenderer();
unsubscribeListeners();
unsubscribeShouldShowStagedImage();
unsubscribeLastProgressEvent();
resizeObserver.disconnect();
};
};

View File

@ -1,40 +1,51 @@
import type { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasProgressPreview';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasProgressImage } from 'features/controlLayers/konva/CanvasProgressImage';
import Konva from 'konva';
import type { CanvasBbox } from './CanvasBbox';
import type { CanvasStagingArea } from './CanvasStagingArea';
import type { CanvasTool } from './CanvasTool';
import { CanvasBbox } from './CanvasBbox';
import { CanvasStagingArea } from './CanvasStagingArea';
import { CanvasTool } from './CanvasTool';
export class CanvasPreview {
layer: Konva.Layer;
manager: CanvasManager;
konva: {
layer: Konva.Layer;
};
tool: CanvasTool;
bbox: CanvasBbox;
stagingArea: CanvasStagingArea;
progressPreview: CanvasProgressPreview;
progressImage: CanvasProgressImage;
constructor(
bbox: CanvasBbox,
tool: CanvasTool,
stagingArea: CanvasStagingArea,
progressPreview: CanvasProgressPreview
) {
this.layer = new Konva.Layer({ listening: true, imageSmoothingEnabled: false });
constructor(manager: CanvasManager) {
this.manager = manager;
this.konva = {
layer: new Konva.Layer({ listening: true, imageSmoothingEnabled: false }),
};
this.stagingArea = stagingArea;
this.layer.add(this.stagingArea.konva.group);
this.stagingArea = new CanvasStagingArea(this);
this.konva.layer.add(...this.stagingArea.getNodes());
this.bbox = bbox;
this.layer.add(this.bbox.konva.group);
this.progressImage = new CanvasProgressImage(this);
this.konva.layer.add(...this.progressImage.getNodes());
this.tool = tool;
this.layer.add(this.tool.konva.group);
this.bbox = new CanvasBbox(this);
this.konva.layer.add(this.bbox.konva.group);
this.progressPreview = progressPreview;
this.layer.add(this.progressPreview.konva.group);
this.tool = new CanvasTool(this);
this.konva.layer.add(this.tool.konva.group);
}
getLayer = () => {
return this.konva.layer;
};
destroy() {
// this.stagingArea.destroy(); // TODO(psyche): implement destroy
this.progressImage.destroy();
// this.bbox.destroy(); // TODO(psyche): implement destroy
this.tool.destroy();
this.layer.destroy();
this.konva.layer.destroy();
}
}

View File

@ -1,5 +1,9 @@
import { loadImage } from 'features/controlLayers/konva/util';
import { Mutex } from 'async-mutex';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasPreview } from 'features/controlLayers/konva/CanvasPreview';
import { getPrefixedId, loadImage } from 'features/controlLayers/konva/util';
import Konva from 'konva';
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
export class CanvasProgressImage {
static NAME_PREFIX = 'progress-image';
@ -7,53 +11,86 @@ export class CanvasProgressImage {
static IMAGE_NAME = `${CanvasProgressImage.NAME_PREFIX}_image`;
id: string;
progressImageId: string | null;
parent: CanvasPreview;
manager: CanvasManager;
/**
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
*/
subscriptions: Set<() => void> = new Set();
progressImageId: string | null = null;
konva: {
group: Konva.Group;
image: Konva.Image | null; // The image is loaded asynchronously, so it may not be available immediately
};
isLoading: boolean;
isError: boolean;
isLoading: boolean = false;
isError: boolean = false;
imageElement: HTMLImageElement | null = null;
constructor(arg: { id: string }) {
const { id } = arg;
lastProgressEvent: InvocationDenoiseProgressEvent | null = null;
mutex: Mutex = new Mutex();
constructor(parent: CanvasPreview) {
this.id = getPrefixedId(CanvasProgressImage.NAME_PREFIX);
this.parent = parent;
this.manager = parent.manager;
this.konva = {
group: new Konva.Group({ name: CanvasProgressImage.GROUP_NAME, listening: false }),
image: null,
};
this.id = id;
this.progressImageId = null;
this.isLoading = false;
this.isError = false;
this.manager.stateApi.$lastProgressEvent.listen((event) => {
this.lastProgressEvent = event;
this.render();
});
}
async updateImageSource(
progressImageId: string,
dataURL: string,
x: number,
y: number,
width: number,
height: number
) {
if (this.isLoading) {
getNodes = () => {
return [this.konva.group];
};
render = async () => {
const release = await this.mutex.acquire();
if (!this.lastProgressEvent) {
this.konva.group.visible(false);
this.imageElement = null;
this.isLoading = false;
this.isError = false;
release();
return;
}
const { isStaging } = this.manager.stateApi.getSession();
if (!isStaging) {
release();
return;
}
this.isLoading = true;
const { x, y } = this.manager.stateApi.getBbox().rect;
const { dataURL, width, height } = this.lastProgressEvent.progress_image;
try {
const imageEl = await loadImage(dataURL);
this.imageElement = await loadImage(dataURL);
if (this.konva.image) {
console.log('UPDATING PROGRESS IMAGE')
this.konva.image.setAttrs({
image: imageEl,
image: this.imageElement,
x,
y,
width,
height,
});
} else {
console.log('CREATING NEW PROGRESS IMAGE')
this.konva.image = new Konva.Image({
name: CanvasProgressImage.IMAGE_NAME,
listening: false,
image: imageEl,
image: this.imageElement,
x,
y,
width,
@ -61,14 +98,19 @@ export class CanvasProgressImage {
});
this.konva.group.add(this.konva.image);
}
this.isLoading = false;
this.id = progressImageId;
this.konva.group.visible(true);
} catch {
this.isError = true;
} finally {
this.isLoading = false;
release();
}
}
};
destroy() {
destroy = () => {
for (const unsubscribe of this.subscriptions) {
unsubscribe();
}
this.konva.group.destroy();
}
};
}

View File

@ -1,46 +0,0 @@
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { CanvasProgressImage } from 'features/controlLayers/konva/CanvasProgressImage';
import Konva from 'konva';
import type { InvocationDenoiseProgressEvent } from 'services/events/types';
export class CanvasProgressPreview {
static NAME_PREFIX = 'progress-preview';
static GROUP_NAME = `${CanvasProgressPreview.NAME_PREFIX}_group`;
konva: {
group: Konva.Group;
progressImage: CanvasProgressImage;
};
manager: CanvasManager;
constructor(manager: CanvasManager) {
this.manager = manager;
this.konva = {
group: new Konva.Group({ name: CanvasProgressPreview.GROUP_NAME, listening: false }),
progressImage: new CanvasProgressImage({ id: 'progress-image' }),
};
this.konva.group.add(this.konva.progressImage.konva.group);
}
async render(lastProgressEvent: InvocationDenoiseProgressEvent | null) {
const bboxRect = this.manager.stateApi.getBbox().rect;
const session = this.manager.stateApi.getSession();
if (lastProgressEvent && session.isStaging) {
const { invocation, step, progress_image } = lastProgressEvent;
const { dataURL } = progress_image;
const { x, y, width, height } = bboxRect;
const progressImageId = `${invocation.id}_${step}`;
if (
!this.konva.progressImage.isLoading &&
!this.konva.progressImage.isError &&
this.konva.progressImage.progressImageId !== progressImageId
) {
await this.konva.progressImage.updateImageSource(progressImageId, dataURL, x, y, width, height);
this.konva.progressImage.konva.group.visible(true);
}
} else {
this.konva.progressImage.konva.group.visible(false);
}
}
}

View File

@ -1,5 +1,6 @@
import { CanvasImageRenderer } from 'features/controlLayers/konva/CanvasImage';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasPreview } from 'features/controlLayers/konva/CanvasPreview';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import type { GetLoggingContext, StagingAreaImage } from 'features/controlLayers/store/types';
import Konva from 'konva';
@ -10,6 +11,7 @@ export class CanvasStagingArea {
static GROUP_NAME = `${CanvasStagingArea.TYPE}_group`;
id: string;
parent: CanvasPreview;
manager: CanvasManager;
log: Logger;
getLoggingContext: GetLoggingContext;
@ -19,9 +21,15 @@ export class CanvasStagingArea {
image: CanvasImageRenderer | null;
selectedImage: StagingAreaImage | null;
constructor(manager: CanvasManager) {
/**
* A set of subscriptions that should be cleaned up when the transformer is destroyed.
*/
subscriptions: Set<() => void> = new Set();
constructor(parent: CanvasPreview) {
this.id = getPrefixedId(CanvasStagingArea.TYPE);
this.manager = manager;
this.parent = parent;
this.manager = this.parent.manager;
this.getLoggingContext = this.manager.buildGetLoggingContext(this);
this.log = this.manager.buildLogger(this.getLoggingContext);
this.log.debug('Creating staging area');
@ -29,14 +37,17 @@ export class CanvasStagingArea {
this.konva = { group: new Konva.Group({ name: CanvasStagingArea.GROUP_NAME, listening: false }) };
this.image = null;
this.selectedImage = null;
this.subscriptions.add(this.manager.stateApi.$shouldShowStagedImage.listen(this.render));
}
render = async () => {
const session = this.manager.stateApi.getSession();
const bboxRect = this.manager.stateApi.getBbox().rect;
const { rect } = this.manager.stateApi.getBbox();
const shouldShowStagedImage = this.manager.stateApi.$shouldShowStagedImage.get();
this.selectedImage = session.stagedImages[session.selectedStagedImageIndex] ?? null;
this.konva.group.position({ x: rect.x, y: rect.y });
if (this.selectedImage) {
const { imageDTO, offsetX, offsetY } = this.selectedImage;
@ -47,10 +58,6 @@ export class CanvasStagingArea {
{
id: 'staging-area-image',
type: 'image',
x: 0,
y: 0,
width,
height,
filters: [],
image: {
image_name: image_name,
@ -63,11 +70,7 @@ export class CanvasStagingArea {
this.konva.group.add(this.image.konva.group);
}
if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) {
this.image.konva.image?.width(imageDTO.width);
this.image.konva.image?.height(imageDTO.height);
this.image.konva.group.x(bboxRect.x + offsetX);
this.image.konva.group.y(bboxRect.y + offsetY);
if (!this.image.isLoading && !this.image.isError) {
await this.image.updateImageSource(imageDTO.image_name);
this.manager.stateApi.$lastProgressEvent.set(null);
}
@ -77,6 +80,22 @@ export class CanvasStagingArea {
}
};
getNodes = () => {
return [this.konva.group];
};
destroy = () => {
if (this.image) {
this.image.destroy();
}
for (const unsubscribe of this.subscriptions) {
unsubscribe();
}
for (const node of this.getNodes()) {
node.destroy();
}
};
repr = () => {
return {
id: this.id,

View File

@ -1,5 +1,6 @@
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import type { CanvasPreview } from 'features/controlLayers/konva/CanvasPreview';
import {
BRUSH_BORDER_INNER_COLOR,
BRUSH_BORDER_OUTER_COLOR,
@ -25,6 +26,7 @@ export class CanvasTool {
static ERASER_INNER_BORDER_CIRCLE_NAME = `${CanvasTool.ERASER_NAME_PREFIX}_inner-border-circle`;
static ERASER_OUTER_BORDER_CIRCLE_NAME = `${CanvasTool.ERASER_NAME_PREFIX}_outer-border-circle`;
parent: CanvasPreview;
manager: CanvasManager;
konva: {
group: Konva.Group;
@ -47,8 +49,9 @@ export class CanvasTool {
*/
subscriptions: Set<() => void> = new Set();
constructor(manager: CanvasManager) {
this.manager = manager;
constructor(parent: CanvasPreview) {
this.parent = parent;
this.manager = this.parent.manager;
this.konva = {
group: new Konva.Group({ name: CanvasTool.GROUP_NAME }),
brush: {

View File

@ -48,13 +48,6 @@ export const bboxReducers = {
state.bbox.aspectRatio.id = 'Free';
state.bbox.aspectRatio.isLocked = false;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
bboxHeightChanged: (
state,
@ -73,13 +66,6 @@ export const bboxReducers = {
state.bbox.aspectRatio.id = 'Free';
state.bbox.aspectRatio.isLocked = false;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
bboxAspectRatioLockToggled: (state) => {
state.bbox.aspectRatio.isLocked = !state.bbox.aspectRatio.isLocked;
@ -99,12 +85,6 @@ export const bboxReducers = {
state.bbox.rect.width = width;
state.bbox.rect.height = height;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
bboxDimensionsSwapped: (state) => {
state.bbox.aspectRatio.value = 1 / state.bbox.aspectRatio.value;
@ -122,12 +102,6 @@ export const bboxReducers = {
state.bbox.rect.height = height;
state.bbox.aspectRatio.id = ASPECT_RATIO_MAP[state.bbox.aspectRatio.id].inverseID;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
bboxSizeOptimized: (state) => {
const optimalDimension = getOptimalDimension(state.params.model);
@ -140,11 +114,5 @@ export const bboxReducers = {
state.bbox.rect.width = optimalDimension;
state.bbox.rect.height = optimalDimension;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
} satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -411,7 +411,6 @@ export const {
bboxSizeOptimized,
// layers
layerAdded,
layerAddedFromImage,
layerRecalled,
layerOpacityChanged,
layerAllDeleted,

View File

@ -4,7 +4,7 @@ import { merge } from 'lodash-es';
import type { ImageDTO } from 'services/api/types';
import { assert } from 'tsafe';
import type { CanvasImageState, CanvasLayerState, CanvasV2State } from './types';
import type { CanvasLayerState, CanvasV2State } from './types';
import { imageDTOToImageWithDims } from './types';
export const selectLayer = (state: CanvasV2State, id: string) => state.layers.entities.find((layer) => layer.id === id);
@ -16,8 +16,11 @@ export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
export const layersReducers = {
layerAdded: {
reducer: (state, action: PayloadAction<{ id: string; overrides?: Partial<CanvasLayerState> }>) => {
const { id } = action.payload;
reducer: (
state,
action: PayloadAction<{ id: string; overrides?: Partial<CanvasLayerState>; isSelected?: boolean }>
) => {
const { id, overrides, isSelected } = action.payload;
const layer: CanvasLayerState = {
id,
type: 'layer',
@ -27,12 +30,14 @@ export const layersReducers = {
position: { x: 0, y: 0 },
imageCache: null,
};
merge(layer, action.payload.overrides);
merge(layer, overrides);
state.layers.entities.push(layer);
state.selectedEntityIdentifier = { type: 'layer', id };
if (isSelected) {
state.selectedEntityIdentifier = { type: 'layer', id };
}
state.layers.imageCache = null;
},
prepare: (payload: { overrides?: Partial<CanvasLayerState> }) => ({
prepare: (payload: { overrides?: Partial<CanvasLayerState>; isSelected?: boolean }) => ({
payload: { ...payload, id: getPrefixedId('layer') },
}),
},
@ -42,26 +47,6 @@ export const layersReducers = {
state.selectedEntityIdentifier = { type: 'layer', id: data.id };
state.layers.imageCache = null;
},
layerAddedFromImage: {
reducer: (state, action: PayloadAction<{ id: string; imageObject: CanvasImageState }>) => {
const { id, imageObject } = action.payload;
const layer: CanvasLayerState = {
id,
type: 'layer',
isEnabled: true,
objects: [imageObject],
opacity: 1,
position: { x: 0, y: 0 },
imageCache: null,
};
state.layers.entities.push(layer);
state.selectedEntityIdentifier = { type: 'layer', id };
state.layers.imageCache = null;
},
prepare: (payload: { imageObject: CanvasImageState }) => ({
payload: { ...payload, id: getPrefixedId('layer') },
}),
},
layerAllDeleted: (state) => {
state.layers.entities = [];
state.layers.imageCache = null;