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