Compare commits

..

No commits in common. "9a4a89c9c764e448f3ae5fbc79b558a3811f9a29" and "4d08b0b4adeab029cb89eead77e3c4a79026f121" have entirely different histories.

3 changed files with 39 additions and 90 deletions

View file

@ -119,7 +119,7 @@ class Drawing:
for p in event["points"]], for p in event["points"]],
) )
) )
return AnimationSlice(self, [self.id, None, None], strokes, viewboxes, audioslice=self.get_audio()) return AnimationSlice([self.id, None, None], strokes, viewboxes, audioslice=self.get_audio())
def get_metadata(self): def get_metadata(self):
canvas = self.get_canvas_metadata() canvas = self.get_canvas_metadata()
@ -159,17 +159,17 @@ class AnimationSlice:
# either a whole drawing or the result of applying an annotation to a drawing (an excerpt) # either a whole drawing or the result of applying an annotation to a drawing (an excerpt)
# TODO rename to AnimationSlice to include audio as well # TODO rename to AnimationSlice to include audio as well
def __init__( def __init__(
self, drawing: Drawing, slice_id: SliceId, strokes: list[Stroke], viewboxes: list[TimedViewbox] = [], t_in: float = 0, t_out: float = None, audioslice: AudioSlice = None self, slice_id: SliceId, strokes: list[Stroke], viewboxes: list[TimedViewbox] = [], t_in: float = 0, t_out: float = None, audioslice: AudioSlice = None
) -> None: ) -> None:
self.drawing = drawing
self.id = slice_id self.id = slice_id
self.strokes = strokes self.strokes = strokes
self.viewboxes = viewboxes self.viewboxes = viewboxes
self.t_in = t_in self.t_in = t_in
self.t_out = t_out self.t_out = t_out
self.audio = audioslice self.audio = audioslice
# TODO: Audio
def asDict(self, include_full_drawing=False) -> dict: def asDict(self) -> dict:
"""Can be used to json-ify the animation-slice """Can be used to json-ify the animation-slice
""" """
@ -187,11 +187,8 @@ class AnimationSlice:
"shape": [s.asDict() for s in self.strokes], "shape": [s.asDict() for s in self.strokes],
"viewboxes": boxes, "viewboxes": boxes,
"bounding_box": self.get_bounding_box().__dict__, "bounding_box": self.get_bounding_box().__dict__,
"audio": self.getAudioDict() if self.audio else None, "audio": self.getAudioDict() if self.audio else None
} }
if include_full_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 return drawing
def getAudioDict(self): def getAudioDict(self):
@ -252,7 +249,7 @@ class AnimationSlice:
viewboxes = self.getViewboxesSlice(t_in, t_out) viewboxes = self.getViewboxesSlice(t_in, t_out)
audio = self.audio.getSlice(t_in, t_out) if self.audio else None audio = self.audio.getSlice(t_in, t_out) if self.audio else None
return AnimationSlice(self.drawing, [self.id[0], t_in, t_out], strokes, viewboxes, t_in, t_out, audio) return AnimationSlice([self.id[0], t_in, t_out], strokes, viewboxes, t_in, t_out, audio)
def get_as_svg_dwg(self) -> svgwrite.Drawing: def get_as_svg_dwg(self) -> svgwrite.Drawing:
box = self.get_bounding_box() box = self.get_bounding_box()
@ -409,7 +406,7 @@ class AudioSlice:
self.t_out = t_out # in ms self.t_out = t_out # in ms
self.offset = offset # in ms TODO: use from self.drawing metadata self.offset = offset # in ms TODO: use from self.drawing metadata
def getSlice(self, t_in: float, t_out: float) -> AudioSlice: def getSlice(self, t_in: float, t_out: float) -> AnimationSlice:
return AudioSlice(self.filename, self.drawing, t_in, t_out, self.offset) return AudioSlice(self.filename, self.drawing, t_in, t_out, self.offset)
def asDict(self): def asDict(self):

View file

@ -280,7 +280,7 @@ class ExportHandler(tornado.web.RequestHandler):
logger.info('write json') logger.info('write json')
data = animation.asDict(include_full_drawing=True) data = animation.asDict()
data['audio']['file'] = f'annotation-{identifier}.mp3'; data['audio']['file'] = f'annotation-{identifier}.mp3';
archive.writestr(f'annotation-{identifier}.json', json.dumps(data)) archive.writestr(f'annotation-{identifier}.json', json.dumps(data))
@ -406,7 +406,7 @@ class AnimationHandler(tornado.web.RequestHandler):
self.write(audio.read()) self.write(audio.read())
else: else:
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
self.write(json.dumps(animation.asDict(include_full_drawing=True))) self.write(json.dumps(animation.asDict()))
class TagHandler(tornado.web.RequestHandler): class TagHandler(tornado.web.RequestHandler):

