diff --git a/app/www/annotate.html b/app/www/annotate.html index bf3558e..eaa981a 100644 --- a/app/www/annotate.html +++ b/app/www/annotate.html @@ -124,7 +124,7 @@ margin: 0; } - .tags li { + .tags .tag { display: block; padding: 5px; border: solid 1px darkgray; @@ -132,16 +132,30 @@ text-align: center; } - .tags li:hover { + .tags li{ + display: block; + } + .tags .subtags{ + padding:0; + font-size: 80%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + } + .tags .subtags .tag{ + padding: 2px; + } + + .tags .tag:hover { cursor: pointer; background: darkgray; } - .tags li.selected { - background: lightsteelblue; + .tags .tag.selected { + background: #3FB8AF; } - .tags li.annotation-rm { + .tags .tag.annotation-rm { /* display: none; */ overflow: hidden; color: red; @@ -156,14 +170,14 @@ /* hide behind bar, instead into nothing */ } - .selected-annotation .tags li.annotation-rm { + .selected-annotation .tags .tag.annotation-rm { color: red; display: block; width: 30px; pointer-events: all; } - .tags li span { + .tags .tag span { display: inline-block; width: 20px; height: 20px; @@ -171,9 +185,15 @@ vertical-align: middle; border-radius: 5px; } + .tags .subtags .tag span{ + width: 10px; + height: 10px; + margin-right: 2px; + + } .annotations { - height: 10px; + height: 30px; /* border: solid 1px darkgray; */ position: relative; } @@ -193,33 +213,6 @@ cursor: pointer; } - .annotation-test { - background-color: red !important; - } - - .annotation-another { - background-color: blue !important; - } - - .annotation-google { - background-color: blueviolet !important; - } - .annotation-map { - background-color: red !important; - } - - .annotation-relation { - background-color: blue !important; - } - - .annotation-text { - background-color: blueviolet !important; - } - - .annotation-figure { - background-color: pink !important; - } - .unsaved::before { content: '*'; color: red; @@ -296,7 +289,7 @@ if (location.search) { ann = new Annotator( document.getElementById("interface"), - ["map", "text", "relation", "figure"], + "tags.json", location.search.substring(1) ); } else { diff --git a/app/www/annotate.js b/app/www/annotate.js index 5f9a213..0f00b39 100644 --- a/app/www/annotate.js +++ b/app/www/annotate.js @@ -110,10 +110,9 @@ class StrokeSlice { } class Annotator extends EventTarget { - constructor(wrapperEl, tags, fileurl) { + constructor(wrapperEl, tagFile, fileurl) { super(); - this.formatter = wNumb({ decimals: 2, edit: (time) => { @@ -181,61 +180,94 @@ class Annotator extends EventTarget { this.scrubberEl.classList.add('scrubber') this.controlsEl.appendChild(this.scrubberEl); - this.tagsEl = document.createElement('ul'); - this.tagsEl.classList.add('tags'); - for (let tag of tags) { - let tagEl = document.createElement('li'); - tagEl.classList.add('tag'); - tagEl.dataset.tag = tag; - tagEl.innerText = tag; - tagEl.addEventListener('click', (e) => { - this.addTag(tag, this.inPointPosition, this.outPointPosition); - }) - - let signEl = document.createElement('span'); - signEl.classList.add('annotation-' + tag); - tagEl.prepend(signEl); - this.tagsEl.appendChild(tagEl); - } - let tagEl = document.createElement('li'); - tagEl.classList.add('tag'); - tagEl.classList.add('annotation-rm'); - tagEl.dataset.tag = 'rm'; - tagEl.title = "Remove annotation"; - tagEl.innerHTML = "🚮"; // × - tagEl.addEventListener('click', (e) => { - if (this.selectedAnnotation) { - this.removeAnnotation(this.selectedAnnotationI); - } - }); - this.tagsEl.appendChild(tagEl); - - this.controlsEl.appendChild(this.tagsEl); this.annotationsEl = document.createElement('div'); this.annotationsEl.classList.add('annotations') this.controlsEl.appendChild(this.annotationsEl); + this.loadTags(tagFile).then(() => { + this.tagsEl = document.createElement('ul'); + this.tagsEl.classList.add('tags'); + const addTags = (tags, tagsEl) => { + Object.entries(tags).forEach(([tag, tagData]) => { + let tagLiEl = document.createElement('li'); + let tagEl = document.createElement('div'); - this.inPointPosition = [0, 0]; - this.inPointTimeMs = null; - this.outPointPosition = null; - this.outPointTimeMs = null; - this._currentTimeMs = 0; - this.videoIsPlaying = false; + tagEl.classList.add('tag'); + tagEl.dataset.tag = tag; + tagEl.innerText = tagData.hasOwnProperty('fullname') ? tagData.fullname : tag; + tagEl.addEventListener('click', (e) => { + this.addTag(tag, this.inPointPosition, this.outPointPosition); + }); - const groups = ['before', 'annotation', 'after'] - this.strokeGroups = {}; - groups.forEach(group => { - let groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g'); - groupEl.classList.add(group) - this.svgEl.appendChild(groupEl); - this.strokeGroups[group] = new StrokeGroup(groupEl, this); + tagEl.title = tagData.hasOwnProperty('description') ? tagData.description : ""; + + let signEl = document.createElement('span'); + signEl.classList.add('annotation-' + tag); + signEl.style.backgroundColor = this.getColorForTag(tag); + tagEl.prepend(signEl); + + tagLiEl.appendChild(tagEl); + + if(tagData.hasOwnProperty('sub')){ + const subEl = document.createElement('ul'); + subEl.classList.add('subtags'); + addTags(tagData.sub, subEl); + tagLiEl.appendChild(subEl); + } + + tagsEl.appendChild(tagLiEl); + }); + }; + addTags(this.tags, this.tagsEl); + + let tagEl = document.createElement('li'); + tagEl.classList.add('tag'); + tagEl.classList.add('annotation-rm'); + tagEl.dataset.tag = 'rm'; + tagEl.title = "Remove annotation"; + tagEl.innerHTML = "🚮"; // × + tagEl.addEventListener('click', (e) => { + if (this.selectedAnnotation) { + this.removeAnnotation(this.selectedAnnotationI); + } + }); + this.tagsEl.appendChild(tagEl); + + this.controlsEl.appendChild(this.tagsEl); + + this.inPointPosition = [0, 0]; + this.inPointTimeMs = null; + this.outPointPosition = null; + this.outPointTimeMs = null; + this._currentTimeMs = 0; + this.videoIsPlaying = false; + + const groups = ['before', 'annotation', 'after'] + this.strokeGroups = {}; + groups.forEach(group => { + let groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g'); + groupEl.classList.add(group) + this.svgEl.appendChild(groupEl); + this.strokeGroups[group] = new StrokeGroup(groupEl, this); + }); + + this.annotations = []; + + this.load(fileurl); }); + } - this.annotations = []; - - this.load(fileurl); + getColorForTag(tag) { + const tagData = this.tagMap[tag]; + console.log(tag, tagData); + if (tagData && tagData.hasOwnProperty('color')) { + return tagData.color; + } + if (tagData && tagData.hasOwnProperty('parent')) { + return this.getColorForTag(tagData['parent']); + } + return 'black'; } updateAnnotations(save) { @@ -251,6 +283,8 @@ class Annotator extends EventTarget { this.annotationEl.style.left = left + '%'; this.annotationEl.style.right = right + '%'; + this.annotationEl.style.backgroundColor = this.getColorForTag(annotation.tag); + this.annotationEl.classList.add('annotation-' + annotation.tag); if (this.selectedAnnotationI == annotation_i) { this.annotationEl.classList.add('selected'); @@ -274,7 +308,7 @@ class Annotator extends EventTarget { this.annotationsEl.appendChild(this.annotationEl); } - this.tagsEl.childNodes.forEach(tagEl => { + this.tagsEl.querySelectorAll('.tag').forEach(tagEl => { if (this.selectedAnnotation && this.selectedAnnotation.tag == tagEl.dataset.tag) { tagEl.classList.add('selected') } else { @@ -464,7 +498,7 @@ class Annotator extends EventTarget { 'min': sliderMin, 'max': sliderMax, }, - keyboardDefaultStep: (sliderMax-sliderMin) / 1000, + keyboardDefaultStep: (sliderMax - sliderMin) / 1000, tooltips: [ this.formatter, this.formatter @@ -519,7 +553,7 @@ class Annotator extends EventTarget { }; ttInputEl.addEventListener('keydown', (keyE) => { keyE.stopPropagation(); //prevent movement of tooltip - if(keyE.key == "Enter") { + if (keyE.key == "Enter") { submit(); } }) @@ -568,8 +602,6 @@ class Annotator extends EventTarget { this.nextViewboxTimeout = null; this._setPausedFlag(true); - - this.setupAudioConfig().then(() => { // this.setUpAnnotator() this.updateAnnotations(false); @@ -591,6 +623,28 @@ class Annotator extends EventTarget { // this.playStrokePosition(0, 1); } + + loadTags(tagFile) { + // tags config + const request = new Request(tagFile); + return fetch(request) + .then(response => response.json()) + .then(tags => { + this.tags = tags; + this.tagMap = {}; + const addTagsToMap = (tags, parent) => { + Object.entries(tags).forEach(([tag, tagData]) => { + tagData['parent'] = typeof parent != "undefined" ? parent : null; + this.tagMap[tag] = tagData; + if (tagData.hasOwnProperty("sub")) { + addTagsToMap(tagData.sub, tag); + } + }); + }; + addTagsToMap(this.tags); + }); + } + setupAudioConfig() { // audio config return new Promise((resolve, reject) => { @@ -799,7 +853,7 @@ class Annotator extends EventTarget { } setViewboxPosition(box_i) { - if(this.currentViewboxI == box_i){ + if (this.currentViewboxI == box_i) { return; } this.currentViewboxI = box_i @@ -864,14 +918,14 @@ class Annotator extends EventTarget { console.log('not playing because of interrupt'); return; } - } + } // else { // this.videoIsPlaying = true; // } this.setViewboxPosition(box_i); - const next_box_i = box_i+1; - if(this.viewboxes.length <= next_box_i){ + const next_box_i = box_i + 1; + if (this.viewboxes.length <= next_box_i) { console.debug('done playing viewbox'); return; } @@ -969,7 +1023,7 @@ class Annotator extends EventTarget { this.playheadEl.value = this._currentTimeMs; this.timeCodeEl.value = this.formatter.to(this._currentTimeMs); } - + _animationFrame(timestamp) { // TODO, move time at end of playStrokePosition to here @@ -1033,7 +1087,7 @@ class Annotator extends EventTarget { this.dispatchEvent(new CustomEvent('seeking', { detail: time })); this._currentTimeMs = Number.parseFloat(time) * 1000; [this.currentPathI, this.currentPointI] = this.findPositionForTime(this._currentTimeMs); - + this._updatePlayhead(); this._updateFrame(); this.dispatchEvent(new CustomEvent('seeked', { detail: this.currentTime })); @@ -1104,7 +1158,7 @@ class Annotator extends EventTarget { return [path_i, point_i]; } - findViewboxForTime(ms){ + findViewboxForTime(ms) { ms = Math.min(Math.max(ms, 0), this.lastFrameTime); // console.log('scrub to', ms) let box_i = 0; diff --git a/app/www/tags.json b/app/www/tags.json new file mode 100644 index 0000000..3d06846 --- /dev/null +++ b/app/www/tags.json @@ -0,0 +1,64 @@ +{ + "human-machine": { + "fullname": "Human/machine Entanglements", + "color": "orange", + "sub": { + "vision": { + "fullname": "Vision", + "color": "orange" + }, + "sound": {}, + "other-senses": { + "fullname": "Other senses" + } + } + }, + "tensions": { + "fullname": "Tensions, contestations & problems", + "description" : "Which problems are identified?, when do they become problems?", + "color": "gray" + }, + "security": { + "color": "blue", + "fullname": "Security & types of data", + "sub": { + "definitions": { + "description": "e.g. domain knowledge" + }, + "input": { + "description": "e.g. fake data" + } + } + }, + "actants":{ + "color": "pink", + "sub":{ + "algorithm": {}, + "technologies": {}, + "entities":{ + "fullname": "Entities: people, institutions etc." + } + } + }, + "relations":{ + "color": "red", + "sub": { + "inside-outside":{}, + "public-private":{} + } + }, + "consequences":{ + "color": "green", + "sub":{ + "effects":{}, + "future-imaginaries":{}, + "speculations": { + "description": "what is & what will/can be done." + }, + "innovations": {} + } + } + + + +} \ No newline at end of file