Files
vue3js-app-proposal-for-sdk…/static/components/PaneSubObjects.js
c0dev0id 7ffa0a9d2d phases 4-6: article system, stem note fields, edit-state cleanup
Phase 4 — remove edit-state flags from domain objects:
- ast-parser.js: drop isDirty from buildInstrument, buildBar, buildStemNote
- store.js: add resetEditState()
- PaneCP.js: use resetEditState() on import; drop flags arg from patchScore
- PaneFO.js: isDirty -> _modified for linked instruments; no per-node dirty tracking
- PaneSubObjects.js: _spliceNode helper; deletion via splice; _isNew on new bars
- exporter.js: on-demand traversal; absence from model means deleted

Phase 5 — article parser and exporter:
- ast-parser.js: _buildArticleEntry replaces _mergeArticleEntry; stage.article and
  voice.article dispatch; article properties carry scope and overwritten flag
- exporter.js: _buildArticlesBlock emits -important: list for overwrites scope

Phase 6 — stem note dead fields:
- ast-parser.js: parse weight and articulatory on stem notes; flatten
  article.definite properties onto stem note model sibling to pitch/chain
2026-07-11 12:07:05 +02:00

165 lines
6.8 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';
function _barGroupKey(id) {
const m = id.match(/(\d+|\D+)$/);
return m ? id.slice(0, m.index) : id;
}
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 _spliceNode(arr, node) {
const idx = arr.indexOf(node);
if (idx !== -1) { arr.splice(idx, 1); props.store.markDirty(); }
}
function deleteItem(kind, node) {
const store = props.store;
const model = store.scoreModel;
if (!model) return;
if (kind === 'instrument') {
_spliceNode(model.instruments, node);
} else if (kind === 'articles') {
_spliceNode(model.articles, node);
} else if (kind === 'bar') {
_spliceNode(model.bars, node);
} 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._modified = 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._modified = 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._modified = 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._modified = 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 ?? [];
newId = liveBars.length ? _incrementId(liveBars[liveBars.length - 1].id) : 'bar001';
}
const newBar = {
type: 'bar', id: newId, _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 barGroups = [];
const seen = new Map();
for (const item of items) {
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,
})
))
);
};
},
};