View file

@ -75,21 +75,6 @@ class StrokeGroup {
} }
return d; return d;
} }
setPrecomputedStrokes(strokeDs) {
const pathEls = this.g.querySelectorAll('path');
for (let pathEl of pathEls) {
pathEl.parentNode.removeChild(pathEl);
}
strokeDs.forEach((strokeD, index) => {
let pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
// pathEl.style.stroke = stroke.color;
// pathEl.classList.add('path');
pathEl.setAttribute('d', strokeD);
this.g.appendChild(pathEl);
});
}
} }
class Stroke { class Stroke {
@ -125,18 +110,6 @@ 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 { class Annotator extends EventTarget {
constructor(wrapperEl, tagFile, fileurl, config) { constructor(wrapperEl, tagFile, fileurl, config) {
fileurl = fileurl.replace("&", "&"); // little hack: tornadoweb does this automatically for some reason fileurl = fileurl.replace("&", "&"); // little hack: tornadoweb does this automatically for some reason
@ -144,8 +117,7 @@ class Annotator extends EventTarget {
this.config = { 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 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, // DEPRECATED don't animate viewport, but show the whole drawing crop_to_fit: config && config.hasOwnProperty('crop_to_fit') ? config.crop_to_fit : false, // 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 autoplay: config && config.hasOwnProperty('autoplay') ? config.autoplay : false, // immediately start playback
} }
@ -181,7 +153,7 @@ class Annotator extends EventTarget {
this.svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.wrapperEl.appendChild(this.svgEl); this.wrapperEl.appendChild(this.svgEl);
this.wrapperEl.classList.add(this.config.is_player ? "svganim_player" : "svganim_annotator"); this.wrapperEl.classList.add(this.config.is_player ? "svganim_player" : "svganim_annotator");
this.wrapperEl.classList.add("crop-" + this.config.crop); this.wrapperEl.classList.add(this.config.crop_to_fit ? "cropped-to-selection" : "follow-drawing");
this.controlsEl = document.createElement('div'); this.controlsEl = document.createElement('div');
@ -247,10 +219,10 @@ class Annotator extends EventTarget {
toggleFutureEl.addEventListener('click', () => this.wrapperEl.classList.toggle('hide-drawing-preview')); toggleFutureEl.addEventListener('click', () => this.wrapperEl.classList.toggle('hide-drawing-preview'));
extraControlsEl.appendChild(toggleFutureEl); extraControlsEl.appendChild(toggleFutureEl);
this.toggleCropPlayerEl = document.createElement('li'); const toggleCropPlayerEl = document.createElement('li');
this.toggleCropPlayerEl.innerText = CropDescriptions[this.config.crop]; toggleCropPlayerEl.innerText = "Crop to selection";
this.toggleCropPlayerEl.addEventListener('click', () => this.toggleCrop()); toggleCropPlayerEl.addEventListener('click', () => this.toggleCrop());
extraControlsEl.appendChild(this.toggleCropPlayerEl); extraControlsEl.appendChild(toggleCropPlayerEl);
extraEl.appendChild(extraControlsEl); extraEl.appendChild(extraControlsEl);
@ -266,7 +238,7 @@ class Annotator extends EventTarget {
this._currentTimeMs = 0; this._currentTimeMs = 0;
this.videoIsPlaying = false; this.videoIsPlaying = false;
const groups = ['background', 'before', 'annotation', 'after'] const groups = ['before', 'annotation', 'after']
this.strokeGroups = {}; this.strokeGroups = {};
groups.forEach(group => { groups.forEach(group => {
let groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g'); let groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g');
@ -766,8 +738,6 @@ class Annotator extends EventTarget {
this.filename = drawing.file; this.filename = drawing.file;
this.strokes = drawing.shape.map(s => new Stroke(s['color'], s['points'])); 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.viewboxes = drawing.viewboxes;
this.currentPathI = null; this.currentPathI = null;
this.currentPointI = null; this.currentPointI = null;
@ -791,10 +761,6 @@ class Annotator extends EventTarget {
this.nextViewboxTimeout = null; this.nextViewboxTimeout = null;
this._setPausedFlag(true); this._setPausedFlag(true);
if(this.backgroundStrokes && this.backgroundStrokes.length){
this.strokeGroups['background'].setPrecomputedStrokes(this.backgroundStrokes)
}
return this.setupAudioConfig().then(() => { return this.setupAudioConfig().then(() => {
// this.setUpAnnotator() // this.setUpAnnotator()
let keyEl; let keyEl;
@ -1069,7 +1035,6 @@ class Annotator extends EventTarget {
return slices; return slices;
} }
// TODO: when drawing, have a group active & inactive. // TODO: when drawing, have a group active & inactive.
// active is getPathRange(currentIn, currentOut) // active is getPathRange(currentIn, currentOut)
// inactive is what comes before and after. // inactive is what comes before and after.
@ -1133,10 +1098,8 @@ class Annotator extends EventTarget {
} }
updateViewbox() { updateViewbox() {
if (this.config.crop == CropOptions.Fit_Selection) { 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 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 { } else {
let x, y, w, h; let x, y, w, h;
if (this.currentViewboxI !== null) { if (this.currentViewboxI !== null) {
@ -1154,31 +1117,18 @@ class Annotator extends EventTarget {
} }
} }
setCrop(crop_option) { setCrop(crop_to_fit) {
if(Object.values(CropOptions).indexOf(crop_option) === -1) { this.config.crop_to_fit = Boolean(crop_to_fit);
console.error('invalid crop option', crop_option); if (this.config.crop_to_fit) {
crop_option = CropOptions.Fit_Selection; this.wrapperEl.classList.add('cropped-to-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 { } else {
this.wrapperEl.classList.remove('crop-' + option); this.wrapperEl.classList.remove('cropped-to-selection');
} }
}
this.toggleCropPlayerEl.innerText = CropDescriptions[this.config.crop];
this.updateViewbox(); this.updateViewbox();
} }
toggleCrop() { toggleCrop() {
console.log(this.config.crop, Object.values(CropOptions), Object.values(CropOptions).indexOf(this.config.crop)) this.setCrop(!this.config.crop_to_fit);
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) { getNextPosition(path_i, point_i) {
@ -1556,7 +1506,7 @@ class AnnotationPlayer extends HTMLElement {
const config = { const config = {
is_player: true, is_player: true,
crop: this.hasAttribute('data-crop') ? this.getAttribute('data-crop') : null, crop_to_fit: this.hasAttribute('data-no-crop') ? false : true,
autoplay: true, autoplay: true,
} }
@ -1715,13 +1665,6 @@ class AnnotationPlayer extends HTMLElement {
opacity:0; opacity:0;
} }
.background{
visibility: hidden
}
.play:not(.crop-selection) .background{
visibility: visible;
}
.gray { .gray {
position: absolute; position: absolute;
background: rgba(255, 255, 255, 0.7); background: rgba(255, 255, 255, 0.7);
@ -1758,10 +1701,11 @@ class AnnotationPlayer extends HTMLElement {
/*text-decoration: line-through;*/ /*text-decoration: line-through;*/
font-weight:bold; font-weight:bold;
} }
.play.crop-selection details > ul li:nth-child(2){ .play.cropped-to-selection details > ul li:nth-child(2){
/*text-decoration: line-through;*/ /*text-decoration: line-through;*/
font-weight:bold; font-weight:bold;
} }
`; `;
this.shadowRoot.appendChild(styleEl); this.shadowRoot.appendChild(styleEl);
@ -1776,18 +1720,26 @@ class AnnotationPlayer extends HTMLElement {
this.setAttribute('data-poster-url', `/annotation/${annotation.id}.svg`) 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) { attributeChangedCallback(name, oldValue, newValue) {
// console.log(name, oldValue, newValue); console.log(name, oldValue, newValue);
if (name == 'data-crop') { if (name == 'data-no-crop') {
if (!this.annotator) { if (!this.annotator) {
return; return;
} }
this.annotator.setCrop(this.hasAttribute('data-crop')); this.annotator.setCrop(!this.hasAttribute('data-no-crop'));
} }
} }
// required for attributeChangedCallback() // required for attributeChangedCallback()
static get observedAttributes() { return ['data-crop']; } static get observedAttributes() { return ['data-no-crop']; }
} }