Annotation Player as rudimentary Web Component

This commit is contained in:
Ruben van de Ven 2023-02-23 11:17:24 +01:00
parent 55475451cf
commit daf0e0dfd4
2 changed files with 186 additions and 50 deletions

View File

@ -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;
}
</style>
<script src="assets/nouislider-15.5.0.js"></script>
<script src="assets/wNumb-1.2.0.min.js"></script>

View File

@ -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
}