label-studio
Version:
Data Labeling Tool that is backend agnostic and can be embedded into your applications
525 lines (408 loc) • 12.9 kB
JavaScript
import { types, getParent, getEnv, getRoot, destroy, detach } from "mobx-state-tree";
import Hotkey from "../core/Hotkey";
import NormalizationStore from "./NormalizationStore";
import RegionStore from "./RegionStore";
import Registry from "../core/Registry";
import RelationStore from "./RelationStore";
import TimeTraveller from "../core/TimeTraveller";
import Tree from "../core/Tree";
import Types from "../core/Types";
import Utils from "../utils";
import { AllRegionsType } from "../regions";
import { guidGenerator } from "../core/Helpers";
const Completion = types
.model("Completion", {
id: types.identifier,
pk: types.optional(types.string, guidGenerator(5)),
selected: types.optional(types.boolean, false),
type: types.enumeration(["completion", "prediction"]),
createdDate: types.optional(types.string, Utils.UDate.currentISODate()),
createdAgo: types.maybeNull(types.string),
createdBy: types.optional(types.string, "Admin"),
loadedDate: types.optional(types.Date, new Date()),
leadTime: types.maybeNull(types.number),
//
userGenerate: types.optional(types.boolean, true),
update: types.optional(types.boolean, false),
sentUserGenerate: types.optional(types.boolean, false),
localUpdate: types.optional(types.boolean, false),
honeypot: types.optional(types.boolean, false),
root: Types.allModelsTypes(),
names: types.map(types.reference(Types.allModelsTypes())),
toNames: types.map(types.array(types.reference(Types.allModelsTypes()))),
history: types.optional(TimeTraveller, { targetPath: "../root" }),
dragMode: types.optional(types.boolean, false),
edittable: types.optional(types.boolean, true),
relationMode: types.optional(types.boolean, false),
relationStore: types.optional(RelationStore, {
relations: [],
}),
normalizationMode: types.optional(types.boolean, false),
normalizationStore: types.optional(NormalizationStore, {
normalizations: [],
}),
regionStore: types.optional(RegionStore, {
regions: [],
}),
highlightedNode: types.maybeNull(types.safeReference(AllRegionsType)),
})
.views(self => ({
get store() {
return getRoot(self);
},
get list() {
return getParent(self, 2);
},
}))
.actions(self => ({
reinitHistory() {
self.history = { targetPath: "../root" };
},
setGroundTruth(value) {
self.honeypot = value;
getEnv(self).onGroundTruth(self.store, self, value);
},
sendUserGenerate() {
self.sentUserGenerate = true;
},
setLocalUpdate(value) {
self.localUpdate = value;
},
setDragMode(val) {
self.dragMode = val;
},
updatePersonalKey(value) {
self.pk = value;
},
setNormalizationMode(val) {
self.normalizationMode = val;
},
setHighlightedNode(node) {
self.highlightedNode = node;
},
startRelationMode(node1) {
self._relationObj = node1;
self.relationMode = true;
},
stopRelationMode() {
self._relationObj = null;
self.relationMode = false;
self.regionStore.unhighlightAll();
},
deleteAllRegions() {
self.regionStore.regions.forEach(r => r.deleteRegion());
},
addRegion(reg) {
self.regionStore.unselectAll();
self.regionStore.addRegion(reg);
if (self.relationMode) {
self.addRelation(reg);
self.stopRelationMode();
}
},
/**
* Add relation
* @param {*} reg
*/
addRelation(reg) {
self.relationStore.addRelation(self._relationObj, reg);
},
addNormalization(normalization) {
self.normalizationStore.addNormalization();
},
traverseTree(cb) {
let visitNode;
visitNode = function(node) {
cb(node);
if (node.children) {
node.children.forEach(chld => visitNode(chld));
}
};
visitNode(self.root);
},
/**
*
*/
beforeSend() {
self.traverseTree(node => {
if (node && node.beforeSend) {
node.beforeSend();
}
});
self.stopRelationMode();
self.regionStore.unselectAll();
},
/**
* Delete region
* @param {*} region
*/
deleteRegion(region) {
if (region.type === "polygonregion") {
detach(region);
return;
}
destroy(region);
},
afterAttach() {
self.traverseTree(node => {
if (node.updateValue) node.updateValue(self.store);
if (node.completionAttached) node.completionAttached();
// Copy tools from control tags into object tools manager
if (node && node.getToolsManager) {
const tools = node.getToolsManager();
const states = self.toNames.get(node.name);
states && states.forEach(s => tools.addToolsFromControl(s));
}
});
},
afterCreate() {
//
// debugger;
if (self.userGenerate && !self.sentUserGenerate) {
self.loadedDate = new Date();
}
// initialize toName bindings
self.traverseTree(node => {
if (node && node.name && node.id) self.names.set(node.name, node.id);
if (node && node.toname && node.id) {
const val = self.toNames.get(node.toname);
if (val) {
val.push(node.id);
} else {
self.toNames.set(node.toname, [node.id]);
}
}
});
},
setupHotKeys() {
Hotkey.unbindAll();
let audiosNum = 0;
let audioNode = null;
let mod = "shift+space";
let comb = mod;
// [TODO] we need to traverse this two times, fix
// Hotkeys setup
self.traverseTree(node => {
if (node && node.onHotKey && node.hotkey) {
Hotkey.addKey(node.hotkey, node.onHotKey, node.hotkeyScope);
}
});
self.traverseTree(node => {
// add Space hotkey for playbacks of audio
if (node && !node.hotkey && node.type === "audio") {
if (audiosNum > 0) comb = mod + "+" + (audiosNum + 1);
else audioNode = node;
node.hotkey = comb;
Hotkey.addKey(comb, node.onHotKey);
audiosNum++;
}
});
self.traverseTree(node => {
/**
* Hotkey for controls
*/
if (node && node.onHotKey && !node.hotkey) {
const comb = Hotkey.makeComb();
if (!comb) return;
node.hotkey = comb;
Hotkey.addKey(node.hotkey, node.onHotKey);
}
});
if (audioNode && audiosNum > 1) {
audioNode.hotkey = mod + "+1";
Hotkey.addKey(audioNode.hotkey, audioNode.onHotKey);
Hotkey.removeKey(mod);
}
// prevent spacebar from scrolling
// document.onkeypress = function(e) {
// e = e || window.event;
// var charCode = e.keyCode || e.which;
// if (charCode === 32) {
// e.preventDefault();
// return false;
// }
// };
Hotkey.setScope("__main__");
},
serializeCompletion() {
const arr = [];
self.traverseTree(node => {
if (node.toStateJSON) {
const val = node.toStateJSON();
if (val) arr.push(val);
}
});
const relations = self.relationStore.serializeCompletion();
if (relations) arr.push(relations);
const flatten = arr => {
return arr.reduce(function(flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
};
return flatten(arr);
},
/**
* Deserialize completion of models
*/
deserializeCompletion(json) {
let objCompletion = json;
if (typeof objCompletion !== "object") {
objCompletion = JSON.parse(objCompletion);
}
self._initialCompletionObj = objCompletion;
objCompletion.forEach(obj => {
if (obj["type"] !== "relation") {
const names = obj.to_name.split(",");
names.forEach(name => {
const toModel = self.names.get(name);
if (!toModel) throw new Error("No model found for " + obj.to_name);
const fromModel = self.names.get(obj.from_name);
if (!fromModel) throw new Error("No model found for " + obj.from_name);
toModel.fromStateJSON(obj, fromModel);
});
} else {
self.relationStore.deserializeRelation(
self.regionStore.findRegion(obj.from_id),
self.regionStore.findRegion(obj.to_id),
);
}
});
},
}));
export default types
.model("CompletionStore", {
selected: types.maybeNull(types.reference(Completion)),
completions: types.array(Completion),
predictions: types.array(Completion),
viewingAllCompletions: types.optional(types.boolean, false),
viewingAllPredictions: types.optional(types.boolean, false),
})
.views(self => ({
get store() {
return getRoot(self);
},
}))
.actions(self => {
function toggleViewingAll() {
if (self.viewingAllCompletions || self.viewingAllPredictions) {
self.completions.forEach(c => {
c.selected = false;
c.edittable = false;
c.regionStore.unselectAll();
});
self.predictions.forEach(c => {
c.selected = false;
c.regionStore.unselectAll();
});
} else {
selectCompletion(self.completions[0].id);
}
}
function toggleViewingAllPredictions() {
self.viewingAllPredictions = !self.viewingAllPredictions;
if (self.viewingAllPredictions) self.viewingAllCompletions = false;
toggleViewingAll();
}
function toggleViewingAllCompletions() {
self.viewingAllCompletions = !self.viewingAllCompletions;
if (self.viewingAllCompletions) self.viewingAllPredictions = false;
toggleViewingAll();
}
function unselectViewingAll() {
self.viewingAllCompletions = false;
self.viewingAllPredictions = false;
}
function selectItem(id, list) {
unselectViewingAll();
if (self.selected) self.selected.selected = false;
const c = list.find(c => c.id === id);
c.selected = true;
self.selected = c;
return c;
}
/**
* Select completion
* @param {*} id
*/
function selectCompletion(id) {
const c = selectItem(id, self.completions);
c.edittable = true;
c.setupHotKeys();
return c;
}
function selectPrediction(id) {
const p = selectItem(id, self.predictions);
p.regionStore.unselectAll();
return p;
}
function deleteCompletion(completion) {
getEnv(self).onDeleteCompletion(self.store, completion);
/**
* MST destroy completion
*/
destroy(completion);
self.selected = null;
/**
* Select other completion
*/
if (self.completions.length > 0) {
self.selectCompletion(self.completions[0].id);
}
}
function addItem(options) {
const { user, config } = self.store;
// convert config to mst model
const completionModel = Tree.treeToModel(config);
const modelClass = Registry.getModelByTag(completionModel.type);
//
let root = modelClass.create(completionModel);
const id = options["id"];
delete options["id"];
//
let node = {
id: id || guidGenerator(5),
root: root,
userGenerate: false,
...options,
};
if (user && !("createdBy" in node)) node["createdBy"] = user.displayName;
//
return Completion.create(node);
}
function addPrediction(options = {}) {
options.edittable = false;
options.type = "prediction";
const item = addItem(options);
self.predictions.unshift(item);
return item;
}
function addCompletion(options = {}) {
options.type = "completion";
const item = addItem(options);
self.completions.unshift(item);
return item;
}
function addCompletionFromPrediction(prediction) {
const c = self.addCompletion({ userGenerate: true });
const s = prediction._initialCompletionObj;
// we need to iterate here and rename all ids, as those might
// clash with the one in the prediction if used as a reference
// somewhere
s.forEach(r => {
if ("id" in r) r["id"] = guidGenerator();
});
selectCompletion(c.id);
c.deserializeCompletion(s);
return c;
}
return {
toggleViewingAllCompletions,
toggleViewingAllPredictions,
addPrediction,
addCompletion,
addCompletionFromPrediction,
selectCompletion,
selectPrediction,
deleteCompletion,
};
});