mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): revised logging and naming setup, fix staging area
This commit is contained in:
parent
3a9f955388
commit
d9487c1df4
@ -2,10 +2,12 @@ import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import {
|
||||
$lastProgressEvent,
|
||||
layerAddedFromStagingArea,
|
||||
layerAdded,
|
||||
sessionStagingAreaImageAccepted,
|
||||
sessionStagingAreaReset,
|
||||
} from 'features/controlLayers/store/canvasV2Slice';
|
||||
import type { LayerEntity } from 'features/controlLayers/store/types';
|
||||
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
@ -50,7 +52,7 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
|
||||
|
||||
startAppListening({
|
||||
actionCreator: sessionStagingAreaImageAccepted,
|
||||
effect: async (action, api) => {
|
||||
effect: (action, api) => {
|
||||
const { index } = action.payload;
|
||||
const state = api.getState();
|
||||
const stagingAreaImage = state.canvasV2.session.stagedImages[index];
|
||||
@ -58,7 +60,14 @@ export const addStagingListeners = (startAppListening: AppStartListening) => {
|
||||
assert(stagingAreaImage, 'No staged image found to accept');
|
||||
const { x, y } = state.canvasV2.bbox.rect;
|
||||
|
||||
api.dispatch(layerAddedFromStagingArea({ stagingAreaImage, position: { x, y } }));
|
||||
const { imageDTO, offsetX, offsetY } = stagingAreaImage;
|
||||
const imageObject = imageDTOToImageObject(imageDTO);
|
||||
const overrides: Partial<LayerEntity> = {
|
||||
position: { x: x + offsetX, y: y + offsetY },
|
||||
objects: [imageObject],
|
||||
};
|
||||
|
||||
api.dispatch(layerAdded({ overrides }));
|
||||
api.dispatch(sessionStagingAreaReset());
|
||||
},
|
||||
});
|
||||
|
@ -1,32 +1,27 @@
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import { CanvasObject } from 'features/controlLayers/konva/CanvasObject';
|
||||
import type { BrushLine } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
|
||||
export class CanvasBrushLine {
|
||||
export class CanvasBrushLine extends CanvasObject {
|
||||
static NAME_PREFIX = 'brush-line';
|
||||
static GROUP_NAME = `${CanvasBrushLine.NAME_PREFIX}_group`;
|
||||
static LINE_NAME = `${CanvasBrushLine.NAME_PREFIX}_line`;
|
||||
static TYPE = 'brush_line';
|
||||
|
||||
state: BrushLine;
|
||||
|
||||
type = 'brush_line';
|
||||
id: string;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
line: Konva.Line;
|
||||
};
|
||||
|
||||
parent: CanvasLayer;
|
||||
|
||||
constructor(state: BrushLine, parent: CanvasLayer) {
|
||||
const { id, strokeWidth, clip, color, points } = state;
|
||||
super(state.id, parent);
|
||||
this._log.trace({ state }, 'Creating brush line');
|
||||
|
||||
this.id = id;
|
||||
|
||||
this.parent = parent;
|
||||
this.parent._log.trace(`Creating brush line ${this.id}`);
|
||||
const { strokeWidth, clip, color, points } = state;
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({
|
||||
@ -36,7 +31,6 @@ export class CanvasBrushLine {
|
||||
}),
|
||||
line: new Konva.Line({
|
||||
name: CanvasBrushLine.LINE_NAME,
|
||||
id,
|
||||
listening: false,
|
||||
shadowForStrokeEnabled: false,
|
||||
strokeWidth,
|
||||
@ -55,7 +49,7 @@ export class CanvasBrushLine {
|
||||
|
||||
update(state: BrushLine, force?: boolean): boolean {
|
||||
if (force || this.state !== state) {
|
||||
this.parent._log.trace(`Updating brush line ${this.id}`);
|
||||
this._log.trace({ state }, 'Updating brush line');
|
||||
const { points, color, clip, strokeWidth } = state;
|
||||
this.konva.line.setAttrs({
|
||||
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
||||
@ -72,23 +66,20 @@ export class CanvasBrushLine {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.parent._log.trace(`Destroying brush line ${this.id}`);
|
||||
this._log.trace('Destroying brush line');
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.konva.group.visible(true);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.konva.group.visible(false);
|
||||
setVisibility(isVisible: boolean): void {
|
||||
this._log.trace({ isVisible }, 'Setting brush line visibility');
|
||||
this.konva.group.visible(isVisible);
|
||||
}
|
||||
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
parent: this.parent.id,
|
||||
type: CanvasBrushLine.TYPE,
|
||||
parent: this._parent.id,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
import type { JSONObject } from 'common/types';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
export abstract class CanvasEntity {
|
||||
id: string;
|
||||
_manager: CanvasManager;
|
||||
_log: Logger;
|
||||
|
||||
constructor(id: string, manager: CanvasManager) {
|
||||
this.id = id;
|
||||
this._manager = manager;
|
||||
this._log = this._manager.buildLogger(this._getLoggingContext);
|
||||
}
|
||||
/**
|
||||
* Get a serializable representation of the entity.
|
||||
*/
|
||||
abstract repr(): JSONObject;
|
||||
|
||||
_getLoggingContext = (extra?: Record<string, unknown>) => {
|
||||
return {
|
||||
...this._manager._getLoggingContext(),
|
||||
layerId: this.id,
|
||||
...extra,
|
||||
};
|
||||
};
|
||||
}
|
@ -1,33 +1,28 @@
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import { CanvasObject } from 'features/controlLayers/konva/CanvasObject';
|
||||
import type { EraserLine } from 'features/controlLayers/store/types';
|
||||
import { RGBA_RED } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
|
||||
export class CanvasEraserLine {
|
||||
export class CanvasEraserLine extends CanvasObject {
|
||||
static NAME_PREFIX = 'eraser-line';
|
||||
static GROUP_NAME = `${CanvasEraserLine.NAME_PREFIX}_group`;
|
||||
static LINE_NAME = `${CanvasEraserLine.NAME_PREFIX}_line`;
|
||||
static TYPE = 'eraser_line';
|
||||
|
||||
state: EraserLine;
|
||||
|
||||
type = 'eraser_line';
|
||||
id: string;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
line: Konva.Line;
|
||||
};
|
||||
|
||||
parent: CanvasLayer;
|
||||
|
||||
constructor(state: EraserLine, parent: CanvasLayer) {
|
||||
const { id, strokeWidth, clip, points } = state;
|
||||
super(state.id, parent);
|
||||
this._log.trace({ state }, 'Creating eraser line');
|
||||
|
||||
this.id = id;
|
||||
|
||||
this.parent = parent;
|
||||
this.parent._log.trace(`Creating eraser line ${this.id}`);
|
||||
const { strokeWidth, clip, points } = state;
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({
|
||||
@ -37,7 +32,6 @@ export class CanvasEraserLine {
|
||||
}),
|
||||
line: new Konva.Line({
|
||||
name: CanvasEraserLine.LINE_NAME,
|
||||
id,
|
||||
listening: false,
|
||||
shadowForStrokeEnabled: false,
|
||||
strokeWidth,
|
||||
@ -56,7 +50,7 @@ export class CanvasEraserLine {
|
||||
|
||||
update(state: EraserLine, force?: boolean): boolean {
|
||||
if (force || this.state !== state) {
|
||||
this.parent._log.trace(`Updating eraser line ${this.id}`);
|
||||
this._log.trace({ state }, 'Updating eraser line');
|
||||
const { points, clip, strokeWidth } = state;
|
||||
this.konva.line.setAttrs({
|
||||
// A line with only one point will not be rendered, so we duplicate the points to make it visible
|
||||
@ -72,23 +66,20 @@ export class CanvasEraserLine {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.parent._log.trace(`Destroying eraser line ${this.id}`);
|
||||
this._log.trace('Destroying eraser line');
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.konva.group.visible(true);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.konva.group.visible(false);
|
||||
setVisibility(isVisible: boolean): void {
|
||||
this._log.trace({ isVisible }, 'Setting brush line visibility');
|
||||
this.konva.group.visible(isVisible);
|
||||
}
|
||||
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
parent: this.parent.id,
|
||||
type: CanvasEraserLine.TYPE,
|
||||
parent: this._parent.id,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
|
@ -1,26 +1,24 @@
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import { CanvasObject } from 'features/controlLayers/konva/CanvasObject';
|
||||
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 { ImageObject } from 'features/controlLayers/store/types';
|
||||
import { t } from 'i18next';
|
||||
import Konva from 'konva';
|
||||
import { getImageDTO } from 'services/api/endpoints/images';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasImage {
|
||||
export class CanvasImage extends CanvasObject {
|
||||
static NAME_PREFIX = 'canvas-image';
|
||||
static GROUP_NAME = `${CanvasImage.NAME_PREFIX}_group`;
|
||||
static IMAGE_NAME = `${CanvasImage.NAME_PREFIX}_image`;
|
||||
static PLACEHOLDER_GROUP_NAME = `${CanvasImage.NAME_PREFIX}_placeholder-group`;
|
||||
static PLACEHOLDER_RECT_NAME = `${CanvasImage.NAME_PREFIX}_placeholder-rect`;
|
||||
static PLACEHOLDER_TEXT_NAME = `${CanvasImage.NAME_PREFIX}_placeholder-text`;
|
||||
static TYPE = 'image';
|
||||
|
||||
state: ImageObject;
|
||||
|
||||
type = 'image';
|
||||
|
||||
id: string;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
placeholder: { group: Konva.Group; rect: Konva.Rect; text: Konva.Text };
|
||||
@ -30,14 +28,11 @@ export class CanvasImage {
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
|
||||
parent: CanvasLayer;
|
||||
constructor(state: ImageObject, parent: CanvasLayer | CanvasStagingArea) {
|
||||
super(state.id, parent);
|
||||
this._log.trace({ state }, 'Creating image');
|
||||
|
||||
constructor(state: ImageObject, parent: CanvasLayer) {
|
||||
const { id, width, height, x, y } = state;
|
||||
this.id = id;
|
||||
|
||||
this.parent = parent;
|
||||
this.parent._log.trace(`Creating image ${this.id}`);
|
||||
const { width, height, x, y } = state;
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasImage.GROUP_NAME, listening: false, x, y }),
|
||||
@ -78,7 +73,7 @@ export class CanvasImage {
|
||||
|
||||
async updateImageSource(imageName: string) {
|
||||
try {
|
||||
this.parent._log.trace(`Updating image source ${this.id}`);
|
||||
this._log.trace({ imageName }, 'Updating image source');
|
||||
|
||||
this.isLoading = true;
|
||||
this.konva.group.visible(true);
|
||||
@ -89,7 +84,10 @@ export class CanvasImage {
|
||||
}
|
||||
|
||||
const imageDTO = await getImageDTO(imageName);
|
||||
assert(imageDTO !== null, 'imageDTO is null');
|
||||
if (imageDTO === null) {
|
||||
this._log.error({ imageName }, 'Image not found');
|
||||
return;
|
||||
}
|
||||
const imageEl = await loadImage(imageDTO.image_url);
|
||||
|
||||
if (this.konva.image) {
|
||||
@ -120,6 +118,7 @@ export class CanvasImage {
|
||||
this.isError = false;
|
||||
this.konva.placeholder.group.visible(false);
|
||||
} catch {
|
||||
this._log({ imageName }, 'Failed to load image');
|
||||
this.konva.image?.visible(false);
|
||||
this.imageName = null;
|
||||
this.isLoading = false;
|
||||
@ -131,7 +130,7 @@ export class CanvasImage {
|
||||
|
||||
async update(state: ImageObject, force?: boolean): Promise<boolean> {
|
||||
if (this.state !== state || force) {
|
||||
this.parent._log.trace(`Updating image ${this.id}`);
|
||||
this._log.trace({ state }, 'Updating image');
|
||||
|
||||
const { width, height, x, y, image, filters } = state;
|
||||
if (this.state.image.name !== image.name || force) {
|
||||
@ -155,23 +154,20 @@ export class CanvasImage {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.parent._log.trace(`Destroying image ${this.id}`);
|
||||
this._log.trace('Destroying image');
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.konva.group.visible(true);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.konva.group.visible(false);
|
||||
setVisibility(isVisible: boolean): void {
|
||||
this._log.trace({ isVisible }, 'Setting image visibility');
|
||||
this.konva.group.visible(isVisible);
|
||||
}
|
||||
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
parent: this.parent.id,
|
||||
type: CanvasImage.TYPE,
|
||||
parent: this._parent.id,
|
||||
imageName: this.imageName,
|
||||
isLoading: this.isLoading,
|
||||
isError: this.isError,
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { getStore } from 'app/store/nanostores/store';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { CanvasBrushLine } from 'features/controlLayers/konva/CanvasBrushLine';
|
||||
import { CanvasEntity } from 'features/controlLayers/konva/CanvasEntity';
|
||||
import { CanvasEraserLine } from 'features/controlLayers/konva/CanvasEraserLine';
|
||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasRect } from 'features/controlLayers/konva/CanvasRect';
|
||||
import { getBrushLineId, getEraserLineId, getRectShapeId } from 'features/controlLayers/konva/naming';
|
||||
import { konvaNodeToBlob, mapId, nanoid, previewBlob } from 'features/controlLayers/konva/util';
|
||||
import { getPrefixedId, konvaNodeToBlob, mapId, previewBlob } from 'features/controlLayers/konva/util';
|
||||
import { layerRasterized } from 'features/controlLayers/store/canvasV2Slice';
|
||||
import {
|
||||
type BrushLine,
|
||||
@ -20,11 +20,10 @@ import {
|
||||
} from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import { debounce, get } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
import { uploadImage } from 'services/api/endpoints/images';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export class CanvasLayer {
|
||||
export class CanvasLayer extends CanvasEntity {
|
||||
static NAME_PREFIX = 'layer';
|
||||
static LAYER_NAME = `${CanvasLayer.NAME_PREFIX}_layer`;
|
||||
static TRANSFORMER_NAME = `${CanvasLayer.NAME_PREFIX}_transformer`;
|
||||
@ -36,8 +35,7 @@ export class CanvasLayer {
|
||||
_drawingBuffer: BrushLine | EraserLine | RectShape | null;
|
||||
_state: LayerEntity;
|
||||
|
||||
id: string;
|
||||
manager: CanvasManager;
|
||||
type = 'layer';
|
||||
|
||||
konva: {
|
||||
layer: Konva.Layer;
|
||||
@ -48,7 +46,6 @@ export class CanvasLayer {
|
||||
};
|
||||
objects: Map<string, CanvasBrushLine | CanvasEraserLine | CanvasRect | CanvasImage>;
|
||||
|
||||
_log: Logger;
|
||||
_bboxNeedsUpdate: boolean;
|
||||
_isFirstRender: boolean;
|
||||
|
||||
@ -59,8 +56,9 @@ export class CanvasLayer {
|
||||
bbox: Rect;
|
||||
|
||||
constructor(state: LayerEntity, manager: CanvasManager) {
|
||||
this.id = state.id;
|
||||
this.manager = manager;
|
||||
super(state.id, manager);
|
||||
this._log.debug({ state }, 'Creating layer');
|
||||
|
||||
this.konva = {
|
||||
layer: new Konva.Layer({ id: this.id, name: CanvasLayer.LAYER_NAME, listening: false }),
|
||||
bbox: new Konva.Rect({
|
||||
@ -79,7 +77,7 @@ export class CanvasLayer {
|
||||
rotateEnabled: true,
|
||||
flipEnabled: true,
|
||||
listening: false,
|
||||
padding: this.manager.getTransformerPadding(),
|
||||
padding: this._manager.getTransformerPadding(),
|
||||
stroke: 'hsl(200deg 76% 59%)', // invokeBlue.400
|
||||
keepRatio: false,
|
||||
}),
|
||||
@ -149,8 +147,8 @@ export class CanvasLayer {
|
||||
// The bbox should be updated to reflect the new position of the interaction rect, taking into account its padding
|
||||
// and border
|
||||
this.konva.bbox.setAttrs({
|
||||
x: this.konva.interactionRect.x() - this.manager.getScaledBboxPadding(),
|
||||
y: this.konva.interactionRect.y() - this.manager.getScaledBboxPadding(),
|
||||
x: this.konva.interactionRect.x() - this._manager.getScaledBboxPadding(),
|
||||
y: this.konva.interactionRect.y() - this._manager.getScaledBboxPadding(),
|
||||
});
|
||||
|
||||
// The object group is translated by the difference between the interaction rect's new and old positions (which is
|
||||
@ -169,7 +167,7 @@ export class CanvasLayer {
|
||||
return;
|
||||
}
|
||||
|
||||
this.manager.stateApi.onPosChanged(
|
||||
this._manager.stateApi.onPosChanged(
|
||||
{
|
||||
id: this.id,
|
||||
position: {
|
||||
@ -190,11 +188,10 @@ export class CanvasLayer {
|
||||
this.isTransforming = false;
|
||||
this._isFirstRender = true;
|
||||
this.isPendingBboxCalculation = false;
|
||||
this._log = this.manager.getLogger(`layer_${this.id}`);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this._log.debug(`Layer ${this.id} - destroying`);
|
||||
this._log.debug('Destroying layer');
|
||||
this.konva.layer.destroy();
|
||||
}
|
||||
|
||||
@ -221,21 +218,21 @@ export class CanvasLayer {
|
||||
// a non-buffer object, and we won't trigger things like bbox calculation
|
||||
|
||||
if (drawingBuffer.type === 'brush_line') {
|
||||
drawingBuffer.id = getBrushLineId(this.id, nanoid());
|
||||
this.manager.stateApi.onBrushLineAdded({ id: this.id, brushLine: drawingBuffer }, 'layer');
|
||||
drawingBuffer.id = getPrefixedId('brush_line');
|
||||
this._manager.stateApi.onBrushLineAdded({ id: this.id, brushLine: drawingBuffer }, 'layer');
|
||||
} else if (drawingBuffer.type === 'eraser_line') {
|
||||
drawingBuffer.id = getEraserLineId(this.id, nanoid());
|
||||
this.manager.stateApi.onEraserLineAdded({ id: this.id, eraserLine: drawingBuffer }, 'layer');
|
||||
drawingBuffer.id = getPrefixedId('brush_line');
|
||||
this._manager.stateApi.onEraserLineAdded({ id: this.id, eraserLine: drawingBuffer }, 'layer');
|
||||
} else if (drawingBuffer.type === 'rect_shape') {
|
||||
drawingBuffer.id = getRectShapeId(this.id, nanoid());
|
||||
this.manager.stateApi.onRectShapeAdded({ id: this.id, rectShape: drawingBuffer }, 'layer');
|
||||
drawingBuffer.id = getPrefixedId('brush_line');
|
||||
this._manager.stateApi.onRectShapeAdded({ id: this.id, rectShape: drawingBuffer }, 'layer');
|
||||
}
|
||||
}
|
||||
|
||||
async update(arg?: { state: LayerEntity; toolState: CanvasV2State['tool']; isSelected: boolean }) {
|
||||
const state = get(arg, 'state', this._state);
|
||||
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
|
||||
const isSelected = get(arg, 'isSelected', this.manager.stateApi.getIsSelected(this.id));
|
||||
const toolState = get(arg, 'toolState', this._manager.stateApi.getToolState());
|
||||
const isSelected = get(arg, 'isSelected', this._manager.stateApi.getIsSelected(this.id));
|
||||
|
||||
if (!this._isFirstRender && state === this._state) {
|
||||
this._log.trace('State unchanged, skipping update');
|
||||
@ -277,7 +274,7 @@ export class CanvasLayer {
|
||||
updatePosition(arg?: { position: Coordinate }) {
|
||||
this._log.trace('Updating position');
|
||||
const position = get(arg, 'position', this._state.position);
|
||||
const bboxPadding = this.manager.getScaledBboxPadding();
|
||||
const bboxPadding = this._manager.getScaledBboxPadding();
|
||||
|
||||
this.konva.objectGroup.setAttrs({
|
||||
x: position.x + this.bbox.x,
|
||||
@ -339,8 +336,8 @@ export class CanvasLayer {
|
||||
updateInteraction(arg?: { toolState: CanvasV2State['tool']; isSelected: boolean }) {
|
||||
this._log.trace('Updating interaction');
|
||||
|
||||
const toolState = get(arg, 'toolState', this.manager.stateApi.getToolState());
|
||||
const isSelected = get(arg, 'isSelected', this.manager.stateApi.getIsSelected(this.id));
|
||||
const toolState = get(arg, 'toolState', this._manager.stateApi.getToolState());
|
||||
const isSelected = get(arg, 'isSelected', this._manager.stateApi.getIsSelected(this.id));
|
||||
|
||||
if (this.objects.size === 0) {
|
||||
// The layer is totally empty, we can just disable the layer
|
||||
@ -397,7 +394,7 @@ export class CanvasLayer {
|
||||
if (this.bbox.width === 0 || this.bbox.height === 0) {
|
||||
if (this.objects.size > 0) {
|
||||
// The layer is fully transparent but has objects - reset it
|
||||
this.manager.stateApi.onEntityReset({ id: this.id }, 'layer');
|
||||
this._manager.stateApi.onEntityReset({ id: this.id }, 'layer');
|
||||
}
|
||||
this.konva.bbox.visible(false);
|
||||
this.konva.interactionRect.visible(false);
|
||||
@ -407,8 +404,8 @@ export class CanvasLayer {
|
||||
this.konva.bbox.visible(true);
|
||||
this.konva.interactionRect.visible(true);
|
||||
|
||||
const onePixel = this.manager.getScaledPixel();
|
||||
const bboxPadding = this.manager.getScaledBboxPadding();
|
||||
const onePixel = this._manager.getScaledPixel();
|
||||
const bboxPadding = this._manager.getScaledBboxPadding();
|
||||
|
||||
this.konva.bbox.setAttrs({
|
||||
x: this._state.position.x + this.bbox.x - bboxPadding,
|
||||
@ -434,8 +431,8 @@ export class CanvasLayer {
|
||||
syncStageScale() {
|
||||
this._log.trace('Syncing scale to stage');
|
||||
|
||||
const onePixel = this.manager.getScaledPixel();
|
||||
const bboxPadding = this.manager.getScaledBboxPadding();
|
||||
const onePixel = this._manager.getScaledPixel();
|
||||
const bboxPadding = this._manager.getScaledBboxPadding();
|
||||
|
||||
this.konva.bbox.setAttrs({
|
||||
x: this.konva.interactionRect.x() - bboxPadding,
|
||||
@ -515,7 +512,7 @@ export class CanvasLayer {
|
||||
// When transforming, we want the stage to still be movable if the view tool is selected. If the transformer or
|
||||
// interaction rect are listening, it will interrupt the stage's drag events. So we should disable listening
|
||||
// when the view tool is selected
|
||||
const listening = this.manager.stateApi.getToolState().selected !== 'view';
|
||||
const listening = this._manager.stateApi.getToolState().selected !== 'view';
|
||||
|
||||
this.konva.layer.listening(listening);
|
||||
this.konva.interactionRect.listening(listening);
|
||||
@ -546,12 +543,12 @@ export class CanvasLayer {
|
||||
const interactionRectClone = this.konva.interactionRect.clone();
|
||||
const rect = interactionRectClone.getClientRect();
|
||||
const blob = await konvaNodeToBlob(objectGroupClone, rect);
|
||||
if (this.manager._isDebugging) {
|
||||
if (this._manager._isDebugging) {
|
||||
previewBlob(blob, 'Rasterized layer');
|
||||
}
|
||||
const imageDTO = await uploadImage(blob, `${this.id}_rasterized.png`, 'other', true);
|
||||
const { dispatch } = getStore();
|
||||
const imageObject = imageDTOToImageObject(this.id, nanoid(), imageDTO);
|
||||
const imageObject = imageDTOToImageObject(imageDTO);
|
||||
await this._renderObject(imageObject, true);
|
||||
for (const obj of this.objects.values()) {
|
||||
if (obj.id !== imageObject.id) {
|
||||
@ -632,7 +629,7 @@ export class CanvasLayer {
|
||||
return;
|
||||
}
|
||||
const imageData = ctx.getImageData(0, 0, rect.width, rect.height);
|
||||
this.manager.requestBbox(
|
||||
this._manager.requestBbox(
|
||||
{ buffer: imageData.data.buffer, width: imageData.width, height: imageData.height },
|
||||
(extents) => {
|
||||
this.rect = deepClone(rect);
|
||||
@ -658,7 +655,7 @@ export class CanvasLayer {
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: 'layer',
|
||||
type: this.type,
|
||||
state: deepClone(this._state),
|
||||
rect: deepClone(this.rect),
|
||||
bbox: deepClone(this.bbox),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { Store } from '@reduxjs/toolkit';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import type { JSONObject } from 'common/types';
|
||||
import { CanvasInitialImage } from 'features/controlLayers/konva/CanvasInitialImage';
|
||||
import { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasProgressPreview';
|
||||
import {
|
||||
@ -107,7 +108,15 @@ export class CanvasManager {
|
||||
this._prevState = this.stateApi.getState();
|
||||
this._isFirstRender = true;
|
||||
|
||||
this.log = logger('canvas');
|
||||
this.log = logger('canvas').child((message) => {
|
||||
return {
|
||||
...message,
|
||||
context: {
|
||||
...message.context,
|
||||
...this._getLoggingContext(),
|
||||
},
|
||||
};
|
||||
});
|
||||
this.workerLog = logger('worker');
|
||||
|
||||
this.util = {
|
||||
@ -173,11 +182,6 @@ export class CanvasManager {
|
||||
this._isDebugging = false;
|
||||
}
|
||||
|
||||
getLogger(namespace: string) {
|
||||
const managerNamespace = this.log.getContext().namespace;
|
||||
return this.log.child({ namespace: `${managerNamespace}.${namespace}` });
|
||||
}
|
||||
|
||||
requestBbox(data: Omit<GetBboxTask['data'], 'id'>, onComplete: (extents: Extents | null) => void) {
|
||||
const id = nanoid();
|
||||
const task: GetBboxTask = {
|
||||
@ -330,7 +334,6 @@ export class CanvasManager {
|
||||
|
||||
for (const canvasLayer of this.layers.values()) {
|
||||
if (!state.layers.entities.find((l) => l.id === canvasLayer.id)) {
|
||||
this.log.debug(`Destroying deleted layer ${canvasLayer.id}`);
|
||||
await canvasLayer.destroy();
|
||||
this.layers.delete(canvasLayer.id);
|
||||
}
|
||||
@ -339,7 +342,6 @@ export class CanvasManager {
|
||||
for (const entityState of state.layers.entities) {
|
||||
let adapter = this.layers.get(entityState.id);
|
||||
if (!adapter) {
|
||||
this.log.debug(`Creating layer layer ${entityState.id}`);
|
||||
adapter = new CanvasLayer(entityState, this);
|
||||
this.layers.set(adapter.id, adapter);
|
||||
this.stage.add(adapter.konva.layer);
|
||||
@ -562,9 +564,29 @@ export class CanvasManager {
|
||||
}
|
||||
}
|
||||
|
||||
_getLoggingContext() {
|
||||
return {
|
||||
// timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
buildLogger(getContext: () => JSONObject): Logger {
|
||||
return this.log.child((message) => {
|
||||
return {
|
||||
...message,
|
||||
context: {
|
||||
...message.context,
|
||||
...getContext(),
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
logDebugInfo() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(this);
|
||||
for (const layer of this.layers.values()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(layer);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
import type { JSONObject } from 'common/types';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { CanvasStagingArea } from 'features/controlLayers/konva/CanvasStagingArea';
|
||||
import type { Logger } from 'roarr';
|
||||
|
||||
export abstract class CanvasObject {
|
||||
id: string;
|
||||
|
||||
_parent: CanvasLayer | CanvasStagingArea;
|
||||
_manager: CanvasManager;
|
||||
_log: Logger;
|
||||
|
||||
constructor(id: string, parent: CanvasLayer | CanvasStagingArea) {
|
||||
this.id = id;
|
||||
this._parent = parent;
|
||||
this._manager = parent._manager;
|
||||
this._log = this._manager.buildLogger(this._getLoggingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the object's konva nodes.
|
||||
*/
|
||||
abstract destroy(): void;
|
||||
|
||||
/**
|
||||
* Set the visibility of the object's konva nodes.
|
||||
*/
|
||||
abstract setVisibility(isVisible: boolean): void;
|
||||
|
||||
/**
|
||||
* Get a serializable representation of the object.
|
||||
*/
|
||||
abstract repr(): JSONObject;
|
||||
|
||||
/**
|
||||
* Get the logging context for this object.
|
||||
* @param extra Extra data to merge into the context
|
||||
* @returns The logging context for this object
|
||||
*/
|
||||
_getLoggingContext = (extra?: Record<string, unknown>) => {
|
||||
return {
|
||||
...this._parent._getLoggingContext(),
|
||||
objectId: this.id,
|
||||
...extra,
|
||||
};
|
||||
};
|
||||
}
|
@ -1,39 +1,32 @@
|
||||
import { rgbaColorToString } from 'common/util/colorCodeTransformers';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import type { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import { CanvasObject } from 'features/controlLayers/konva/CanvasObject';
|
||||
import type { RectShape } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
|
||||
export class CanvasRect {
|
||||
export class CanvasRect extends CanvasObject {
|
||||
static NAME_PREFIX = 'canvas-rect';
|
||||
static GROUP_NAME = `${CanvasRect.NAME_PREFIX}_group`;
|
||||
static RECT_NAME = `${CanvasRect.NAME_PREFIX}_rect`;
|
||||
static TYPE = 'rect';
|
||||
|
||||
state: RectShape;
|
||||
|
||||
type = 'rect';
|
||||
|
||||
id: string;
|
||||
konva: {
|
||||
group: Konva.Group;
|
||||
rect: Konva.Rect;
|
||||
};
|
||||
|
||||
parent: CanvasLayer;
|
||||
|
||||
constructor(state: RectShape, parent: CanvasLayer) {
|
||||
const { id, x, y, width, height, color } = state;
|
||||
super(state.id, parent);
|
||||
this._log.trace({ state }, 'Creating rect');
|
||||
|
||||
this.id = id;
|
||||
|
||||
this.parent = parent;
|
||||
this.parent._log.trace(`Creating rect ${this.id}`);
|
||||
const { x, y, width, height, color } = state;
|
||||
|
||||
this.konva = {
|
||||
group: new Konva.Group({ name: CanvasRect.GROUP_NAME, listening: false }),
|
||||
rect: new Konva.Rect({
|
||||
name: CanvasRect.RECT_NAME,
|
||||
id,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
@ -48,7 +41,7 @@ export class CanvasRect {
|
||||
|
||||
update(state: RectShape, force?: boolean): boolean {
|
||||
if (this.state !== state || force) {
|
||||
this.parent._log.trace(`Updating rect ${this.id}`);
|
||||
this._log.trace({ state }, 'Updating rect');
|
||||
const { x, y, width, height, color } = state;
|
||||
this.konva.rect.setAttrs({
|
||||
x,
|
||||
@ -65,23 +58,20 @@ export class CanvasRect {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.parent._log.trace(`Destroying rect ${this.id}`);
|
||||
this._log.trace('Destroying rect');
|
||||
this.konva.group.destroy();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.konva.group.visible(true);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.konva.group.visible(false);
|
||||
setVisibility(isVisible: boolean): void {
|
||||
this._log.trace({ isVisible }, 'Setting rect visibility');
|
||||
this.konva.group.visible(isVisible);
|
||||
}
|
||||
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
parent: this.parent.id,
|
||||
type: CanvasRect.TYPE,
|
||||
parent: this._parent.id,
|
||||
state: deepClone(this.state),
|
||||
};
|
||||
}
|
||||
|
@ -1,29 +1,30 @@
|
||||
import { CanvasEntity } from 'features/controlLayers/konva/CanvasEntity';
|
||||
import { CanvasImage } from 'features/controlLayers/konva/CanvasImage';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { StagingAreaImage } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
|
||||
export class CanvasStagingArea {
|
||||
export class CanvasStagingArea extends CanvasEntity {
|
||||
static NAME_PREFIX = 'staging-area';
|
||||
static GROUP_NAME = `${CanvasStagingArea.NAME_PREFIX}_group`;
|
||||
|
||||
type = 'staging_area';
|
||||
konva: { group: Konva.Group };
|
||||
|
||||
image: CanvasImage | null;
|
||||
selectedImage: StagingAreaImage | null;
|
||||
manager: CanvasManager;
|
||||
|
||||
constructor(manager: CanvasManager) {
|
||||
this.manager = manager;
|
||||
super('staging-area', manager);
|
||||
this.konva = { group: new Konva.Group({ name: CanvasStagingArea.GROUP_NAME, listening: false }) };
|
||||
this.image = null;
|
||||
this.selectedImage = null;
|
||||
}
|
||||
|
||||
async render() {
|
||||
const session = this.manager.stateApi.getSession();
|
||||
const bboxRect = this.manager.stateApi.getBbox().rect;
|
||||
const shouldShowStagedImage = this.manager.stateApi.getShouldShowStagedImage();
|
||||
const session = this._manager.stateApi.getSession();
|
||||
const bboxRect = this._manager.stateApi.getBbox().rect;
|
||||
const shouldShowStagedImage = this._manager.stateApi.getShouldShowStagedImage();
|
||||
|
||||
this.selectedImage = session.stagedImages[session.selectedStagedImageIndex] ?? null;
|
||||
|
||||
@ -32,34 +33,45 @@ export class CanvasStagingArea {
|
||||
|
||||
if (!this.image) {
|
||||
const { image_name, width, height } = imageDTO;
|
||||
this.image = new CanvasImage({
|
||||
id: 'staging-area-image',
|
||||
type: 'image',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
filters: [],
|
||||
image: {
|
||||
name: image_name,
|
||||
this.image = new CanvasImage(
|
||||
{
|
||||
id: 'staging-area-image',
|
||||
type: 'image',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
filters: [],
|
||||
image: {
|
||||
name: image_name,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
},
|
||||
});
|
||||
this
|
||||
);
|
||||
this.konva.group.add(this.image.konva.group);
|
||||
}
|
||||
|
||||
if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) {
|
||||
this.image.image?.width(imageDTO.width);
|
||||
this.image.image?.height(imageDTO.height);
|
||||
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);
|
||||
await this.image.updateImageSource(imageDTO.image_name);
|
||||
this.manager.stateApi.resetLastProgressEvent();
|
||||
this._manager.stateApi.resetLastProgressEvent();
|
||||
}
|
||||
this.image.konva.group.visible(shouldShowStagedImage);
|
||||
} else {
|
||||
this.image?.konva.group.visible(false);
|
||||
}
|
||||
}
|
||||
|
||||
repr() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
selectedImage: this.selectedImage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -248,6 +248,9 @@ export class CanvasStateApi {
|
||||
getIsSelected = (id: string) => {
|
||||
return this.getSelectedEntity()?.id === id;
|
||||
};
|
||||
getLogLevel = () => {
|
||||
return this.store.getState().system.consoleLogLevel;
|
||||
};
|
||||
|
||||
// Read-only state, derived from nanostores
|
||||
resetLastProgressEvent = () => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { getScaledFlooredCursorPosition, nanoid } from 'features/controlLayers/konva/util';
|
||||
import { getObjectId, getScaledFlooredCursorPosition } from 'features/controlLayers/konva/util';
|
||||
import type {
|
||||
CanvasV2State,
|
||||
Coordinate,
|
||||
@ -14,7 +14,6 @@ import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import { clamp } from 'lodash-es';
|
||||
|
||||
import { BRUSH_SPACING_TARGET_SCALE, CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from './constants';
|
||||
import { getBrushLineId, getEraserLineId, getRectShapeId } from './naming';
|
||||
|
||||
/**
|
||||
* Updates the last cursor position atom with the current cursor position, returning the new position or `null` if the
|
||||
@ -187,7 +186,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
id: getBrushLineId(selectedEntityAdapter.id, nanoid(), true),
|
||||
id: getObjectId('brush_line', true),
|
||||
type: 'brush_line',
|
||||
points: [
|
||||
// The last point of the last line is already normalized to the entity's coordinates
|
||||
@ -205,7 +204,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
id: getBrushLineId(selectedEntityAdapter.id, nanoid(), true),
|
||||
id: getObjectId('brush_line', true),
|
||||
type: 'brush_line',
|
||||
points: [pos.x - selectedEntity.position.x, pos.y - selectedEntity.position.y],
|
||||
strokeWidth: toolState.brush.width,
|
||||
@ -224,7 +223,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
id: getEraserLineId(selectedEntityAdapter.id, nanoid(), true),
|
||||
id: getObjectId('eraser_line', true),
|
||||
type: 'eraser_line',
|
||||
points: [
|
||||
// The last point of the last line is already normalized to the entity's coordinates
|
||||
@ -241,7 +240,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
id: getEraserLineId(selectedEntityAdapter.id, nanoid(), true),
|
||||
id: getObjectId('eraser_line', true),
|
||||
type: 'eraser_line',
|
||||
points: [pos.x - selectedEntity.position.x, pos.y - selectedEntity.position.y],
|
||||
strokeWidth: toolState.eraser.width,
|
||||
@ -256,7 +255,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
id: getRectShapeId(selectedEntityAdapter.id, nanoid(), true),
|
||||
id: getObjectId('rect_shape', true),
|
||||
type: 'rect_shape',
|
||||
x: pos.x - selectedEntity.position.x,
|
||||
y: pos.y - selectedEntity.position.y,
|
||||
@ -356,7 +355,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
id: getBrushLineId(selectedEntityAdapter.id, nanoid(), true),
|
||||
id: getObjectId('brush_line', true),
|
||||
type: 'brush_line',
|
||||
points: [pos.x - selectedEntity.position.x, pos.y - selectedEntity.position.y],
|
||||
strokeWidth: toolState.brush.width,
|
||||
@ -388,7 +387,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
|
||||
await selectedEntityAdapter.finalizeDrawingBuffer();
|
||||
}
|
||||
await selectedEntityAdapter.setDrawingBuffer({
|
||||
id: getEraserLineId(selectedEntityAdapter.id, nanoid(), true),
|
||||
id: getObjectId('eraser_line', true),
|
||||
type: 'eraser_line',
|
||||
points: [pos.x - selectedEntity.position.x, pos.y - selectedEntity.position.y],
|
||||
strokeWidth: toolState.eraser.width,
|
||||
|
@ -6,13 +6,13 @@
|
||||
export const getRGId = (entityId: string) => `region_${entityId}`;
|
||||
export const getLayerId = (entityId: string) => `layer_${entityId}`;
|
||||
export const getBrushLineId = (entityId: string, lineId: string, isBuffer?: boolean) =>
|
||||
`${entityId}.${isBuffer ? 'buffer_' : ''}brush_line_${lineId}`;
|
||||
`${isBuffer ? 'buffer_' : ''}brush_line_${lineId}`;
|
||||
export const getEraserLineId = (entityId: string, lineId: string, isBuffer?: boolean) =>
|
||||
`${entityId}.${isBuffer ? 'buffer_' : ''}eraser_line_${lineId}`;
|
||||
`${isBuffer ? 'buffer_' : ''}eraser_line_${lineId}`;
|
||||
export const getRectShapeId = (entityId: string, rectId: string, isBuffer?: boolean) =>
|
||||
`${entityId}.${isBuffer ? 'buffer_' : ''}rect_${rectId}`;
|
||||
export const getImageObjectId = (entityId: string, imageId: string) => `${entityId}.image_${imageId}`;
|
||||
export const getObjectGroupId = (entityId: string, groupId: string) => `${entityId}.objectGroup_${groupId}`;
|
||||
`${isBuffer ? 'buffer_' : ''}rect_${rectId}`;
|
||||
export const getImageObjectId = (entityId: string, imageId: string) => `image_${imageId}`;
|
||||
export const getObjectGroupId = (entityId: string, groupId: string) => `objectGroup_${groupId}`;
|
||||
export const getLayerBboxId = (entityId: string) => `${entityId}.bbox`;
|
||||
export const getCAId = (entityId: string) => `control_adapter_${entityId}`;
|
||||
export const getIPAId = (entityId: string) => `ip_adapter_${entityId}`;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { getImageDataTransparency } from 'common/util/arrayBuffer';
|
||||
import { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { GenerationMode, Rect, RgbaColor } from 'features/controlLayers/store/types';
|
||||
import type { GenerationMode, Rect, RenderableObject, RgbaColor } from 'features/controlLayers/store/types';
|
||||
import { isValidLayer } from 'features/nodes/util/graph/generation/addLayers';
|
||||
import Konva from 'konva';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
import type { Vector2d } from 'konva/lib/types';
|
||||
import { customAlphabet, urlAlphabet } from 'nanoid';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
@ -575,4 +575,19 @@ export function loadImage(src: string, imageEl?: HTMLImageElement): Promise<HTML
|
||||
});
|
||||
}
|
||||
|
||||
export const nanoid = customAlphabet(urlAlphabet, 10);
|
||||
/**
|
||||
* Generates a random alphanumeric string of length 10. Probably not secure at all.
|
||||
*/
|
||||
export const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 10);
|
||||
|
||||
export function getPrefixedId(prefix: string): string {
|
||||
return `${prefix}:${nanoid()}`;
|
||||
}
|
||||
|
||||
export function getObjectId(type: RenderableObject['type'], isBuffer?: boolean): string {
|
||||
if (isBuffer) {
|
||||
return getPrefixedId(`buffer_${type}`);
|
||||
} else {
|
||||
return getPrefixedId(type);
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +207,6 @@ export const {
|
||||
bboxSizeOptimized,
|
||||
// layers
|
||||
layerAdded,
|
||||
layerAddedFromStagingArea,
|
||||
layerRecalled,
|
||||
layerDeleted,
|
||||
layerReset,
|
||||
|
@ -162,7 +162,7 @@ export const controlAdaptersReducers = {
|
||||
ca.bboxNeedsUpdate = true;
|
||||
ca.isEnabled = true;
|
||||
if (imageDTO) {
|
||||
const newImageObject = imageDTOToImageObject(id, objectId, imageDTO, { filters: ca.filters });
|
||||
const newImageObject = imageDTOToImageObject(imageDTO, { filters: ca.filters });
|
||||
if (isEqual(newImageObject, ca.imageObject)) {
|
||||
return;
|
||||
}
|
||||
@ -185,9 +185,7 @@ export const controlAdaptersReducers = {
|
||||
ca.bbox = null;
|
||||
ca.bboxNeedsUpdate = true;
|
||||
ca.isEnabled = true;
|
||||
ca.processedImageObject = imageDTO
|
||||
? imageDTOToImageObject(id, objectId, imageDTO, { filters: ca.filters })
|
||||
: null;
|
||||
ca.processedImageObject = imageDTO ? imageDTOToImageObject(imageDTO, { filters: ca.filters }) : null;
|
||||
},
|
||||
prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }),
|
||||
},
|
||||
|
@ -25,7 +25,7 @@ export const initialImageReducers = {
|
||||
if (!state.initialImage) {
|
||||
return;
|
||||
}
|
||||
const newImageObject = imageDTOToImageObject('initial_image', 'initial_image_object', imageDTO);
|
||||
const newImageObject = imageDTOToImageObject(imageDTO);
|
||||
if (isEqual(newImageObject, state.initialImage.imageObject)) {
|
||||
return;
|
||||
}
|
||||
|
@ -4,13 +4,7 @@ import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type {
|
||||
CanvasV2State,
|
||||
CLIPVisionModelV2,
|
||||
IPAdapterConfig,
|
||||
IPAdapterEntity,
|
||||
IPMethodV2,
|
||||
} from './types';
|
||||
import type { CanvasV2State, CLIPVisionModelV2, IPAdapterConfig, IPAdapterEntity, IPMethodV2 } from './types';
|
||||
import { imageDTOToImageObject } from './types';
|
||||
|
||||
export const selectIPA = (state: CanvasV2State, id: string) => state.ipAdapters.entities.find((ipa) => ipa.id === id);
|
||||
@ -61,7 +55,7 @@ export const ipAdaptersReducers = {
|
||||
if (!ipa) {
|
||||
return;
|
||||
}
|
||||
ipa.imageObject = imageDTO ? imageDTOToImageObject(id, objectId, imageDTO) : null;
|
||||
ipa.imageObject = imageDTO ? imageDTOToImageObject(imageDTO) : null;
|
||||
},
|
||||
prepare: (payload: { id: string; imageDTO: ImageDTO | null }) => ({ payload: { ...payload, objectId: uuidv4() } }),
|
||||
},
|
||||
|
@ -1,7 +1,8 @@
|
||||
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||
import { nanoid } from 'features/controlLayers/konva/util';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { merge } from 'lodash-es';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
@ -16,7 +17,6 @@ import type {
|
||||
PositionChangedArg,
|
||||
RectShape,
|
||||
ScaleChangedArg,
|
||||
StagingAreaImage,
|
||||
} from './types';
|
||||
import { imageDTOToImageObject, imageDTOToImageWithDims } from './types';
|
||||
|
||||
@ -29,42 +29,23 @@ export const selectLayerOrThrow = (state: CanvasV2State, id: string) => {
|
||||
|
||||
export const layersReducers = {
|
||||
layerAdded: {
|
||||
reducer: (state, action: PayloadAction<{ id: string }>) => {
|
||||
reducer: (state, action: PayloadAction<{ id: string; overrides?: Partial<LayerEntity> }>) => {
|
||||
const { id } = action.payload;
|
||||
state.layers.entities.push({
|
||||
const layer: LayerEntity = {
|
||||
id,
|
||||
type: 'layer',
|
||||
isEnabled: true,
|
||||
objects: [],
|
||||
opacity: 1,
|
||||
position: { x: 0, y: 0 },
|
||||
});
|
||||
};
|
||||
merge(layer, action.payload.overrides);
|
||||
state.layers.entities.push(layer);
|
||||
state.selectedEntityIdentifier = { type: 'layer', id };
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
prepare: () => ({ payload: { id: nanoid() } }),
|
||||
},
|
||||
layerAddedFromStagingArea: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadAction<{ id: string; objectId: string; stagingAreaImage: StagingAreaImage; position: Coordinate }>
|
||||
) => {
|
||||
const { id, objectId, stagingAreaImage, position } = action.payload;
|
||||
const { imageDTO, offsetX, offsetY } = stagingAreaImage;
|
||||
const imageObject = imageDTOToImageObject(id, objectId, imageDTO);
|
||||
state.layers.entities.push({
|
||||
id,
|
||||
type: 'layer',
|
||||
isEnabled: true,
|
||||
objects: [imageObject],
|
||||
opacity: 1,
|
||||
position: { x: position.x + offsetX, y: position.y + offsetY },
|
||||
});
|
||||
state.selectedEntityIdentifier = { type: 'layer', id };
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
prepare: (payload: { stagingAreaImage: StagingAreaImage; position: Coordinate }) => ({
|
||||
payload: { ...payload, id: nanoid(), objectId: nanoid() },
|
||||
prepare: (payload: { overrides?: Partial<LayerEntity> }) => ({
|
||||
payload: { ...payload, id: getPrefixedId('layer') },
|
||||
}),
|
||||
},
|
||||
layerRecalled: (state, action: PayloadAction<{ data: LayerEntity }>) => {
|
||||
@ -227,27 +208,22 @@ export const layersReducers = {
|
||||
layer.position.y = Math.round(position.y);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerImageAdded: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadAction<ImageObjectAddedArg & { objectId: string; pos?: { x: number; y: number } }>
|
||||
) => {
|
||||
const { id, objectId, imageDTO, pos } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
const imageObject = imageDTOToImageObject(id, objectId, imageDTO);
|
||||
if (pos) {
|
||||
imageObject.x = pos.x;
|
||||
imageObject.y = pos.y;
|
||||
}
|
||||
layer.objects.push(imageObject);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
prepare: (payload: ImageObjectAddedArg & { pos?: { x: number; y: number } }) => ({
|
||||
payload: { ...payload, objectId: nanoid() },
|
||||
}),
|
||||
layerImageAdded: (
|
||||
state,
|
||||
action: PayloadAction<ImageObjectAddedArg & { objectId: string; pos?: { x: number; y: number } }>
|
||||
) => {
|
||||
const { id, imageDTO, pos } = action.payload;
|
||||
const layer = selectLayer(state, id);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
const imageObject = imageDTOToImageObject(imageDTO);
|
||||
if (pos) {
|
||||
imageObject.x = pos.x;
|
||||
imageObject.y = pos.y;
|
||||
}
|
||||
layer.objects.push(imageObject);
|
||||
state.layers.imageCache = null;
|
||||
},
|
||||
layerImageCacheChanged: (state, action: PayloadAction<{ imageDTO: ImageDTO | null }>) => {
|
||||
const { imageDTO } = action.payload;
|
||||
|
@ -2,7 +2,7 @@ import type { CanvasControlAdapter } from 'features/controlLayers/konva/CanvasCo
|
||||
import { CanvasInpaintMask } from 'features/controlLayers/konva/CanvasInpaintMask';
|
||||
import { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
|
||||
import { CanvasRegion } from 'features/controlLayers/konva/CanvasRegion';
|
||||
import { getImageObjectId } from 'features/controlLayers/konva/naming';
|
||||
import { getObjectId } from 'features/controlLayers/konva/util';
|
||||
import { zModelIdentifierField } from 'features/nodes/types/common';
|
||||
import type { AspectRatioState } from 'features/parameters/components/DocumentSize/types';
|
||||
import type {
|
||||
@ -777,15 +777,10 @@ export const imageDTOToImageWithDims = ({ image_name, width, height }: ImageDTO)
|
||||
height,
|
||||
});
|
||||
|
||||
export const imageDTOToImageObject = (
|
||||
entityId: string,
|
||||
objectId: string,
|
||||
imageDTO: ImageDTO,
|
||||
overrides?: Partial<ImageObject>
|
||||
): ImageObject => {
|
||||
export const imageDTOToImageObject = (imageDTO: ImageDTO, overrides?: Partial<ImageObject>): ImageObject => {
|
||||
const { width, height, image_name } = imageDTO;
|
||||
return {
|
||||
id: getImageObjectId(entityId, objectId),
|
||||
id: getObjectId('image'),
|
||||
type: 'image',
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
Loading…
Reference in New Issue
Block a user