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, ]); }; }, };