Add delete buttons and bar add/create to sub-objects pane

This commit is contained in:
c0dev0id
2026-06-30 13:18:29 +02:00
parent e011c9e7d8
commit 66c1f2c151
8 changed files with 261 additions and 39 deletions

View File

@@ -9,6 +9,15 @@ function barGroupKey(id) {
return m ? id.slice(0, m.index) : id;
}
// Increment the trailing numeric run of an ID, preserving zero-padding width.
function incrementId(id) {
const m = id.match(/(\d+)$/);
if (!m) return id + '1';
const num = parseInt(m[1], 10) + 1;
const padded = String(num).padStart(m[1].length, '0');
return id.slice(0, m.index) + padded;
}
export const PaneSubObjects = {
props: ['store', 'kind', 'onFocusFO'],
setup(props) {
@@ -17,6 +26,67 @@ export const PaneSubObjects = {
return fp.length ? fp[fp.length - 1] : props.store.scoreModel;
}
function deleteItem(kind, node) {
const store = props.store;
const model = store.scoreModel;
if (!model) return;
if (kind === 'instrument') {
const idx = model.instruments.indexOf(node);
if (idx !== -1) { model.instruments.splice(idx, 1); store.markDirty(); }
} else if (kind === 'articles') {
const idx = (model.articles ?? []).indexOf(node);
if (idx !== -1) { model.articles.splice(idx, 1); model.articlesModified = true; store.markDirty(); }
} else if (kind === 'bar') {
node.deleted = true;
node.isDirty = true;
store.markDirty();
} else if (kind === 'variation') {
for (const instr of model.instruments) {
const idx = instr.variations.indexOf(node);
if (idx !== -1) { instr.variations.splice(idx, 1); instr.isDirty = true; store.markDirty(); return; }
for (const v of instr.variations) {
const sidx = v.subvariations.indexOf(node);
if (sidx !== -1) { v.subvariations.splice(sidx, 1); instr.isDirty = true; store.markDirty(); return; }
}
}
} else if (kind === 'label_spec') {
for (const instr of model.instruments) {
for (const v of instr.variations) {
const idx = v.labelSpecs.indexOf(node);
if (idx !== -1) { v.labelSpecs.splice(idx, 1); instr.isDirty = true; store.markDirty(); return; }
for (const sv of v.subvariations) {
const idx2 = sv.labelSpecs.indexOf(node);
if (idx2 !== -1) { sv.labelSpecs.splice(idx2, 1); instr.isDirty = true; store.markDirty(); return; }
}
}
}
}
}
function addBar(afterId) {
const store = props.store;
const model = store.scoreModel;
if (!model) return;
let newId;
if (afterId) {
newId = incrementId(afterId);
} else {
const liveBars = (model.bars ?? []).filter(b => !b.deleted);
newId = liveBars.length ? incrementId(liveBars[liveBars.length - 1].id) : 'bar001';
}
const newBar = {
type: 'bar', id: newId, isDirty: true, isNew: true,
stressor: null, tempoLevels: null,
upperStressBound: null, lowerStressBound: null, tempoShape: null,
voices: {},
};
model.bars.push(newBar);
store.markDirty();
store.pushFocus(newBar);
props.onFocusFO?.();
}
return () => {
const node = focused();
const groups = getKindGroups(node);
@@ -25,13 +95,14 @@ export const PaneSubObjects = {
: groups[0];
const items = group?.items ?? [];
if (!items.length) return h('div', null, h('em', null, 'No sub-objects'));
if (!items.length && props.kind !== 'bar') return h('div', null, h('em', null, 'No sub-objects'));
if (props.kind === 'bar') {
// Group bars by shared P?L? key; render each group as a row of inline chips.
const visibleItems = items.filter(item => !item.node.deleted);
const barGroups = [];
const seen = new Map();
for (const item of items) {
for (const item of visibleItems) {
const key = barGroupKey(item.label);
if (!seen.has(key)) {
const g = { key, items: [] };
@@ -41,17 +112,39 @@ export const PaneSubObjects = {
seen.get(key).items.push(item);
}
return h('div', { class: 'se-bar-groups' }, barGroups.map(g =>
h('div', { key: g.key, class: 'se-bar-group' },
g.items.map((item, idx) =>
h('button', {
const groupEls = barGroups.map(g => {
const lastId = g.items[g.items.length - 1].label;
return h('div', { key: g.key, class: 'se-bar-group' }, [
...g.items.map((item, idx) =>
h('span', {
key: idx,
class: ['se-bar-chip', props.store.focusPath.includes(item.node) ? 'focused' : null],
onClick: () => { props.store.pushFocus(item.node); props.onFocusFO?.(); },
}, item.label)
)
)
));
}, [
h('span', {
class: 'se-bar-chip-label',
onClick: () => { props.store.pushFocus(item.node); props.onFocusFO?.(); },
}, item.label),
h('button', {
class: 'se-bar-chip-delete',
title: 'Delete bar',
onClick: e => { e.stopPropagation(); deleteItem('bar', item.node); },
}, '×'),
])
),
h('button', {
class: 'se-bar-add',
title: `Add bar after ${lastId}`,
onClick: () => addBar(lastId),
}, '+ add'),
]);
});
return h('div', { class: 'se-bar-groups' }, [
...groupEls,
h('div', { class: 'se-bar-group' },
h('button', { class: 'se-bar-add', onClick: () => addBar(null) }, '+ add bar')
),
]);
}
return h('div', null,
@@ -63,8 +156,10 @@ export const PaneSubObjects = {
focused: props.store.focusPath.includes(item.node),
hasChildren: item.hasChildren,
readOnly: item.readOnly ?? false,
onFocus: () => { props.store.pushFocus(item.node); props.onFocusFO?.(); },
deletable: item.deletable ?? false,
onFocus: () => { props.store.pushFocus(item.node); props.onFocusFO?.(); },
onDrillDown: () => { props.store.pushFocus(item.node); props.onFocusFO?.(); },
onDelete: item.deletable ? () => deleteItem(item.kind, item.node) : undefined,
})
))
);