Style Player component and move to annotate.js
This commit is contained in:
parent
daf0e0dfd4
commit
f7e9fd99fc
2 changed files with 247 additions and 177 deletions
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
||||||
this.scrubberEl = document.createElement('div');
|
|
||||||
this.scrubberEl.classList.add('scrubber')
|
|
||||||
this.controlsEl.appendChild(this.scrubberEl);
|
|
||||||
|
|
||||||
|
|
||||||
if(!this.config.is_player){
|
if(!this.config.is_player){
|
||||||
|
this.scrubberEl = document.createElement('div');
|
||||||
|
this.scrubberEl.classList.add('scrubber')
|
||||||
|
this.controlsEl.appendChild(this.scrubberEl);
|
||||||
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue