diff --git a/__init__.py b/__init__.py index 3b3084a..69a3984 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,5 @@ from flask import Blueprint, render_template, request +from jinja2 import TemplateNotFound blueprint = Blueprint( 'vue3_neusik', @@ -14,3 +15,14 @@ def index(): 'vue3_neusik/index.html', import_on_load='true' if request.args.get('import') == '1' else 'false', ) + +@blueprint.route('/audiowidget', methods=['GET']) +def audiowidget(): + try: + return render_template( + 'vue3_neusik/audiowidget.tmpl', + result_url=request.args.get('result_url', ''), + errors=request.args.get('errors', ''), + ) + except TemplateNotFound: + return '', 204 diff --git a/static/api.js b/static/api.js index d0aeaf0..0ff49da 100644 --- a/static/api.js +++ b/static/api.js @@ -44,3 +44,12 @@ export async function fetchStatus(credentials) { if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); return res.json(); } + +export async function fetchAudioWidget(resultUrl, errors) { + const params = new URLSearchParams(); + if (resultUrl) params.set('result_url', resultUrl); + if (errors) params.set('errors', errors); + const res = await fetch(`${U.audiowidget}?${params}`); + if (!res.ok || res.status === 204) return ''; + return res.text(); +} diff --git a/static/components/PaneCP.js b/static/components/PaneCP.js index 65649f0..f1e1670 100644 --- a/static/components/PaneCP.js +++ b/static/components/PaneCP.js @@ -1,5 +1,5 @@ -import { h, ref } from 'vue'; -import { fetchScoreText, putScoreText, URLS } from '../api.js'; +import { h, ref, watch } from 'vue'; +import { fetchScoreText, putScoreText, fetchAudioWidget, URLS } from '../api.js'; import { patchScore } from '../exporter.js'; import { StatusPoller } from './StatusPoller.js'; @@ -40,6 +40,18 @@ export const PaneCP = { setup(props) { const exporting = ref(false); const exportError = ref(''); + const audioWidgetHtml = ref(''); + + watch( + () => props.store.synthesisStatus, + async (s) => { + if (!s?.frozen) { audioWidgetHtml.value = ''; return; } + audioWidgetHtml.value = await fetchAudioWidget( + s.file_accomplished ? URLS.result : null, + s.errors ?? null, + ); + }, + ); async function doExport() { exportError.value = ''; @@ -118,18 +130,10 @@ export const PaneCP = { store.synthesisStatus && !store.synthesisStatus.frozen ? h(StatusPoller, { store }) : null, - // Synthesis error - store.synthesisStatus?.frozen && store.synthesisStatus?.errors - ? h('div', { class: 'se-error', style: 'margin-top:0.5rem' }, - store.synthesisStatus.errors) : null, - - // Audio result - store.synthesisStatus?.file_accomplished - ? h('audio', { - controls: true, - src: URLS.result, - style: 'display:block;width:100%;margin-top:0.5rem', - }) : null, + // Audio widget (rendered server-side from audiowidget.tmpl) + audioWidgetHtml.value + ? h('div', { innerHTML: audioWidgetHtml.value, style: 'margin-top:0.5rem' }) + : null, ]); }; }, diff --git a/templates/vue3_neusik/audiowidget.tmpl.stub b/templates/vue3_neusik/audiowidget.tmpl.stub new file mode 100644 index 0000000..1ff33ed --- /dev/null +++ b/templates/vue3_neusik/audiowidget.tmpl.stub @@ -0,0 +1,18 @@ +{# audiowidget.tmpl.stub — copy to audiowidget.tmpl in the same directory to activate. + Customise to match your site's look and feel. + + Context variables (passed as query params by the score editor): + result_url – URL of the rendered MP3, or empty string + errors – synthesis error message, or empty string +#} +{% if errors %} +