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