label-studio
Version:
Data Labeling Tool that is backend agnostic and can be embedded into your applications
210 lines (177 loc) • 5.09 kB
JavaScript
import React from "react";
import arrayMove from "array-move";
import { List } from "antd";
import { SortableContainer, SortableElement, sortableHandle } from "react-sortable-hoc";
import { observer, inject } from "mobx-react";
import { types } from "mobx-state-tree";
import Registry from "../../core/Registry";
import { guidGenerator } from "../../core/Helpers";
const RankerItemModel = types
.model({
backgroundColor: types.optional(types.string, "transparent"),
value: types.maybeNull(types.string),
_value: types.maybeNull(types.string),
selected: types.optional(types.boolean, false),
idx: types.number,
})
.views(self => ({}))
.actions(self => ({
setBG(val) {
self.backgroundColor = val;
},
setIdx(idx) {
self.idx = idx;
},
setSelected(val) {
self.selected = val;
},
}));
/**
* Ranker tag, used to ranking models
* @example
* <View>
* <Ranker name="ranker" value="$items"></Ranker>
* </View>
* @name Ranker
* @param {string} name of group
* @param {y|x=} [axis=y] axis direction
* @param {string} sortedHighlightColor sorted color
*/
const TagAttrs = types.model({
axis: types.optional(types.string, "y"),
lockaxis: types.maybeNull(types.string),
// elementvalue: types.maybeNull(types.string),
elementtag: types.optional(types.string, "Text"),
ranked: types.optional(types.boolean, true),
sortable: types.optional(types.boolean, true),
sortedhighlightcolor: types.maybeNull(types.string),
name: types.maybeNull(types.string),
value: types.maybeNull(types.string),
});
const Model = types
.model({
id: types.optional(types.identifier, guidGenerator),
type: "ranker",
update: types.optional(types.number, 1),
regions: types.array(RankerItemModel),
// update: types.optional(types.boolean, false)
})
.views(self => ({}))
.actions(self => ({
setUpdate() {
self.update = self.update + 1;
},
_addRegion(val, idx) {
const reg = RankerItemModel.create({
value: val,
idx: idx,
_value: val,
});
self.regions.push(reg);
},
moveItems({ oldIndex, newIndex }) {
if (oldIndex === newIndex) return;
if (self.sortedhighlightcolor) {
self.regions[oldIndex].setBG(self.sortedhighlightcolor);
}
self.regions[oldIndex].setSelected(true);
if (self._value) self._value = arrayMove(self._value, oldIndex, newIndex);
self.regions = arrayMove(self.regions, oldIndex, newIndex);
self.setUpdate();
},
toStateJSON() {
return {
from_name: self.name,
to_name: self.name,
value: {
// weights: ranked,
items: self.regions.map(r => r.value),
selected: self.regions.map(r => r.selected),
},
};
},
fromStateJSON(obj, fromModel) {
obj.value.items.forEach((v, idx) => {
self._addRegion(v, idx);
});
self.setUpdate();
},
}));
const RankerModel = types.compose("RankerModel", TagAttrs, Model);
const DragHandle = sortableHandle(() => <div className="drag-handle"></div>);
function isMobileDevice() {
try {
return typeof window.orientation !== "undefined" || navigator.userAgent.indexOf("IEMobile") !== -1;
} catch (e) {
return false;
}
}
const SortableText = SortableElement(({ item, value }) => {
let classNames;
if (isMobileDevice) {
classNames = "noselect";
}
const map = {
text: v => <span className={classNames}>{v._value}</span>,
image: v => <img src={v._value} alt="" />,
audio: v => <audio src={v._value} />,
};
return (
<div
style={{
padding: "1em",
userSelect: "none",
display: "flex",
alignItems: "center",
background: value.selected ? item.sortedhighlightcolor : "transparent",
}}
className={classNames}
onClick={ev => {
if (value.selected) {
value.setSelected(false);
item.setUpdate();
} else {
value.setSelected(true);
item.setUpdate();
}
ev.preventDefault();
return false;
}}
>
<DragHandle />
{map[item.elementtag.toLowerCase()](value)}
</div>
);
});
const SortableList = SortableContainer(({ item, items }) => {
return (
<List celled>
{items.map((value, index) => (
<SortableText
key={`item-${index}`}
index={index}
value={value}
color={value.backgroundColor}
item={item}
onClick={ev => {}}
/>
))}
</List>
);
});
const HtxRankerView = observer(({ store, item }) => {
const props = {};
if (isMobileDevice()) {
props["pressDelay"] = 100;
} else {
props["distance"] = 7;
}
return (
<div>
<SortableList update={item.update} item={item} items={item.regions} onSortEnd={item.moveItems} {...props} />
</div>
);
});
const HtxRanker = inject("store")(observer(HtxRankerView));
Registry.addTag("ranker", RankerModel, HtxRanker);
export { RankerModel, HtxRanker };