Files
vue3js-app-proposal-for-sdk…/static/components/PaneSubObjects.js

169 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { h } from 'vue';
import { ObjectShort } from './ObjectShort.js';
import { getKindGroups } from '../subobject-kinds.js';
// Strip the final maximal run of same-class characters (all-digits or all-non-digits)
// from the ID tail. Everything before that run is the group key.
function _barGroupKey(id) {
const m = id.match(/(\d+|\D+)$/);
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) {
function focused() {
const fp = props.store.focusPath;
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);
const group = props.kind
? groups.find(g => g.kind === props.kind)
: groups[0];
const items = group?.items ?? [];
if (!items.length && props.kind !== 'bar') return h('div', null, h('em', null, 'No sub-objects'));
if (props.kind === 'bar') {
const visibleItems = items.filter(item => !item.node.deleted);
const barGroups = [];
const seen = new Map();
for (const item of visibleItems) {
const key = _barGroupKey(item.label);
if (!seen.has(key)) {
const g = { key, items: [] };
barGroups.push(g);
seen.set(key, g);
}
seen.get(key).items.push(item);
}
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],
}, [
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,
h('ul', { class: 'se-object-list' }, items.map((item, idx) =>
h(ObjectShort, {
key: idx,
label: item.label,
typeTag: item.kind,
focused: props.store.focusPath.includes(item.node),
hasChildren: item.hasChildren,
readOnly: item.readOnly ?? false,
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,
})
))
);
};
},
};