feat: add contextual layer inspector
This commit is contained in:
@@ -6,6 +6,7 @@ import type { ArtboardId, AssetId, LayerId } from "@core/id";
|
||||
import type { ImageLayer } from "@core/image-layer";
|
||||
import { getLayerMask } from "@core/layer-mask-utils";
|
||||
import type { RasterLayer } from "@core/raster-layer";
|
||||
import type { Layer } from "@core/layer";
|
||||
import type { LayerGroup } from "@core/layer-group";
|
||||
import type { Command } from "./command";
|
||||
import { commandIds } from "./ids";
|
||||
@@ -98,6 +99,16 @@ export type DocumentSetLayerLockedPayload = {
|
||||
locked: boolean;
|
||||
};
|
||||
|
||||
export type DocumentSetLayerOpacityPayload = {
|
||||
layerId: LayerId;
|
||||
opacity: number;
|
||||
};
|
||||
|
||||
export type DocumentDuplicateLayerPayload = {
|
||||
layerId: LayerId;
|
||||
idByLayerId: Record<LayerId, LayerId>;
|
||||
};
|
||||
|
||||
export type DocumentRenameLayerPayload = {
|
||||
layerId: LayerId;
|
||||
name: string;
|
||||
@@ -402,6 +413,56 @@ export const documentSetLayerLockedCommand: Command<DocumentSetLayerLockedPayloa
|
||||
},
|
||||
};
|
||||
|
||||
export const documentSetLayerOpacityCommand: Command<DocumentSetLayerOpacityPayload> = {
|
||||
id: commandIds.documentSetLayerOpacity,
|
||||
name: "Set layer opacity",
|
||||
execute({ state }, payload) {
|
||||
const location = findLayerLocation(state.document, payload.layerId);
|
||||
if (!location || location.layer.locked || !Number.isFinite(payload.opacity)) return state;
|
||||
const opacity = Math.min(1, Math.max(0, payload.opacity));
|
||||
return { ...state, document: mapLayerInDocument(state.document, payload.layerId, (layer) => ({ ...layer, opacity })) };
|
||||
},
|
||||
};
|
||||
|
||||
export const documentDuplicateLayerCommand: Command<DocumentDuplicateLayerPayload> = {
|
||||
id: commandIds.documentDuplicateLayer,
|
||||
name: "Duplicate layer",
|
||||
execute({ state }, payload) {
|
||||
const location = findLayerLocation(state.document, payload.layerId);
|
||||
if (!location || location.layer.locked) return state;
|
||||
const maskId = getLayerMask(location.layer)?.maskLayerId;
|
||||
const mask = maskId ? location.siblings.find((layer) => layer.id === maskId) : undefined;
|
||||
const sourceLayers = mask ? [mask, location.layer] : [location.layer];
|
||||
const sourceIds = new Set(sourceLayers.flatMap((layer) => [...collectLayerIds(layer)]));
|
||||
const mappedIds = [...sourceIds].map((id) => payload.idByLayerId[id]);
|
||||
if (mappedIds.some((id) => !id) || new Set(mappedIds).size !== mappedIds.length || mappedIds.some((id) => findLayerLocation(state.document, id!))) return state;
|
||||
|
||||
const duplicates = sourceLayers.map((layer) => duplicateLayerTree(layer, payload.idByLayerId));
|
||||
const insertionIndex = Math.max(...sourceLayers.map((layer) => location.siblings.findIndex((candidate) => candidate.id === layer.id))) + 1;
|
||||
const siblings = [...location.siblings.slice(0, insertionIndex), ...duplicates, ...location.siblings.slice(insertionIndex)];
|
||||
const duplicatedLayerId = payload.idByLayerId[payload.layerId];
|
||||
if (!duplicatedLayerId) return state;
|
||||
return {
|
||||
...state,
|
||||
document: replaceLayerListInDocument(state.document, location.artboardId, location.parentGroupId, siblings),
|
||||
editor: { ...state.editor, selection: { artboardId: location.artboardId, layerIds: [duplicatedLayerId] } },
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function duplicateLayerTree(layer: Layer, idByLayerId: Record<LayerId, LayerId>): Layer {
|
||||
const layerMask = getLayerMask(layer);
|
||||
const duplicatedMaskId = layerMask ? idByLayerId[layerMask.maskLayerId] : undefined;
|
||||
const duplicated = {
|
||||
...layer,
|
||||
id: idByLayerId[layer.id]!,
|
||||
name: `${layer.name} copy`,
|
||||
transform: { ...layer.transform, position: { ...layer.transform.position }, scale: { ...layer.transform.scale } },
|
||||
...(layerMask && duplicatedMaskId ? { layerMask: { ...layerMask, maskLayerId: duplicatedMaskId }, clippingMask: layer.clippingMask ? { maskLayerId: duplicatedMaskId } : undefined } : {}),
|
||||
};
|
||||
return layer.type === "group" ? { ...duplicated, type: "group", children: layer.children.map((child) => duplicateLayerTree(child, idByLayerId)) } : duplicated;
|
||||
}
|
||||
|
||||
export const documentRenameLayerCommand: Command<DocumentRenameLayerPayload> = {
|
||||
id: commandIds.documentRenameLayer,
|
||||
name: "Rename layer",
|
||||
@@ -619,6 +680,8 @@ export const documentCommands = [
|
||||
documentRemoveLayerCommand,
|
||||
documentSetLayerVisibleCommand,
|
||||
documentSetLayerLockedCommand,
|
||||
documentSetLayerOpacityCommand,
|
||||
documentDuplicateLayerCommand,
|
||||
documentRenameLayerCommand,
|
||||
documentSetLayerClippingMaskCommand,
|
||||
documentAddLayerMaskCommand,
|
||||
|
||||
Reference in New Issue
Block a user