Skip to content

Commit

Permalink
Make narrative file name, type, and time-to-slide configurable in editor
Browse files Browse the repository at this point in the history
  • Loading branch information
momeni committed Jul 30, 2021
1 parent 5b540ca commit aedc541
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/js/Storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ export class Storage {
* @param {string} htmlFileName - The name of the presentation HTML file.
*/
async createNarratedHTMLFile(name, location, htmlFileName) {
const t = this.presentation.narrativeType;
if (!t || t === "none") {
return;
}
try {
const fileDescriptor = await this.backend.find(name, location);
await this.backend.save(fileDescriptor, this.exportNarratedHTML(htmlFileName));
Expand Down
25 changes: 25 additions & 0 deletions src/js/model/Presentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,25 @@ export class Presentation extends EventEmitter {
* @type {boolean} */
this.updateURLOnFrameChange = true;

/** The narrative file type or "none" if narration is disabled.
*
* @default
* @type {string} */
this.narrativeType = "none";

/** The narrative file name or relative path.
*
* @default
* @type {string} */
this.narrativeFile = "narrative.flac";

/** The narrative time-to-slide data mapping the time
* moments (in seconds) to their corresponding frame number (one-based).
*
* @default
* @type {string} */
this.narrativeTimeToSlide = "0";

/** The last export document type.
*
* @default
Expand Down Expand Up @@ -846,6 +865,9 @@ export class Presentation extends EventEmitter {
enableMouseRotation : this.enableMouseRotation,
enableMouseNavigation : this.enableMouseNavigation,
updateURLOnFrameChange : this.updateURLOnFrameChange,
narrativeType : this.narrativeType,
narrativeFile : this.narrativeFile,
narrativeTimeToSlide : this.narrativeTimeToSlide,
exportType : this.exportType,
exportToPDFPageSize : this.exportToPDFPageSize,
exportToPDFPageOrientation: this.exportToPDFPageOrientation,
Expand Down Expand Up @@ -902,6 +924,9 @@ export class Presentation extends EventEmitter {
copyIfSet(this, storable, "enableMouseRotation");
copyIfSet(this, storable, "enableMouseNavigation");
copyIfSet(this, storable, "updateURLOnFrameChange");
copyIfSet(this, storable, "narrativeType");
copyIfSet(this, storable, "narrativeFile");
copyIfSet(this, storable, "narrativeTimeToSlide");
copyIfSet(this, storable, "exportType");
copyIfSet(this, storable, "exportToPDFPageSize");
copyIfSet(this, storable, "exportToPDFPageOrientation");
Expand Down
81 changes: 81 additions & 0 deletions src/js/view/Properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export class Properties extends VirtualDOMView {
render() {
switch (this.mode) {
case "preferences": return this.renderPreferences();
case "narration": return this.renderNarrationProperties();
case "export": return this.renderExportTool();
default: return this.renderPresentationProperties();
}
Expand Down Expand Up @@ -351,6 +352,86 @@ export class Properties extends VirtualDOMView {
]);
}

/** Render the properties view for a presentation-wide narrative.
*
* Three properties will be configured:
* 1. narrativeType: the audio tag type; this field is a hint to
* guide user in selection of an appropriate format, although
* the browser can automatically detect the correct type by
* itself. If this value is set to "none", narration will be
* disabled. If it is set to flac or ogg, the corresponding
* mimetype will be set for the <audio> type:
* audio/flac or audio/ogg
* 2. narrativeFile: the name (or relative path) of the narrative
* file which must be available in side of the HTML file.
* 3. narrativeTimeToSlide: the time-to-slide data value
* Check the {narrativeTimeToSlideHelp} for details.
*
* @returns {VNode} - A virtual DOM tree.
*/
renderNarrationProperties() {
const controller = this.controller;
const _ = controller.gettext;
const disabled = controller.presentation.narrativeType === "none";
const toDefaultMode = () => this.toggleMode("default");

return h("div.properties", [
h("div.back", {
title: _("Back to presentation properties"),
onClick() { toDefaultMode(); }
}, h("i.fas.fa-arrow-left", )),

h("h1", _("Narration")),

h("label", {for: "field-narrativeType"}, _("Narrative file type")),
this.renderSelectField("narrativeType", controller.getPresentationProperty, controller.setPresentationProperty, {
none: _("None (disable narration)"),
flac: _("Free Lossless Audio Codec (FLAC)"),
ogg: _("Ogg (a container for Vorbis or Opus)"),
}),

h("label.side-by-side", {for: "field-narrativeFile"}, [
_("Narrative file name"),
this.renderHelp(_("Name or path of the narrative file"),
() => controller.info(
_("Name or path of the narrative file (relative to the HTML file location)"),
true
)
)
]),
this.renderTextField("narrativeFile", disabled, controller.getPresentationProperty, controller.setPresentationProperty, false),

h("label.side-by-side", {for: "field-narrativeTimeToSlide"}, [
_("Time to frame number mapping"),
this.renderHelp(_("Click here to see the syntax for this field"), () => controller.info(this.narrativeTimeToSlideHelp, true))
]),
this.renderTextField("narrativeTimeToSlide", disabled, controller.getPresentationProperty, controller.setPresentationProperty, false)
]);
}

/** The HTML of the help message for time-to-slide data.
*
* @readonly
* @type {string}
*/
get narrativeTimeToSlideHelp() {
const _ = this.controller.gettext;
return [
_("Format of the narrative time-to-slide data:"),
"<ul><li>" + [
_("A string of comma-separated descriptors"),
_("Each descriptor specifies the time instant (in seconds) which should trigger a slide transition"),
_("Descriptor can specify the target slide number (counting from one); for this purpose the time and slide index must be separated by a colon"),
].join("<li>") + "</ul>",
_("Examples:"),
"<ul><li>" + [
_("\"0\": Switch to the first slide when narrative is at zero second without any other slide transition"),
_("\"0,5,7\": Switch to the 1st, 2nd, and 3rd slides at 0, 5, and 7 seconds of narrative"),
_("\"0,5,7:1,10:4\": At time 7s, return to the 1st slide and at 10s resume to the 4th slide"),
].join("<li>") + "</ul>",
].join("<br>");
}

/** The HTML of the help message for include/exclude lists in export.
*
* @readonly
Expand Down
5 changes: 5 additions & 0 deletions src/js/view/Toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ export class Toolbar extends VirtualDOMView {
title: _("Reload the SVG document"),
onclick() { controller.reload(); }
}, h("i.fas.fa-sync")),
h("button", {
title: _("Narrate the presentation"),
className: properties.mode === "narration" ? "active" : undefined,
onclick() { properties.toggleMode("narration"); }
}, h("i.fas.fa-voicemail")), // alternatives are file-audio, microphone-alt, and volume-up
// TODO disable the Export button if the feature is not available
h("button", {
title: _("Export the presentation"),
Expand Down
4 changes: 2 additions & 2 deletions src/templates/narrated.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
<iframe class="presentation-container" src="{{ soziHtml }}">
The Sozi presentation should play here.
</iframe>
<audio id="narrative" controls data-time-to-slide="0:1,1:3,2:6">
<source src="narrative.flac" type="audio/flac" preload="auto">
<audio id="narrative" controls data-time-to-slide="{{ pres.narrativeTimeToSlide }}">
<source src="{{ pres.narrativeFile }}" type="audio/{{ pres.narrativeType }}" preload="auto">
Audio HTML5 tag is not supported!
</audio>
{% endraw %}
Expand Down

0 comments on commit aedc541

Please sign in to comment.