Files
odysseus/static/js/editor/layer-selection.js
T

97 lines
3.8 KiB
JavaScript

/** Shared multi-layer selection semantics for panel and canvas tools. */
import { isLayerEffectivelyLocked } from './layer-groups.js';
function existingIds(editorState) {
return new Set((editorState.layers || []).map(layer => layer.id));
}
export function normalizeLayerSelection(editorState) {
const valid = existingIds(editorState);
const selected = [];
for (const id of editorState.selectedLayerIds || []) {
if (valid.has(id) && !selected.includes(id)) selected.push(id);
}
if (valid.has(editorState.activeLayerId) && !selected.includes(editorState.activeLayerId)) {
selected.length = 0;
selected.push(editorState.activeLayerId);
editorState.activeGroupId = null;
}
if (!selected.length && editorState.layers?.length) {
const fallback = editorState.layers[editorState.layers.length - 1];
editorState.activeLayerId = fallback.id;
selected.push(fallback.id);
editorState.activeGroupId = null;
}
editorState.selectedLayerIds = selected;
if (!valid.has(editorState.selectionAnchorId)) {
editorState.selectionAnchorId = editorState.activeLayerId || null;
}
return selected;
}
export function selectedLayers(editorState, { unlockedOnly = false } = {}) {
const ids = new Set(normalizeLayerSelection(editorState));
return (editorState.layers || []).filter(layer =>
ids.has(layer.id) && (!unlockedOnly || !isLayerEffectivelyLocked(editorState, layer))
);
}
export function selectOnlyLayer(editorState, layerId) {
if (!(editorState.layers || []).some(layer => layer.id === layerId)) return [];
editorState.activeLayerId = layerId;
editorState.selectionAnchorId = layerId;
editorState.selectedLayerIds = [layerId];
editorState.activeGroupId = null;
return editorState.selectedLayerIds;
}
export function toggleLayerSelection(editorState, layerId) {
const valid = (editorState.layers || []).some(layer => layer.id === layerId);
if (!valid) return normalizeLayerSelection(editorState);
const selected = normalizeLayerSelection(editorState);
const index = selected.indexOf(layerId);
if (index >= 0 && selected.length > 1) {
selected.splice(index, 1);
if (editorState.activeLayerId === layerId) {
editorState.activeLayerId = selected[selected.length - 1];
}
} else if (index < 0) {
selected.push(layerId);
editorState.activeLayerId = layerId;
}
editorState.selectionAnchorId = layerId;
editorState.selectedLayerIds = selected;
editorState.activeGroupId = null;
return selected;
}
export function selectLayerRange(editorState, layerId) {
const layers = editorState.layers || [];
const target = layers.findIndex(layer => layer.id === layerId);
if (target < 0) return normalizeLayerSelection(editorState);
const anchorId = editorState.selectionAnchorId || editorState.activeLayerId || layerId;
const anchor = layers.findIndex(layer => layer.id === anchorId);
if (anchor < 0) return selectOnlyLayer(editorState, layerId);
const start = Math.min(anchor, target);
const end = Math.max(anchor, target);
editorState.selectedLayerIds = layers.slice(start, end + 1).map(layer => layer.id);
editorState.activeLayerId = layerId;
editorState.activeGroupId = null;
return editorState.selectedLayerIds;
}
export function selectAllLayers(editorState) {
editorState.selectedLayerIds = (editorState.layers || []).map(layer => layer.id);
if (editorState.selectedLayerIds.length && !editorState.selectedLayerIds.includes(editorState.activeLayerId)) {
editorState.activeLayerId = editorState.selectedLayerIds[editorState.selectedLayerIds.length - 1];
}
editorState.selectionAnchorId = editorState.activeLayerId || null;
editorState.activeGroupId = null;
return editorState.selectedLayerIds;
}
export function selectionLabel(editorState, singular = 'layer') {
const count = normalizeLayerSelection(editorState).length;
return count === 1 ? singular : `${count} layers`;
}