From f45ff0fd973136ad9d95fc9dc68afb528c7fa60b Mon Sep 17 00:00:00 2001 From: afrmtbl Date: Fri, 18 Jan 2019 23:16:56 -0500 Subject: [PATCH] Added a popup to load annotation data --- .gitignore | 2 ++ js/AnnotationParser.js | 7 +++-- js/AnnotationRenderer.js | 20 ++++++++++++++- js/background.js | 4 ++- js/content.js | 55 ++++++++++++++++++++++++++++++++-------- manifest.json | 5 ++++ popup/index.css | 16 ++++++++++++ popup/index.html | 22 ++++++++++++++++ popup/index.js | 40 +++++++++++++++++++++++++++++ 9 files changed, 154 insertions(+), 17 deletions(-) create mode 100644 .gitignore create mode 100644 popup/index.css create mode 100644 popup/index.html create mode 100644 popup/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf5ba70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +*.zip \ No newline at end of file diff --git a/js/AnnotationParser.js b/js/AnnotationParser.js index cfa24c6..4eee98a 100644 --- a/js/AnnotationParser.js +++ b/js/AnnotationParser.js @@ -102,9 +102,8 @@ class AnnotationParser { const dom = parser.parseFromString(xml, "application/xml"); return dom; } - getAnnotationsFromXml(xmlDom) { - const parser = new DOMParser(); - const dom = parser.parseFromString(xmlDom, "application/xml"); + getAnnotationsFromXml(xml) { + const dom = this.xmlToDom(xml); return dom.getElementsByTagName("annotation"); } parseYoutubeFormat(annotationElements) { @@ -278,4 +277,4 @@ class AnnotationParser { } } } -} \ No newline at end of file +} diff --git a/js/AnnotationRenderer.js b/js/AnnotationRenderer.js index e444b43..2f683f4 100644 --- a/js/AnnotationRenderer.js +++ b/js/AnnotationRenderer.js @@ -24,6 +24,13 @@ class AnnotationRenderer { this.updateInterval = updateInterval; } + changeAnnotationData(annotations) { + this.stop(); + this.removeAnnotationElements(); + this.annotations = annotations; + this.createAnnotationElements(); + this.start(); + } createAnnotationElements() { for (const annotation of this.annotations) { const el = document.createElement("div"); @@ -49,7 +56,13 @@ class AnnotationRenderer { this.annotationsContainer.append(el); } } + removeAnnotationElements() { + for (const annotation of this.annotations) { + annotation.__element.remove(); + } + } update(videoTime) { + console.log("updating: ", videoTime); for (const annotation of this.annotations) { const el = annotation.__element; const start = annotation.timeStart; @@ -63,6 +76,11 @@ class AnnotationRenderer { } } } + hideAll() { + for (const annotation of this.annotations) { + annotation.__element.setAttribute("hidden", ""); + } + } start() { window.postMessage({type: "__annotations_restored_renderer_start", updateInterval: this.updateInterval}, this.postMessageOrigin); } @@ -94,4 +112,4 @@ class AnnotationRenderer { this.stop(); this.start(); } -} \ No newline at end of file +} diff --git a/js/background.js b/js/background.js index d1885ea..dbfdc21 100644 --- a/js/background.js +++ b/js/background.js @@ -8,6 +8,7 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (videoId) { chrome.tabs.sendMessage(tab.id, {type: "check_description_for_annotations"}, response => { + console.log(response); if (response.requestAnnotations) { const requestUrl = annotationsEndpoint + videoId; console.log(`Loading annotations for '${videoId}' from '${requestUrl}'`); @@ -22,7 +23,8 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { chrome.tabs.sendMessage(tab.id, {type: "annotations_unavailable"}); } }).catch(e => { - throw e; + console.log("Annotation data is unavailable for this video"); + chrome.tabs.sendMessage(tab.id, {type: "annotations_unavailable"}); }); } else { diff --git a/js/content.js b/js/content.js index 4eedb1f..0c5e29a 100644 --- a/js/content.js +++ b/js/content.js @@ -1,4 +1,5 @@ const annotationParser = new AnnotationParser(); +let renderer; function setupExternalScript() { // must be done this way due to the "x-ray" mode the content scripts are run in @@ -11,17 +12,21 @@ function setupExternalScript() { const data = e.data; const type = data.type; if (type === "__annotations_restored_renderer_start") { - rendererUpdateIntervalId = setInterval(() => { - const videoTime = player.getCurrentTime(); - const updateEvent = new CustomEvent("__annotations_restored_renderer_update", { - detail: {videoTime} - }); - window.dispatchEvent(updateEvent) - }, data.updateInterval); + if (!rendererUpdateIntervalId) { + rendererUpdateIntervalId = setInterval(() => { + const videoTime = player.getCurrentTime(); + const updateEvent = new CustomEvent("__annotations_restored_renderer_update", { + detail: {videoTime} + }); + window.dispatchEvent(updateEvent) + }, data.updateInterval); + } } else if (type === "__annotations_restored_renderer_stop") { - clearInterval(rendererUpdateIntervalId); - rendererUpdateIntervalId = null; + if (rendererUpdateIntervalId) { + clearInterval(rendererUpdateIntervalId); + rendererUpdateIntervalId = null; + } } else if (type === "__annotations_restored_renderer_seek_to") { player.seekTo(data.seconds); @@ -43,6 +48,8 @@ function setupExternalScript() { document.body.append(script); } +setupExternalScript(); + function getAnnotationsFromDescription() { return new Promise((resolve, reject) => { let intervalCount = 0; @@ -94,9 +101,9 @@ function getAnnotationsFromDescription() { } chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + console.log(request); if (request.type === "check_description_for_annotations") { getAnnotationsFromDescription().then(annotations => { - setupExternalScript(); const videoContainer = document.getElementById("movie_player"); renderer = new AnnotationRenderer(annotations, videoContainer, videoContainer); renderer.start(); @@ -112,7 +119,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { const annotationData = request.xml; if (annotationData) { console.info("Received annotation data from server"); - setupExternalScript(); const annotationDom = annotationParser.xmlToDom(annotationData); const annotationElements = annotationDom.getElementsByTagName("annotation"); @@ -126,4 +132,31 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { else if (request.type === "annotations_unavailable") { console.info("Annotation data for this video is unavailable"); } + // popup annotation loading + else if (request.type === "popup_load_youtube" && request.data) { + console.info("loading youtube data"); + const annotationDom = annotationParser.xmlToDom(request.data); + const annotationElements = annotationDom.getElementsByTagName("annotation"); + const annotations = annotationParser.parseYoutubeFormat(annotationElements); + if (!renderer) { + const videoContainer = document.getElementById("movie_player"); + renderer = new AnnotationRenderer(annotations, videoContainer, "https://www.youtube.com/"); + renderer.start(); + } + else { + renderer.changeAnnotationData(annotations); + } + } + else if (request.type === "popup_load_converted" && request.data) { + console.info("loading converted data"); + const annotations = annotationParser.deserializeAnnotationList(request.data); + if (!renderer) { + const videoContainer = document.getElementById("movie_player"); + renderer = new AnnotationRenderer(annotations, videoContainer, "https://www.youtube.com/"); + renderer.start(); + } + else { + renderer.changeAnnotationData(annotations); + } + } }); diff --git a/manifest.json b/manifest.json index 0859d6f..1ca2d0b 100644 --- a/manifest.json +++ b/manifest.json @@ -9,6 +9,11 @@ "permissions": [ "tabs" ], + "browser_action": { + "default_title": "Annotations Restored", + "default_popup": "popup/index.html" + }, + "content_scripts": [{ "matches": ["*://www.youtube.com/watch?*"], "js": ["js/AnnotationParser.js", "js/AnnotationRenderer.js", "js/content.js"], diff --git a/popup/index.css b/popup/index.css new file mode 100644 index 0000000..805218a --- /dev/null +++ b/popup/index.css @@ -0,0 +1,16 @@ +textarea { + width: 98%; + height: 5rem; +} +input { + margin-bottom: 0.5rem; +} +#buttons { + display: flex; + width: 100%; + + justify-content: center; +} +#buttons > input:first-child { + margin-right: 1rem; +} diff --git a/popup/index.html b/popup/index.html new file mode 100644 index 0000000..baccaa9 --- /dev/null +++ b/popup/index.html @@ -0,0 +1,22 @@ + + + + + + + +

YouTube Annotation Data

+ + +

Converted YouTube Annotation Data

+ + + +
+ + +
+ + + + diff --git a/popup/index.js b/popup/index.js new file mode 100644 index 0000000..ac9a33b --- /dev/null +++ b/popup/index.js @@ -0,0 +1,40 @@ +const youtubeFile = document.getElementById("youtube-file"); +const youtubeTextArea = document.getElementById("youtube-data"); + +const convertedFile = document.getElementById("converted-file"); +const convertedTextArea = document.getElementById("converted-data"); + +const loadYoutube = document.getElementById("load-youtube"); +const loadConverted = document.getElementById("load-converted"); + +loadYoutube.addEventListener("click", e => { + const data = youtubeTextArea.value; + sendLoadMessage("popup_load_youtube", data); +}); +loadConverted.addEventListener("click", e => { + const data = convertedTextArea.value; + sendLoadMessage("popup_load_converted", data); +}); + +youtubeFile.addEventListener("change", e => loadFileData(youtubeFile.files[0], youtubeTextArea)); +convertedFile.addEventListener("change", e => loadFileData(convertedFile.files[0], convertedTextArea)); + +function loadFileData(file, textarea) { + const reader = new FileReader(); + reader.addEventListener("load", e => { + textarea.value = reader.result; + }); + reader.readAsText(file); +} + +function sendLoadMessage(type, data) { + if (!type || !data) return; + console.log("sending load message:", type); + chrome.tabs.query({currentWindow: true, active: true}, tabs => { + if (tabs[0]) { + const tab = tabs[0]; + console.log(tab); + chrome.tabs.sendMessage(tab.id, {type, data}); + } + }); +}