Annotate using tags.json

This commit is contained in:
Ruben van de Ven 2022-04-29 12:05:48 +02:00
parent 438c09a48f
commit 4a0d605595
3 changed files with 207 additions and 96 deletions

View file

@ -124,7 +124,7 @@
margin: 0; margin: 0;
} }
.tags li { .tags .tag {
display: block; display: block;
padding: 5px; padding: 5px;
border: solid 1px darkgray; border: solid 1px darkgray;
@ -132,16 +132,30 @@
text-align: center; 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; cursor: pointer;
background: darkgray; background: darkgray;
} }
.tags li.selected { .tags .tag.selected {
background: lightsteelblue; background: #3FB8AF;
} }
.tags li.annotation-rm { .tags .tag.annotation-rm {
/* display: none; */ /* display: none; */
overflow: hidden; overflow: hidden;
color: red; color: red;
@ -156,14 +170,14 @@
/* hide behind bar, instead into nothing */ /* hide behind bar, instead into nothing */
} }
.selected-annotation .tags li.annotation-rm { .selected-annotation .tags .tag.annotation-rm {
color: red; color: red;
display: block; display: block;
width: 30px; width: 30px;
pointer-events: all; pointer-events: all;
} }
.tags li span { .tags .tag span {
display: inline-block; display: inline-block;
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -171,9 +185,15 @@
vertical-align: middle; vertical-align: middle;
border-radius: 5px; border-radius: 5px;
} }
.tags .subtags .tag span{
width: 10px;
height: 10px;
margin-right: 2px;
}
.annotations { .annotations {
height: 10px; height: 30px;
/* border: solid 1px darkgray; */ /* border: solid 1px darkgray; */
position: relative; position: relative;
} }
@ -193,33 +213,6 @@
cursor: pointer; 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 { .unsaved::before {
content: '*'; content: '*';
color: red; color: red;
@ -296,7 +289,7 @@
if (location.search) { if (location.search) {
ann = new Annotator( ann = new Annotator(
document.getElementById("interface"), document.getElementById("interface"),
["map", "text", "relation", "figure"], "tags.json",
location.search.substring(1) location.search.substring(1)
); );
} else { } else {

View file

@ -110,10 +110,9 @@ class StrokeSlice {
} }
class Annotator extends EventTarget { class Annotator extends EventTarget {
constructor(wrapperEl, tags, fileurl) { constructor(wrapperEl, tagFile, fileurl) {
super(); super();
this.formatter = wNumb({ this.formatter = wNumb({
decimals: 2, decimals: 2,
edit: (time) => { edit: (time) => {
@ -181,61 +180,94 @@ class Annotator extends EventTarget {
this.scrubberEl.classList.add('scrubber') this.scrubberEl.classList.add('scrubber')
this.controlsEl.appendChild(this.scrubberEl); 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 = document.createElement('div');
this.annotationsEl.classList.add('annotations') this.annotationsEl.classList.add('annotations')
this.controlsEl.appendChild(this.annotationsEl); 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]; tagEl.classList.add('tag');
this.inPointTimeMs = null; tagEl.dataset.tag = tag;
this.outPointPosition = null; tagEl.innerText = tagData.hasOwnProperty('fullname') ? tagData.fullname : tag;
this.outPointTimeMs = null; tagEl.addEventListener('click', (e) => {
this._currentTimeMs = 0; this.addTag(tag, this.inPointPosition, this.outPointPosition);
this.videoIsPlaying = false; });
const groups = ['before', 'annotation', 'after'] tagEl.title = tagData.hasOwnProperty('description') ? tagData.description : "";
this.strokeGroups = {};
groups.forEach(group => { let signEl = document.createElement('span');
let groupEl = document.createElementNS('http://www.w3.org/2000/svg', 'g'); signEl.classList.add('annotation-' + tag);
groupEl.classList.add(group) signEl.style.backgroundColor = this.getColorForTag(tag);
this.svgEl.appendChild(groupEl); tagEl.prepend(signEl);
this.strokeGroups[group] = new StrokeGroup(groupEl, this);
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 = []; getColorForTag(tag) {
const tagData = this.tagMap[tag];
this.load(fileurl); 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) { updateAnnotations(save) {
@ -251,6 +283,8 @@ class Annotator extends EventTarget {
this.annotationEl.style.left = left + '%'; this.annotationEl.style.left = left + '%';
this.annotationEl.style.right = right + '%'; this.annotationEl.style.right = right + '%';
this.annotationEl.style.backgroundColor = this.getColorForTag(annotation.tag);
this.annotationEl.classList.add('annotation-' + annotation.tag); this.annotationEl.classList.add('annotation-' + annotation.tag);
if (this.selectedAnnotationI == annotation_i) { if (this.selectedAnnotationI == annotation_i) {
this.annotationEl.classList.add('selected'); this.annotationEl.classList.add('selected');
@ -274,7 +308,7 @@ class Annotator extends EventTarget {
this.annotationsEl.appendChild(this.annotationEl); 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) { if (this.selectedAnnotation && this.selectedAnnotation.tag == tagEl.dataset.tag) {
tagEl.classList.add('selected') tagEl.classList.add('selected')
} else { } else {
@ -464,7 +498,7 @@ class Annotator extends EventTarget {
'min': sliderMin, 'min': sliderMin,
'max': sliderMax, 'max': sliderMax,
}, },
keyboardDefaultStep: (sliderMax-sliderMin) / 1000, keyboardDefaultStep: (sliderMax - sliderMin) / 1000,
tooltips: [ tooltips: [
this.formatter, this.formatter,
this.formatter this.formatter
@ -519,7 +553,7 @@ class Annotator extends EventTarget {
}; };
ttInputEl.addEventListener('keydown', (keyE) => { ttInputEl.addEventListener('keydown', (keyE) => {
keyE.stopPropagation(); //prevent movement of tooltip keyE.stopPropagation(); //prevent movement of tooltip
if(keyE.key == "Enter") { if (keyE.key == "Enter") {
submit(); submit();
} }
}) })
@ -568,8 +602,6 @@ class Annotator extends EventTarget {
this.nextViewboxTimeout = null; this.nextViewboxTimeout = null;
this._setPausedFlag(true); this._setPausedFlag(true);
this.setupAudioConfig().then(() => { this.setupAudioConfig().then(() => {
// this.setUpAnnotator() // this.setUpAnnotator()
this.updateAnnotations(false); this.updateAnnotations(false);
@ -591,6 +623,28 @@ class Annotator extends EventTarget {
// this.playStrokePosition(0, 1); // 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() { setupAudioConfig() {
// audio config // audio config
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -799,7 +853,7 @@ class Annotator extends EventTarget {
} }
setViewboxPosition(box_i) { setViewboxPosition(box_i) {
if(this.currentViewboxI == box_i){ if (this.currentViewboxI == box_i) {
return; return;
} }
this.currentViewboxI = box_i this.currentViewboxI = box_i
@ -870,8 +924,8 @@ class Annotator extends EventTarget {
// } // }
this.setViewboxPosition(box_i); this.setViewboxPosition(box_i);
const next_box_i = box_i+1; const next_box_i = box_i + 1;
if(this.viewboxes.length <= next_box_i){ if (this.viewboxes.length <= next_box_i) {
console.debug('done playing viewbox'); console.debug('done playing viewbox');
return; return;
} }
@ -1104,7 +1158,7 @@ class Annotator extends EventTarget {
return [path_i, point_i]; return [path_i, point_i];
} }
findViewboxForTime(ms){ findViewboxForTime(ms) {
ms = Math.min(Math.max(ms, 0), this.lastFrameTime); ms = Math.min(Math.max(ms, 0), this.lastFrameTime);
// console.log('scrub to', ms) // console.log('scrub to', ms)
let box_i = 0; let box_i = 0;

64
app/www/tags.json Normal file
View file

@ -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": {}
}
}
}