Annotation Player as rudimentary Web Component
This commit is contained in:
parent
55475451cf
commit
daf0e0dfd4
2 changed files with 186 additions and 50 deletions
|
@ -168,29 +168,16 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#annotations .svganim_player {
|
#annotations .svganim_player,
|
||||||
|
#annotations annotation-player {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 200px;
|
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>
|
</style>
|
||||||
<script src="assets/nouislider-15.5.0.js"></script>
|
<script src="assets/nouislider-15.5.0.js"></script>
|
||||||
<script src="assets/wNumb-1.2.0.min.js"></script>
|
<script src="assets/wNumb-1.2.0.min.js"></script>
|
||||||
|
|
|
@ -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 {
|
class AnnotationManager {
|
||||||
constructor(rootEl, tagsUrl) {
|
constructor(rootEl, tagsUrl) {
|
||||||
this.rootEl = rootEl;
|
this.rootEl = rootEl;
|
||||||
|
@ -86,7 +244,7 @@ class AnnotationManager {
|
||||||
|
|
||||||
selectTag(tag) {
|
selectTag(tag) {
|
||||||
this.clearSelectedTag();
|
this.clearSelectedTag();
|
||||||
this.selectedTag = tag;
|
this.selectedTag = tag;
|
||||||
tag.menuLiEl.classList.add('selected');
|
tag.menuLiEl.classList.add('selected');
|
||||||
this.loadAnnotationsForTag(tag)
|
this.loadAnnotationsForTag(tag)
|
||||||
}
|
}
|
||||||
|
@ -141,14 +299,12 @@ class AnnotationManager {
|
||||||
const ulEl = document.createElement('ul');
|
const ulEl = document.createElement('ul');
|
||||||
this.annotations.forEach((annotation, idx) => {
|
this.annotations.forEach((annotation, idx) => {
|
||||||
const liEl = document.createElement('li');
|
const liEl = document.createElement('li');
|
||||||
const imgEl = document.createElement('img');
|
const playerEl = new AnnotationPlayer(); //document.createElement('annotation-player');
|
||||||
const playerEl = document.createElement('div');
|
playerEl.setAnnotation(annotation);
|
||||||
const infoEl = document.createElement('span');
|
|
||||||
infoEl.classList.add('annotation-info');
|
|
||||||
|
|
||||||
const selectEl = document.createElement('input');
|
const selectEl = document.createElement('input');
|
||||||
selectEl.type = 'checkbox';
|
selectEl.type = 'checkbox';
|
||||||
selectEl.id = 'select-'+annotation.id_hash;
|
selectEl.id = 'select-' + annotation.id_hash;
|
||||||
selectEl.addEventListener('change', (ev) => {
|
selectEl.addEventListener('change', (ev) => {
|
||||||
if (ev.target.checked) {
|
if (ev.target.checked) {
|
||||||
this.addAnnotationToSelection(annotation);
|
this.addAnnotationToSelection(annotation);
|
||||||
|
@ -157,25 +313,14 @@ class AnnotationManager {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const tag = this.rootTag.find_by_id(annotation.tag);
|
const tag = this.rootTag.find_by_id(annotation.tag);
|
||||||
console.log(tag)
|
console.log(tag)
|
||||||
|
|
||||||
|
const infoEl = document.createElement('span');
|
||||||
|
infoEl.classList.add('annotation-info');
|
||||||
infoEl.innerText = `[${tag.get_name()}] ${annotation.comment}`;
|
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(playerEl);
|
||||||
liEl.appendChild(selectEl);
|
liEl.appendChild(selectEl);
|
||||||
liEl.appendChild(infoEl);
|
liEl.appendChild(infoEl);
|
||||||
|
@ -195,22 +340,26 @@ class AnnotationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
addAnnotationToSelection(annotation) {
|
addAnnotationToSelection(annotation) {
|
||||||
if (this.selectedAnnotations.indexOf(annotation) === -1){
|
if (this.selectedAnnotations.indexOf(annotation) === -1) {
|
||||||
this.selectedAnnotations.push(annotation);
|
this.selectedAnnotations.push(annotation);
|
||||||
}
|
}
|
||||||
this.annotationsEl.querySelector('#select-'+annotation.id_hash).checked = true;
|
this.annotationsEl.querySelector('#select-' + annotation.id_hash).checked = true;
|
||||||
this.buildAnnotationActions()
|
this.buildAnnotationActions()
|
||||||
}
|
}
|
||||||
removeAnnotationFromSelection(annotation) {
|
removeAnnotationFromSelection(annotation) {
|
||||||
if (this.selectedAnnotations.indexOf(annotation) !== -1){
|
if (this.selectedAnnotations.indexOf(annotation) !== -1) {
|
||||||
this.selectedAnnotations.splice(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()
|
this.buildAnnotationActions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the form items to select & move the annotations
|
||||||
|
* @returns undefined
|
||||||
|
*/
|
||||||
buildAnnotationActions() {
|
buildAnnotationActions() {
|
||||||
if(!this.actionsEl || !this.annotations.length) return
|
if (!this.actionsEl || !this.annotations.length) return
|
||||||
this.actionsEl.innerHTML = "";
|
this.actionsEl.innerHTML = "";
|
||||||
|
|
||||||
const selectAllLabelEl = document.createElement('label');
|
const selectAllLabelEl = document.createElement('label');
|
||||||
|
@ -220,7 +369,7 @@ class AnnotationManager {
|
||||||
selectAllEl.checked = this.annotations.length === this.selectedAnnotations.length;
|
selectAllEl.checked = this.annotations.length === this.selectedAnnotations.length;
|
||||||
// selectAllEl.innerText = `Select all ${this.annotations.length} items`;
|
// selectAllEl.innerText = `Select all ${this.annotations.length} items`;
|
||||||
selectAllEl.addEventListener('change', (ev) => {
|
selectAllEl.addEventListener('change', (ev) => {
|
||||||
if(ev.target.checked) {
|
if (ev.target.checked) {
|
||||||
this.annotations.forEach((a) => this.addAnnotationToSelection(a));
|
this.annotations.forEach((a) => this.addAnnotationToSelection(a));
|
||||||
} else {
|
} else {
|
||||||
this.resetSelectedAnnotations();
|
this.resetSelectedAnnotations();
|
||||||
|
@ -231,7 +380,7 @@ class AnnotationManager {
|
||||||
|
|
||||||
this.actionsEl.appendChild(selectAllLabelEl)
|
this.actionsEl.appendChild(selectAllLabelEl)
|
||||||
|
|
||||||
if(!this.selectedAnnotations.length) return;
|
if (!this.selectedAnnotations.length) return;
|
||||||
|
|
||||||
const moveLabelEl = document.createElement('label');
|
const moveLabelEl = document.createElement('label');
|
||||||
moveLabelEl.innerText = `Change tag for ${this.selectedAnnotations.length} items`
|
moveLabelEl.innerText = `Change tag for ${this.selectedAnnotations.length} items`
|
||||||
|
@ -239,7 +388,7 @@ class AnnotationManager {
|
||||||
this.rootTag.descendants().forEach((tag, i) => {
|
this.rootTag.descendants().forEach((tag, i) => {
|
||||||
const tagEl = document.createElement('option');
|
const tagEl = document.createElement('option');
|
||||||
tagEl.value = tag.id;
|
tagEl.value = tag.id;
|
||||||
if(tag.id == this.selectedTag.id){
|
if (tag.id == this.selectedTag.id) {
|
||||||
tagEl.selected = true;
|
tagEl.selected = true;
|
||||||
}
|
}
|
||||||
tagEl.innerHTML = tag.get_indented_name();
|
tagEl.innerHTML = tag.get_indented_name();
|
||||||
|
@ -304,19 +453,19 @@ class AnnotationManager {
|
||||||
async moveSelectedAnnotations(tag) {
|
async moveSelectedAnnotations(tag) {
|
||||||
// TODO: add button for this
|
// TODO: add button for this
|
||||||
// alert(`This doesn't work yet! (move to tag ${tag.get_name()})`)
|
// alert(`This doesn't work yet! (move to tag ${tag.get_name()})`)
|
||||||
|
|
||||||
await Promise.all(this.selectedAnnotations.map(async (annotation) => {
|
await Promise.all(this.selectedAnnotations.map(async (annotation) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('tag_id', tag.id)
|
formData.append('tag_id', tag.id)
|
||||||
return await fetch('/annotation/'+annotation.id, {
|
return await fetch('/annotation/' + annotation.id, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
// headers: {
|
// headers: {
|
||||||
// 'Content-Type': 'application/json'
|
// 'Content-Type': 'application/json'
|
||||||
// },
|
// },
|
||||||
body: formData //JSON.stringify({'tag_id': tag.id})
|
body: formData //JSON.stringify({'tag_id': tag.id})
|
||||||
}).catch((e) => alert('Something went wrong saving the tags'));
|
}).catch((e) => alert('Something went wrong saving the tags'));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.loadAnnotationsForTag(this.selectedTag)
|
this.loadAnnotationsForTag(this.selectedTag)
|
||||||
this.loadTags() //updates the counts
|
this.loadTags() //updates the counts
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue