mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-31 11:37:30 +09:00
Initial updates for Reader View.
This commit is contained in:
parent
faaff24342
commit
1c494e80a6
27 changed files with 1757 additions and 1786 deletions
|
|
@ -16,9 +16,10 @@ this.EXPORTED_SYMBOLS = ["NarrateControls"];
|
|||
|
||||
var gStrings = Services.strings.createBundle("chrome://global/locale/narrate.properties");
|
||||
|
||||
function NarrateControls(mm, win) {
|
||||
function NarrateControls(mm, win, languagePromise) {
|
||||
this._mm = mm;
|
||||
this._winRef = Cu.getWeakReference(win);
|
||||
this._languagePromise = languagePromise;
|
||||
|
||||
win.addEventListener("unload", this);
|
||||
|
||||
|
|
@ -37,16 +38,12 @@ function NarrateControls(mm, win) {
|
|||
}
|
||||
|
||||
let dropdown = win.document.createElement("ul");
|
||||
dropdown.className = "dropdown";
|
||||
dropdown.id = "narrate-dropdown";
|
||||
dropdown.className = "dropdown narrate-dropdown";
|
||||
// We need inline svg here for the animation to work (bug 908634 & 1190881).
|
||||
// The style animation can't be scoped (bug 830056).
|
||||
// eslint-disable-next-line no-unsanitized/property
|
||||
dropdown.innerHTML =
|
||||
localize`<style scoped>
|
||||
@import url("chrome://global/skin/narrateControls.css");
|
||||
</style>
|
||||
<li>
|
||||
<button class="dropdown-toggle button" id="narrate-toggle"
|
||||
localize`<li>
|
||||
<button class="dropdown-toggle button narrate-toggle"
|
||||
title="${"narrate"}" hidden>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
|
|
@ -60,11 +57,11 @@ function NarrateControls(mm, win) {
|
|||
100% { transform: scaleY(1); }
|
||||
}
|
||||
|
||||
#waveform > rect {
|
||||
.waveform > rect {
|
||||
fill: #808080;
|
||||
}
|
||||
|
||||
.speaking #waveform > rect {
|
||||
.speaking .waveform > rect {
|
||||
fill: #58bf43;
|
||||
transform-box: fill-box;
|
||||
transform-origin: 50% 50%;
|
||||
|
|
@ -74,15 +71,15 @@ function NarrateControls(mm, win) {
|
|||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
#waveform > rect:nth-child(2) { animation-delay: 250ms; }
|
||||
#waveform > rect:nth-child(3) { animation-delay: 500ms; }
|
||||
#waveform > rect:nth-child(4) { animation-delay: 750ms; }
|
||||
#waveform > rect:nth-child(5) { animation-delay: 1000ms; }
|
||||
#waveform > rect:nth-child(6) { animation-delay: 1250ms; }
|
||||
#waveform > rect:nth-child(7) { animation-delay: 1500ms; }
|
||||
.waveform > rect:nth-child(2) { animation-delay: 250ms; }
|
||||
.waveform > rect:nth-child(3) { animation-delay: 500ms; }
|
||||
.waveform > rect:nth-child(4) { animation-delay: 750ms; }
|
||||
.waveform > rect:nth-child(5) { animation-delay: 1000ms; }
|
||||
.waveform > rect:nth-child(6) { animation-delay: 1250ms; }
|
||||
.waveform > rect:nth-child(7) { animation-delay: 1500ms; }
|
||||
|
||||
</style>
|
||||
<g id="waveform">
|
||||
<g class="waveform">
|
||||
<rect x="1" y="8" width="2" height="8" rx=".5" ry=".5" />
|
||||
<rect x="4" y="5" width="2" height="14" rx=".5" ry=".5" />
|
||||
<rect x="7" y="8" width="2" height="8" rx=".5" ry=".5" />
|
||||
|
|
@ -95,35 +92,35 @@ function NarrateControls(mm, win) {
|
|||
</button>
|
||||
</li>
|
||||
<li class="dropdown-popup">
|
||||
<div id="narrate-control" class="narrate-row">
|
||||
<button disabled id="narrate-skip-previous"
|
||||
<div class="narrate-row narrate-control">
|
||||
<button disabled class="narrate-skip-previous"
|
||||
title="${"back"}"></button>
|
||||
<button id="narrate-start-stop" title="${"start"}"></button>
|
||||
<button disabled id="narrate-skip-next"
|
||||
<button class="narrate-start-stop" title="${"start"}"></button>
|
||||
<button disabled class="narrate-skip-next"
|
||||
title="${"forward"}"></button>
|
||||
</div>
|
||||
<div id="narrate-rate" class="narrate-row">
|
||||
<input id="narrate-rate-input" value="0" title="${"speed"}"
|
||||
<div class="narrate-row narrate-rate">
|
||||
<input class="narrate-rate-input" value="0" title="${"speed"}"
|
||||
step="5" max="100" min="-100" type="range">
|
||||
</div>
|
||||
<div id="narrate-voices" class="narrate-row"></div>
|
||||
<div class="narrate-row narrate-voices"></div>
|
||||
<div class="dropdown-arrow"></div>
|
||||
</li>`;
|
||||
|
||||
this.narrator = new Narrator(win);
|
||||
this.narrator = new Narrator(win, languagePromise);
|
||||
|
||||
let branch = Services.prefs.getBranch("narrate.");
|
||||
let selectLabel = gStrings.GetStringFromName("selectvoicelabel");
|
||||
this.voiceSelect = new VoiceSelect(win, selectLabel);
|
||||
this.voiceSelect.element.addEventListener("change", this);
|
||||
this.voiceSelect.element.id = "voice-select";
|
||||
this.voiceSelect.element.classList.add("voice-select");
|
||||
win.speechSynthesis.addEventListener("voiceschanged", this);
|
||||
dropdown.querySelector("#narrate-voices").appendChild(
|
||||
dropdown.querySelector(".narrate-voices").appendChild(
|
||||
this.voiceSelect.element);
|
||||
|
||||
dropdown.addEventListener("click", this, true);
|
||||
|
||||
let rateRange = dropdown.querySelector("#narrate-rate > input");
|
||||
let rateRange = dropdown.querySelector(".narrate-rate > input");
|
||||
rateRange.addEventListener("change", this);
|
||||
|
||||
// The rate is stored as an integer.
|
||||
|
|
@ -131,15 +128,15 @@ function NarrateControls(mm, win) {
|
|||
|
||||
this._setupVoices();
|
||||
|
||||
let tb = win.document.getElementById("reader-toolbar");
|
||||
let tb = win.document.querySelector(".reader-toolbar");
|
||||
tb.appendChild(dropdown);
|
||||
}
|
||||
|
||||
NarrateControls.prototype = {
|
||||
handleEvent: function(evt) {
|
||||
handleEvent(evt) {
|
||||
switch (evt.type) {
|
||||
case "change":
|
||||
if (evt.target.id == "narrate-rate-input") {
|
||||
if (evt.target.classList.contains("narrate-rate-input")) {
|
||||
this._onRateInput(evt);
|
||||
} else {
|
||||
this._onVoiceChange();
|
||||
|
|
@ -162,8 +159,8 @@ NarrateControls.prototype = {
|
|||
/**
|
||||
* Returns true if synth voices are available.
|
||||
*/
|
||||
_setupVoices: function() {
|
||||
return this.narrator.languagePromise.then(language => {
|
||||
_setupVoices() {
|
||||
return this._languagePromise.then(language => {
|
||||
this.voiceSelect.clear();
|
||||
let win = this._win;
|
||||
let voicePrefs = this._getVoicePref();
|
||||
|
|
@ -190,7 +187,7 @@ NarrateControls.prototype = {
|
|||
this.voiceSelect.addOptions(options);
|
||||
}
|
||||
|
||||
let narrateToggle = win.document.getElementById("narrate-toggle");
|
||||
let narrateToggle = win.document.querySelector(".narrate-toggle");
|
||||
let histogram = Services.telemetry.getKeyedHistogramById(
|
||||
"NARRATE_CONTENT_BY_LANGUAGE_2");
|
||||
let initial = !this._voicesInitialized;
|
||||
|
|
@ -210,7 +207,7 @@ NarrateControls.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
_getVoicePref: function() {
|
||||
_getVoicePref() {
|
||||
let voicePref = Services.prefs.getCharPref("narrate.voice");
|
||||
try {
|
||||
return JSON.parse(voicePref);
|
||||
|
|
@ -219,15 +216,15 @@ NarrateControls.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_onRateInput: function(evt) {
|
||||
_onRateInput(evt) {
|
||||
AsyncPrefs.set("narrate.rate", parseInt(evt.target.value, 10));
|
||||
this.narrator.setRate(this._convertRate(evt.target.value));
|
||||
},
|
||||
|
||||
_onVoiceChange: function() {
|
||||
_onVoiceChange() {
|
||||
let voice = this.voice;
|
||||
this.narrator.setVoice(voice);
|
||||
this.narrator.languagePromise.then(language => {
|
||||
this._languagePromise.then(language => {
|
||||
if (language) {
|
||||
let voicePref = this._getVoicePref();
|
||||
voicePref[language || "default"] = voice;
|
||||
|
|
@ -236,42 +233,39 @@ NarrateControls.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
_onButtonClick: function(evt) {
|
||||
switch (evt.target.id) {
|
||||
case "narrate-skip-previous":
|
||||
this.narrator.skipPrevious();
|
||||
break;
|
||||
case "narrate-skip-next":
|
||||
this.narrator.skipNext();
|
||||
break;
|
||||
case "narrate-start-stop":
|
||||
if (this.narrator.speaking) {
|
||||
this.narrator.stop();
|
||||
} else {
|
||||
this._updateSpeechControls(true);
|
||||
let options = { rate: this.rate, voice: this.voice };
|
||||
this.narrator.start(options).then(() => {
|
||||
this._updateSpeechControls(false);
|
||||
}, err => {
|
||||
Cu.reportError(`Narrate failed: ${err}.`);
|
||||
this._updateSpeechControls(false);
|
||||
});
|
||||
}
|
||||
break;
|
||||
_onButtonClick(evt) {
|
||||
let classList = evt.target.classList;
|
||||
if (classList.contains("narrate-skip-previous")) {
|
||||
this.narrator.skipPrevious();
|
||||
} else if (classList.contains("narrate-skip-next")) {
|
||||
this.narrator.skipNext();
|
||||
} else if (classList.contains("narrate-start-stop")) {
|
||||
if (this.narrator.speaking) {
|
||||
this.narrator.stop();
|
||||
} else {
|
||||
this._updateSpeechControls(true);
|
||||
let options = { rate: this.rate, voice: this.voice };
|
||||
this.narrator.start(options).then(() => {
|
||||
this._updateSpeechControls(false);
|
||||
}, err => {
|
||||
Cu.reportError(`Narrate failed: ${err}.`);
|
||||
this._updateSpeechControls(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_updateSpeechControls: function(speaking) {
|
||||
let dropdown = this._doc.getElementById("narrate-dropdown");
|
||||
_updateSpeechControls(speaking) {
|
||||
let dropdown = this._doc.querySelector(".narrate-dropdown");
|
||||
dropdown.classList.toggle("keep-open", speaking);
|
||||
dropdown.classList.toggle("speaking", speaking);
|
||||
|
||||
let startStopButton = this._doc.getElementById("narrate-start-stop");
|
||||
let startStopButton = this._doc.querySelector(".narrate-start-stop");
|
||||
startStopButton.title =
|
||||
gStrings.GetStringFromName(speaking ? "stop" : "start");
|
||||
|
||||
this._doc.getElementById("narrate-skip-previous").disabled = !speaking;
|
||||
this._doc.getElementById("narrate-skip-next").disabled = !speaking;
|
||||
this._doc.querySelector(".narrate-skip-previous").disabled = !speaking;
|
||||
this._doc.querySelector(".narrate-skip-next").disabled = !speaking;
|
||||
|
||||
if (speaking) {
|
||||
TelemetryStopwatch.start("NARRATE_CONTENT_SPEAKTIME_MS", this);
|
||||
|
|
@ -280,7 +274,7 @@ NarrateControls.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_createVoiceLabel: function(voice) {
|
||||
_createVoiceLabel(voice) {
|
||||
// This is a highly imperfect method of making human-readable labels
|
||||
// for system voices. Because each platform has a different naming scheme
|
||||
// for voices, we use a different method for each platform.
|
||||
|
|
@ -303,7 +297,7 @@ NarrateControls.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_getLanguageName: function(lang) {
|
||||
_getLanguageName(lang) {
|
||||
if (!this._langStrings) {
|
||||
this._langStrings = Services.strings.createBundle(
|
||||
"chrome://global/locale/languageNames.properties ");
|
||||
|
|
@ -317,7 +311,7 @@ NarrateControls.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_convertRate: function(rate) {
|
||||
_convertRate(rate) {
|
||||
// We need to convert a relative percentage value to a fraction rate value.
|
||||
// eg. -100 is half the speed, 100 is twice the speed in percentage,
|
||||
// 0.5 is half the speed and 2 is twice the speed in fractions.
|
||||
|
|
@ -334,7 +328,7 @@ NarrateControls.prototype = {
|
|||
|
||||
get rate() {
|
||||
return this._convertRate(
|
||||
this._doc.getElementById("narrate-rate-input").value);
|
||||
this._doc.querySelector(".narrate-rate-input").value);
|
||||
},
|
||||
|
||||
get voice() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue