Changeset 37031
- Timestamp:
- 2022-12-15T15:43:21+13:00 (6 months ago)
- Location:
- main/trunk/greenstone3/web/interfaces/default
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/greenstone3/web/interfaces/default/js/utility_scripts.js
r36906 r37031 11 11 */ 12 12 function urlEncodeChar(single_char_string) { 13 /* varhex = Number(single_char_string.charCodeAt(0)).toString(16);13 /*let hex = Number(single_char_string.charCodeAt(0)).toString(16); 14 14 var str = "" + hex; 15 15 str = "%" + str.toUpperCase(); … … 292 292 293 293 function loadAudio(audio, sectionData) { 294 var speakerObjects = []; 295 var uniqueSpeakers; 294 let editMode = false; 295 let currentRegion = {speaker: '', start: '', end: ''}; 296 let currentRegions = []; 297 298 // let speakerObjects = []; 299 // let tempSpeakerObjects = []; 300 // let uniqueSpeakers; 296 301 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"); 302 let itemType; 303 304 let dualMode = false; 305 let secondaryLoaded = false; 306 307 let editsMade = false; 308 let undoLevel = 0; 309 let undoStates = []; 310 let prevUndoState = ""; 311 let tempZoomSave = 0; 312 let isZooming; 313 314 let accentColour = "#66d640"; 315 // let accentColour = "#F8C537"; 316 let regionTransparency = "50"; 317 318 let waveformContainer = document.getElementById("waveform"); 304 319 305 varwavesurfer = WaveSurfer.create({ // wavesurfer options320 let wavesurfer = WaveSurfer.create({ // wavesurfer options 306 321 container: waveformContainer, 307 322 backend: "MediaElement", 308 backgroundColor: "rgb(54, 73, 78)", 323 backgroundColor: "rgb(40, 54, 58)", 324 // backgroundColor: "rgb(24, 36, 39)", 309 325 waveColor: "white", 310 326 progressColor: accentColour, 311 barWidth: 2, 327 // progressColor: "grey", 328 // barWidth: 2, 312 329 barHeight: 1.2, 313 barGap: 2,314 barRadius: 1,330 // barGap: 2, 331 // barRadius: 1, 315 332 cursorColor: 'black', 333 cursorWidth: 2, 334 normalize: true, // normalizes by maximum peak 316 335 plugins: [ 317 WaveSurfer.regions.create(), 336 WaveSurfer.regions.create({ 337 // formatTimeCallback: function(a, b) { 338 // return "TEST"; 339 // } 340 }), 318 341 WaveSurfer.timeline.create({ 319 342 container: "#wave-timeline", … … 329 352 'background-color': '#000', 330 353 color: '#fff', 331 padding: '0.2 em',354 padding: '0.25rem', 332 355 'font-size': '12px' 333 356 }, … … 341 364 // wavesurfer events 342 365 343 wavesurfer.on('region-click', function(region, e) { // play region audio on click 366 wavesurfer.on('region-click', handleRegionClick); 367 368 function handleRegionClick(region, e) { 344 369 e.stopPropagation(); 345 handleRegionColours(region, true); 346 wavesurfer.play(region.start); // plays from start of region 347 // region.play(); // plays region only 370 if (!editMode) { // play region audio on click 371 wavesurfer.play(region.start); // plays from start of region 372 } else { // select / deselect current region 373 if (region.element.classList.contains("region-top")) caretClicked("primary-caret"); 374 else if (region.element.classList.contains("region-bottom")) caretClicked("secondary-caret"); 375 prevUndoState = ""; 376 377 if (!e.ctrlKey && !e.shiftKey) { 378 currentRegions = []; 379 if (getCurrentRegionIndex() != -1 && isCurrentRegion(region)) { 380 removeCurrentRegion(); // deselect current region on click 381 } else { 382 currentRegion = region; 383 currentRegion.speaker = currentRegion.attributes.label; 384 region.play(); // start and stop to move play cursor to beginning of region 385 wavesurfer.playPause(); 386 } 387 } else if (e.ctrlKey) { // control was held during click 388 if (currentRegions.length == 0 && isCurrentRegion(region)) { 389 removeCurrentRegion(); 390 } else if (getCurrentRegionIndex() != -1 && isInCurrentRegions(region)) { 391 const removeIndex = getIndexInCurrentRegions(region); 392 if (removeIndex != -1) currentRegions.splice(removeIndex, 1); 393 if (currentRegions.length > 0 && isCurrentRegion(region)) { // change current region if removed 394 currentRegion = currentRegions[0]; 395 // currentRegions = []; 396 } 397 } else { 398 if (currentRegions.length < 1) currentRegions.push(currentRegion); 399 if (getIndexInCurrentRegions(region) == -1) currentRegions.push(region); // add if it doesn't already exist 400 currentRegion = region; 401 currentRegion.speaker = currentRegion.attributes.label; 402 region.play(); 403 wavesurfer.playPause(); 404 } 405 if (currentRegions.length == 1) currentRegions = []; // clear selected regions if there is only one 406 } else if (e.shiftKey) { // shift was held during click 407 if (getCurrentRegionIndex() != -1 && getIndexOfRegion(region) != -1) { 408 if (currentRegions && currentRegions.length > 0) { 409 if (Math.max(...getCurrentRegionsIndexes()) < getIndexOfRegion(region)) { // shifting forwards / down 410 currentRegions = currSpeakerSet.tempSpeakerObjects.slice(Math.min(...getCurrentRegionsIndexes()), getIndexOfRegion(region)+1); 411 } else { // shifting backwards / up 412 currentRegions = currSpeakerSet.tempSpeakerObjects.slice(getIndexOfRegion(region), Math.max(...getCurrentRegionsIndexes())+1); 413 } 414 } else { 415 if (getCurrentRegionIndex() < getIndexOfRegion(region)) { // shifting forwards / down 416 currentRegions = currSpeakerSet.tempSpeakerObjects.slice(getCurrentRegionIndex(), getIndexOfRegion(region)+1); 417 } else { // shifting backwards / up 418 currentRegions = currSpeakerSet.tempSpeakerObjects.slice(getIndexOfRegion(region), getCurrentRegionIndex()+1); 419 } 420 } 421 } 422 } 423 if (speakerCheckbox.checked) { currentRegions = getRegionsWithSpeaker(currentRegion.speaker) } 424 reloadRegionsAndChapters(); 425 } 426 } 427 428 function getIndexInCurrentRegions(region) { 429 for (const reg of currentRegions) { 430 const regSpeaker = reg.attributes ? reg.attributes.label : reg.speaker; 431 if (reg.start == region.start && reg.end == region.end && regSpeaker == region.attributes.label) { 432 return currentRegions.indexOf(reg); 433 } 434 } 435 return -1; 436 } 437 438 function getIndexOfRegion(region) { 439 for (const reg of currSpeakerSet.tempSpeakerObjects) { 440 if (reg.start == region.start && reg.end == region.end && reg.speaker == region.attributes.label) { 441 return currSpeakerSet.tempSpeakerObjects.indexOf(reg); 442 } 443 } 444 return -1; 445 } 446 447 wavesurfer.on('region-mouseenter', function(region) { // region hover effects 448 handleRegionColours(region, true); 449 hoverSpeaker.innerHTML = region.attributes.label; 450 hoverSpeaker.style.marginLeft = parseInt(region.element.style.left.slice(0, -2)) - waveform.scrollLeft + "px"; 451 if (!isInCurrentRegions(region)) { 452 removeRegionBounds(); 453 drawRegionBounds(region, waveform.scrollLeft, "black"); 454 } 455 if (isCurrentRegion(region)) drawRegionBounds(region, waveform.scrollLeft); 348 456 }); 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); }); 457 wavesurfer.on('region-mouseleave', function(region) { 458 if (!(wavesurfer.getCurrentTime() <= region.end && wavesurfer.getCurrentTime() >= region.start)) handleRegionColours(region, false); 459 removeRegionBounds(); 460 if (currentRegion.speaker && getCurrentRegionIndex() != -1) { 461 hoverSpeaker.innerHTML = currentRegion.speaker; 462 hoverSpeaker.style.marginLeft = parseInt(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region.element.style.left.slice(0, -2)) - waveform.scrollLeft + "px"; 463 let currIndexes = getCurrentRegionsIndexes(); 464 for (let i = 0; i < currIndexes.length; i++) { 465 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[currIndexes[i]].region, waveform.scrollLeft, "black"); 466 } 467 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region, waveform.scrollLeft); 468 } 469 if (!currentRegion.speaker) hoverSpeaker.innerHTML = ""; 470 }); 352 471 wavesurfer.on('region-in', function(region) { 353 472 handleRegionColours(region, true); … … 359 478 } 360 479 }); 361 wavesurfer.on('region-out', function(region) { handleRegionColours(region, false); }); 362 363 var loader = document.createElement("span"); // loading audio element 480 wavesurfer.on('region-out', function(region) { handleRegionColours(region, false) }); 481 wavesurfer.on('region-update-end', handleRegionEdit); // end of click-drag event 482 // wavesurfer.on('region-update-end', (region, e) => { handleRegionEdit(region, e)} ); // end of click-drag event 483 484 let loader = document.createElement("span"); // loading audio element 364 485 loader.innerHTML = "Loading audio"; 365 486 loader.id = "waveform-loader"; … … 369 490 if (inputFile.endsWith("csv")) { // diarization if csv 370 491 itemType = "chapter"; 371 loadCSVFile(inputFile, ["speaker", "start", "end"] );492 loadCSVFile(inputFile, ["speaker", "start", "end"], primarySet); 372 493 } else if (inputFile.endsWith("json")) { // transcription if json 373 494 itemType = "word"; … … 377 498 } 378 499 loader.remove(); // remove load text 500 chapters.style.cursor = "pointer"; 501 waveform.className = "audio-scroll"; 379 502 }); 380 503 381 504 function downloadURI(loc, name) { 382 varlink = document.createElement("a");505 let link = document.createElement("a"); 383 506 link.download = name; 384 507 link.href = loc; … … 387 510 388 511 // 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"); 512 const audioContainer = document.getElementById("audioContainer"); 513 const dualModeCheckbox = document.getElementById("dual-mode-checkbox"); 514 const waveform = document.getElementsByTagName("wave")[0]; 515 const primaryCaret = document.getElementById("primary-caret"); 516 const secondaryCaret = document.getElementById("secondary-caret"); 517 const chapters = document.getElementById("chapters"); 518 const editPanel = document.getElementById("edit-panel"); 519 const chapterButton = document.getElementById("chapterButton"); 520 const zoomOutButton = document.getElementById("zoomOutButton"); 521 const zoomSlider = document.getElementById("zoom-slider"); 522 const zoomInButton = document.getElementById("zoomInButton"); 523 const backButton = document.getElementById("backButton"); 524 const playPauseButton = document.getElementById("playPauseButton"); 525 const forwardButton = document.getElementById("forwardButton"); 526 const editButton = document.getElementById("editButton"); 527 const downloadButton = document.getElementById("downloadButton"); 528 const muteButton = document.getElementById("muteButton"); 529 const volumeSlider = document.getElementById("volume-slider"); 530 const fullscreenButton = document.getElementById("fullscreenButton"); 531 const speakerCheckbox = document.getElementById("change-all-checkbox"); 532 const changeAllLabel = document.getElementById("change-all-label"); 533 const speakerInput = document.getElementById("speaker-input"); 534 const startTimeInput = document.getElementById("start-time-input"); 535 const endTimeInput = document.getElementById("end-time-input"); 536 const removeButton = document.getElementById("remove-button"); 537 const createButton = document.getElementById("create-button"); 538 const discardButton = document.getElementById("discard-button"); 539 const undoButton = document.getElementById("undo-button"); 540 const redoButton = document.getElementById("redo-button"); 541 const saveButton = document.getElementById("save-button"); 542 const hoverSpeaker = document.getElementById("hover-speaker"); 543 544 audioContainer.addEventListener('fullscreenchange', (e) => { fullscreenChanged() }); 545 dualModeCheckbox.addEventListener("change", dualModeChanged); 546 waveform.addEventListener('scroll', (e) => { waveformScrolled() }) 547 primaryCaret.addEventListener("click", (e) => caretClicked(e.target.id)); 548 secondaryCaret.addEventListener("click", (e) => caretClicked(e.target.id)); 397 549 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(); }); 550 editPanel.style.height = "0px"; 551 chapterButton.addEventListener("click", () => { toggleChapters() }); 552 zoomOutButton.addEventListener("click", () => { zoomSlider.stepDown(); zoomSlider.dispatchEvent(new Event("input")) }); 553 zoomInButton.addEventListener("click", () => { zoomSlider.stepUp(); zoomSlider.dispatchEvent(new Event("input")) }); 554 backButton.addEventListener("click", () => { wavesurfer.skipBackward(); }); 555 playPauseButton.addEventListener("click", () => { wavesurfer.playPause() }); 556 forwardButton.addEventListener("click", () => { wavesurfer.skipForward(); }); 557 editButton.addEventListener("click", toggleEditMode); 558 downloadButton.addEventListener("click", () => { downloadURI(audio, audio.split(".dir/")[1]) }); 559 muteButton.addEventListener("click", () => { wavesurfer.toggleMute() }); 560 volumeSlider.style["accent-color"] = accentColour; 561 fullscreenButton.addEventListener("click", toggleFullscreen); 405 562 zoomSlider.style["accent-color"] = accentColour; 563 speakerCheckbox.addEventListener("change", speakerCheckboxChanged); 564 speakerInput.addEventListener("input", speakerChange); 565 speakerInput.addEventListener("blur", speakerInputUnfocused); 566 createButton.addEventListener("click", createNewRegion); 567 removeButton.addEventListener("click", removeRegion); 568 discardButton.addEventListener("click", discardRegionChanges); 569 undoButton.addEventListener("click", undo); 570 redoButton.addEventListener("click", redo); 571 saveButton.addEventListener("click", saveRegionChanges); 572 document.querySelectorAll('input[type=number]').forEach(e => { 573 e.onchange = (e) => { changeStartEndTime(e) }; // updates speaker objects when number input(s) are changed 574 e.onblur = () => { prevUndoState = "" }; 575 }); 576 audioContainer.addEventListener("keyup", keyPressed); 577 578 function keyPressed(e) { 579 if (e.target.tagName !== "INPUT") { 580 if (e.code === "Backspace" || e.code === "Delete") removeRegion(); 581 else if (e.code === "Space") wavesurfer.playPause(); 582 else if (e.code === "ArrowLeft") wavesurfer.skipBackward(); 583 else if (e.code === "ArrowRight") wavesurfer.skipForward(); 584 } 585 } 586 587 function dualModeChanged(e) { // on dualmode checkbox value change 588 dualMode = e.target.checked; 589 currSpeakerSet = primarySet; 590 reloadRegionsAndChapters(); 591 if (dualMode) { 592 if (!secondaryLoaded) { 593 loadCSVFile(inputFile.replace(".csv", "-2.csv"), ["speaker", "start", "end"], secondarySet); 594 secondaryLoaded = true; // ensure secondarySet doesn't get re-read > once 595 } 596 document.getElementById("caret-container").style.display = "flex"; 597 } else { 598 document.getElementById("caret-container").style.display = "none"; 599 } 600 currSpeakerSet = primarySet; 601 } 406 602 407 603 // 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"; }); 604 let interface_bootstrap_images = "interfaces/" + gs.xsltParams.interface_name + "/images/bootstrap/"; 605 606 function caretClicked(id) { 607 wavesurfer.clearRegions(); 608 // flashChapters(); 609 if (id === "primary-caret") { 610 currSpeakerSet = primarySet; 611 swapCarets(true); 612 } else if (id === "secondary-caret") { 613 currSpeakerSet = secondarySet; 614 swapCarets(false); 615 } 616 $(".region-top").remove(); 617 $(".region-bottom").remove(); 618 populateChapters(primarySet); 619 populateChapters(secondarySet); 620 } 621 622 function swapCarets(toPrimary) { 623 const currCaretIsPrimary = primaryCaret.src.includes("fill") ? true : false; 624 if ((toPrimary && !currCaretIsPrimary) || (!toPrimary && currCaretIsPrimary)) removeCurrentRegion(); // ensure currentRegion is only removed if changing speakerSet 625 if (toPrimary) { 626 primaryCaret.src = interface_bootstrap_images + "caret-right-fill.svg"; 627 secondaryCaret.src = interface_bootstrap_images + "caret-right.svg"; 628 } else { 629 primaryCaret.src = interface_bootstrap_images + "caret-right.svg"; 630 secondaryCaret.src = interface_bootstrap_images + "caret-right-fill.svg"; 631 } 632 } 633 634 wavesurfer.on("play", () => { playPauseButton.src = interface_bootstrap_images + "pause.svg"; }); 635 wavesurfer.on("pause", () => { playPauseButton.src = interface_bootstrap_images + "play.svg"; }); 412 636 wavesurfer.on("mute", function(mute) { 413 637 if (mute) { 414 638 muteButton.src = interface_bootstrap_images + "mute.svg"; 415 639 muteButton.style.opacity = 0.6; 640 volumeSlider.value = 0; 416 641 } 417 642 else { 418 643 muteButton.src = interface_bootstrap_images + "unmute.svg"; 419 644 muteButton.style.opacity = 1; 645 volumeSlider.value = 1; 420 646 } 421 647 }); 422 648 423 zoomSlider.oninput = function () { // slider changes waveform zoom 649 volumeSlider.addEventListener("input", function() { 650 wavesurfer.setVolume(this.value); 651 if (this.value == 0) { 652 muteButton.src = interface_bootstrap_images + "mute.svg"; 653 muteButton.style.opacity = 0.6; 654 } else { 655 muteButton.src = interface_bootstrap_images + "unmute.svg"; 656 muteButton.style.opacity = 1; 657 } 658 }); 659 660 zoomSlider.addEventListener("input", function() { // slider changes waveform zoom 424 661 wavesurfer.zoom(Number(this.value) / 4); 425 }; 662 if (currentRegion.speaker && getCurrentRegionIndex() != -1) { 663 hoverSpeaker.innerHTML = currentRegion.speaker; 664 hoverSpeaker.style.marginLeft = parseInt(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region.element.style.left.slice(0, -2)) - waveform.scrollLeft + "px"; 665 removeRegionBounds(); 666 let currIndexes = getCurrentRegionsIndexes(); 667 for (let i = 0; i < currIndexes.length; i++) { 668 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[currIndexes[i]].region, waveform.scrollLeft, "black"); 669 } 670 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region, waveform.scrollLeft); 671 } 672 let handles = document.getElementsByClassName("wavesurfer-handle"); 673 if (this.value < 20) { 674 for (const handle of handles) { 675 handle.style.setProperty("width", "1px", "important"); 676 } 677 } else { 678 for (const handle of handles) { 679 handle.style.setProperty("width", "3px", "important"); 680 } 681 } 682 }); 426 683 wavesurfer.zoom(zoomSlider.value / 4); // set default zoom point 427 684 428 vartoggleChapters = function() { // show & hide chapter section685 let toggleChapters = function() { // show & hide chapter section 429 686 if (chapters.style.height == "0px") { 430 687 chapters.style.height = "30vh"; … … 434 691 } 435 692 436 function loadCSVFile(filename, manualHeader) { // based around: https://stackoverflow.com/questions/7431268/how-to-read-data-from-csv-file-using-javascript 693 function SpeakerSet(isSecondary, uniqueSpeakers, speakerObjects, tempSpeakerObjects) { 694 this.isSecondary = isSecondary; 695 this.uniqueSpeakers = uniqueSpeakers; 696 this.speakerObjects = speakerObjects; 697 this.tempSpeakerObjects = tempSpeakerObjects; 698 } 699 let primarySet = new SpeakerSet(false, [], [], []); 700 let secondarySet = new SpeakerSet(true, [], [], []); 701 let currSpeakerSet = primarySet; 702 703 function loadCSVFile(filename, manualHeader, speakerSet) { // based on: https://stackoverflow.com/questions/7431268/how-to-read-data-from-csv-file-using-javascript 704 // if (speakerSet) currSpeakerSet = speakerSet; // if parameter is given, set 437 705 $.ajax({ 438 706 type: "GET", … … 440 708 dataType: "text", 441 709 }).then(function(data) { 442 vardataLines = data.split(/\r\n|\n/);443 varheaders;444 varstartIndex;445 uniqueSpeakers = []; // used for obtaining unique colours446 speaker Objects = []; // list of speaker items710 let dataLines = data.split(/\r\n|\n/); 711 let headers; 712 let startIndex; 713 speakerSet.uniqueSpeakers = []; // used for obtaining unique colours 714 speakerSet.speakerObjects = []; // list of speaker items 447 715 448 716 if (manualHeader) { // headers for columns can be provided if not existent in csv … … 454 722 } 455 723 456 for ( vari = startIndex; i < dataLines.length; i++) {457 vardata = dataLines[i].split(',');724 for (let i = startIndex; i < dataLines.length; i++) { 725 let data = dataLines[i].split(','); 458 726 if (data.length == headers.length) { 459 varitem = {};460 for ( varj = 0; j < headers.length; j++) {727 let item = {}; 728 for (let j = 0; j < headers.length; j++) { 461 729 item[headers[j]] = data[j]; 462 if (j == 0) { 463 if (!uniqueSpeakers.includes(data[j])) { 464 uniqueSpeakers.push(data[j]); 465 } 730 if (j == 0 && !speakerSet.uniqueSpeakers.includes(data[j])) { 731 speakerSet.uniqueSpeakers.push(data[j]); 466 732 } 467 733 } 468 speaker Objects.push(item);734 speakerSet.speakerObjects.push(item); 469 735 } 470 736 } 471 populateChapters(speakerObjects); 737 speakerSet.tempSpeakerObjects = cloneSpeakerObjectArray(speakerSet.speakerObjects); 738 populateChapters(speakerSet); 739 resetUndoStates(); // undo stack init 472 740 }); 473 741 } 474 742 475 743 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.744 // colorbrewer is a web tool for guidance in choosing map colour schemes based on a letiety of settings. 477 745 // this colour scheme is designed for qualitative data 478 746 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]); 747 if (data.uniqueSpeakers.length > 8) colourbrewerset = colorbrewer.Set2[8]; 748 else if (data.uniqueSpeakers.length < 3) colourbrewerset = colorbrewer.Set2[3]; 749 else colourbrewerset = colorbrewer.Set2[data.uniqueSpeakers.length]; 750 751 let dataIsSelected = false; 752 753 if ((!data.isSecondary && primaryCaret.src.includes("fill")) || (data.isSecondary && secondaryCaret.src.includes("fill"))) dataIsSelected = true; 754 if (dataIsSelected || !dualMode) chapters.innerHTML = ""; // clear chapter div for re-population 755 data.tempSpeakerObjects = sortSpeakerObjectsByStart(data.tempSpeakerObjects); // sort speakerObjects by start time 756 757 for (let i = 0; i < data.tempSpeakerObjects.length; i++) { 758 let chapter = document.createElement("div"); 759 chapter.classList.add("chapter"); 486 760 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({ 761 let speakerName = data.tempSpeakerObjects[i].speaker; 762 let speakerTime = document.createElement("span"); 763 speakerTime.classList.add("speakerTime"); 764 speakerTime.innerHTML = minutize(data.tempSpeakerObjects[i].start) + " - " + minutize(data.tempSpeakerObjects[i].end) + "s"; 765 chapter.innerHTML = speakerName; 766 chapter.appendChild(speakerTime); 767 chapter.addEventListener("click", chapterClicked); 768 chapter.addEventListener("mouseover", e => { chapterEnter(Array.from(e.target.parentElement.children).indexOf(e.target)) }); 769 chapter.addEventListener("mouseleave", e => { chapterLeave(Array.from(e.target.parentElement.children).indexOf(e.target)) }); 770 771 let selected = false; 772 let dummyRegion = { start: data.tempSpeakerObjects[i].start, end: data.tempSpeakerObjects[i].end }; 773 774 if ((dataIsSelected || !dualMode) && (isCurrentRegion(dummyRegion) || isInCurrentRegions(dummyRegion))) { 775 chapter.classList.add("selected-chapter"); 776 selected = true; 777 } 778 779 if (dataIsSelected || !dualMode) chapters.appendChild(chapter); 780 781 let associatedReg = wavesurfer.addRegion({ // create associated wavesurfer region 496 782 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, 783 start: data.tempSpeakerObjects[i].start, 784 end: data.tempSpeakerObjects[i].end, 785 drag: editMode, 786 resize: editMode, 787 attributes: { 788 label: speakerName, 789 }, 790 color: colourbrewerset[data.uniqueSpeakers.indexOf(data.tempSpeakerObjects[i].speaker)%8] + regionTransparency, 791 ...(selected) && {color: "rgba(255,50,50,0.5)"}, 502 792 }); 503 } 793 data.tempSpeakerObjects[i].region = associatedReg; 794 } 795 796 let regions = document.getElementsByTagName("region"); 797 if (dualMode) { 798 if (document.getElementsByClassName("region-top").length === 0) for (const reg of regions) reg.classList.add("region-top"); 799 else for (const rego of regions) if (!rego.classList.contains("region-top")) rego.classList.add("region-bottom"); 800 } 801 if (editMode) for (const reg of regions) reg.style.setProperty("z-index", "3", "important"); 802 else for (const reg of regions) reg.style.setProperty("z-index", "1", "important"); 504 803 } 505 804 … … 513 812 514 813 function populateWords(data) { // populates word section and adds regions to waveform 515 vartranscription = data.transcription;516 varwords = data.words;517 varwordContainer = document.createElement("div");814 let transcription = data.transcription; 815 let words = data.words; 816 let wordContainer = document.createElement("div"); 518 817 wordContainer.id = "word-container"; 519 for ( vari = 0; i < words.length; i++) {520 varword = document.createElement("span");818 for (let i = 0; i < words.length; i++) { 819 let word = document.createElement("span"); 521 820 word.id = "word" + i; 522 821 word.classList.add("word"); 523 822 word.innerHTML = transcription.split(" ")[i]; 524 823 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) });824 word.addEventListener("mouseover", e => { chapterEnter(Array.from(e.target.parentElement.children).indexOf(e.target)) }); 825 word.addEventListener("mouseleave", e => { chapterLeave(Array.from(e.target.parentElement.children).indexOf(e.target)) }); 527 826 wordContainer.appendChild(word); 528 827 wavesurfer.addRegion({ … … 538 837 } 539 838 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); 839 let chapterClicked = function(e) { // plays audio from start of chapter 840 let index = e.target.id.replace("chapter", ""); 841 let clickedRegion = currSpeakerSet.tempSpeakerObjects[index].region; 842 handleRegionClick(clickedRegion, e); 843 } 844 845 function wordClicked(data, id) { // plays audio from start of word 846 let index = id.replace("word", ""); 847 let start = data.words[index].startTime; 545 848 wavesurfer.play(start); 546 849 } 547 850 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, "")]); 851 function chapterEnter(idx) { 852 let reg = currSpeakerSet.tempSpeakerObjects[idx].region; 853 regionEnter(reg); 854 hoverSpeaker.innerHTML = reg.attributes.label; 855 hoverSpeaker.style.marginLeft = parseInt(reg.element.style.left.slice(0, -2)) - waveform.scrollLeft + "px"; 856 if (!isInCurrentRegions(reg)) { 857 removeRegionBounds(); 858 drawRegionBounds(reg, waveform.scrollLeft, "black"); 859 } 860 } 861 862 function chapterLeave(idx) { 863 regionLeave(currSpeakerSet.tempSpeakerObjects[idx].region); 864 removeRegionBounds(); 865 hoverSpeaker.innerHTML = ""; 866 if (currentRegion.speaker && getCurrentRegionIndex() != -1) { 867 hoverSpeaker.innerHTML = currentRegion.speaker; 868 hoverSpeaker.style.marginLeft = parseInt(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region.element.style.left.slice(0, -2)) - waveform.scrollLeft + "px"; 869 let currIndexes = getCurrentRegionsIndexes(); 870 for (let i = 0; i < currIndexes.length; i++) { 871 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[currIndexes[i]].region, waveform.scrollLeft, "black"); 872 } 873 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region, waveform.scrollLeft); 874 } 562 875 } 563 876 564 877 function handleRegionColours(region, highlight) { // handles region, chapter & word colours 565 var colour; 566 if (highlight) { 567 colour = "rgb(101, 116, 116)"; 568 regionEnter(region); 878 if (!dualMode || (region.element.classList.contains("region-top") && primaryCaret.src.includes("fill")) || region.element.classList.contains("region-bottom") && secondaryCaret.src.includes("fill")) { 879 let colour; 880 if (highlight) { 881 colour = "rgb(101, 116, 116)"; 882 regionEnter(region); 883 } else { 884 colour = ""; 885 regionLeave(region); 886 } 887 if (isCurrentRegion(region) || isInCurrentRegions(region)) { 888 colour = "rgba(255, 50, 50, 0.5)"; 889 } 890 let regionIndex = region.id.replace("region",""); 891 let corrItem = document.getElementById(itemType + regionIndex); 892 corrItem.style.backgroundColor = colour; // updates chapter background (not region) 893 } 894 } 895 896 function regionEnter(region) { 897 if (isCurrentRegion(region) || isInCurrentRegions(region)) { 898 if (region.element.classList.contains("region-top") && !currSpeakerSet.isSecondary) region.update({ color: "rgba(255, 50, 50, 0.5)" }); 569 899 } 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)" }); 900 region.update({ color: "rgba(255, 255, 255, 0.35)" }); 901 } 580 902 } 581 903 582 904 function regionLeave(region) { 583 905 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 }); 906 if (isCurrentRegion(region) || isInCurrentRegions(region)) { 907 region.update({ color: "rgba(255, 50, 50, 0.5)" }); 908 } else if (!(wavesurfer.getCurrentTime() + 0.1 < region.end && wavesurfer.getCurrentTime() > region.start)) { 909 let index = region.id.replace("region", ""); 910 region.update({ color: colourbrewerset[currSpeakerSet.uniqueSpeakers.indexOf(currSpeakerSet.tempSpeakerObjects[index].speaker)%8] + regionTransparency }); 587 911 } 588 912 } else { … … 592 916 593 917 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; 918 // return (num - (num %= 60)) / 60 + (9 < num ? ':' : ':0') + ~~num; // https://stackoverflow.com/questions/3733227/javascript-seconds-to-minutes-and-seconds 919 920 let date = new Date(null); 921 date.setSeconds(num); 922 return date.toTimeString().split(" ")[0].substring(3); 597 923 } 598 924 599 925 function getLetter(val) { 600 var speakerNum = parseInt(val.speaker.replace("SPEAKER_","")); 926 // return val.replace("SPEAKER_",""); 927 let speakerNum = parseInt(val.replace("SPEAKER_","")); 601 928 return String.fromCharCode(65 + speakerNum); // 'A' == UTF-16 65 602 929 } 930 931 932 933 // edit functionality 934 935 function toggleEditMode() { // toggles edit panel and redraws regions with resize handles 936 toggleEditPanel(); 937 updateRegionEditPanel(); 938 } 939 940 function toggleEditPanel() { // show & hide edit panel 941 currentRegion.speaker = ''; 942 currentRegion.start = ''; 943 currentRegion.end = ''; 944 currentRegions = []; 945 removeRegionBounds(); 946 hoverSpeaker.innerHTML = ""; 947 if (editPanel.style.height == "0px") { 948 if (chapters.style.height == "0px") chapters.style.height = "30vh"; // expands chapter panel 949 editPanel.style.height = "30vh"; 950 editPanel.style.padding = "1rem"; 951 setRegionEditMode(true); 952 } else { 953 editPanel.style.height = "0px"; 954 editPanel.style.padding = "0px"; 955 setRegionEditMode(false); 956 } 957 } 958 959 function setRegionEditMode(state) { 960 editMode = state; 961 chapters.innerHTML = ''; 962 wavesurfer.clearRegions(); 963 populateChapters(currSpeakerSet); 964 } 965 966 function handleRegionEdit(region, e) { 967 if (e.target.localName === "region" || e.target.localName === "handle") { 968 if (region.element.classList.contains("region-top")) { currSpeakerSet = primarySet; swapCarets(true) } 969 else if (region.element.classList.contains("region-bottom")) { currSpeakerSet = secondarySet; swapCarets(false) } 970 editsMade = true; 971 currentRegion = region; 972 region.play(); 973 wavesurfer.pause(); 974 let regionIndex = getCurrentRegionIndex(); 975 currentRegion.speaker = currSpeakerSet.tempSpeakerObjects[regionIndex].speaker; 976 currSpeakerSet.tempSpeakerObjects[regionIndex].region = region; 977 currSpeakerSet.tempSpeakerObjects[regionIndex].start = region.start; 978 currSpeakerSet.tempSpeakerObjects[regionIndex].end = region.end; 979 reloadRegionsAndChapters(); 980 handleSameSpeakerOverlap(getCurrentRegionIndex()); // recalculate index in case start pos has changed 981 addUndoState(primarySet, secondarySet, currSpeakerSet.isSecondary, "dragdrop", getCurrentRegionIndex()); 982 editPanel.click(); // fixes buttons needing to be clicked twice (unknown cause!) 983 } else console.log("resizing too fast, selected region not updated."); 984 } 985 986 function handleSameSpeakerOverlap(regionIdx) { // consumes/merges same-speaker regions with overlapping bounds 987 let draggedRegion = currSpeakerSet.tempSpeakerObjects[regionIdx]; // regionIdx may point to a different region within the for-loop after adjustments, so defined here 988 let draggedRegionSpeaker = draggedRegion.speaker; 989 for (let i = 0; i < currSpeakerSet.tempSpeakerObjects.length; i++) { 990 if (currSpeakerSet.tempSpeakerObjects[i].speaker === draggedRegionSpeaker && !regionsMatch(draggedRegion, currSpeakerSet.tempSpeakerObjects[i])) { // ensure speaker name match 991 if (currSpeakerSet.tempSpeakerObjects[i].start < draggedRegion.end && draggedRegion.start < currSpeakerSet.tempSpeakerObjects[i].end) { // ensure overlap 992 draggedRegion.start = Math.min(currSpeakerSet.tempSpeakerObjects[i].start, draggedRegion.start); 993 draggedRegion.end = Math.max(currSpeakerSet.tempSpeakerObjects[i].end, draggedRegion.end); 994 currentRegion = draggedRegion; 995 currSpeakerSet.tempSpeakerObjects.splice(i, 1); // remove consumed region 996 i = -1; // reset for loop to support multiple consumptions 997 } 998 } 999 } 1000 reloadRegionsAndChapters(); 1001 } 1002 1003 function updateRegionEditPanel() { // updates edit panel content/inputs 1004 if (currentRegion && currentRegion.speaker == "") { 1005 removeButton.classList.add("disabled"); 1006 speakerInput.classList.add("disabled"); 1007 speakerCheckbox.classList.add("disabled"); 1008 speakerCheckbox.disabled = true; 1009 disableStartEndInputs(); 1010 speakerInput.readOnly = true; 1011 speakerInput.value = ""; 1012 } else { 1013 removeButton.classList.remove("disabled"); 1014 speakerInput.classList.remove("disabled"); 1015 speakerCheckbox.classList.remove("disabled"); 1016 if (!isZooming) speakerCheckbox.disabled = false; 1017 enableStartEndInputs(); 1018 speakerInput.readOnly = false; 1019 } 1020 if (editsMade) { 1021 discardButton.classList.remove("disabled"); 1022 saveButton.classList.remove("disabled"); 1023 } else { 1024 discardButton.classList.add("disabled"); 1025 saveButton.classList.add("disabled"); 1026 } 1027 if (speakerCheckbox.checked) { 1028 // changeAllLabel.innerHTML = "Change all (x" + currentRegions.length + ")"; 1029 disableStartEndInputs(); 1030 } 1031 if (currentRegion && currentRegion.speaker != "") { 1032 speakerInput.value = currentRegion.speaker; 1033 setInputInSeconds(startTimeInput, currentRegion.start); 1034 setInputInSeconds(endTimeInput, currentRegion.end); 1035 } 1036 } 1037 1038 function createNewRegion() { // adds a new region to the waveform 1039 const speaker = "NEW_SPEAKER"; // default name 1040 let offset = 0; 1041 if (!currSpeakerSet.uniqueSpeakers.includes(speaker)) { currSpeakerSet.uniqueSpeakers.push(speaker) } 1042 else { offset = 5 * getRegionsWithSpeaker(speaker).length } // offset new region if multiple new regions are created. TODO: check region has different start time 1043 const start = offset + wavesurfer.getCurrentTime(); 1044 const end = offset + wavesurfer.getCurrentTime() + 15; 1045 currSpeakerSet.tempSpeakerObjects.push({speaker: speaker, start: start, end: end}); 1046 editsMade = true; 1047 currentRegions = []; 1048 currentRegion = getRegionFromProps({speaker: speaker, start: start, end: end}); 1049 reloadRegionsAndChapters(); 1050 addUndoState(primarySet, secondarySet, currSpeakerSet.isSecondary, "create", getCurrentRegionIndex()); 1051 } 1052 1053 function getRegionFromProps(props) { // find region using speaker, start & end time 1054 for (let i = 0; i < currSpeakerSet.tempSpeakerObjects.length; i++) { 1055 if (currSpeakerSet.tempSpeakerObjects[i].speaker === props.speaker && currSpeakerSet.tempSpeakerObjects[i].start === props.start && currSpeakerSet.tempSpeakerObjects[i].end === props.end) { 1056 return currSpeakerSet.tempSpeakerObjects[i]; 1057 } 1058 } 1059 console.log("getRegionFromProps failed to find matching region"); 1060 } 1061 1062 function removeRegion() { // removes currently selected region or regions 1063 if (!removeButton.classList.contains("disabled")) { 1064 if (getCurrentRegionIndex() != -1) { // if currentRegion has been set 1065 let currentRegionIndex = getCurrentRegionIndex(); 1066 let currentRegionIndexes = getCurrentRegionsIndexes(); 1067 for (let i = 0; i < currSpeakerSet.tempSpeakerObjects.length; i++) { 1068 if (isCurrentRegion(currSpeakerSet.tempSpeakerObjects[i].region)) { 1069 // if (!currentRegion.region) currentRegion.remove(); // remove from wavesurfer.regions.list 1070 currSpeakerSet.tempSpeakerObjects.splice(i, 1); // remove from tempSpeakerObjects 1071 // else currentRegion.region.remove(); // remove if region was just added 1072 editsMade = true; 1073 if (i >= 0) i--; // decrement index for side-by-side regions 1074 if (!speakerCheckbox.checked && currentRegions.length < 1) { 1075 removeCurrentRegion(); 1076 reloadRegionsAndChapters(); 1077 addUndoState(primarySet, secondarySet, currSpeakerSet.isSecondary, "remove", currentRegionIndex); 1078 return; // jump out of for loop 1079 } 1080 } else if (isInCurrentRegions(currSpeakerSet.tempSpeakerObjects[i])) { 1081 currSpeakerSet.tempSpeakerObjects.splice(i, 1); 1082 if (i >= 0) i--; 1083 } 1084 } 1085 removeCurrentRegion(); 1086 reloadRegionsAndChapters(); 1087 addUndoState(primarySet, secondarySet, currSpeakerSet.isSecondary, "remove", currentRegionIndex, currentRegionIndexes); // multiple regions removed 1088 } else { console.log("no region selected") } 1089 } 1090 } 1091 1092 function regionsMatch(reg1, reg2) { 1093 if (reg1.start == reg2.start && reg1.end == reg2.end) return true; 1094 return false; 1095 } 1096 1097 function isCurrentRegion(region) { 1098 if (regionsMatch(currentRegion, region)) return true; 1099 return false; 1100 } 1101 1102 function isInCurrentRegions(region) { 1103 if (currentRegions != []) { 1104 for (let i = 0; i < currentRegions.length; i++) { 1105 if (currentRegions[i].start == region.start && currentRegions[i].end == region.end) { 1106 return true; 1107 } 1108 } 1109 } 1110 return false; 1111 } 1112 1113 function getCurrentRegionIndex() { // returns the index of currently selected region 1114 for (let i = 0; i < currSpeakerSet.tempSpeakerObjects.length; i++) { 1115 if (isCurrentRegion(currSpeakerSet.tempSpeakerObjects[i].region)) { return i } 1116 } 1117 // if (dualMode) { 1118 // for (let i = 0; i < secondarySet.tempSpeakerObjects.length; i++) { 1119 // if (isCurrentRegion(secondarySet.tempSpeakerObjects[i].region)) { return i } 1120 // } 1121 // } 1122 return -1; 1123 } 1124 1125 function getCurrentRegionsIndexes() { // returns the indexes of currently selected regions 1126 let indexes = []; 1127 for (let i = 0; i < currSpeakerSet.tempSpeakerObjects.length; i++) { 1128 if (isInCurrentRegions(currSpeakerSet.tempSpeakerObjects[i].region)) { indexes.push(i) } 1129 } 1130 return indexes; 1131 } 1132 1133 function removeCurrentRegion() { // removes current region, regions and bound markers 1134 currentRegion = {speaker: '', start: '', end: ''}; 1135 currentRegions = []; 1136 removeRegionBounds(); 1137 hoverSpeaker.innerHTML = ""; 1138 } 1139 1140 function getRegionsWithSpeaker(speaker) { // returns all regions with the given speaker name 1141 let out = []; 1142 for (let i = 0; i < currSpeakerSet.tempSpeakerObjects.length; i++) { 1143 if (currSpeakerSet.tempSpeakerObjects[i].speaker === speaker) { out.push(currSpeakerSet.tempSpeakerObjects[i]) } 1144 } 1145 return out; 1146 } 1147 1148 function sortSpeakerObjectsByStart(speakerOb) { // sorts the speaker object array by start time 1149 return speakerOb.sort(function(a,b) { 1150 return a.start - b.start; 1151 }); 1152 } 1153 1154 function speakerChange() { // speaker input name onInput handler 1155 const newSpeaker = speakerInput.value; 1156 if (newSpeaker && newSpeaker != "") { 1157 speakerInput.style.border = "2px solid transparent"; 1158 if (getCurrentRegionIndex() != -1) { // if a region is selected 1159 if (!currSpeakerSet.uniqueSpeakers.includes(newSpeaker)) { currSpeakerSet.uniqueSpeakers.push(newSpeaker) } 1160 if (currentRegions && currentRegions.length < 1) { currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].speaker = newSpeaker } // single change 1161 else if (currentRegions && currentRegions.length > 1) { // multiple changes 1162 for (idx of getCurrentRegionsIndexes()) currSpeakerSet.tempSpeakerObjects[idx].speaker = newSpeaker; 1163 } 1164 speakerInput.value = ""; 1165 currentRegion.speaker = newSpeaker; 1166 editsMade = true; 1167 reloadRegionsAndChapters(); 1168 addUndoState(primarySet, secondarySet, currSpeakerSet.isSecondary, "speaker-change", getCurrentRegionIndex(), getCurrentRegionsIndexes()); 1169 } else { console.log("no region selected") } 1170 } else { console.log("no text in speaker input"); speakerInput.style.border = "2px solid firebrick"; } 1171 } 1172 1173 function speakerInputUnfocused() { 1174 prevUndoState = ""; 1175 if (speakerInput.value == "" && !speakerInput.classList.contains("disabled")) { 1176 speakerInput.style.border = "2px solid firebrick"; 1177 window.alert("Speaker input cannot be left empty. Please enter a speaker name."); 1178 setTimeout(() => speakerInput.focus(), 10); // timeout needed otherwise input isn't selected 1179 } else speakerInput.style.border = "2px transparent"; 1180 } 1181 1182 function speakerCheckboxChanged() { // "Change all" toggled 1183 if (speakerCheckbox.checked) { 1184 if (!isZooming) { 1185 tempZoomSave = zoomSlider.value; 1186 zoomTo(0); // zoom out to encompass all selected regions 1187 } 1188 let uniqueSelectedSpeakers; 1189 if (currentRegions && currentRegions.length > 0) { // if more than one region selected 1190 uniqueSelectedSpeakers = [... new Set(currentRegions.map(a => a.speaker))]; // gets unique speakers in currentRegions 1191 uniqueSelectedSpeakers.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); 1192 } else uniqueSelectedSpeakers = [currentRegion.speaker]; 1193 currentRegions = []; 1194 for (const speaker of uniqueSelectedSpeakers) { 1195 for (const region of getRegionsWithSpeaker(speaker)) currentRegions.push(region); 1196 } 1197 reloadRegionsAndChapters(); 1198 } else { 1199 if (!isZooming) { 1200 zoomTo(tempZoomSave / 4); // zoom back in to previous level 1201 } 1202 currentRegions = []; // this will lose track of previously selected region*s* 1203 // changeAllLabel.innerHTML = "Change all"; 1204 reloadRegionsAndChapters(); 1205 } 1206 } 1207 1208 function enableStartEndInputs() { // removes the 'disabled' tag from all time inputs 1209 for (idx in startTimeInput.childNodes) { startTimeInput.childNodes[idx].disabled = false } 1210 for (idx in endTimeInput.childNodes) { endTimeInput.childNodes[idx].disabled = false } 1211 } 1212 1213 function disableStartEndInputs() { // adds the 'disabled' tag to all time inputs 1214 for (idx in startTimeInput.childNodes) { startTimeInput.childNodes[idx].disabled = true; startTimeInput.childNodes[idx].value = 0; } 1215 for (idx in endTimeInput.childNodes) { endTimeInput.childNodes[idx].disabled = true; endTimeInput.childNodes[idx].value = 0; } 1216 } 1217 1218 function zoomTo(dest) { // (smoothly?) zooms wavesurfer waveform to destination 1219 isZooming = true; 1220 speakerCheckbox.disabled = true; 1221 let isOut = false; 1222 if (dest == 0) isOut = true; 1223 zoomInterval = setInterval(() => { 1224 if (isOut) { 1225 if (zoomSlider.value != 0) { 1226 if (zoomSlider.value > 50) zoomSlider.value -= 30; // ramp up for finer adjustments 1227 else zoomSlider.stepDown(); 1228 wavesurfer.zoom(zoomSlider.value / 4); 1229 } else { 1230 clearInterval(zoomInterval); 1231 isZooming = false; 1232 speakerCheckbox.disabled = false; 1233 zoomSlider.dispatchEvent(new Event("input")); 1234 } 1235 } else { 1236 if (zoomSlider.value / 4 < dest) { 1237 if (zoomSlider.value > 50) zoomSlider.value += 30; // ramp up for finer adjustments 1238 else zoomSlider.stepUp(); 1239 wavesurfer.zoom(zoomSlider.value / 4); 1240 } else { 1241 clearInterval(zoomInterval); 1242 isZooming = false; 1243 speakerCheckbox.disabled = false; 1244 zoomSlider.dispatchEvent(new Event("input")); 1245 } 1246 } 1247 }, 10); // interval 1248 1249 } 1250 1251 function saveRegionChanges() { // saves tempSpeakerObjects to speakerObjects 1252 if (!saveButton.classList.contains("disabled")) { 1253 currSpeakerSet.speakerObjects = cloneSpeakerObjectArray(currSpeakerSet.tempSpeakerObjects); 1254 editsMade = false; 1255 removeCurrentRegion(); 1256 reloadRegionsAndChapters(); 1257 console.log("saved changes"); 1258 } 1259 } 1260 1261 function discardRegionChanges() { // resets tempSpeakerObjects to speakerObjects 1262 if (!discardButton.classList.contains("disabled")) { 1263 let confirm = window.confirm("Are you sure you want to discard changes?"); 1264 if (confirm) { 1265 currSpeakerSet.tempSpeakerObjects = cloneSpeakerObjectArray(currSpeakerSet.speakerObjects); 1266 editsMade = false; 1267 removeCurrentRegion(); 1268 resetUndoStates(); 1269 reloadRegionsAndChapters(); 1270 console.log("discarded changes"); 1271 } 1272 } 1273 } 1274 1275 function reloadRegionsAndChapters() { // redraws edit panel, chapter list, wavesurfer regions 1276 updateRegionEditPanel(); 1277 wavesurfer.clearRegions(); 1278 $(".region-top").remove(); 1279 $(".region-bottom").remove(); 1280 populateChapters(primarySet); 1281 if (dualMode) { 1282 populateChapters(secondarySet); 1283 currSpeakerSet = primarySet; 1284 } 1285 updateCurrSpeakerSet(); 1286 if (currentRegion.speaker && getCurrentRegionIndex() != -1) { 1287 hoverSpeaker.innerHTML = currentRegion.speaker; 1288 hoverSpeaker.style.marginLeft = parseInt(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region.element.style.left.slice(0, -2)) - waveform.scrollLeft + "px"; 1289 removeRegionBounds(); 1290 let currIndexes = getCurrentRegionsIndexes(); 1291 for (let i = 0; i < currIndexes.length; i++) { 1292 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[currIndexes[i]].region, waveform.scrollLeft, "black"); 1293 } 1294 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region, waveform.scrollLeft); 1295 } 1296 if (currentRegions.length < 1) { 1297 removeButton.innerHTML = "Remove Selected Region"; 1298 enableStartEndInputs(); 1299 } else { 1300 removeButton.innerHTML = "Remove Selected Regions (x" + currentRegions.length + ")"; 1301 disableStartEndInputs(); 1302 const uniqueSelectedSpeakers = [... new Set(currentRegions.map(a => a.speaker))]; // gets unique speakers in currentRegions 1303 uniqueSelectedSpeakers.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); 1304 // console.log(uniqueSelectedSpeakers); // CLG 1305 speakerInput.value = uniqueSelectedSpeakers.join(", "); 1306 } 1307 } 1308 1309 function changeStartEndTime(e) { // start/end time input handler 1310 let newStart = getTimeInSecondsFromInput(startTimeInput); 1311 let newEnd = getTimeInSecondsFromInput(endTimeInput); 1312 let duration = Math.floor(wavesurfer.getDuration()); // total duration of current audio 1313 1314 if (getCurrentRegionIndex() != -1) { // if there is a selected region 1315 if (newEnd <= newStart) newStart = newEnd - 1; // when start time > end time, push region forward 1316 if (newEnd <= 0) newEnd = 1; 1317 if (newStart < 0) newStart = 0; // ensures region start doesn't go < 0s 1318 if (newEnd > duration) newEnd = duration; // ensures region start doesn't go > duration 1319 1320 setInputInSeconds(startTimeInput, newStart); 1321 setInputInSeconds(endTimeInput, newEnd); 1322 1323 let currRegIdx = getCurrentRegionIndex(); 1324 currSpeakerSet.tempSpeakerObjects[currRegIdx].start = newStart; 1325 currSpeakerSet.tempSpeakerObjects[currRegIdx].end = newEnd; 1326 currentRegion.start = newStart; 1327 currentRegion.end = newEnd; 1328 editsMade = true; 1329 reloadRegionsAndChapters(); 1330 handleSameSpeakerOverlap(currRegIdx); 1331 addUndoState(primarySet, secondarySet, currSpeakerSet.isSecondary, "change-time", getCurrentRegionIndex()); 1332 } else { 1333 console.log("no region selected"); 1334 setInputInSeconds(startTimeInput, 0); 1335 setInputInSeconds(endTimeInput, 0); 1336 } 1337 } 1338 1339 function getTimeInSecondsFromInput(input) { // returns time in seconds from start or end input 1340 let hours = input.children[0].valueAsNumber; 1341 let mins = input.children[1].valueAsNumber; 1342 let secs = input.children[2].valueAsNumber; 1343 return (hours * 3600) + (mins * 60) + secs; 1344 } 1345 1346 function setInputInSeconds(input, seconds) { // sets start or end input time when given seconds 1347 let date = new Date(null); 1348 date.setMilliseconds(seconds * 1000); 1349 input.children[0].value = date.getHours() % 12; 1350 input.children[1].value = date.getMinutes(); 1351 input.children[2].value = date.getSeconds() + "." + date.getMilliseconds(); 1352 1353 document.querySelectorAll('input[type=number]').forEach(e => { 1354 e.value = Math.round(e.valueAsNumber * 10) / 10; // to 1dp 1355 if (e.classList.contains("seconds") && !e.value.includes(".")) e.value = e.value + ".0"; 1356 else if (e.value.length === 1) e.value = '0' + e.value; // 0 on left for hrs & mins 1357 if (e.value.length === 3) e.value = '0' + e.value; // 0 on the left (doesn't work on FF) 1358 // if (e.value.length < 4) e.value = e.value.slice(0, 4); // always 4 digits (3 numbers, 1 fullstop) 1359 // if (!e.value) e.value = '00'; // avoiding letters on FF 1360 }); 1361 } 1362 1363 function addUndoState(state, secState, isSec, type, currRegIdx, currRegIdxs) { // adds a new state to the undoStates stack 1364 let newState = cloneSpeakerObjectArray(state.tempSpeakerObjects); // clone method removes references 1365 let newSecState = cloneSpeakerObjectArray(secState.tempSpeakerObjects); // clone method removes references 1366 undoButton.classList.remove("disabled"); 1367 undoStates = undoStates.slice(0, undoLevel + 1); // trim to current level if undos have already been made 1368 undoStates.push({state: newState, secState: newSecState, isSec: isSec, currentRegionIndex: currRegIdx, currentRegionIndexes: currRegIdxs, type: type}); 1369 if ((type === "change-time" && prevUndoState === "change-time") || (type === "speaker-change" && prevUndoState === "speaker-change")) { // checks if similar change was made previously 1370 undoStates.splice(-2, 1); // remove second-to-last item in undoStates stack (merge last two changes into one to avoid multiple small edits) 1371 prevUndoState = type; 1372 } else undoLevel++; 1373 prevUndoState = type; 1374 redoButton.classList.add("disabled"); 1375 // console.log(undoStates.at(-1)); 1376 } 1377 1378 function undo() { // undo action: go back one state in the undoStates stack 1379 if (!undoButton.classList.contains("disabled")) { // ensure there exist states to undo to 1380 if (undoLevel - 1 < 0) console.log("ran out of undos"); 1381 else { 1382 let adjustedUndoLevel = undoLevel-1; 1383 primarySet.tempSpeakerObjects = cloneSpeakerObjectArray(undoStates[adjustedUndoLevel].state.slice(0)); // slice & clone removes potential references between arrays 1384 if (dualMode && undoStates[adjustedUndoLevel].secState && undoStates[adjustedUndoLevel].secState.length > 0) { // if secondary undoState exists 1385 secondarySet.tempSpeakerObjects = cloneSpeakerObjectArray(undoStates[adjustedUndoLevel].secState.slice(0)); // slice & clone removes potential references between arrays 1386 } 1387 editsMade = true; 1388 1389 let selectedSpeakerSet; 1390 1391 // handle currentRegion change 1392 removeCurrentRegion(); 1393 if (undoStates[undoLevel] && undoStates[undoLevel].type && undoStates[undoLevel].type == "remove") { // if destination state type is remove 1394 selectedSpeakerSet = (undoStates[undoLevel].isSec) ? secondarySet : primarySet; 1395 if (selectedSpeakerSet.isSecondary) caretClicked("secondary-caret"); 1396 else caretClicked("primary-caret"); 1397 currentRegion = selectedSpeakerSet.tempSpeakerObjects[undoStates[undoLevel].currentRegionIndex]; // restore previous current state 1398 // console.log("undo-ing to index " + undoStates[undoLevel].currentRegionIndex); 1399 } else if (undoStates[undoLevel-1].currentRegionIndex) { 1400 if (!dualMode) selectedSpeakerSet = primarySet; 1401 else { 1402 selectedSpeakerSet = (undoStates[undoLevel-1].isSec) ? secondarySet : primarySet; 1403 if (selectedSpeakerSet.isSecondary) caretClicked("secondary-caret"); 1404 else caretClicked("primary-caret"); 1405 } 1406 currentRegion = selectedSpeakerSet.tempSpeakerObjects[undoStates[undoLevel-1].currentRegionIndex]; // restore previous current state 1407 // console.log("undo-ing to index " + undoStates[undoLevel-1].currentRegionIndex); 1408 } 1409 // handle currentRegions change NEEDS REVISION xxxxx 1410 if (undoStates[undoLevel-1].currentRegionIndexes && undoStates[undoLevel-1].currentRegionIndexes.length > 1) { 1411 for (const idx of undoStates[undoLevel-1].currentRegionIndexes) currentRegions.push(currSpeakerSet.tempSpeakerObjects[idx]); 1412 1413 // currentRegions = getRegionsWithSpeaker(currentRegion.speaker); 1414 // if (!speakerCheckbox.checked) speakerCheckbox.click(); // manually fires onChange event 1415 } 1416 reloadRegionsAndChapters(); 1417 undoLevel--; // decrement undoLevel 1418 if (undoLevel - 1 < 0) undoButton.classList.add("disabled"); 1419 else undoButton.classList.remove("disabled"); 1420 } 1421 if (undoLevel < undoStates.length) redoButton.classList.remove("disabled"); 1422 } 1423 } 1424 1425 function redo() { // redo action: go forward one state in the undoStates stack 1426 if (!redoButton.classList.contains("disabled")) { // ensure there exist states to redo to 1427 if (undoLevel + 1 > undoStates.length - 1) console.log("ran out of redos"); 1428 else { 1429 primarySet.tempSpeakerObjects = cloneSpeakerObjectArray(undoStates[undoLevel+1].state.slice(0)); // set primary to new state 1430 secondarySet.tempSpeakerObjects = cloneSpeakerObjectArray(undoStates[undoLevel+1].secState.slice(0)); // set secondary to new state 1431 editsMade = true; 1432 let selectedSpeakerSet; 1433 1434 // handle currentRegion change 1435 if (undoLevel+1 <= undoStates.length-1) { 1436 removeCurrentRegion(); 1437 if (undoStates[undoLevel+2] && undoStates[undoLevel+2].type && undoStates[undoLevel+2].type == "remove") { 1438 selectedSpeakerSet = (undoStates[undoLevel+2].isSec) ? secondarySet : primarySet; 1439 if (selectedSpeakerSet.isSecondary) caretClicked("secondary-caret"); 1440 else caretClicked("primary-caret"); 1441 currentRegion = selectedSpeakerSet.tempSpeakerObjects[undoStates[undoLevel+2].currentRegionIndex]; 1442 } else { 1443 selectedSpeakerSet = (undoStates[undoLevel+1].isSec) ? secondarySet : primarySet; 1444 if (selectedSpeakerSet.isSecondary) caretClicked("secondary-caret"); 1445 else caretClicked("primary-caret"); 1446 currentRegion = selectedSpeakerSet.tempSpeakerObjects[undoStates[undoLevel+1].currentRegionIndex]; 1447 } 1448 1449 // console.log("redo-ing to index " + undoStates[undoLevel+1].currentRegionIndex); 1450 if (undoStates[undoLevel+1].currentRegionIndexes && undoStates[undoLevel+1].currentRegionIndexes.length > 1) { 1451 for (const idx of undoStates[undoLevel-1].currentRegionIndexes) currentRegions.push(currSpeakerSet.tempSpeakerObjects[idx]); 1452 // currentRegions = getRegionsWithSpeaker(currentRegion.speaker); 1453 // if (!speakerCheckbox.checked) speakerCheckbox.click(); // ensures onchange event is fired 1454 } 1455 } 1456 1457 reloadRegionsAndChapters(); 1458 undoLevel++; // increment undoLevel 1459 if (undoLevel + 1 > undoStates.length - 1) redoButton.classList.add("disabled"); 1460 else redoButton.classList.remove("disabled"); 1461 } 1462 if (undoLevel < undoStates.length) undoButton.classList.remove("disabled"); 1463 // console.log("new undoLevel: " + undoLevel); 1464 } 1465 } 1466 1467 function resetUndoStates() { // clear undo history 1468 undoStates = [{state: cloneSpeakerObjectArray(primarySet.tempSpeakerObjects), secState: cloneSpeakerObjectArray(secondarySet.tempSpeakerObjects)}]; 1469 undoLevel = 0; 1470 undoButton.classList.add("disabled"); 1471 redoButton.classList.add("disabled"); 1472 } 1473 1474 function waveformScrolled() { // waveform scroll handler 1475 if (currentRegion.speaker && getCurrentRegionIndex() != -1) { // updates region bound markers if selected region exists 1476 hoverSpeaker.innerHTML = currentRegion.speaker; 1477 hoverSpeaker.style.marginLeft = parseInt(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region.element.style.left.slice(0, -2)) - waveform.scrollLeft + "px"; 1478 removeRegionBounds(); 1479 let currIndexes = getCurrentRegionsIndexes(); 1480 for (let i = 0; i < currIndexes.length; i++) { 1481 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[currIndexes[i]].region, waveform.scrollLeft, "black"); 1482 } 1483 drawRegionBounds(currSpeakerSet.tempSpeakerObjects[getCurrentRegionIndex()].region, waveform.scrollLeft); 1484 } 1485 } 1486 1487 function drawRegionBounds(region, scrollPos) { // draws on canvas to show bounds of hovered/selected region 1488 const hoverSpeakerCanvas = document.createElement("canvas"); 1489 let colour = "black"; 1490 hoverSpeakerCanvas.id = "hover-speaker-canvas"; 1491 hoverSpeakerCanvas.classList.add("region-bounds"); 1492 hoverSpeakerCanvas.width = audioContainer.clientWidth; // max width of drawn bounds 1493 const ctx = hoverSpeakerCanvas.getContext("2d"); 1494 1495 ctx.translate(0.5, 0.5); // fixes lineWidth inconsistency 1496 ctx.lineWidth = 1; 1497 if (currentRegions && currentRegions.length < 1 && isCurrentRegion(region)) { 1498 colour = "FireBrick"; 1499 ctx.lineWidth = 3; 1500 } 1501 ctx.strokeStyle = colour; 1502 ctx.beginPath(); 1503 ctx.moveTo(parseInt(region.element.style.left.slice(0, -2)) - scrollPos, 28); 1504 ctx.lineTo(parseInt(region.element.style.left.slice(0, -2)) - scrollPos, 20); 1505 ctx.lineTo(parseInt(region.element.style.left.slice(0, -2)) + parseInt(region.element.style.width.slice(0, -2)) - scrollPos, 20); 1506 ctx.lineTo(parseInt(region.element.style.left.slice(0, -2)) + parseInt(region.element.style.width.slice(0, -2)) - scrollPos, 28); 1507 ctx.stroke(); 1508 audioContainer.prepend(hoverSpeakerCanvas); 1509 } 1510 1511 function removeRegionBounds() { // remove all region bound markers 1512 let canvases = document.getElementsByClassName('region-bounds'); 1513 while (canvases[0]) canvases[0].parentNode.removeChild(canvases[0]); 1514 } 1515 1516 function updateCurrSpeakerSet() { 1517 if (primaryCaret.src.includes("fill")) currSpeakerSet = primarySet; 1518 else if (secondaryCaret.src.includes("fill")) currSpeakerSet = secondarySet; 1519 } 1520 1521 function cloneSpeakerObjectArray(inputArray) { // clones speakerObjectArray without references (wavesurfer regions) 1522 let output = []; 1523 for (let i = 0; i < inputArray.length; i++) { output.push({speaker: inputArray[i].speaker, start: inputArray[i].start, end: inputArray[i].end }) } 1524 return output; 1525 } 1526 1527 function flashInput(valid) { // flashes background of input to show validity of input 1528 if (valid) speakerInput.style.backgroundColor = "rgb(50,255,50)"; 1529 else speakerInput.style.backgroundColor = "rgb(255,50,50)"; 1530 setTimeout(() => { speakerInput.style.backgroundColor = "rgb(255,255,255)" }, 750); 1531 } 1532 1533 function flashChapters() { 1534 chapters.style.backgroundColor = "rgb(66, 84, 88)"; 1535 setTimeout(() => { chapters.style.backgroundColor = "rgb(40, 54, 58)" }, 500); 1536 } 1537 1538 function fullscreenChanged() { // fullscreen onChange handler, increases waveform height & adjusts padding/margin 1539 if (!audioContainer.classList.contains("fullscreen")) { 1540 audioContainer.classList.add("fullscreen"); 1541 wavesurfer.setHeight(175); 1542 } else { 1543 audioContainer.classList.remove("fullscreen"); 1544 wavesurfer.setHeight(128); 1545 } 1546 } 1547 1548 function toggleFullscreen() { // toggles fullscreen mode of audio player/editor 1549 if ((document.fullscreenElement && document.fullscreenElement !== null) || 1550 (document.webkitFullscreenElement && document.webkitFullscreenElement !== null) || 1551 (document.mozFullScreenElement && document.mozFullScreenElement !== null) || 1552 (document.msFullscreenElement && document.msFullscreenElement !== null)) { 1553 document.exitFullscreen(); 1554 } else { 1555 audioContainer.requestFullscreen(); 1556 } 1557 } 603 1558 } 604 1559 605 1560 function formatAudioDuration(duration) { 606 console.log(duration);607 var[hrs, mins, secs, ms] = duration.replace(".", ":").split(":");1561 // console.log('duration: ' + duration); 1562 let [hrs, mins, secs, ms] = duration.replace(".", ":").split(":"); 608 1563 return hrs + ":" + mins + ":" + secs; 609 1564 } -
main/trunk/greenstone3/web/interfaces/default/style/core.css
r37003 r37031 1392 1392 #audioContainer { 1393 1393 width: 100%; 1394 /* background-color: rgb(24, 36, 39); */ 1395 scrollbar-color: white transparent; 1394 overflow: hidden; 1395 font-family: 'Courier New', monospace; 1396 } 1397 1398 #audioContainer::backdrop { 1399 background-color: rgb(245, 243, 229); 1400 } 1401 1402 .fullscreen { 1403 padding-top: 20vh; 1404 padding-left: 5vw; 1405 padding-right: 5vw; 1396 1406 } 1397 1407 … … 1401 1411 1402 1412 #toolbar { 1403 background-color: rgb(2 4, 36, 39);1413 background-color: rgb(20, 30, 32); 1404 1414 position: relative; 1405 1415 display: flex; … … 1426 1436 1427 1437 #audioContainer img:hover { 1428 filter: invert(0. 6);1438 filter: invert(0.5); 1429 1439 } 1430 1440 1431 1441 #audioContainer img:active { 1432 filter: invert(0. 5);1442 filter: invert(0.4); 1433 1443 } 1434 1444 … … 1438 1448 max-height: 30vh; 1439 1449 font-size: 14px; 1440 background-color: rgb( 54, 73, 78);1450 background-color: rgb(40, 54, 58); 1441 1451 color: white; 1442 1452 overflow-y: scroll; 1443 transition: height 0.4s ease; 1453 /* transition: background-color 0.4s ease-in-out; 1454 transition: height 0.4s ease; */ 1455 transition: 0.3s ease-in-out; 1456 cursor: wait; 1457 user-select: none; 1444 1458 } 1459 1460 /* .audio-scroll::-webkit-scrollbar { 1461 height: 8px; 1462 width: 8px; 1463 background: rgb(24, 36, 39); 1464 } 1465 1466 .audio-scroll::-webkit-scrollbar-thumb { 1467 background: #66d640; 1468 } */ 1445 1469 1446 1470 .chapter { … … 1450 1474 border-top-right-radius: 5px; 1451 1475 border-top-left-radius: 5px; 1452 padding: 0.5 em;1476 padding: 0.5rem; 1453 1477 transition: 0.1s ease; 1478 1454 1479 } 1455 1480 … … 1459 1484 } 1460 1485 1486 .selected-chapter { 1487 background-color: rgba(255, 50, 50, 0.5); 1488 } 1489 1490 .selected-chapter:hover { 1491 background-color: rgba(255, 100, 100, 0.5); 1492 } 1493 1461 1494 #playPauseButton { 1462 padding-left: 1em; 1463 padding-right: 1em; 1464 } 1465 1466 #downloadButton { 1467 transform: scale(0.8); 1468 padding-right: 0.75em; 1469 } 1470 1471 .wavesurfer-region { 1472 cursor: pointer !important; 1473 /* border: 1px solid rgba(255, 255, 255, 0.3) !important; */ 1474 transition: 0.1s ease; 1495 padding-left: 1rem; 1496 padding-right: 1rem; 1497 } 1498 1499 #downloadButton, #editButton, #fullscreenButton { 1500 transform: scale(0.75); 1501 padding-right: 0.75rem; 1502 } 1503 1504 #zoomInButton, #zoomOutButton { 1505 transform: scale(0.75); 1506 } 1507 1508 #muteButton { 1509 padding-right: 0.25rem; 1510 } 1511 1512 #volume-slider { 1513 position: absolute; 1514 display: none; 1515 cursor: pointer; 1516 z-index: 10; 1517 height: 8rem; 1518 margin-top: -8rem; 1519 padding-left: 1rem; 1520 padding-right: 1rem; 1521 margin-left: -1rem; 1522 box-shadow: 0 0 15px; 1523 } 1524 1525 #volume-container { 1526 position: relative; 1527 display: flex; 1528 flex-direction: row; 1529 } 1530 1531 #volume-container:hover #volume-slider { 1532 display: inline; 1533 } 1534 1535 canvas { 1536 /* transition: width 0.5s ease; */ 1537 /* z-index: 4 !important; */ 1538 pointer-events: none; 1475 1539 } 1476 1540 … … 1479 1543 } 1480 1544 1481 # slider {1482 width: 10 em;1483 margin-left: 0.5 em;1484 margin-right: 0.5 em;1545 #zoom-slider { 1546 width: 10rem; 1547 margin-left: 0.5rem; 1548 margin-right: 0.5rem; 1485 1549 cursor: pointer; 1486 1550 } 1487 1551 1488 #zoomIcon {1489 width: 1.2em !important;1490 }1491 1492 1552 #chapterButton { 1493 padding-left: 0.2 em;1494 padding-right: 0.75 em;1553 padding-left: 0.2rem; 1554 padding-right: 0.75rem; 1495 1555 } 1496 1556 1497 1557 #wave-timeline { 1498 background-color: rgb(54, 73, 78); 1558 background-color: rgb(40, 54, 58); 1559 /* background-color: rgb(24, 36, 39); */ 1499 1560 } 1500 1561 1501 1562 #waveform-loader { 1502 1563 color: white; 1503 padding: 0.2 em;1564 padding: 0.2rem; 1504 1565 } 1505 1566 … … 1535 1596 flex-wrap: wrap; 1536 1597 justify-content: space-evenly; 1537 padding: 0.5 em;1598 padding: 0.5rem; 1538 1599 } 1539 1600 1540 1601 .word { 1541 margin-right: 0.5 em;1602 margin-right: 0.5rem; 1542 1603 cursor: pointer; 1543 1604 } … … 1555 1616 1556 1617 #tapeDetails td { 1557 padding: 0.2 em;1618 padding: 0.2rem; 1558 1619 } 1559 1620 1560 1621 #tapeDetails td:nth-child(1) { 1561 padding-right: 6 em;1622 padding-right: 6rem; 1562 1623 } 1563 1624 … … 1578 1639 .metadataTable td { 1579 1640 padding: 2px; 1641 <<<<<<< .mine 1642 } 1643 1644 /* edit functionality */ 1645 #edit-panel { 1646 width: 50%; 1647 height: 0px; 1648 position: relative; 1649 right: 0; 1650 max-height: 30vh; 1651 font-size: 15px; 1652 background-color: rgb(40, 54, 58); 1653 color: white; 1654 overflow-y: auto; 1655 transition: height 0.4s ease; 1656 box-sizing: border-box; /* ensures padding doesn't modify width */ 1657 font-family: 'Courier New', monospace; 1658 font-size: 0.85rem; 1659 border-left: 1px solid rgb(24, 36, 39); 1660 1661 display: flex; 1662 flex-direction: column; 1663 flex-wrap: nowrap; 1664 justify-content: space-between; 1665 } 1666 1667 #edit-panel button { 1668 padding: 0.5rem; 1669 /* border: 1px solid white; */ 1670 cursor: pointer; 1671 background-color: #F8C537; 1672 transition: 0.1s ease-in-out; 1673 font-size: 15px; 1674 font-family: 'Courier New', monospace; 1675 } 1676 1677 #edit-panel h3 { 1678 font-size: 1.2rem; 1679 margin: 0; 1680 } 1681 1682 #edit-panel button:hover { 1683 background-color: #af8b26; 1684 } 1685 1686 .flex-row { 1687 display: flex; 1688 flex-direction: row; 1689 flex-wrap: nowrap; 1690 } 1691 1692 #edit-panel input[type='text'] { 1693 width: 50%; 1694 /* border: 1px solid white; */ 1695 outline: none; 1696 font-size: 15px; 1697 margin-right: 0.5rem; 1698 margin-bottom: 0.25rem; 1699 transition: 0.25s ease-in; 1700 background-color: white; 1701 } 1702 1703 #edit-panel input::placeholder { 1704 color: black; 1705 opacity: 0.75; 1706 } 1707 1708 #edit-panel input[type='checkbox'] { 1709 transform: scale(1.25); 1710 margin-right: 0.5rem; 1711 margin-top: -0.25rem; 1712 } 1713 1714 #edit-panel input[type='number'], 1715 .time-label { 1716 border: none; 1717 text-align: center; 1718 width: 1.5rem; 1719 padding: 0; 1720 margin: 0 !important; 1721 font-size: 15px; 1722 } 1723 1724 .time-label { 1725 background-color: transparent !important; 1726 font-family: 'Courier New', Courier, monospace !important; 1727 font-size: 14px !important; 1728 } 1729 1730 .time-label-container { 1731 width: 20%; 1732 background-color: transparent; 1733 display: inline-flex; 1734 } 1735 1736 #edit-panel input[type='number']:focus { 1737 border: none; 1738 outline: none; 1739 } 1740 1741 #change-all-label { 1742 /* width: 100%; */ 1743 } 1744 1745 #save-discard { 1746 /* margin-bottom: 1rem; */ 1747 justify-content: space-between; 1748 } 1749 1750 #audio-dropdowns { 1751 width: 100%; 1752 display: flex; 1753 flex-direction: row; 1754 flex-wrap: nowrap; 1755 justify-content: space-between; 1756 } 1757 1758 #hover-speaker { 1759 height: 2rem; 1760 overflow: hidden; 1761 font-family: 'Courier New', monospace; 1762 } 1763 1764 #hover-speaker-canvas { 1765 position: absolute; 1766 overflow: hidden; 1767 } 1768 1769 #region-details { 1770 1771 } 1772 1773 .time-picker { 1774 background-color: white; 1775 display: inline-flex; 1776 border: 1px solid #ccc; 1777 color: #555; 1778 margin-bottom: 0.25rem; 1779 } 1780 1781 .no-arrows { 1782 -moz-appearance: textfield; 1783 -webkit-appearance: textfield; 1784 appearance: textfield; 1785 } 1786 1787 /* input::-webkit-outer-spin-button, 1788 input::-webkit-inner-spin-button { 1789 opacity: 0.5; 1790 pointer-events: none; 1791 } */ 1792 1793 .seconds { 1794 width: 3.25rem !important; 1795 } 1796 1797 .disabled { 1798 /* background-color: #888 !important; */ 1799 opacity: 0.5; 1800 filter: grayscale(100%); 1801 -webkit-filter: grayscale(100%); 1802 cursor: not-allowed !important; 1803 } 1804 1805 .wavesurfer-region { 1806 cursor: pointer !important; 1807 transition: background-color 0.1s ease; 1808 z-index: 1 !important; 1809 height: 70% !important; 1810 top: 15% !important; 1811 /* transition: width 2s ease; */ 1812 } 1813 1814 .wavesurfer-handle { 1815 /* background-color: rgb(255, 0, 0) !important; */ 1816 /* z-index: 20 !important; */ 1817 width: 3px !important; 1818 } 1819 1820 .region-top { 1821 height: 30% !important; 1822 top: 10% !important; 1823 } 1824 1825 .region-bottom { 1826 height: 30% !important; 1827 top: 60% !important; 1828 } 1829 1830 #caret-container { 1831 position: absolute; 1832 /* background-color: #090; */ 1833 height: 128px; /* match waveform */ 1834 width: 20px; /* match gs_content padding */ 1835 flex-direction: column; 1836 justify-content: space-around; 1837 left: -4px; /* padding */ 1838 cursor: pointer; 1839 display: none; 1840 } 1841 1842 #caret-container img { 1843 filter: none; 1844 opacity: 0.85; 1845 } 1846 1847 #caret-container img:hover { 1848 filter: none; 1849 opacity: 1; 1850 } 1851 1852 #selected-header { 1853 justify-content: space-between; 1854 padding-top: 0.5rem; 1855 padding-bottom: 0.5rem; 1580 1856 } 1581 1857 … … 1583 1859 color: black; 1584 1860 } 1861
Note:
See TracChangeset
for help on using the changeset viewer.