Style Player component and move to annotate.js

This commit is contained in:
Ruben van de Ven 2023-02-23 13:25:03 +01:00
parent daf0e0dfd4
commit f7e9fd99fc
2 changed files with 247 additions and 177 deletions

View file

@ -130,10 +130,11 @@ class Annotator extends EventTarget {
time *= -1; time *= -1;
} }
const s = Math.floor(time / 1000); const s = Math.floor(time / 1000);
const minutes = Math.floor(s / 60); const minutes = String(Math.floor(s / 60)).padStart(2, '0');
const seconds = s - minutes * 60; const seconds = String(s - minutes * 60).padStart(2, '0');
const ms = Math.floor((time / 1000 - s) * 1000); // show miliseconds only in annotator
return `${neg}${minutes}:${seconds}.${ms}`; const ms = !this.config.is_player ? "." + String(Math.floor((time / 1000 - s) * 1000)).padStart(3, '0') : "";
return `${neg}${minutes}:${seconds}${ms}`;
}, },
undo: (tc) => { undo: (tc) => {
let [rest, ms] = tc.split(/[\.\,]/); let [rest, ms] = tc.split(/[\.\,]/);
@ -143,7 +144,7 @@ class Annotator extends EventTarget {
ms += v * factor; ms += v * factor;
factor *= 60; factor *= 60;
}); });
return `${ms}`; return `${ ms } `;
} }
}); });
@ -192,12 +193,12 @@ class Annotator extends EventTarget {
ev.preventDefault(); // we don't want to spacebar, as this is captured in the overall keydown event ev.preventDefault(); // we don't want to spacebar, as this is captured in the overall keydown event
}) })
if(!this.config.is_player){
this.scrubberEl = document.createElement('div'); this.scrubberEl = document.createElement('div');
this.scrubberEl.classList.add('scrubber') this.scrubberEl.classList.add('scrubber')
this.controlsEl.appendChild(this.scrubberEl); this.controlsEl.appendChild(this.scrubberEl);
if(!this.config.is_player){
this.annotationsEl = document.createElement('div'); this.annotationsEl = document.createElement('div');
this.annotationsEl.classList.add('annotations') this.annotationsEl.classList.add('annotations')
this.controlsEl.appendChild(this.annotationsEl); this.controlsEl.appendChild(this.annotationsEl);
@ -339,7 +340,7 @@ class Annotator extends EventTarget {
if (this.selectedAnnotationI == annotation_i) { if (this.selectedAnnotationI == annotation_i) {
this.annotationEl.classList.add('selected'); this.annotationEl.classList.add('selected');
} }
this.annotationEl.title = `[${annotation.tag}] ${annotation.comment}`; this.annotationEl.title = `[${ annotation.tag }] ${ annotation.comment } `;
this.annotationEl.addEventListener('mouseover', (e) => { this.annotationEl.addEventListener('mouseover', (e) => {
@ -425,7 +426,7 @@ class Annotator extends EventTarget {
// draw full stroke of annotation // draw full stroke of annotation
console.debug('setInOut'); console.debug('setInOut');
this.drawStrokePosition(this.inPointPosition, this.outPointPosition); this.drawStrokePosition(this.inPointPosition, this.outPointPosition);
console.debug([`${this.inPointTimeMs}`, `${this.outPointTimeMs}`]) console.debug([`${ this.inPointTimeMs } `, `${ this.outPointTimeMs } `])
this.slider.set([this.inPointTimeMs, this.outPointTimeMs]); this.slider.set([this.inPointTimeMs, this.outPointTimeMs]);
// console.debug(this.selectedAnnotation); // console.debug(this.selectedAnnotation);
@ -460,7 +461,7 @@ class Annotator extends EventTarget {
.then(data => { .then(data => {
if (!this.config.is_player) { if (!this.config.is_player) {
const metadata_req = new Request(`/annotations/${data.file}`, { const metadata_req = new Request(`/ annotations / ${ data.file } `, {
method: 'GET', method: 'GET',
}); });
return fetch(metadata_req) return fetch(metadata_req)
@ -1032,7 +1033,7 @@ class Annotator extends EventTarget {
// } // }
// for (let index = 0; index < inPath_i; index++) { // for (let index = 0; index < inPath_i; index++) {
// const pathEl = this.svgEl.querySelector(`.path${index}`); // const pathEl = this.svgEl.querySelector(`.path${ index } `);
// if (pathEl) { // if (pathEl) {
// pathEl.classList.add('before_in'); // pathEl.classList.add('before_in');
// } // }
@ -1044,7 +1045,7 @@ class Annotator extends EventTarget {
// const path = this.strokes[path_i]; // const path = this.strokes[path_i];
// // console.log(path); // // console.log(path);
// let pathEl = this.svgEl.querySelector(`.path${path_i}`); // let pathEl = this.svgEl.querySelector(`.path${ path_i } `);
// if (!pathEl) { // if (!pathEl) {
// pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path'); // pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// pathEl.style.stroke = path.color; // pathEl.style.stroke = path.color;
@ -1072,7 +1073,7 @@ class Annotator extends EventTarget {
updateViewbox() { updateViewbox() {
if (this.config.crop_to_fit) { if (this.config.crop_to_fit) {
this.svgEl.setAttribute('viewBox', `${this.bounding_box.x} ${this.bounding_box.y} ${this.bounding_box.width} ${this.bounding_box.height}`); this.svgEl.setAttribute('viewBox', `${ this.bounding_box.x } ${ this.bounding_box.y } ${ this.bounding_box.width } ${ this.bounding_box.height } `);
} else { } else {
let x,y,w,h; let x,y,w,h;
if(this.currentViewboxI !== null) { if(this.currentViewboxI !== null) {
@ -1086,15 +1087,19 @@ class Annotator extends EventTarget {
w = this.dimensions[0], w = this.dimensions[0],
h = this.dimensions[1]; h = this.dimensions[1];
} }
this.svgEl.setAttribute('viewBox', `${x} ${y} ${w} ${h}`); this.svgEl.setAttribute('viewBox', `${ x } ${ y } ${ w } ${ h } `);
} }
} }
toggleCrop(){ setCrop(crop_to_fit) {
this.config.crop_to_fit = !this.config.crop_to_fit; this.config.crop_to_fit = Boolean(crop_to_fit);
this.updateViewbox(); this.updateViewbox();
} }
toggleCrop(){
this.setCrop(!this.config.crop_to_fit);
}
getNextPosition(path_i, point_i) { getNextPosition(path_i, point_i) {
const path = this.strokes[path_i]; const path = this.strokes[path_i];
let next_path, next_point; let next_path, next_point;
@ -1446,3 +1451,226 @@ class Annotator extends EventTarget {
} }
class AnnotationPlayer extends HTMLElement {
constructor() {
super();
// We don't use constructor() because an element's attributes
// are unavailable until connected to the DOM.
// attributes:
// - data-no-crop
// - autoplay
// - preload
// - data-poster-src
// - data-annotation-url
}
connectedCallback() {
// Create a shadow root
this.attachShadow({ mode: "open" });
const imgEl = document.createElement('img');
const playerEl = document.createElement('div');
const config = {
is_player: true,
crop_to_fit: this.hasAttribute('data-no-crop') ? false : true,
autoplay: true,
}
imgEl.src = this.getAttribute('data-poster-url');
imgEl.addEventListener('click', () => {
imgEl.style.display = 'none';
this.annotator = new Annotator(
playerEl,
null, //"tags.json",
this.getAttribute('data-annotation-url'),
config
);
})
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;
background: rgba(0,0,0,.5);
border-radius: 3px;
}
.timecode {
width: 30px;
font-size: 8px;
background: none;
border: none;
color: white;
}
.controls--playback input[type='range'] {
flex-grow: 1;
-webkit-appearance: none;
background: none;
}
input[type="range"]::-webkit-slider-runnable-track,
input[type="range"]::-moz-range-track {
background: lightgray;
height: 5px;
border-radius: 3px;
}
input[type="range"]::-moz-range-progress {
background-color: white;
height: 5px;
border-radius: 3px 0 0 3px;
}
input[type="range"]::-webkit-slider-thumb,
input[type="range"]::-moz-range-thumb {
-webkit-appearance: none;
height: 15px;
width: 15px;
background: white;
margin-top: -5px;
border-radius: 50%;
border:none;
}
.controls button.paused,
.controls button.playing {
order: -1;
width: 30px;
height: 30px;
border: none;
background: none;
color: white;
line-height: 1;
}
.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;
this.setAttribute('data-annotation-url', annotation.url)
this.setAttribute('data-poster-url', `/annotation/${annotation.id}.svg`)
}
toggleCrop() {
if (this.hasAttribute('data-no-crop')) {
this.removeAttribute('data-no-crop');
} else {
this.setAttribute('data-no-crop', true);
}
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(name, oldValue, newValue);
if(name == 'data-no-crop'){
if(!this.annotator) {
return;
}
this.annotator.setCrop(!this.hasAttribute('data-no-crop'));
}
}
// required for attributeChangedCallback()
static get observedAttributes() { return ['data-no-crop']; }
}
window.customElements.define('annotation-player', AnnotationPlayer);

View file

@ -1,161 +1,3 @@
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;