1 | /** JavaScript file of utility functions.
|
---|
2 | * At present contains functions for sanitising of URLs,
|
---|
3 | * since tomcat 8+, being more compliant with URL/URI standards, is more strict about URLs.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | Given a string consisting of a single character, returns the %hex (%XX)
|
---|
8 | https://www.w3resource.com/javascript-exercises/javascript-string-exercise-27.php
|
---|
9 | https://stackoverflow.com/questions/40100096/what-is-equivalent-php-chr-and-ord-functions-in-javascript
|
---|
10 | https://www.w3resource.com/javascript-exercises/javascript-string-exercise-27.php
|
---|
11 | */
|
---|
12 | function urlEncodeChar(single_char_string) {
|
---|
13 | /*var hex = Number(single_char_string.charCodeAt(0)).toString(16);
|
---|
14 | var str = "" + hex;
|
---|
15 | str = "%" + str.toUpperCase();
|
---|
16 | return str;
|
---|
17 | */
|
---|
18 |
|
---|
19 | var hex = "%" + Number(single_char_string.charCodeAt(0)).toString(16).toUpperCase();
|
---|
20 | return hex;
|
---|
21 | }
|
---|
22 |
|
---|
23 | /*
|
---|
24 | Tomcat 8 appears to be stricter in requiring unsafe and reserved chars
|
---|
25 | in URLs to be escaped with URL encoding
|
---|
26 | See section "Character Encoding Chart of
|
---|
27 | https://perishablepress.com/stop-using-unsafe-characters-in-urls/
|
---|
28 | Reserved chars:
|
---|
29 | ; / ? : @ = &
|
---|
30 | -----> %3B %2F %3F %3A %40 %3D %26
|
---|
31 | [Now also reserved, but no special meaning yet in URLs (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent)
|
---|
32 | and not required to be enforced yet, so we're aren't at present dealing with these:
|
---|
33 | ! ' ( ) *
|
---|
34 | ]
|
---|
35 | Unsafe chars:
|
---|
36 | " < > # % { } | \ ^ ~ [ ] ` and SPACE/BLANK
|
---|
37 | ----> %22 %3C %3E %23 %25 %7B %7D %7C %5C %5E ~ %5B %5D %60 and %20
|
---|
38 | But the above conflicts with the reserved vs unreserved listings at
|
---|
39 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
|
---|
40 | Possibly more info: https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid
|
---|
41 |
|
---|
42 | And the bottom of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
---|
43 | lists additional characters that have been reserved since and which need encoding when in a URL component.
|
---|
44 |
|
---|
45 | Javascript already provides functions encodeURI() and encodeURIComponent(), see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
|
---|
46 | However, the set of chars they deal with only partially overlap with the set of chars that need encoding as per the RFC3986 for URIs and RFC1738 for URLs discussed at
|
---|
47 | https://perishablepress.com/stop-using-unsafe-characters-in-urls/
|
---|
48 | We want to handle all the characters listed as unsafe and reserved at https://perishablepress.com/stop-using-unsafe-characters-in-urls/
|
---|
49 | so we define and use our own conceptually equivalent methods for both existing JavaScript methods:
|
---|
50 | - makeSafeURL() for Javascript's encodeURI() to make sure all unsafe characters in URLs are escaped by being URL encoded
|
---|
51 | - and makeSafeURLComponent() for JavaScript's encodeURIComponent to additionally make sure all reserved characters in a URL portion are escaped by being URL encoded too
|
---|
52 |
|
---|
53 | Function makeSafeURL() is passed a string that represents a URL and therefore only deals with characters that are unsafe in a URL and which therefore require escaping.
|
---|
54 | Function makeSafeURLComponent() deals with portions of a URL that when decoded need not represent a URL at all, for example data like inline templates passed in as a
|
---|
55 | URL query string's parameter values. As such makeSafeURLComponent() should escape both unsafe URL characters and characters that are reserved in URLs since reserved
|
---|
56 | characters in the query string part (as query param values representing data) may take on a different meaning from their reserved meaning in a URL context.
|
---|
57 | */
|
---|
58 |
|
---|
59 | /* URL encodes both
|
---|
60 | - UNSAFE characters to make URL safe, by calling makeSafeURL()
|
---|
61 | - and RESERVED characters (characters that have reserved meanings within a URL) to make URL valid, since the url component parameter could use reserved characters
|
---|
62 | in a non-URL sense. For example, the inline template (ilt) parameter value of a URL could use '=' and '&' signs where these would have XSLT rather than URL meanings.
|
---|
63 |
|
---|
64 | See end of https://www.w3schools.com/jsref/jsref_replace.asp to use a callback passing each captured element of a regex in str.replace()
|
---|
65 | */
|
---|
66 | function makeURLComponentSafe(url_part, encode_percentages) {
|
---|
67 | // https://stackoverflow.com/questions/12797118/how-can-i-declare-optional-function-parameters-in-javascript
|
---|
68 | encode_percentages = encode_percentages || 1; // this method forces the URL-encoding of any % in url_part, e.g. do this for inline-templates that haven't ever been encoded
|
---|
69 |
|
---|
70 | var url_encoded = makeURLSafe(url_part, encode_percentages);
|
---|
71 | //return url_encoded.replace(/;/g, "%3B").replace(/\//g, "%2F").replace(/\?/g, "%3F").replace(/\:/g, "%3A").replace(/\@/g, "%40").replace(/=/g, "%3D").replace(/\&/g,"%26");
|
---|
72 | url_encoded = url_encoded.replace(/[\;\/\?\:\@\=\&]/g, function(s) {
|
---|
73 | return urlEncodeChar(s);
|
---|
74 | });
|
---|
75 | return url_encoded;
|
---|
76 | }
|
---|
77 |
|
---|
78 | /*
|
---|
79 | URL encode UNSAFE characters to make URL passed in safe.
|
---|
80 | Set encode_percentages to 1 (true) if you don't want % signs encoded: you'd do so if the url is already partly URL encoded.
|
---|
81 | */
|
---|
82 | function makeURLSafe(url, encode_percentages) {
|
---|
83 | encode_percentages = encode_percentages || 0; // https://stackoverflow.com/questions/12797118/how-can-i-declare-optional-function-parameters-in-javascript
|
---|
84 |
|
---|
85 | var url_encoded = url;
|
---|
86 | if(encode_percentages) { url_encoded = url_encoded.replace(/\%/g,"%25"); } // encode % first
|
---|
87 | //url_encoded = url_encoded.replace(/ /g, "%20").replace(/\"/g,"%22").replace(/\</g,"%3C").replace(/\>/g,"%3E").replace(/\#/g,"%23").replace(/\{/g,"%7B").replace(/\}/g,"%7D");
|
---|
88 | //url_encoded = url_encoded.replace(/\|/g,"%7C").replace(/\\/g,"%5C").replace(/\^/g,"%5E").replace(/\[/g,"%5B").replace(/\]/g,"%5D").replace(/\`/g,"%60");
|
---|
89 | // Should we handle ~, but then what is its URL encoded value? Because https://meyerweb.com/eric/tools/dencoder/ URLencodes ~ to ~.
|
---|
90 | //return url_encoded;
|
---|
91 | url_encoded = url_encoded.replace(/[\ \"\<\>\#\{\}\|\\^\~\[\]\`]/g, function(s) {
|
---|
92 | return urlEncodeChar(s);
|
---|
93 | });
|
---|
94 | return url_encoded;
|
---|
95 | }
|
---|
96 |
|
---|
97 | /***************
|
---|
98 | * MENU SCRIPTS *
|
---|
99 | ***************/
|
---|
100 | function moveScroller() {
|
---|
101 | var move = function() {
|
---|
102 | var editbar = $("#editBar");
|
---|
103 | var st = $(window).scrollTop();
|
---|
104 | var fa = $("#float-anchor").offset().top;
|
---|
105 | if(st > fa) {
|
---|
106 |
|
---|
107 | editbar.css({
|
---|
108 | position: "fixed",
|
---|
109 | top: "0px",
|
---|
110 | width: editbar.data("width"),
|
---|
111 | //width: "30%"
|
---|
112 | });
|
---|
113 | } else {
|
---|
114 | editbar.data("width", editbar.css("width"));
|
---|
115 | editbar.css({
|
---|
116 | position: "relative",
|
---|
117 | top: "",
|
---|
118 | width: ""
|
---|
119 | });
|
---|
120 | }
|
---|
121 | };
|
---|
122 | $(window).scroll(move);
|
---|
123 | move();
|
---|
124 | }
|
---|
125 |
|
---|
126 |
|
---|
127 | function floatMenu(enabled)
|
---|
128 | {
|
---|
129 | var menu = $(".tableOfContentsContainer");
|
---|
130 | if(enabled)
|
---|
131 | {
|
---|
132 | menu.data("position", menu.css("position"));
|
---|
133 | menu.data("width", menu.css("width"));
|
---|
134 | menu.data("right", menu.css("right"));
|
---|
135 | menu.data("top", menu.css("top"));
|
---|
136 | menu.data("max-height", menu.css("max-height"));
|
---|
137 | menu.data("overflow", menu.css("overflow"));
|
---|
138 | menu.data("z-index", menu.css("z-index"));
|
---|
139 |
|
---|
140 | menu.css("position", "fixed");
|
---|
141 | menu.css("width", "300px");
|
---|
142 | menu.css("right", "0px");
|
---|
143 | menu.css("top", "100px");
|
---|
144 | menu.css("max-height", "600px");
|
---|
145 | menu.css("overflow", "auto");
|
---|
146 | menu.css("z-index", "200");
|
---|
147 |
|
---|
148 | $("#unfloatTOCButton").show();
|
---|
149 | }
|
---|
150 | else
|
---|
151 | {
|
---|
152 | menu.css("position", menu.data("position"));
|
---|
153 | menu.css("width", menu.data("width"));
|
---|
154 | menu.css("right", menu.data("right"));
|
---|
155 | menu.css("top", menu.data("top"));
|
---|
156 | menu.css("max-height", menu.data("max-height"));
|
---|
157 | menu.css("overflow", menu.data("overflow"));
|
---|
158 | menu.css("z-index", menu.data("z-index"));
|
---|
159 |
|
---|
160 | $("#unfloatTOCButton").hide();
|
---|
161 | $("#floatTOCToggle").prop("checked", false);
|
---|
162 | }
|
---|
163 |
|
---|
164 | var url = gs.xsltParams.library_name + "?a=d&ftoc=" + (enabled ? "1" : "0") + "&c=" + gs.cgiParams.c;
|
---|
165 |
|
---|
166 | $.ajax(url);
|
---|
167 | }
|
---|
168 |
|
---|
169 | // TK Label Scripts
|
---|
170 |
|
---|
171 | var tkMetadataSetStatus = "needs-to-be-loaded";
|
---|
172 | var tkMetadataElements = null;
|
---|
173 |
|
---|
174 |
|
---|
175 | function addTKLabelToImage(labelName, definition, name, comment) {
|
---|
176 | // lists of tkLabels and their corresponding codes, in order
|
---|
177 | let tkLabels = ["Attribution","Clan","Family","MultipleCommunities","CommunityVoice","Creative","Verified","NonVerified","Seasonal","WomenGeneral","MenGeneral",
|
---|
178 | "MenRestricted","WomenRestricted","CulturallySensitive","SecretSacred","OpenToCommercialization","NonCommercial","CommunityUseOnly","Outreach","OpenToCollaboration"];
|
---|
179 | let tkCodes = ["tk_a","tk_cl","tk_f","tk_mc","tk_cv","tk_cr","tk_v","tk_nv","tk_s","tk_wg","tk_mg","tk_mr","tk_wr","tk_cs","tk_ss","tk_oc","tk_nc","tk_co","tk_o","tk_cb"];
|
---|
180 | for (let i = 0; i < tkLabels.length; i++) {
|
---|
181 | if (labelName == tkLabels[i]) {
|
---|
182 | let labeldiv = document.querySelectorAll(".tklabels img");
|
---|
183 | for (image of labeldiv) {
|
---|
184 | let labelCode = image.src.substr(image.src.lastIndexOf("/") + 1).replace(".png", ""); // get tk label code from image file name
|
---|
185 | if (labelCode == tkCodes[i]) {
|
---|
186 | image.title = "TK " + name + ": " + definition + " Click for more details."; // set tooltip
|
---|
187 | if (image.parentElement.parentElement.parentElement.classList[0] != "tocSectionTitle") { // disable onclick event in favourites section
|
---|
188 | image.addEventListener("click", function(e) {
|
---|
189 | let currPopup = document.getElementsByClassName("tkPopup")[0];
|
---|
190 | if (currPopup == undefined || (currPopup != undefined && currPopup.id != labelCode)) {
|
---|
191 | let popup = document.createElement("div");
|
---|
192 | popup.classList.add("tkPopup");
|
---|
193 | popup.id = labelCode;
|
---|
194 | let popupText = document.createElement("span");
|
---|
195 | let heading = "<h1>Traditional Knowledge Label:<br><h2>" + name + "</h2></h1>";
|
---|
196 | let moreInformation = "<br> For more information about TK Labels, ";
|
---|
197 | let link = document.createElement("a");
|
---|
198 | link.innerHTML = "click here.";
|
---|
199 | link.href = "https://localcontexts.org/labels/traditional-knowledge-labels/";
|
---|
200 | link.target = "_blank";
|
---|
201 | popupText.innerHTML = heading + comment + moreInformation;
|
---|
202 | popupText.appendChild(link);
|
---|
203 | let closeButton = document.createElement("span");
|
---|
204 | closeButton.innerHTML = "×";
|
---|
205 | closeButton.id = "tkCloseButton";
|
---|
206 | closeButton.title = "Click to close window."
|
---|
207 | closeButton.addEventListener("click", function(e) {
|
---|
208 | closeButton.parentElement.remove();
|
---|
209 | });
|
---|
210 | popup.appendChild(closeButton);
|
---|
211 | popup.appendChild(popupText);
|
---|
212 | e.target.parentElement.appendChild(popup);
|
---|
213 | }
|
---|
214 | if (currPopup) currPopup.remove(); // remove existing popup div
|
---|
215 | });
|
---|
216 | }
|
---|
217 | }
|
---|
218 | }
|
---|
219 | }
|
---|
220 | }
|
---|
221 | }
|
---|
222 |
|
---|
223 | function addTKLabelsToImages(lang) {
|
---|
224 | if (tkMetadataElements == null) {
|
---|
225 | console.error("ajax call not yet loaded tk label metadata set");
|
---|
226 | } else {
|
---|
227 | for (label of tkMetadataElements) { // for each tklabel element in tk.mds
|
---|
228 | let tkLabelName = label.attributes.name.value; // Element name=""
|
---|
229 | let attributes = label.querySelectorAll("[code=" + lang + "] Attribute"); // gets attributes for selected language
|
---|
230 | let tkName = attributes[0].textContent; // name="label"
|
---|
231 | let tkDefinition = attributes[1].textContent; // name="definition"
|
---|
232 | let tkComment = attributes[2].textContent; // name="comment"
|
---|
233 | addTKLabelToImage(tkLabelName, tkDefinition, tkName, tkComment);
|
---|
234 | }
|
---|
235 | }
|
---|
236 | }
|
---|
237 |
|
---|
238 | function loadTKMetadataSetOld(lang) {
|
---|
239 | tkMetadataSetStatus = "loading";
|
---|
240 | $.ajax({
|
---|
241 | url: gs.variables["tkMetadataURL"],
|
---|
242 | success: function(xml) {
|
---|
243 | tkMetadataSetStatus = "loaded";
|
---|
244 | let parser = new DOMParser();
|
---|
245 | let tkmds = parser.parseFromString(xml, "text/xml");
|
---|
246 | tkMetadataElements = tkmds.querySelectorAll("Element");
|
---|
247 | if (document.readyState === "complete") {
|
---|
248 | addTKLabelsToImages(lang);
|
---|
249 | } else {
|
---|
250 | window.onload = function() {
|
---|
251 | addTKLabelsToImages(lang);
|
---|
252 | }
|
---|
253 | }
|
---|
254 | },
|
---|
255 | error: function() {
|
---|
256 | tkMetadataSetStatus = "no-metadata-set-for-this-collection";
|
---|
257 | console.log("No TK Label Metadata-set found for this collection");
|
---|
258 | }
|
---|
259 | });
|
---|
260 | };
|
---|
261 | function loadTKMetadataSet(lang, type) {
|
---|
262 | if (gs.variables["tkMetadataURL_"+type] == undefined) {
|
---|
263 | console.error("tkMetadataURL_"+type+" variable is not defined, can't load TK Metadata Set");
|
---|
264 | tkMetadataSetStatus = "no-metadata-set-for-this-"+type;
|
---|
265 | return;
|
---|
266 | }
|
---|
267 | tkMetadataSetStatus = "loading";
|
---|
268 | $.ajax({
|
---|
269 | url: gs.variables["tkMetadataURL_"+type],
|
---|
270 | async: false,
|
---|
271 | success: function(xml) {
|
---|
272 | tkMetadataSetStatus = "loaded";
|
---|
273 | let parser = new DOMParser();
|
---|
274 | let tkmds = parser.parseFromString(xml, "text/xml");
|
---|
275 | tkMetadataElements = tkmds.querySelectorAll("Element");
|
---|
276 | if (document.readyState === "complete") {
|
---|
277 | addTKLabelsToImages(lang);
|
---|
278 | } else {
|
---|
279 | window.onload = function() {
|
---|
280 | addTKLabelsToImages(lang);
|
---|
281 | }
|
---|
282 | }
|
---|
283 | },
|
---|
284 | error: function() {
|
---|
285 | tkMetadataSetStatus = "no-metadata-set-for-this-"+type;
|
---|
286 | console.log("No TK Label Metadata-set found for this "+type);
|
---|
287 | }
|
---|
288 | });
|
---|
289 | };
|
---|
290 |
|
---|
291 | // Audio Scripts for Enriched Playback
|
---|
292 |
|
---|
293 | function loadAudio(audio, sectionData) {
|
---|
294 | var speakerObjects = [];
|
---|
295 | var uniqueSpeakers;
|
---|
296 | const inputFile = sectionData;
|
---|
297 | var itemType;
|
---|
298 |
|
---|
299 | // var accentColour = "#4CA72D";
|
---|
300 | var accentColour = "#F8C537";
|
---|
301 | var regionTransparency = "59";
|
---|
302 |
|
---|
303 | var waveformContainer = document.getElementById("waveform");
|
---|
304 |
|
---|
305 | var wavesurfer = WaveSurfer.create({ // wavesurfer options
|
---|
306 | container: waveformContainer,
|
---|
307 | backend: "MediaElement",
|
---|
308 | backgroundColor: "rgb(54, 73, 78)",
|
---|
309 | waveColor: "white",
|
---|
310 | progressColor: accentColour,
|
---|
311 | barWidth: 2,
|
---|
312 | barHeight: 1.2,
|
---|
313 | barGap: 2,
|
---|
314 | barRadius: 1,
|
---|
315 | cursorColor: 'black',
|
---|
316 | plugins: [
|
---|
317 | WaveSurfer.regions.create(),
|
---|
318 | WaveSurfer.timeline.create({
|
---|
319 | container: "#wave-timeline",
|
---|
320 | secondaryColor: "white",
|
---|
321 | secondaryFontColor: "white",
|
---|
322 | notchPercentHeight: "0",
|
---|
323 | fontSize: "12"
|
---|
324 | }),
|
---|
325 | WaveSurfer.cursor.create({
|
---|
326 | showTime: true,
|
---|
327 | opacity: 1,
|
---|
328 | customShowTimeStyle: {
|
---|
329 | 'background-color': '#000',
|
---|
330 | color: '#fff',
|
---|
331 | padding: '0.2em',
|
---|
332 | 'font-size': '12px'
|
---|
333 | },
|
---|
334 | formatTimeCallback: (num) => { return minutize(num); }
|
---|
335 | }),
|
---|
336 | ],
|
---|
337 | });
|
---|
338 |
|
---|
339 | wavesurfer.load(audio);
|
---|
340 |
|
---|
341 | // wavesurfer events
|
---|
342 |
|
---|
343 | wavesurfer.on('region-click', function(region, e) { // play region audio on click
|
---|
344 | e.stopPropagation();
|
---|
345 | handleRegionColours(region, true);
|
---|
346 | wavesurfer.play(region.start); // plays from start of region
|
---|
347 | // region.play(); // plays region only
|
---|
348 | });
|
---|
349 |
|
---|
350 | wavesurfer.on('region-mouseenter', function(region) { handleRegionColours(region, true); });
|
---|
351 | wavesurfer.on('region-mouseleave', function(region) { if (!(wavesurfer.getCurrentTime() <= region.end && wavesurfer.getCurrentTime() >= region.start)) handleRegionColours(region, false); });
|
---|
352 | wavesurfer.on('region-in', function(region) {
|
---|
353 | handleRegionColours(region, true);
|
---|
354 | if (itemType == "chapter") {
|
---|
355 | document.getElementById("chapter" + region.id.replace("region", "")).scrollIntoView({
|
---|
356 | behavior: "smooth",
|
---|
357 | block: "nearest"
|
---|
358 | });
|
---|
359 | }
|
---|
360 | });
|
---|
361 | wavesurfer.on('region-out', function(region) { handleRegionColours(region, false); });
|
---|
362 |
|
---|
363 | var loader = document.createElement("span"); // loading audio element
|
---|
364 | loader.innerHTML = "Loading audio";
|
---|
365 | loader.id = "waveform-loader";
|
---|
366 | document.querySelector("#waveform wave").prepend(loader);
|
---|
367 |
|
---|
368 | wavesurfer.on('waveform-ready', function() { // retrieve regions once waveforms have loaded
|
---|
369 | if (inputFile.endsWith("csv")) { // diarization if csv
|
---|
370 | itemType = "chapter";
|
---|
371 | loadCSVFile(inputFile, ["speaker", "start", "end"]);
|
---|
372 | } else if (inputFile.endsWith("json")) { // transcription if json
|
---|
373 | itemType = "word";
|
---|
374 | loadJSONFile(inputFile);
|
---|
375 | } else {
|
---|
376 | console.log("Filetype of " + inputFile + " not supported.")
|
---|
377 | }
|
---|
378 | loader.remove(); // remove load text
|
---|
379 | });
|
---|
380 |
|
---|
381 | function downloadURI(loc, name) {
|
---|
382 | var link = document.createElement("a");
|
---|
383 | link.download = name;
|
---|
384 | link.href = loc;
|
---|
385 | link.click();
|
---|
386 | }
|
---|
387 |
|
---|
388 | // toolbar elements & event handlers
|
---|
389 | var chapters = document.getElementById("chapters");
|
---|
390 | var chapterButton = document.getElementById("chapterButton");
|
---|
391 | var backButton = document.getElementById("backButton");
|
---|
392 | var playPauseButton = document.getElementById("playPauseButton");
|
---|
393 | var forwardButton = document.getElementById("forwardButton");
|
---|
394 | var downloadButton = document.getElementById("downloadButton");
|
---|
395 | var muteButton = document.getElementById("muteButton");
|
---|
396 | var zoomSlider = document.getElementById("slider");
|
---|
397 | chapters.style.height = "0px";
|
---|
398 | chapterButton.addEventListener("click", function() { toggleChapters(); });
|
---|
399 | backButton.addEventListener("click", function() { wavesurfer.skipBackward(); });
|
---|
400 | playPauseButton.addEventListener("click", function() { wavesurfer.playPause() });
|
---|
401 | forwardButton.addEventListener("click", function() { wavesurfer.skipForward(); });
|
---|
402 | // audio = /greenstone3/library/sites/localsite/collect/tiriana-audio/index/assoc/HASHa6b7.dir/Te_Kakano_CH09_B.mp3
|
---|
403 | downloadButton.addEventListener("click", function() { downloadURI(audio, audio.split(".dir/")[1]) });
|
---|
404 | muteButton.addEventListener("click", function() { wavesurfer.toggleMute(); });
|
---|
405 | zoomSlider.style["accent-color"] = accentColour;
|
---|
406 |
|
---|
407 | // path to toolbar images
|
---|
408 | var interface_bootstrap_images = "interfaces/" + gs.xsltParams.interface_name + "/images/bootstrap/";
|
---|
409 |
|
---|
410 | wavesurfer.on("play", function() { playPauseButton.src = interface_bootstrap_images + "pause.svg"; });
|
---|
411 | wavesurfer.on("pause", function() { playPauseButton.src = interface_bootstrap_images + "play.svg"; });
|
---|
412 | wavesurfer.on("mute", function(mute) {
|
---|
413 | if (mute) {
|
---|
414 | muteButton.src = interface_bootstrap_images + "mute.svg";
|
---|
415 | muteButton.style.opacity = 0.6;
|
---|
416 | }
|
---|
417 | else {
|
---|
418 | muteButton.src = interface_bootstrap_images + "unmute.svg";
|
---|
419 | muteButton.style.opacity = 1;
|
---|
420 | }
|
---|
421 | });
|
---|
422 |
|
---|
423 | zoomSlider.oninput = function () { // slider changes waveform zoom
|
---|
424 | wavesurfer.zoom(Number(this.value) / 4);
|
---|
425 | };
|
---|
426 | wavesurfer.zoom(zoomSlider.value / 4); // set default zoom point
|
---|
427 |
|
---|
428 | var toggleChapters = function() { // show & hide chapter section
|
---|
429 | if (chapters.style.height == "0px") {
|
---|
430 | chapters.style.height = "30vh";
|
---|
431 | } else {
|
---|
432 | chapters.style.height = "0px";
|
---|
433 | }
|
---|
434 | }
|
---|
435 |
|
---|
436 | function loadCSVFile(filename, manualHeader) { // based around: https://stackoverflow.com/questions/7431268/how-to-read-data-from-csv-file-using-javascript
|
---|
437 | $.ajax({
|
---|
438 | type: "GET",
|
---|
439 | url: filename,
|
---|
440 | dataType: "text",
|
---|
441 | }).then(function(data) {
|
---|
442 | var dataLines = data.split(/\r\n|\n/);
|
---|
443 | var headers;
|
---|
444 | var startIndex;
|
---|
445 | uniqueSpeakers = []; // used for obtaining unique colours
|
---|
446 | speakerObjects = []; // list of speaker items
|
---|
447 |
|
---|
448 | if (manualHeader) { // headers for columns can be provided if not existent in csv
|
---|
449 | headers = manualHeader;
|
---|
450 | startIndex = 0;
|
---|
451 | } else {
|
---|
452 | headers = dataLines[0].split(',');
|
---|
453 | startIndex = 1;
|
---|
454 | }
|
---|
455 |
|
---|
456 | for (var i = startIndex; i < dataLines.length; i++) {
|
---|
457 | var data = dataLines[i].split(',');
|
---|
458 | if (data.length == headers.length) {
|
---|
459 | var item = {};
|
---|
460 | for (var j = 0; j < headers.length; j++) {
|
---|
461 | item[headers[j]] = data[j];
|
---|
462 | if (j == 0) {
|
---|
463 | if (!uniqueSpeakers.includes(data[j])) {
|
---|
464 | uniqueSpeakers.push(data[j]);
|
---|
465 | }
|
---|
466 | }
|
---|
467 | }
|
---|
468 | speakerObjects.push(item);
|
---|
469 | }
|
---|
470 | }
|
---|
471 | populateChapters(speakerObjects);
|
---|
472 | });
|
---|
473 | }
|
---|
474 |
|
---|
475 | function populateChapters(data) { // populates chapter section and adds regions to waveform
|
---|
476 | // colorbrewer is a web tool for guidance in choosing map colour schemes based on a variety of settings.
|
---|
477 | // this colour scheme is designed for qualitative data
|
---|
478 |
|
---|
479 | if (uniqueSpeakers.length > 8) colourbrewerset = colorbrewer.Set2[8];
|
---|
480 | else if (uniqueSpeakers.length < 3) colourbrewerset = colorbrewer.Set2[3];
|
---|
481 | else colourbrewerset = colorbrewer.Set2[uniqueSpeakers.length];
|
---|
482 |
|
---|
483 | for (var i = 0; i < data.length; i++) {
|
---|
484 | var chapter = document.createElement("div");
|
---|
485 | var speakerLetter = getLetter(data[i]);
|
---|
486 | chapter.id = "chapter" + i;
|
---|
487 | chapter.classList.add("chapter");
|
---|
488 | chapter.innerHTML = "Speaker " + speakerLetter + "<span class='speakerTime' id='" + "chapter" + i + "'>" + minutize(data[i].start) + " - " + minutize(data[i].end) + "</span>";
|
---|
489 | chapter.addEventListener("click", e => { chapterClicked(e.target.id) });
|
---|
490 | chapter.addEventListener("mouseover", e => { chapterEnter(e.target.id) });
|
---|
491 | chapter.addEventListener("mouseleave", e => { chapterLeave(e.target.id) });
|
---|
492 | chapters.appendChild(chapter);
|
---|
493 | // console.log("index: " + uniqueSpeakers.indexOf(data[i].speaker)%8);
|
---|
494 | // console.log("colour: " + colourbrewerset[uniqueSpeakers.indexOf(data[i].speaker)%8]);
|
---|
495 | wavesurfer.addRegion({
|
---|
496 | id: "region" + i,
|
---|
497 | start: data[i].start,
|
---|
498 | end: data[i].end,
|
---|
499 | drag: false,
|
---|
500 | resize: false,
|
---|
501 | color: colourbrewerset[uniqueSpeakers.indexOf(data[i].speaker)%8] + regionTransparency,
|
---|
502 | });
|
---|
503 | }
|
---|
504 | }
|
---|
505 |
|
---|
506 | function loadJSONFile(filename) {
|
---|
507 | $.ajax({
|
---|
508 | type: "GET",
|
---|
509 | url: filename,
|
---|
510 | dataType: "text",
|
---|
511 | }).then(function(data){ populateWords(JSON.parse(data)) });
|
---|
512 | }
|
---|
513 |
|
---|
514 | function populateWords(data) { // populates word section and adds regions to waveform
|
---|
515 | var transcription = data.transcription;
|
---|
516 | var words = data.words;
|
---|
517 | var wordContainer = document.createElement("div");
|
---|
518 | wordContainer.id = "word-container";
|
---|
519 | for (var i = 0; i < words.length; i++) {
|
---|
520 | var word = document.createElement("span");
|
---|
521 | word.id = "word" + i;
|
---|
522 | word.classList.add("word");
|
---|
523 | word.innerHTML = transcription.split(" ")[i];
|
---|
524 | word.addEventListener("click", e => { wordClicked(data, e.target.id) });
|
---|
525 | word.addEventListener("mouseover", e => { chapterEnter(e.target.id) });
|
---|
526 | word.addEventListener("mouseleave", e => { chapterLeave(e.target.id) });
|
---|
527 | wordContainer.appendChild(word);
|
---|
528 | wavesurfer.addRegion({
|
---|
529 | id: "region" + i,
|
---|
530 | start: words[i].startTime,
|
---|
531 | end: words[i].endTime,
|
---|
532 | drag: false,
|
---|
533 | resize: false,
|
---|
534 | color: "rgba(255, 255, 255, 0.1)",
|
---|
535 | });
|
---|
536 | }
|
---|
537 | chapters.appendChild(wordContainer);
|
---|
538 | }
|
---|
539 |
|
---|
540 | var chapterClicked = function(id) { // plays audio from start of chapter
|
---|
541 | var index = id.replace("chapter", "");
|
---|
542 | var start = speakerObjects[index].start;
|
---|
543 | var end = speakerObjects[index].end;
|
---|
544 | // wavesurfer.play(start, end);
|
---|
545 | wavesurfer.play(start);
|
---|
546 | }
|
---|
547 |
|
---|
548 | function wordClicked(data, id) { // plays audio from start of word
|
---|
549 | var index = id.replace("word", "");
|
---|
550 | var start = data.words[index].startTime;
|
---|
551 | var end = data.words[index].endTime;
|
---|
552 | // wavesurfer.play(start, end);
|
---|
553 | wavesurfer.play(start);
|
---|
554 | }
|
---|
555 |
|
---|
556 | function chapterEnter(id) {
|
---|
557 | regionEnter(wavesurfer.regions.list["region" + id.replace(itemType, "")]);
|
---|
558 | }
|
---|
559 |
|
---|
560 | function chapterLeave(id) {
|
---|
561 | regionLeave(wavesurfer.regions.list["region" + id.replace(itemType, "")]);
|
---|
562 | }
|
---|
563 |
|
---|
564 | function handleRegionColours(region, highlight) { // handles region, chapter & word colours
|
---|
565 | var colour;
|
---|
566 | if (highlight) {
|
---|
567 | colour = "rgb(101, 116, 116)";
|
---|
568 | regionEnter(region);
|
---|
569 | } else {
|
---|
570 | colour = "";
|
---|
571 | regionLeave(region);
|
---|
572 | }
|
---|
573 | var regionIndex = region.id.replace("region","");
|
---|
574 | var corrItem = document.getElementById(itemType + regionIndex);
|
---|
575 | corrItem.style.backgroundColor = colour;
|
---|
576 | }
|
---|
577 |
|
---|
578 | function regionEnter(region) {
|
---|
579 | region.update({ color: "rgba(255, 255, 255, 0.35)" });
|
---|
580 | }
|
---|
581 |
|
---|
582 | function regionLeave(region) {
|
---|
583 | if (itemType == "chapter") {
|
---|
584 | if (!(wavesurfer.getCurrentTime() + 0.1 < region.end && wavesurfer.getCurrentTime() > region.start)) {
|
---|
585 | var index = region.id.replace("region", "");
|
---|
586 | region.update({ color: colourbrewerset[uniqueSpeakers.indexOf(speakerObjects[index].speaker)%8] + regionTransparency });
|
---|
587 | }
|
---|
588 | } else {
|
---|
589 | region.update({ color: "rgba(255, 255, 255, 0.1)" });
|
---|
590 | }
|
---|
591 | }
|
---|
592 |
|
---|
593 | function minutize(num) { // converts seconds to m:ss for chapters & waveform hover
|
---|
594 | var seconds = Math.round(num % 60);
|
---|
595 | if (seconds.toString().length == 1) seconds = "0" + seconds;
|
---|
596 | return Math.floor(num / 60) + ":" + seconds;
|
---|
597 | }
|
---|
598 |
|
---|
599 | function getLetter(val) {
|
---|
600 | var speakerNum = parseInt(val.speaker.replace("SPEAKER_",""));
|
---|
601 | return String.fromCharCode(65 + speakerNum); // 'A' == UTF-16 65
|
---|
602 | }
|
---|
603 | }
|
---|
604 |
|
---|
605 | function formatAudioDuration(duration) {
|
---|
606 | console.log(duration);
|
---|
607 | var [hrs, mins, secs, ms] = duration.replace(".", ":").split(":");
|
---|
608 | return hrs + ":" + mins + ":" + secs;
|
---|
609 | }
|
---|