forked from flow/vue3js-app-proposal-for-sdk-claude
99 lines
4.0 KiB
JavaScript
99 lines
4.0 KiB
JavaScript
import { h, ref } from 'vue';
|
||
import { fetchScoreText, putScoreText } from '../api.js';
|
||
import { patchScore } from '../exporter.js';
|
||
import { StatusPoller } from './StatusPoller.js';
|
||
|
||
export const PaneCP = {
|
||
props: ['store', 'onImportClick'],
|
||
setup(props) {
|
||
const exporting = ref(false);
|
||
const exportError = ref('');
|
||
|
||
function breadcrumbLabel(node) {
|
||
if (!node) return '?';
|
||
if (node.type === 'score') return 'Score';
|
||
if (node.type === 'instrument') return node.name;
|
||
if (node.type === 'variation') return node.dependsOn ? `var(${node.dependsOn})` : 'variation';
|
||
if (node.type === 'label_spec') return node.label ?? 'label';
|
||
if (node.type === 'bar') return node.id;
|
||
return node.type;
|
||
}
|
||
|
||
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);
|
||
// Start polling
|
||
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;
|
||
|
||
return h('div', { class: 'se-pane' }, [
|
||
// Header
|
||
h('div', { class: 'se-cp-header' }, [
|
||
h('span', { class: 'se-cp-title' },
|
||
model ? (model.info?.title ?? 'Untitled score') : 'No score loaded'),
|
||
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'),
|
||
model ? h('button', {
|
||
class: 'se-btn se-btn-primary',
|
||
disabled: !store.isDirty || exporting.value,
|
||
onClick: doExport,
|
||
}, exporting.value ? 'Exporting…' : 'Export') : null,
|
||
]),
|
||
|
||
// Breadcrumb
|
||
fp.length ? h('div', { class: 'se-breadcrumb' }, [
|
||
h('span', { onClick: () => store.setFocus([]) }, 'Score'),
|
||
...fp.map((node, i) => [
|
||
' › ',
|
||
h('span', { onClick: () => store.setFocus(fp.slice(0, i + 1)) },
|
||
breadcrumbLabel(node)),
|
||
]).flat(),
|
||
]) : null,
|
||
|
||
// Score info
|
||
model ? h('dl', { style: 'font-size:0.8rem;margin:0.5rem 0' }, [
|
||
h('dt', null, 'Instruments'),
|
||
h('dd', null, String(model.instruments.length)),
|
||
h('dt', null, 'Bars'),
|
||
h('dd', null, String(model.bars.length)),
|
||
]) : null,
|
||
|
||
// Export error
|
||
exportError.value ? h('div', { class: 'se-error' }, exportError.value) : null,
|
||
|
||
// Status poller (shown after export started)
|
||
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,
|
||
]);
|
||
};
|
||
},
|
||
};
|