diff --git a/app/templates/index.html b/app/templates/index.html
index 6395ed1..8914d49 100644
--- a/app/templates/index.html
+++ b/app/templates/index.html
@@ -168,29 +168,16 @@
padding: 10px;
}
- #annotations .svganim_player {
+ #annotations .svganim_player,
+ #annotations annotation-player {
display: inline-block;
position: relative;
width: 300px;
height: 200px;
- overflow: hidden;
- padding: 10px;
-
- background: white;
+
}
- #annotations .svganim_player svg {
- width: 100%;
- height: 100%;
- }
-
- #annotations .svganim_player.play:not(.loading) .controls {
- visibility: hidden;
- }
-
- #annotations .svganim_player:hover .controls {
- visibility: visible !important;
- }
+
diff --git a/app/www/annotations.js b/app/www/annotations.js
index 2227e66..f09b3de 100644
--- a/app/www/annotations.js
+++ b/app/www/annotations.js
@@ -1,3 +1,161 @@
+class AnnotationPlayer extends HTMLElement {
+ constructor() {
+ super();
+ // We don't use constructor() because an element's attributes
+ // are unavailable until connected to the DOM.
+ }
+
+ connectedCallback() {
+ // Create a shadow root
+ this.attachShadow({ mode: "open" });
+
+ const imgEl = document.createElement('img');
+ const playerEl = document.createElement('div');
+
+
+ imgEl.src = `/annotation/${this.annotation.id}.svg`;
+ imgEl.addEventListener('click', () => {
+ imgEl.style.display = 'none';
+ new Annotator(
+ playerEl,
+ "tags.json",
+ this.annotation.url,
+ { is_player: true, crop_to_fit: true, autoplay: true }
+ );
+ })
+
+ playerEl.classList.add('play');
+
+ const styleEl = document.createElement('style');
+ styleEl.textContent = `
+ :host{
+ overflow: hidden;
+ padding: 10px;
+
+ background: white;
+ }
+
+ svg, img {
+ width: 100%;
+ height: 100%;
+ }
+
+ .play:not(.loading) .controls {
+ visibility: hidden;
+ }
+
+ :host(:hover) .controls {
+ visibility: visible !important;
+ }
+
+ .controls--playback {
+ /* display:flex; */
+ position: relative;
+ }
+
+ .timecode {
+ position: absolute;
+ right: 100%;
+ width: 5%;
+ font-size: 8px;
+ }
+
+
+ .controls--playback input[type='range'] {
+ /* position: absolute;
+ z-index: 100;
+ bottom: 0;
+ left: 0;
+ right: 0; */
+ width: 100%;
+
+ }
+
+ .controls button.paused,
+ .controls button.playing {
+ position: absolute;
+ left: 100%;
+ width: 30px;
+ height: 30px;
+ }
+
+ .controls button.paused::before {
+ content: '⏵';
+ }
+
+ .controls button.playing::before {
+ content: '⏸';
+ }
+
+
+ .loading .controls button:is(.playing, .paused)::before {
+ content: '↺';
+ display: inline-block;
+ animation: rotate 1s infinite;
+ }
+
+ @keyframes rotate {
+ 0% {
+ transform: rotate(359deg)
+ }
+
+ 100% {
+ transform: rotate(0deg)
+ }
+ }
+
+ .controls {
+ position: absolute !important;
+ z-index: 100;
+ bottom: 10px;
+ left: 5%;
+ right: 0;
+ width: 90%;
+ }
+ svg .background {
+ fill: white
+ }
+
+ path {
+ fill: none;
+ stroke: gray;
+ stroke-width: 1mm;
+ stroke-linecap: round;
+ }
+
+ g.before path {
+ opacity: 0.5;
+ stroke: gray !important;
+ }
+
+ g.after path,
+ path.before_in {
+ opacity: .1;
+ stroke: gray !important;
+ }
+
+ .gray {
+ position: absolute;
+ background: rgba(255, 255, 255, 0.7);
+ }
+
+
+ `;
+
+ this.shadowRoot.appendChild(styleEl);
+ this.shadowRoot.appendChild(imgEl);
+ this.shadowRoot.appendChild(playerEl);
+
+ }
+
+ setAnnotation(annotation) {
+ this.annotation = annotation;
+ }
+}
+
+window.customElements.define('annotation-player', AnnotationPlayer);
+
+
class AnnotationManager {
constructor(rootEl, tagsUrl) {
this.rootEl = rootEl;
@@ -86,7 +244,7 @@ class AnnotationManager {
selectTag(tag) {
this.clearSelectedTag();
- this.selectedTag = tag;
+ this.selectedTag = tag;
tag.menuLiEl.classList.add('selected');
this.loadAnnotationsForTag(tag)
}
@@ -141,14 +299,12 @@ class AnnotationManager {
const ulEl = document.createElement('ul');
this.annotations.forEach((annotation, idx) => {
const liEl = document.createElement('li');
- const imgEl = document.createElement('img');
- const playerEl = document.createElement('div');
- const infoEl = document.createElement('span');
- infoEl.classList.add('annotation-info');
+ const playerEl = new AnnotationPlayer(); //document.createElement('annotation-player');
+ playerEl.setAnnotation(annotation);
const selectEl = document.createElement('input');
selectEl.type = 'checkbox';
- selectEl.id = 'select-'+annotation.id_hash;
+ selectEl.id = 'select-' + annotation.id_hash;
selectEl.addEventListener('change', (ev) => {
if (ev.target.checked) {
this.addAnnotationToSelection(annotation);
@@ -157,25 +313,14 @@ class AnnotationManager {
}
})
+
const tag = this.rootTag.find_by_id(annotation.tag);
console.log(tag)
+ const infoEl = document.createElement('span');
+ infoEl.classList.add('annotation-info');
infoEl.innerText = `[${tag.get_name()}] ${annotation.comment}`;
- imgEl.src = `/annotation/${annotation.id}.svg`;
- imgEl.addEventListener('click', () => {
- imgEl.style.display = 'none';
- new Annotator(
- playerEl,
- "tags.json",
- annotation.url,
- { is_player: true, crop_to_fit: true, autoplay: true }
- );
- })
-
- playerEl.classList.add('play');
-
- liEl.appendChild(imgEl);
liEl.appendChild(playerEl);
liEl.appendChild(selectEl);
liEl.appendChild(infoEl);
@@ -195,22 +340,26 @@ class AnnotationManager {
}
addAnnotationToSelection(annotation) {
- if (this.selectedAnnotations.indexOf(annotation) === -1){
+ if (this.selectedAnnotations.indexOf(annotation) === -1) {
this.selectedAnnotations.push(annotation);
}
- this.annotationsEl.querySelector('#select-'+annotation.id_hash).checked = true;
+ this.annotationsEl.querySelector('#select-' + annotation.id_hash).checked = true;
this.buildAnnotationActions()
}
removeAnnotationFromSelection(annotation) {
- if (this.selectedAnnotations.indexOf(annotation) !== -1){
+ if (this.selectedAnnotations.indexOf(annotation) !== -1) {
this.selectedAnnotations.splice(this.selectedAnnotations.indexOf(annotation), 1)
}
- this.annotationsEl.querySelector('#select-'+annotation.id_hash).checked = false;
+ this.annotationsEl.querySelector('#select-' + annotation.id_hash).checked = false;
this.buildAnnotationActions()
}
-
+
+ /**
+ * Build the form items to select & move the annotations
+ * @returns undefined
+ */
buildAnnotationActions() {
- if(!this.actionsEl || !this.annotations.length) return
+ if (!this.actionsEl || !this.annotations.length) return
this.actionsEl.innerHTML = "";
const selectAllLabelEl = document.createElement('label');
@@ -220,7 +369,7 @@ class AnnotationManager {
selectAllEl.checked = this.annotations.length === this.selectedAnnotations.length;
// selectAllEl.innerText = `Select all ${this.annotations.length} items`;
selectAllEl.addEventListener('change', (ev) => {
- if(ev.target.checked) {
+ if (ev.target.checked) {
this.annotations.forEach((a) => this.addAnnotationToSelection(a));
} else {
this.resetSelectedAnnotations();
@@ -231,7 +380,7 @@ class AnnotationManager {
this.actionsEl.appendChild(selectAllLabelEl)
- if(!this.selectedAnnotations.length) return;
+ if (!this.selectedAnnotations.length) return;
const moveLabelEl = document.createElement('label');
moveLabelEl.innerText = `Change tag for ${this.selectedAnnotations.length} items`
@@ -239,7 +388,7 @@ class AnnotationManager {
this.rootTag.descendants().forEach((tag, i) => {
const tagEl = document.createElement('option');
tagEl.value = tag.id;
- if(tag.id == this.selectedTag.id){
+ if (tag.id == this.selectedTag.id) {
tagEl.selected = true;
}
tagEl.innerHTML = tag.get_indented_name();
@@ -304,19 +453,19 @@ class AnnotationManager {
async moveSelectedAnnotations(tag) {
// TODO: add button for this
// alert(`This doesn't work yet! (move to tag ${tag.get_name()})`)
-
+
await Promise.all(this.selectedAnnotations.map(async (annotation) => {
const formData = new FormData();
formData.append('tag_id', tag.id)
- return await fetch('/annotation/'+annotation.id, {
+ return await fetch('/annotation/' + annotation.id, {
method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
body: formData //JSON.stringify({'tag_id': tag.id})
}).catch((e) => alert('Something went wrong saving the tags'));
- }));
-
+ }));
+
this.loadAnnotationsForTag(this.selectedTag)
this.loadTags() //updates the counts
}