Numeric dependsOn values (merge-type variations) are RFC edge values, not ATTR names. Display them as the number, not as "ATTR: N" or "subvariation N".
124 lines
5.2 KiB
JavaScript
124 lines
5.2 KiB
JavaScript
import { h, ref } from 'vue';
|
|
import { fetchScoreText, putScoreText } from '../api.js';
|
|
import { patchScore } from '../exporter.js';
|
|
import { StatusPoller } from './StatusPoller.js';
|
|
|
|
// Short label + identifying meta for each node type.
|
|
function shortView(node) {
|
|
if (!node) return { typeTag: '?', label: '?', meta: [] };
|
|
switch (node.type) {
|
|
case 'score':
|
|
return {
|
|
typeTag: 'score',
|
|
label: node.info?.title ?? '(untitled)',
|
|
meta: node.info?.composer ? [{ key: 'composer', value: node.info.composer }] : [],
|
|
};
|
|
case 'instrument':
|
|
return { typeTag: 'instrument', label: node.name, meta: [] };
|
|
case 'variation': {
|
|
const dep = node.dependsOn;
|
|
const label = dep == null ? '(root variation)'
|
|
: isNaN(Number(dep)) ? `ATTR: ${dep}`
|
|
: String(dep);
|
|
return { typeTag: 'variation', label, meta: [] };
|
|
}
|
|
case 'label_spec':
|
|
return { typeTag: 'label', label: node.label ?? '(no label)', meta: [] };
|
|
case 'bar':
|
|
return { typeTag: 'bar', label: node.id, meta: [] };
|
|
default:
|
|
return { typeTag: node.type, label: node.type, meta: [] };
|
|
}
|
|
}
|
|
|
|
export const PaneCP = {
|
|
props: ['store', 'onImportClick'],
|
|
setup(props) {
|
|
const exporting = ref(false);
|
|
const exportError = ref('');
|
|
|
|
async function doExport() {
|
|
exportError.value = '';
|
|
exporting.value = true;
|
|
try {
|
|
const raw = await fetchScoreText(props.store.credentials);
|
|
props.store.rawScoreText = raw;
|
|
const patched = patchScore(raw, props.store.scoreModel.instruments);
|
|
await putScoreText(patched, props.store.credentials);
|
|
props.store.synthesisStatus = { frozen: false, progress: 0 };
|
|
} catch (e) {
|
|
exportError.value = e.message;
|
|
} finally {
|
|
exporting.value = false;
|
|
}
|
|
}
|
|
|
|
return () => {
|
|
const store = props.store;
|
|
const model = store.scoreModel;
|
|
const fp = store.focusPath;
|
|
|
|
// Build vertical path list: score root + each focus node.
|
|
const pathItems = model ? [
|
|
{ node: model, idx: -1 },
|
|
...fp.map((node, idx) => ({ node, idx })),
|
|
] : [];
|
|
|
|
return h('div', null, [
|
|
// Header
|
|
h('div', { class: 'se-cp-header' }, [
|
|
h('button', {
|
|
class: 'se-btn',
|
|
disabled: store.isDirty,
|
|
title: store.isDirty ? 'Save or discard edits before re-importing' : 'Import from server',
|
|
onClick: props.onImportClick,
|
|
}, '→ Import'),
|
|
h('span', { class: 'se-cp-title' },
|
|
model ? (model.info?.title ?? 'Untitled score') : 'No score loaded'),
|
|
model ? h('button', {
|
|
class: 'se-btn se-btn-primary',
|
|
disabled: !store.isDirty || exporting.value,
|
|
onClick: doExport,
|
|
}, exporting.value ? 'Exporting…' : 'Export ↑') : null,
|
|
]),
|
|
|
|
// Vertical focus path — short views, clickable to navigate up
|
|
pathItems.length ? h('ul', { class: 'se-object-list se-cp-path' },
|
|
pathItems.map(({ node, idx }) => {
|
|
const { typeTag, label, meta } = shortView(node);
|
|
const isCurrent = idx === fp.length - 1 || (idx === -1 && fp.length === 0);
|
|
return h('li', {
|
|
class: ['se-object-item', isCurrent ? 'focused' : null],
|
|
onClick: () => {
|
|
if (idx === -1) store.setFocus([]);
|
|
else store.setFocus(fp.slice(0, idx + 1));
|
|
},
|
|
}, [
|
|
h('span', { class: 'se-object-type' }, typeTag),
|
|
h('span', { class: 'se-object-label' }, [
|
|
h('strong', null, label),
|
|
...meta.map(({ key, value }) =>
|
|
h('span', { style: 'color:#888;margin-left:0.5rem;font-size:0.8em' },
|
|
`${key}=${value}`)
|
|
),
|
|
]),
|
|
]);
|
|
})
|
|
) : null,
|
|
|
|
// Export error
|
|
exportError.value ? h('div', { class: 'se-error' }, exportError.value) : null,
|
|
|
|
// Status poller
|
|
store.synthesisStatus && !store.synthesisStatus.frozen
|
|
? h(StatusPoller, { store }) : null,
|
|
|
|
// Result link
|
|
store.synthesisStatus?.frozen && !store.synthesisStatus?.error
|
|
? h('a', { href: '/sompyle/result.mp3', style: 'display:block;margin-top:0.5rem' },
|
|
'Download result') : null,
|
|
]);
|
|
};
|
|
},
|
|
};
|