Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 5495: Add a note to a relation #5995

Draft
wants to merge 6 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { observer } from "mobx-react";
import { type FC, useCallback, useMemo, useState } from "react";
import { type FC, useCallback, useMemo, useEffect, useState} from "react";
import { IconMenu, IconRelationBi, IconRelationLeft, IconRelationRight, IconTrash } from "../../../assets/icons";
import { IconEyeClosed, IconEyeOpened } from "../../../assets/icons/timeline";
import { Button } from "../../../common/Button/Button";
Expand All @@ -8,13 +8,14 @@ import { wrapArray } from "../../../utils/utilities";
import { RegionItem } from "./RegionItem";
import { Select } from "antd";
import "./Relations.styl";
import TextArea from "antd/lib/input/TextArea";
import DOMPurify from "dompurify";

const RealtionsComponent: FC<any> = ({ relationStore }) => {
const RelationsComponent: FC<any> = ({ relationStore }) => {
const relations = relationStore.orderedRelations;

return (
<Block name="relations">
<RelationsList relations={relations} />
<RelationsList relations={relationStore.relations} />
</Block>
);
};
Expand All @@ -23,6 +24,11 @@ interface RelationsListProps {
relations: any[];
}

const sanitizeInput = (input: string): string => {
return DOMPurify.sanitize(input);
};


const RelationsList: FC<RelationsListProps> = observer(({ relations }) => {
return (
<>
Expand All @@ -35,14 +41,15 @@ const RelationsList: FC<RelationsListProps> = observer(({ relations }) => {

const RelationItem: FC<{ relation: any }> = observer(({ relation }) => {
const [hovered, setHovered] = useState(false);
const [showDescription, setShowDescription] = useState(false);

const onMouseEnter = useCallback(() => {
if (!!relation.node1 && !!relation.node2) {
setHovered(true);
relation.toggleHighlight();
relation.setSelfHighlight(true);
}
}, []);
}, [relation]);

const onMouseLeave = useCallback(() => {
if (!!relation.node1 && !!relation.node2) {
Expand All @@ -66,11 +73,18 @@ const RelationItem: FC<{ relation: any }> = observer(({ relation }) => {
return null;
}
}, [relation.direction]);


// const;
const handleToggleDescription = () => {
setShowDescription(!showDescription);
};

return (
<Elem name="item" mod={{ hidden: !relation.visible }} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
<Elem name="item"
mod={{ hidden: !relation.visible }}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
data-cy="relation-item">
<Elem name="content">
<Elem name="icon" onClick={relation.rotateDirection}>
<Elem name="direction">{directionIcon}</Elem>
Expand All @@ -81,15 +95,15 @@ const RelationItem: FC<{ relation: any }> = observer(({ relation }) => {
</Elem>
<Elem name="actions">
<Elem name="action">
{(hovered || relation.showMeta) && relation.hasRelations && (
{(hovered || !relation.visible) && (
<Button
primary={relation.showMeta}
aria-label={`${relation.showMeta ? "Hide" : "Show"} Relation Labels`}
data-cy={"toggle-meta-button"}
type={relation.showMeta ? undefined : "text"}
onClick={relation.toggleMeta}
style={{ padding: 0 }}
>
<IconMenu />
><IconMenu/>
</Button>
)}
</Elem>
Expand All @@ -98,6 +112,7 @@ const RelationItem: FC<{ relation: any }> = observer(({ relation }) => {
<Button
type="text"
onClick={relation.toggleVisibility}
data-cy={"toggle-visibility"}
aria-label={`${relation.visible ? "Hide" : "Show"} Relation`}
>
{relation.visible ? <IconEyeOpened /> : <IconEyeClosed />}
Expand All @@ -109,6 +124,7 @@ const RelationItem: FC<{ relation: any }> = observer(({ relation }) => {
<Button
type="text"
danger
style={{ background: '#0099FF', color: 'white'}}
aria-label="Delete Relation"
onClick={() => {
relation.node1.setHighlight(false);
Expand All @@ -122,13 +138,34 @@ const RelationItem: FC<{ relation: any }> = observer(({ relation }) => {
</Elem>
</Elem>
</Elem>
{relation.showMeta && <RelationMeta relation={relation} />}
{relation.showMeta && <RelationMeta relation={relation} />}
{relation.showMeta && (
<>
<Button
size="compact"
look="primary"
type="text"
style={{ background: '#0099FF', color: 'white', padding: 3 , margin: 20}}
onClick={handleToggleDescription}
danger
aria-label="Change Description">
Note
</Button>
{showDescription && (
<RelationDescription
relation={relation}
/>
)}
</>
)}
</Elem>
);
});


const RelationMeta: FC<any> = observer(({ relation }) => {
const { selectedValues, control } = relation;
const selectedValues = relation.selectedValues || [];
const control = relation.control || { children: [], choice: "single" };
const { children, choice } = control;

const selectionMode = useMemo(() => {
Expand All @@ -138,17 +175,18 @@ const RelationMeta: FC<any> = observer(({ relation }) => {
const onChange = useCallback(
(val: any) => {
const values: any[] = wrapArray(val);

relation.setRelations(values);
},
[relation],
);


return (
<Block name="relation-meta">
<Select
mode={selectionMode}
style={{ width: "100%" }}
data-cy="relation-select"
placeholder="Select labels"
value={selectedValues}
onChange={onChange}
Expand All @@ -163,4 +201,33 @@ const RelationMeta: FC<any> = observer(({ relation }) => {
);
});

export const Relations = observer(RealtionsComponent);
const RelationDescription: FC<{ relation: any }> = observer(
({ relation }) => {
const [text, setText] = useState<string>(relation.description);

useEffect(() => {
setText(relation.getDescription);
}, [relation]);


const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const newValue = sanitizeInput(event.target.value);
relation.setDescription(newValue);
};

return (
<Block name="relation-description">
<TextArea
defaultValue={text}
data-cy="relation-textArea"
onChange={handleChange}
style={{ padding:"20 20 20 20", width: "100%", minHeight: "100px" }}
id="free-text"
/>
</Block>
);
}
);

export const Relations = observer(RelationsComponent);
export default RelationDescription;
3 changes: 2 additions & 1 deletion web/libs/editor/src/stores/Annotation/Annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,7 @@ export const Annotation = types
`${obj.to_id}#${self.id}`,
obj.direction,
obj.labels,
obj.description,
);
}
});
Expand Down Expand Up @@ -1436,4 +1437,4 @@ export const Annotation = types
self.objects.forEach((object) => object.setReady && object.setReady(false));
self.areas.forEach((area) => area.setReady && area.setReady(false));
},
}));
}));
27 changes: 20 additions & 7 deletions web/libs/editor/src/stores/RelationStore.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { destroy, getParentOfType, getRoot, isAlive, types } from "mobx-state-tree";
import { destroy, getParentOfType, getRoot, isAlive, onSnapshot, types } from "mobx-state-tree";

import { guidGenerator } from "../core/Helpers";
import Tree, { TRAVERSE_SKIP } from "../core/Tree";
import Area from "../regions/Area";
import { isDefined } from "../utils/utilities";


const localStorageKeys = {
order: "relations:order",
};
Expand All @@ -23,6 +24,8 @@ const Relation = types

// labels
labels: types.maybeNull(types.array(types.string)),
// description
description: types.optional(types.string, "")
})
.volatile(() => ({
showMeta: false,
Expand All @@ -47,6 +50,10 @@ const Relation = types
return self.control?.children?.length > 0;
},

get getDescription() {
return self.description;
},

get shouldRender() {
if (!isAlive(self)) return false;
const { node1: start, node2: end } = self;
Expand Down Expand Up @@ -96,11 +103,14 @@ const Relation = types

toggleVisibility() {
self.visible = !self.visible;
},

},
setRelations(values) {
self.labels = values;
},
setDescription(description) {
self.description = description;
},
}));

const RelationStore = types
Expand Down Expand Up @@ -202,14 +212,14 @@ const RelationStore = types
self.relations.forEach((rl) => destroy(rl));
self.relations = [];
},

serialize() {
return self.relations.map((r) => {
const s = {
from_id: r.node1.cleanId,
to_id: r.node2.cleanId,
type: "relation",
direction: r.direction,
description: r.description ?? "",
};

if (r.selectedValues) s.labels = r.selectedValues;
Expand All @@ -218,19 +228,19 @@ const RelationStore = types
});
},

deserializeRelation(node1, node2, direction, labels) {
deserializeRelation(node1, node2, direction, labels, description) {
const rl = self.addRelation(node1, node2);

if (!rl) return; // duplicated relation

rl.direction = direction;
rl.labels = labels;
rl.description = description ?? "";
},

toggleConnections() {
self.showConnections = !self.showConnections;
},

toggleOrder() {
self.order = self.order === "asc" ? "desc" : "asc";
window.localStorage.setItem(localStorageKeys.order, self.order);
Expand All @@ -246,13 +256,16 @@ const RelationStore = types
});
},


setHighlight(relation) {
self._highlighted = relation.id;
},

removeHighlight() {
self._highlighted = null;
},
},
}));



export default RelationStore;
Loading