WIP upgraded index, slash annotation editor
This commit is contained in:
parent
ab9c66794c
commit
5fa5096c44
5 changed files with 382 additions and 124 deletions
|
@ -728,6 +728,9 @@ class Tag(NodeMixin):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<svganim.strokes.Tag {self.id}>"
|
return f"<svganim.strokes.Tag {self.id}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return RenderTree(self).by_attr('name')
|
||||||
|
|
||||||
def get_color(self):
|
def get_color(self):
|
||||||
if self.color is None and self.parent is not None:
|
if self.color is None and self.parent is not None:
|
||||||
return self.parent.get_color()
|
return self.parent.get_color()
|
||||||
|
@ -742,11 +745,17 @@ class Tag(NodeMixin):
|
||||||
return t
|
return t
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def toJson(self) -> str:
|
||||||
|
return JsonExporter(indent=2).export(self)
|
||||||
|
|
||||||
|
def loadTagFromJson(string) -> Tag:
|
||||||
|
tree: Tag = JsonImporter(DictImporter(Tag)).import_(string)
|
||||||
|
return tree
|
||||||
|
|
||||||
def getRootTag(file = 'www/tags.json') -> Tag:
|
def getRootTag(file = 'www/tags.json') -> Tag:
|
||||||
with open(file, 'r') as fp:
|
with open(file, 'r') as fp:
|
||||||
tree: Tag = JsonImporter(DictImporter(Tag)).read(fp)
|
tree: Tag = JsonImporter(DictImporter(Tag)).read(fp)
|
||||||
|
|
||||||
# print(tree.descendants)
|
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
# print(RenderTree(tree))
|
# print(RenderTree(tree))
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#tags li {
|
#tags li {
|
||||||
|
line-height: 1.5;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,20 +33,31 @@
|
||||||
|
|
||||||
#tags li>div,
|
#tags li>div,
|
||||||
#tags li.add-tag {
|
#tags li.add-tag {
|
||||||
cursor: pointer
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tags .tag-id-root>div {
|
#tags .tag-id-root>div {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tags li:hover>ul>li.add-tag {
|
/* #tags li:hover>ul>li.add-tag {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
;
|
;
|
||||||
|
} */
|
||||||
|
#tags li.selected > ul>li.add-tag {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tags > li > ul >li.add-tag {
|
||||||
|
display: block;
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#tags .add-tag {
|
#tags .add-tag {
|
||||||
visibility: hidden;
|
display: none;
|
||||||
;
|
;
|
||||||
color: lightgray;
|
color: lightgray;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
|
@ -59,9 +70,30 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: solid 1px black;
|
border: solid 1px black;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
vertical-align: bottom;
|
vertical-align: middle;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
#tags li:hover > div > input.rm-tag{
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#tags li div{
|
||||||
|
position:relative
|
||||||
|
}
|
||||||
|
#tags input.rm-tag:hover{
|
||||||
|
color: red;
|
||||||
|
transform: rotate(20deg);
|
||||||
|
}
|
||||||
|
#tags input.rm-tag{
|
||||||
|
/* display: none; */
|
||||||
|
position:absolute;
|
||||||
|
right:0;
|
||||||
|
top:0;
|
||||||
|
padding:0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
||||||
#tags .selected>div {
|
#tags .selected>div {
|
||||||
background: lightblue
|
background: lightblue
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
import tornado.web
|
import tornado.web
|
||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
|
@ -519,7 +520,34 @@ class TagsHandler(tornado.web.RequestHandler):
|
||||||
self.index = index
|
self.index = index
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
raise Exception('todo')
|
self.set_header("Content-Type", "application/json")
|
||||||
|
with open('www/tags.json', 'r') as fp:
|
||||||
|
# TODO: enrich with counts
|
||||||
|
self.write(fp.read())
|
||||||
|
|
||||||
|
def put(self):
|
||||||
|
# data = json.loads(self.request.body)
|
||||||
|
tree = svganim.strokes.loadTagFromJson(self.request.body)
|
||||||
|
logger.info(f"New tag tree:\n{tree}")
|
||||||
|
newTagsContent = tree.toJson()
|
||||||
|
# save at minute resolution
|
||||||
|
now = datetime.datetime.utcnow().isoformat(timespec='minutes')
|
||||||
|
|
||||||
|
backup_dir = os.path.join(self.config.storage, 'tag_versions')
|
||||||
|
if not os.path.exists(backup_dir):
|
||||||
|
logger.warning(f"Creating tags backupdir {backup_dir}")
|
||||||
|
os.mkdir(backup_dir)
|
||||||
|
|
||||||
|
bakfile = os.path.join(backup_dir, f'tags.{now}.json')
|
||||||
|
logger.info(f"Creating tags backup {bakfile}" )
|
||||||
|
shutil.copyfile('www/tags.json', bakfile)
|
||||||
|
|
||||||
|
with open('www/tags.json', 'w') as fp:
|
||||||
|
fp.write(newTagsContent)
|
||||||
|
|
||||||
|
self.set_status(204)
|
||||||
|
# print()
|
||||||
|
|
||||||
|
|
||||||
class IndexHandler(tornado.web.RequestHandler):
|
class IndexHandler(tornado.web.RequestHandler):
|
||||||
"""Get annotation as svg"""
|
"""Get annotation as svg"""
|
||||||
|
@ -603,6 +631,9 @@ class Server:
|
||||||
(r"/index", IndexHandler,
|
(r"/index", IndexHandler,
|
||||||
{"config": self.config, "index": self.index}),
|
{"config": self.config, "index": self.index}),
|
||||||
|
|
||||||
|
(r"/tags.json", TagsHandler,
|
||||||
|
{"config": self.config, "index": self.index}),
|
||||||
|
|
||||||
(r"/(.*)", StaticFileWithHeaderHandler,
|
(r"/(.*)", StaticFileWithHeaderHandler,
|
||||||
{"path": self.web_root, 'default_filename': 'index.html'}),
|
{"path": self.web_root, 'default_filename': 'index.html'}),
|
||||||
],
|
],
|
||||||
|
|
|
@ -6,6 +6,8 @@ class AnnotationManager {
|
||||||
this.tagsEl = this.rootEl.querySelector('#tags');
|
this.tagsEl = this.rootEl.querySelector('#tags');
|
||||||
this.annotationsEl = this.rootEl.querySelector('#annotations');
|
this.annotationsEl = this.rootEl.querySelector('#annotations');
|
||||||
|
|
||||||
|
this.selectedAnnotations = [];
|
||||||
|
this.selectedTag = null;
|
||||||
this.loadTags(tagsUrl);
|
this.loadTags(tagsUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +35,9 @@ class AnnotationManager {
|
||||||
tagEl.addEventListener('click', (ev) => {
|
tagEl.addEventListener('click', (ev) => {
|
||||||
this.selectTag(tag);
|
this.selectTag(tag);
|
||||||
})
|
})
|
||||||
|
tagEl.addEventListener('dblclick', (ev) => {
|
||||||
|
this.renameTag(tag);
|
||||||
|
})
|
||||||
|
|
||||||
const colorEl = document.createElement('input');
|
const colorEl = document.createElement('input');
|
||||||
colorEl.type = 'color';
|
colorEl.type = 'color';
|
||||||
|
@ -42,6 +47,16 @@ class AnnotationManager {
|
||||||
});
|
});
|
||||||
tagEl.prepend(colorEl)
|
tagEl.prepend(colorEl)
|
||||||
|
|
||||||
|
const rmEl = document.createElement('input');
|
||||||
|
rmEl.type = 'button';
|
||||||
|
rmEl.classList.add('rm-tag');
|
||||||
|
rmEl.value = '🗑';
|
||||||
|
rmEl.addEventListener('click', (ev) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.removeTag(tag);
|
||||||
|
});
|
||||||
|
tagEl.appendChild(rmEl)
|
||||||
|
|
||||||
tagLiEl.classList.add('tag-id-' + tag.id);
|
tagLiEl.classList.add('tag-id-' + tag.id);
|
||||||
tagLiEl.appendChild(tagEl);
|
tagLiEl.appendChild(tagEl);
|
||||||
|
|
||||||
|
@ -70,14 +85,16 @@ class AnnotationManager {
|
||||||
|
|
||||||
selectTag(tag) {
|
selectTag(tag) {
|
||||||
this.clearSelectedTag();
|
this.clearSelectedTag();
|
||||||
|
this.selectedTag = tag;
|
||||||
tag.menuLiEl.classList.add('selected');
|
tag.menuLiEl.classList.add('selected');
|
||||||
this.loadAnnotationsForTag(tag)
|
this.loadAnnotationsForTag(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelectedTag() {
|
clearSelectedTag() {
|
||||||
|
this.selectedTag = null;
|
||||||
const selected = this.tagsEl.querySelectorAll('.selected');
|
const selected = this.tagsEl.querySelectorAll('.selected');
|
||||||
selected.forEach((s) => s.classList.remove('selected'));
|
selected.forEach((s) => s.classList.remove('selected'));
|
||||||
|
this.resetSelectedAnnotations()
|
||||||
//TODO empty annotationEl
|
//TODO empty annotationEl
|
||||||
this.annotationsEl.innerHTML = "";
|
this.annotationsEl.innerHTML = "";
|
||||||
}
|
}
|
||||||
|
@ -112,14 +129,33 @@ class AnnotationManager {
|
||||||
buildAnnotationList() {
|
buildAnnotationList() {
|
||||||
this.annotationsEl.innerHTML = "";
|
this.annotationsEl.innerHTML = "";
|
||||||
|
|
||||||
|
this.actionsEl = document.createElement('div');
|
||||||
|
this.actionsEl.classList.add('annotations-actions')
|
||||||
|
|
||||||
|
this.buildAnnotationActions();
|
||||||
|
|
||||||
|
this.annotationsEl.appendChild(this.actionsEl);
|
||||||
|
|
||||||
|
|
||||||
const ulEl = document.createElement('ul');
|
const ulEl = document.createElement('ul');
|
||||||
this.annotations.forEach((annotation, idx) => {
|
this.annotations.forEach((annotation, idx) => {
|
||||||
const liEl = document.createElement('li');
|
const liEl = document.createElement('li');
|
||||||
const imgEl = document.createElement('img');
|
const imgEl = document.createElement('img');
|
||||||
const playerEl = document.createElement('div');
|
const playerEl = document.createElement('div');
|
||||||
const infoEl = document.createElement('div');
|
const infoEl = document.createElement('span');
|
||||||
infoEl.classList.add('annotation-info');
|
infoEl.classList.add('annotation-info');
|
||||||
|
|
||||||
|
const selectEl = document.createElement('input');
|
||||||
|
selectEl.type = 'checkbox';
|
||||||
|
selectEl.id = 'select-'+annotation.id_hash;
|
||||||
|
selectEl.addEventListener('change', (ev) => {
|
||||||
|
if (ev.target.checked) {
|
||||||
|
this.addAnnotationToSelection(annotation);
|
||||||
|
} else {
|
||||||
|
this.removeAnnotationFromSelection(annotation);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const tag = this.rootTag.find_by_id(annotation.tag);
|
const tag = this.rootTag.find_by_id(annotation.tag);
|
||||||
console.log(tag)
|
console.log(tag)
|
||||||
|
|
||||||
|
@ -140,6 +176,7 @@ class AnnotationManager {
|
||||||
|
|
||||||
liEl.appendChild(imgEl);
|
liEl.appendChild(imgEl);
|
||||||
liEl.appendChild(playerEl);
|
liEl.appendChild(playerEl);
|
||||||
|
liEl.appendChild(selectEl);
|
||||||
liEl.appendChild(infoEl);
|
liEl.appendChild(infoEl);
|
||||||
ulEl.appendChild(liEl);
|
ulEl.appendChild(liEl);
|
||||||
|
|
||||||
|
@ -149,6 +186,76 @@ class AnnotationManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetSelectedAnnotations() {
|
||||||
|
this.selectedAnnotations = [];
|
||||||
|
|
||||||
|
this.annotationsEl.querySelectorAll("li input[type='checkbox']").forEach((box) => box.checked = false);
|
||||||
|
this.buildAnnotationActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
addAnnotationToSelection(annotation) {
|
||||||
|
if (this.selectedAnnotations.indexOf(annotation) === -1){
|
||||||
|
this.selectedAnnotations.push(annotation);
|
||||||
|
}
|
||||||
|
this.annotationsEl.querySelector('#select-'+annotation.id_hash).checked = true;
|
||||||
|
this.buildAnnotationActions()
|
||||||
|
}
|
||||||
|
removeAnnotationFromSelection(annotation) {
|
||||||
|
if (this.selectedAnnotations.indexOf(annotation) !== -1){
|
||||||
|
this.selectedAnnotations.splice(this.selectedAnnotations.indexOf(annotation), 1)
|
||||||
|
}
|
||||||
|
this.annotationsEl.querySelector('#select-'+annotation.id_hash).checked = true;
|
||||||
|
this.buildAnnotationActions()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
buildAnnotationActions() {
|
||||||
|
if(!this.actionsEl || !this.annotations.length) return
|
||||||
|
this.actionsEl.innerHTML = "";
|
||||||
|
|
||||||
|
const selectAllLabelEl = document.createElement('label');
|
||||||
|
selectAllLabelEl.innerText = `Select all`
|
||||||
|
const selectAllEl = document.createElement('input');
|
||||||
|
selectAllEl.type = 'checkbox';
|
||||||
|
selectAllEl.checked = this.annotations.length === this.selectedAnnotations.length;
|
||||||
|
// selectAllEl.innerText = `Select all ${this.annotations.length} items`;
|
||||||
|
selectAllEl.addEventListener('change', (ev) => {
|
||||||
|
if(ev.target.checked) {
|
||||||
|
this.annotations.forEach((a) => this.addAnnotationToSelection(a));
|
||||||
|
} else {
|
||||||
|
this.resetSelectedAnnotations();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
selectAllLabelEl.appendChild(selectAllEl)
|
||||||
|
|
||||||
|
this.actionsEl.appendChild(selectAllLabelEl)
|
||||||
|
|
||||||
|
if(!this.selectedAnnotations.length) return;
|
||||||
|
|
||||||
|
const moveLabelEl = document.createElement('label');
|
||||||
|
moveLabelEl.innerText = `Change tag for ${this.selectedAnnotations.length} items`
|
||||||
|
const moveSelectEl = document.createElement('select');
|
||||||
|
this.rootTag.descendants().forEach((tag, i) => {
|
||||||
|
const tagEl = document.createElement('option');
|
||||||
|
tagEl.value = tag.id;
|
||||||
|
if(tag.id == this.selectedTag.id){
|
||||||
|
tagEl.selected = true;
|
||||||
|
}
|
||||||
|
tagEl.innerHTML = tag.get_indented_name();
|
||||||
|
moveSelectEl.appendChild(tagEl);
|
||||||
|
});
|
||||||
|
moveSelectEl.addEventListener('change', (ev) => {
|
||||||
|
const tag = this.rootTag.find_by_id(ev.target.value);
|
||||||
|
console.log(tag);
|
||||||
|
this.moveSelectedAnnotations(tag);
|
||||||
|
})
|
||||||
|
moveLabelEl.appendChild(moveSelectEl)
|
||||||
|
|
||||||
|
|
||||||
|
this.actionsEl.appendChild(moveLabelEl)
|
||||||
|
}
|
||||||
|
|
||||||
renameTag(tag) {
|
renameTag(tag) {
|
||||||
// TODO: add button for this
|
// TODO: add button for this
|
||||||
const name = prompt(`Rename tag '${tag.get_name()}':`);
|
const name = prompt(`Rename tag '${tag.get_name()}':`);
|
||||||
|
@ -156,15 +263,25 @@ class AnnotationManager {
|
||||||
tag.name = name;
|
tag.name = name;
|
||||||
|
|
||||||
this.saveTags();
|
this.saveTags();
|
||||||
|
this.buildTagList();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTag(tag) {
|
removeTag(tag) {
|
||||||
|
if (!confirm(`Do you want to delete ${tag.get_name()}`)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// TODO: add button for this
|
// TODO: add button for this
|
||||||
const request = new Request("/tags/" + tag.id);
|
const request = new Request("/tags/" + tag.id);
|
||||||
return fetch(request)
|
return fetch(request)
|
||||||
.then(response => response.json())
|
.then(response => {
|
||||||
|
if (response.status == 404) {
|
||||||
|
return [] // not existing tag surely has no annotations
|
||||||
|
} else {
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
})
|
||||||
.then(annotations => {
|
.then(annotations => {
|
||||||
if(annotations.length) {
|
if (annotations.length) {
|
||||||
alert(`Cannot remove '${tag.get_name()}', as it is used for ${annotations.length} annotations.`)
|
alert(`Cannot remove '${tag.get_name()}', as it is used for ${annotations.length} annotations.`)
|
||||||
} else {
|
} else {
|
||||||
// TODO: remove tag
|
// TODO: remove tag
|
||||||
|
@ -172,8 +289,9 @@ class AnnotationManager {
|
||||||
tag.parent = null;
|
tag.parent = null;
|
||||||
|
|
||||||
this.saveTags();
|
this.saveTags();
|
||||||
|
this.buildTagList();
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setTagColor(tag, color) {
|
setTagColor(tag, color) {
|
||||||
|
@ -183,15 +301,22 @@ class AnnotationManager {
|
||||||
this.saveTags();
|
this.saveTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
moveSelectedAnnotations(newTag) {
|
moveSelectedAnnotations(tag) {
|
||||||
// TODO: add button for this
|
// TODO: add button for this
|
||||||
throw new Error("Not implemented");
|
alert(`This doesn't work yet! (move to tag ${tag.get_name()})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveTags() {
|
async saveTags() {
|
||||||
const json = this.rootTag.export();
|
const json = this.rootTag.export();
|
||||||
// TODO: save to remote
|
|
||||||
console.log('save', json)
|
console.log('save', json)
|
||||||
|
const response = await fetch('/tags.json', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: json
|
||||||
|
}).catch((e) => alert('Something went wrong saving the tags'));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,14 +368,19 @@ class Tag {
|
||||||
}
|
}
|
||||||
|
|
||||||
get_name() {
|
get_name() {
|
||||||
if (this.hasOwnProperty('name')) {
|
if (this.hasOwnProperty('name') && this.name !== null) {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
return this.id;
|
return this.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_indented_name() {
|
||||||
|
const name = this.get_name();
|
||||||
|
return ' '.repeat((this.depth() - 1) * 2) + '- ' + name
|
||||||
|
}
|
||||||
|
|
||||||
get_color() {
|
get_color() {
|
||||||
if (this.hasOwnProperty('color')) {
|
if (this.hasOwnProperty('color') && this.color !== null) {
|
||||||
return this.color;
|
return this.color;
|
||||||
}
|
}
|
||||||
if (this.parent !== null) {
|
if (this.parent !== null) {
|
||||||
|
|
|
@ -1,116 +1,172 @@
|
||||||
{
|
{
|
||||||
"id": "root",
|
"id": "root",
|
||||||
"children": [
|
"name": "root",
|
||||||
|
"color": null,
|
||||||
|
"description": "",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "human-machine",
|
||||||
|
"name": "Human/machine Entanglements (Appearance/disappearance)",
|
||||||
|
"color": "#ffa348",
|
||||||
|
"description": "",
|
||||||
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "human-machine",
|
"id": "vision",
|
||||||
"name": "Human/machine Entanglements (Appearance/disappearance)",
|
"name": "Vision",
|
||||||
"color": "orange",
|
"color": null,
|
||||||
"children": [
|
"description": ""
|
||||||
{
|
|
||||||
"id": "vision",
|
|
||||||
"name": "Vision",
|
|
||||||
"color": "orange"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sound"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "behaviour"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "other-senses",
|
|
||||||
"name": "Other senses"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "tensions",
|
"id": "sound",
|
||||||
"name": "Tensions, contestations & problems",
|
"name": "sound",
|
||||||
"description": "Which problems are identified?, when do they become problems?",
|
"color": null,
|
||||||
"color": "gray"
|
"description": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "security",
|
"id": "behaviour",
|
||||||
"color": "blue",
|
"name": "behaviour",
|
||||||
"name": "Security & types of data",
|
"color": null,
|
||||||
"children": [
|
"description": ""
|
||||||
{
|
|
||||||
"id": "definitions",
|
|
||||||
"description": "e.g. domain knowledge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "input",
|
|
||||||
"description": "e.g. fake data"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "actants",
|
"id": "other-senses",
|
||||||
"name": "Actants in relation",
|
"name": "Other senses",
|
||||||
"color": "pink",
|
"color": null,
|
||||||
"children": [
|
"description": ""
|
||||||
{
|
|
||||||
"id": "algorithm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "technologies"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "frt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cameras",
|
|
||||||
"name": "CCTV & camera's"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "entities",
|
|
||||||
"name": "Entities: people, institutions etc."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "positioning",
|
|
||||||
"name": "Positioning",
|
|
||||||
"description": "the positioning of a field/person/oneself in relation to others"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "inside-outside"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "public-private"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "consequences",
|
|
||||||
"color": "green",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"id": "effects"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "future-imaginaries"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "speculations",
|
|
||||||
"description": "what is & what will/can be done."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "innovations"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hesitation",
|
|
||||||
"name": "Hesitations & corrections",
|
|
||||||
"color": "yellow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "skip",
|
|
||||||
"color": "black"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "todo",
|
|
||||||
"name": "to do / interesting",
|
|
||||||
"color": "red"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tensions",
|
||||||
|
"name": "Tensions, contestations & problems",
|
||||||
|
"color": "#77767b",
|
||||||
|
"description": "Which problems are identified?, when do they become problems?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "security",
|
||||||
|
"name": "Security & types of data",
|
||||||
|
"color": "#3584e4",
|
||||||
|
"description": "",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "definitions",
|
||||||
|
"name": "definitions",
|
||||||
|
"color": null,
|
||||||
|
"description": "e.g. domain knowledge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "input",
|
||||||
|
"name": "input",
|
||||||
|
"color": null,
|
||||||
|
"description": "e.g. fake data"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "actants",
|
||||||
|
"name": "Actants in relation",
|
||||||
|
"color": "#fa08ff",
|
||||||
|
"description": "",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "algorithm",
|
||||||
|
"name": "algorithm",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "technologies",
|
||||||
|
"name": "technologies",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "frt",
|
||||||
|
"name": "frt",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cameras",
|
||||||
|
"name": "CCTV & camera's",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "entities",
|
||||||
|
"name": "Entities: people, institutions etc.",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "positioning",
|
||||||
|
"name": "Positioning",
|
||||||
|
"color": null,
|
||||||
|
"description": "the positioning of a field/person/oneself in relation to others"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "inside-outside",
|
||||||
|
"name": "inside-outside",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "public-private",
|
||||||
|
"name": "public-private",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "consequences",
|
||||||
|
"name": "consequences",
|
||||||
|
"color": "#0add32",
|
||||||
|
"description": "",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "effects",
|
||||||
|
"name": "effects",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "future-imaginaries",
|
||||||
|
"name": "future-imaginaries",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "speculations",
|
||||||
|
"name": "speculations",
|
||||||
|
"color": null,
|
||||||
|
"description": "what is & what will/can be done."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "innovations",
|
||||||
|
"name": "innovations",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hesitation",
|
||||||
|
"name": "Hesitations & corrections",
|
||||||
|
"color": "#f8e45c",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "skip",
|
||||||
|
"name": "skip",
|
||||||
|
"color": null,
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "todo",
|
||||||
|
"name": "to do / interesting",
|
||||||
|
"color": "#ff0000",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
Loading…
Reference in a new issue