From 9a4a89c9c764e448f3ae5fbc79b558a3811f9a29 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Mon, 27 Feb 2023 16:38:40 +0100 Subject: [PATCH] Crop option cycle button (TODO: pollish) --- app/svganim/strokes.py | 2 +- app/www/annotate.js | 79 ++++++++++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/app/svganim/strokes.py b/app/svganim/strokes.py index 729b471..dc33aa6 100644 --- a/app/svganim/strokes.py +++ b/app/svganim/strokes.py @@ -190,8 +190,8 @@ class AnimationSlice: "audio": self.getAudioDict() if self.audio else None, } if include_full_drawing: - print(type(self.drawing)) drawing["background"] = [s.get_as_d() for s in self.drawing.get_animation().strokes] + drawing["background_bounding_box"] = self.drawing.get_animation().get_bounding_box().__dict__ return drawing def getAudioDict(self): diff --git a/app/www/annotate.js b/app/www/annotate.js index e598faa..6938e3f 100644 --- a/app/www/annotate.js +++ b/app/www/annotate.js @@ -125,6 +125,18 @@ class StrokeSlice { } } +const CropOptions = { + Fit_Selection: 'selection', + Follow_Drawing: 'follow', + Whole_Drawing: 'whole', + }; + +const CropDescriptions = { + selection: 'Crop to annotation', + follow: 'Follow drawing canvas', + whole: 'Show whole drawing', +} + class Annotator extends EventTarget { constructor(wrapperEl, tagFile, fileurl, config) { fileurl = fileurl.replace("&", "&"); // little hack: tornadoweb does this automatically for some reason @@ -132,7 +144,8 @@ class Annotator extends EventTarget { this.config = { is_player: config && config.hasOwnProperty('is_player') ? config.is_player : false, // in player mode annotations are not loaded, nor is the annotator shown - crop_to_fit: config && config.hasOwnProperty('crop_to_fit') ? config.crop_to_fit : false, // don't animate viewport, but show the whole drawing + crop_to_fit: config && config.hasOwnProperty('crop_to_fit') ? config.crop_to_fit : false, // DEPRECATED don't animate viewport, but show the whole drawing + crop: config && config.hasOwnProperty('crop') && Object.values(CropOptions).indexOf(config.crop) !== -1 ? config.crop : CropOptions.Fit_Selection, // don't animate viewport, but show the whole drawing autoplay: config && config.hasOwnProperty('autoplay') ? config.autoplay : false, // immediately start playback } @@ -168,7 +181,7 @@ class Annotator extends EventTarget { this.svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.wrapperEl.appendChild(this.svgEl); this.wrapperEl.classList.add(this.config.is_player ? "svganim_player" : "svganim_annotator"); - this.wrapperEl.classList.add(this.config.crop_to_fit ? "cropped-to-selection" : "follow-drawing"); + this.wrapperEl.classList.add("crop-" + this.config.crop); this.controlsEl = document.createElement('div'); @@ -234,10 +247,10 @@ class Annotator extends EventTarget { toggleFutureEl.addEventListener('click', () => this.wrapperEl.classList.toggle('hide-drawing-preview')); extraControlsEl.appendChild(toggleFutureEl); - const toggleCropPlayerEl = document.createElement('li'); - toggleCropPlayerEl.innerText = "Crop to selection"; - toggleCropPlayerEl.addEventListener('click', () => this.toggleCrop()); - extraControlsEl.appendChild(toggleCropPlayerEl); + this.toggleCropPlayerEl = document.createElement('li'); + this.toggleCropPlayerEl.innerText = CropDescriptions[this.config.crop]; + this.toggleCropPlayerEl.addEventListener('click', () => this.toggleCrop()); + extraControlsEl.appendChild(this.toggleCropPlayerEl); extraEl.appendChild(extraControlsEl); @@ -754,6 +767,7 @@ class Annotator extends EventTarget { this.filename = drawing.file; this.strokes = drawing.shape.map(s => new Stroke(s['color'], s['points'])); this.backgroundStrokes = drawing.hasOwnProperty('background') ? drawing.background : []; + this.backgroundBoundingBox = drawing.hasOwnProperty('background_bounding_box') ? drawing.background_bounding_box : null; this.viewboxes = drawing.viewboxes; this.currentPathI = null; this.currentPointI = null; @@ -1119,8 +1133,10 @@ class Annotator extends EventTarget { } updateViewbox() { - if (this.config.crop_to_fit) { + if (this.config.crop == CropOptions.Fit_Selection) { this.svgEl.setAttribute('viewBox', `${this.bounding_box.x} ${this.bounding_box.y} ${this.bounding_box.width} ${this.bounding_box.height}`); + } else if (this.config.crop == CropOptions.Whole_Drawing && this.backgroundBoundingBox) { + this.svgEl.setAttribute('viewBox', `${this.backgroundBoundingBox.x} ${this.backgroundBoundingBox.y} ${this.backgroundBoundingBox.width} ${this.backgroundBoundingBox.height}`); } else { let x, y, w, h; if (this.currentViewboxI !== null) { @@ -1138,18 +1154,31 @@ class Annotator extends EventTarget { } } - setCrop(crop_to_fit) { - this.config.crop_to_fit = Boolean(crop_to_fit); - if (this.config.crop_to_fit) { - this.wrapperEl.classList.add('cropped-to-selection'); - } else { - this.wrapperEl.classList.remove('cropped-to-selection'); + setCrop(crop_option) { + if(Object.values(CropOptions).indexOf(crop_option) === -1) { + console.error('invalid crop option', crop_option); + crop_option = CropOptions.Fit_Selection; } + + this.config.crop = crop_option; + for(let option of Object.values(CropOptions)) { + if (this.config.crop == option) { + this.wrapperEl.classList.add('crop-' + option); + } else { + this.wrapperEl.classList.remove('crop-' + option); + } + } + + this.toggleCropPlayerEl.innerText = CropDescriptions[this.config.crop]; this.updateViewbox(); } toggleCrop() { - this.setCrop(!this.config.crop_to_fit); + console.log(this.config.crop, Object.values(CropOptions), Object.values(CropOptions).indexOf(this.config.crop)) + const i = (Object.values(CropOptions).indexOf(this.config.crop) + 1) % Object.keys(CropOptions).length; + const newCrop = Object.values(CropOptions)[i]; + console.log(i, newCrop); + this.setCrop(newCrop); } getNextPosition(path_i, point_i) { @@ -1527,7 +1556,7 @@ class AnnotationPlayer extends HTMLElement { const config = { is_player: true, - crop_to_fit: this.hasAttribute('data-no-crop') ? false : true, + crop: this.hasAttribute('data-crop') ? this.getAttribute('data-crop') : null, autoplay: true, } @@ -1689,7 +1718,7 @@ class AnnotationPlayer extends HTMLElement { .background{ visibility: hidden } - .play:not(.cropped-to-selection) .background{ + .play:not(.crop-selection) .background{ visibility: visible; } @@ -1729,7 +1758,7 @@ class AnnotationPlayer extends HTMLElement { /*text-decoration: line-through;*/ font-weight:bold; } - .play.cropped-to-selection details > ul li:nth-child(2){ + .play.crop-selection details > ul li:nth-child(2){ /*text-decoration: line-through;*/ font-weight:bold; } @@ -1747,26 +1776,18 @@ class AnnotationPlayer extends HTMLElement { 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') { + // console.log(name, oldValue, newValue); + if (name == 'data-crop') { if (!this.annotator) { return; } - this.annotator.setCrop(!this.hasAttribute('data-no-crop')); + this.annotator.setCrop(this.hasAttribute('data-crop')); } } // required for attributeChangedCallback() - static get observedAttributes() { return ['data-no-crop']; } + static get observedAttributes() { return ['data-crop']; } }