diff --git a/apps/editor/src/assets/de.json b/apps/editor/src/assets/de.json index 280a835ed..0dcadd145 100644 --- a/apps/editor/src/assets/de.json +++ b/apps/editor/src/assets/de.json @@ -121,6 +121,8 @@ "export-tooltip": "Export (Strg + E)", "exporting": "Exportieren", + "annotation-time": "Annotationszeit", + "show-delta": "Delta anzeigen", "confirm-task-annotation-tooltip": "Bestätigen", "skip-task-annotation-tooltip": "Überspringen", diff --git a/apps/editor/src/assets/en.json b/apps/editor/src/assets/en.json index 124fbf133..f9e1dd973 100644 --- a/apps/editor/src/assets/en.json +++ b/apps/editor/src/assets/en.json @@ -121,6 +121,8 @@ "export-tooltip": "Export (Ctrl + E)", "exporting": "Exporting", + "annotation-time": "Annotation Time", + "show-delta": "Show delta", "confirm-task-annotation-tooltip": "Confirm", "skip-task-annotation-tooltip": "Skip", diff --git a/apps/editor/src/components/editor/ai-bar/ai-bar.tsx b/apps/editor/src/components/editor/ai-bar/ai-bar.tsx index 691248e94..8c5425eda 100644 --- a/apps/editor/src/components/editor/ai-bar/ai-bar.tsx +++ b/apps/editor/src/components/editor/ai-bar/ai-bar.tsx @@ -1,4 +1,5 @@ import { + AnnotationStatus, BlueButtonParam, color, fontSize, @@ -20,7 +21,6 @@ import styled from "styled-components"; import { useStore } from "../../../app/root-store"; import { whoHome } from "../../../constants"; -import { AnnotationStatus } from "../../../models/who/annotation"; import { AnnotationData } from "../../../models/who/annotationData"; const AIBarSheet = styled(Sheet)` diff --git a/apps/editor/src/components/editor/layers/layers.tsx b/apps/editor/src/components/editor/layers/layers.tsx index 12b5fc4c4..1c843a295 100644 --- a/apps/editor/src/components/editor/layers/layers.tsx +++ b/apps/editor/src/components/editor/layers/layers.tsx @@ -15,6 +15,7 @@ import { stopPropagation, styledScrollbarMixin, SubtleText, + TaskType, useDelay, useDoubleTap, useForwardEvent, @@ -22,7 +23,7 @@ import { useShortTap, useTranslation, } from "@visian/ui-shared"; -import { Pixel } from "@visian/utils"; +import { isFromWHO, Pixel } from "@visian/utils"; import { Observer, observer } from "mobx-react-lite"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { @@ -365,6 +366,8 @@ export const Layers: React.FC = observer(() => { const layerCount = layers?.length; const activeLayer = store?.editor.activeDocument?.activeLayer; const activeLayerIndex = layers?.findIndex((layer) => layer === activeLayer); + const isSupervisorMode = + isFromWHO() && store?.currentTask?.kind === TaskType.Review; return ( <> { tooltipTx="add-annotation-layer" isDisabled={ !layerCount || - layerCount >= (store?.editor.activeDocument?.maxLayers || 0) + layerCount >= (store?.editor.activeDocument?.maxLayers || 0) || + isSupervisorMode } onPointerDown={ store?.editor.activeDocument?.addNewAnnotationLayer diff --git a/apps/editor/src/components/editor/supervisor-panel/index.ts b/apps/editor/src/components/editor/supervisor-panel/index.ts new file mode 100644 index 000000000..8fbba3d89 --- /dev/null +++ b/apps/editor/src/components/editor/supervisor-panel/index.ts @@ -0,0 +1 @@ +export * from "./supervisor-panel"; diff --git a/apps/editor/src/components/editor/supervisor-panel/supervisor-panel.tsx b/apps/editor/src/components/editor/supervisor-panel/supervisor-panel.tsx new file mode 100644 index 000000000..1102acd1d --- /dev/null +++ b/apps/editor/src/components/editor/supervisor-panel/supervisor-panel.tsx @@ -0,0 +1,150 @@ +import { + BooleanParam, + color, + Divider, + fontSize, + Icon, + Modal, + TaskType, + Text, + UserRole, +} from "@visian/ui-shared"; +import { observer } from "mobx-react-lite"; +import React, { useState } from "react"; +import styled from "styled-components"; +import { useStore } from "../../../app/root-store"; + +interface AnnotatorSectionProps { + annotatorRole: UserRole; + annotatorName: string; + annotationTime?: string; + colorAddition: string; + colorDeletion?: string; + isLast?: boolean; +} + +const SectionContainer = styled.div<{ isLast?: boolean }>` + display: flex; + flex-direction: column; + margin-bottom: ${(props) => (props.isLast ? "0px" : "12px")}; + width: 100%; +`; + +const AnnotatorInformationContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +`; + +const AnnotationTimeContainer = styled.div` + display: flex; + flex-direction: column; + margin-top: 8px; +`; + +const TypeText = styled(Text)` + color: ${color("lightText")}; + font-size: ${fontSize("small")}; +`; + +const InformationText = styled(Text)` + font-size: 18px; +`; + +const EditCircle = styled.div<{ circleColor: string }>` + width: 17px; + height: 17px; + background-color: ${(props) => + color(props.circleColor as any) || props.circleColor}; + border-radius: 50%; +`; +const CircleContainer = styled.div` + display: flex; + flex-direction: row; + min-width: 40px; + justify-content: space-between; +`; + +const AnnotatorSection: React.FC = ({ + annotatorRole, + annotatorName, + annotationTime, + colorAddition, + colorDeletion, + isLast = false, +}) => ( + <> + + + + + + + + + + {colorDeletion && ( + + + + )} + + + {annotationTime && ( + + + + + )} + + +); + +export const SupervisorPanel = observer(() => { + const store = useStore(); + if (!(store?.currentTask?.kind === TaskType.Review)) return <>; + const annotationCount = store.currentTask.annotations.length; + const annotations = store.currentTask.annotations.sort( + (firstAnnotation, secondAnnotation) => + new Date(firstAnnotation.submittedAt).getTime() - + new Date(secondAnnotation.submittedAt).getTime(), + ); + + const [shouldShowDelta, setShouldShowDelta] = useState(false); + + return ( + + {/* TODO: Set delta options */} + { + setShouldShowDelta(!shouldShowDelta); + }} + /> + {annotations.map((annotation, index) => { + const isLast = index === annotationCount - 1; + // TODO: Make coloring work properly + const correspondingLayer = store.editor.activeDocument?.getLayer( + annotation.data[0].correspondingLayerId, + ); + const annotationColor = correspondingLayer?.color || "yellow"; + return ( + + ); + })} + + ); +}); diff --git a/apps/editor/src/components/editor/ui-overlay/ui-overlay.tsx b/apps/editor/src/components/editor/ui-overlay/ui-overlay.tsx index 899c38ca6..edab5f2d7 100644 --- a/apps/editor/src/components/editor/ui-overlay/ui-overlay.tsx +++ b/apps/editor/src/components/editor/ui-overlay/ui-overlay.tsx @@ -4,6 +4,7 @@ import { FloatingUIButton, Notification, Spacer, + TaskType, Text, } from "@visian/ui-shared"; import { isFromWHO } from "@visian/utils"; @@ -37,6 +38,7 @@ import { ViewSettings } from "../view-settings"; import { UIOverlayProps } from "./ui-overlay.props"; import { SettingsPopUp } from "../settings-popup"; import { MeasurementPopUp } from "../measurement-popup"; +import { SupervisorPanel } from "../supervisor-panel"; const Container = styled(AbsoluteCover)` align-items: stretch; @@ -245,6 +247,10 @@ export const UIOverlay = observer( + {isFromWHO() && + store?.currentTask?.kind === TaskType.Review && ( + + )} diff --git a/apps/editor/src/models/editor/document.ts b/apps/editor/src/models/editor/document.ts index 4cec3c9e5..cc102f45d 100644 --- a/apps/editor/src/models/editor/document.ts +++ b/apps/editor/src/models/editor/document.ts @@ -13,6 +13,7 @@ import { ErrorNotification, ValueType, PerformanceMode, + ITask, } from "@visian/ui-shared"; import { handlePromiseSettledResult, @@ -824,6 +825,10 @@ export class Document return this.editor.performanceMode; } + public get currentTask(): ITask | undefined { + return this.context?.getCurrentTask(); + } + // Serialization public toJSON(): DocumentSnapshot { return { diff --git a/apps/editor/src/models/editor/tools/tool.ts b/apps/editor/src/models/editor/tools/tool.ts index 8b2f28a94..dcee16aca 100644 --- a/apps/editor/src/models/editor/tools/tool.ts +++ b/apps/editor/src/models/editor/tools/tool.ts @@ -3,9 +3,10 @@ import { IconType, IDocument, ITool, + TaskType, ViewMode, } from "@visian/ui-shared"; -import { ISerializable } from "@visian/utils"; +import { ISerializable, isFromWHO } from "@visian/utils"; import { makeObservable, observable } from "mobx"; import { Parameter, ParameterSnapshot } from "../parameters"; @@ -96,11 +97,17 @@ export class Tool } public canActivate(): boolean { + const isSupervisorMode = + isFromWHO() && this.document.currentTask?.kind === TaskType.Review; return Boolean( - (!this.supportedViewModes || - this.supportedViewModes.includes( - this.document.viewSettings.viewMode, - )) && + !( + isSupervisorMode && + (this.isDrawingTool || this.supportAnnotationsOnly) + ) && + (!this.supportedViewModes || + this.supportedViewModes.includes( + this.document.viewSettings.viewMode, + )) && (!this.supportedLayerKinds || (this.document.activeLayer && this.supportedLayerKinds.includes( diff --git a/apps/editor/src/models/root.ts b/apps/editor/src/models/root.ts index eb6dc3ec7..662d97c0f 100644 --- a/apps/editor/src/models/root.ts +++ b/apps/editor/src/models/root.ts @@ -6,6 +6,8 @@ import { IStorageBackend, Tab, ErrorNotification, + TaskType, + ITask, } from "@visian/ui-shared"; import { createFileFromBase64, @@ -21,7 +23,7 @@ import { DICOMWebServer } from "./dicomweb-server"; import { Editor, EditorSnapshot } from "./editor"; import { Tracker } from "./tracking"; import { ProgressNotification } from "./types"; -import { Task, TaskType } from "./who"; +import { Task } from "./who"; export interface RootSnapshot { editor: EditorSnapshot; @@ -54,7 +56,7 @@ export class RootStore implements ISerializable, IDisposable { public refs: { [key: string]: React.RefObject } = {}; public pointerDispatch?: IDispatch; - public currentTask?: Task; + public currentTask?: ITask; public tracker?: Tracker; @@ -96,6 +98,7 @@ export class RootStore implements ISerializable, IDisposable { setError: this.setError, getTracker: () => this.tracker, getColorMode: () => this.colorMode, + getCurrentTask: () => this.currentTask, }); deepObserve(this.editor, this.persist, { @@ -194,24 +197,31 @@ export class RootStore implements ISerializable, IDisposable { } else { // Task Type is Correct or Review await Promise.all( - whoTask.annotations.map(async (annotation, index) => { - const title = - whoTask.samples[index].title || - whoTask.samples[0].title || - `annotation_${index}`; + whoTask.annotations.map(async (annotation, annotationIndex) => { + let baseTitle = + whoTask.samples[annotationIndex]?.title || + whoTask.samples[0]?.title || + // TODO: Translation for unnamed + "unnamed.nii"; + // TODO: Handle base title without ".nii" ending + baseTitle = baseTitle.replace( + ".nii", + `_annotation_${annotationIndex}`, + ); await Promise.all( - annotation.data.map(async (annotationData) => { + annotation.data.map(async (annotationData, dataIndex) => { + const title = `${baseTitle}_${dataIndex}`; const createdLayerId = await this.editor.activeDocument?.importFiles( createFileFromBase64( - title.replace(".nii", "_annotation").concat(".nii"), + title.concat(".nii"), annotationData.data, ), - title.replace(".nii", "_annotation"), + title, true, ); if (createdLayerId) - annotationData.correspondingLayerId = createdLayerId; + annotationData.setCorrespondingLayerId(createdLayerId); }), ); }), diff --git a/apps/editor/src/models/types.ts b/apps/editor/src/models/types.ts index d3de07a79..9a84a62ba 100644 --- a/apps/editor/src/models/types.ts +++ b/apps/editor/src/models/types.ts @@ -1,5 +1,10 @@ import type React from "react"; -import type { ColorMode, ErrorNotification, Theme } from "@visian/ui-shared"; +import type { + ColorMode, + ErrorNotification, + ITask, + Theme, +} from "@visian/ui-shared"; import type { Tracker } from "./tracking"; @@ -27,6 +32,8 @@ export interface StoreContext { getTracker(): Tracker | undefined; getColorMode(): ColorMode; + + getCurrentTask(): ITask | undefined; } export interface ProgressNotification { diff --git a/apps/editor/src/models/who/annotation.ts b/apps/editor/src/models/who/annotation.ts index f0c8a69e1..99284cf37 100644 --- a/apps/editor/src/models/who/annotation.ts +++ b/apps/editor/src/models/who/annotation.ts @@ -1,21 +1,12 @@ -import { AnnotationData, AnnotationDataSnapshot } from "./annotationData"; -import { User, UserSnapshot } from "./user"; +import { + AnnotationSnapshot, + AnnotationStatus, + IAnnotation, +} from "@visian/ui-shared"; +import { AnnotationData } from "./annotationData"; +import { User } from "./user"; -export enum AnnotationStatus { - Pending = "PENDING", - Completed = "COMPLETED", - Rejected = "REJECTED", -} - -export interface AnnotationSnapshot { - annotationUUID: string; - status: AnnotationStatus; - annotationDataList: AnnotationDataSnapshot[]; - annotator: UserSnapshot; - submittedAt: string; -} - -export class Annotation { +export class Annotation implements IAnnotation { public annotationUUID: string; public status: AnnotationStatus; public data: AnnotationData[]; diff --git a/apps/editor/src/models/who/annotationData.ts b/apps/editor/src/models/who/annotationData.ts index 904af2c1f..669db1463 100644 --- a/apps/editor/src/models/who/annotationData.ts +++ b/apps/editor/src/models/who/annotationData.ts @@ -1,9 +1,7 @@ -export interface AnnotationDataSnapshot { - annotationDataUUID: string; - data: string; -} +import { AnnotationDataSnapshot, IAnnotationData } from "@visian/ui-shared"; +import { action, makeObservable, observable } from "mobx"; -export class AnnotationData { +export class AnnotationData implements IAnnotationData { public annotationDataUUID: string; public data: string; public correspondingLayerId = ""; @@ -12,6 +10,18 @@ export class AnnotationData { constructor(annotationData: any) { this.annotationDataUUID = annotationData.annotationDataUUID; this.data = annotationData.data; + + makeObservable(this, { + annotationDataUUID: observable, + data: observable, + correspondingLayerId: observable, + + setCorrespondingLayerId: action, + }); + } + + public setCorrespondingLayerId(layerId: string): void { + this.correspondingLayerId = layerId; } public toJSON(): AnnotationDataSnapshot { diff --git a/apps/editor/src/models/who/annotationTask.ts b/apps/editor/src/models/who/annotationTask.ts index 953c2f2ed..0b60d0164 100644 --- a/apps/editor/src/models/who/annotationTask.ts +++ b/apps/editor/src/models/who/annotationTask.ts @@ -1,24 +1,17 @@ -export interface AnnotationTaskSnapshot { - annotationTaskUUID: string; - kind: string; - title: string; - description: string; -} - -export enum AnnotationTaskType { - Classification = "classification", - ObjectDetection = "object_detection", - SemanticSegmentation = "semantic_segmentation", - InstanceSegmentation = "instance_segmentation", -} +import { + AnnotationTaskSnapshot, + AnnotationTaskType, + IAnnotationTask, +} from "@visian/ui-shared"; -export class AnnotationTask { +export class AnnotationTask implements IAnnotationTask { public annotationTaskUUID: string; public kind: AnnotationTaskType; public title: string; public description: string; // TODO: Properly type API response data + // TODO: Make observable constructor(annotationTask: any) { this.annotationTaskUUID = annotationTask.annotationTaskUUID; this.kind = annotationTask.kind; diff --git a/apps/editor/src/models/who/annotator.ts b/apps/editor/src/models/who/annotator.ts index 0492fb40b..a6c481ed9 100644 --- a/apps/editor/src/models/who/annotator.ts +++ b/apps/editor/src/models/who/annotator.ts @@ -1,15 +1,6 @@ -export interface AnnotatorSnapshot { - annotatorUUID: string; - expertise: string; - yearsInPractice: number; - expectedSalary: number; - workCountry: string; - studyCountry: string; - selfAssessment: number; - degree: string; -} +import { AnnotatorSnapshot, IAnnotator } from "@visian/ui-shared"; -export class Annotator { +export class Annotator implements IAnnotator { public annotatorUUID: string; public expertise: string; public yearsInPractice: number; @@ -20,6 +11,7 @@ export class Annotator { public degree: string; // TODO: Properly type API response data + // TODO: Make observable constructor(annotator: any) { this.annotatorUUID = annotator.annotatorUUID; this.expertise = annotator.expertise; diff --git a/apps/editor/src/models/who/campaign.ts b/apps/editor/src/models/who/campaign.ts new file mode 100644 index 000000000..8966f0487 --- /dev/null +++ b/apps/editor/src/models/who/campaign.ts @@ -0,0 +1,44 @@ +import { CampaignSnapshot, ICampaign } from "@visian/ui-shared"; +import { User } from "./user"; + +export class Campaign implements ICampaign { + public campaignUUID: string; + public name: string; + public description: string; + public status: string; + public datasets: string[]; + public annotators: User[]; + public reviewers: User[]; + + // TODO: Properly type API response data + // TODO: Make observable + constructor(campaign: any) { + this.campaignUUID = campaign.campaignUUID; + this.name = campaign.name; + this.description = campaign.description; + this.status = campaign.status; + this.datasets = campaign.datasets; + this.annotators = campaign.annotators.map( + (annotator: any) => new User(annotator), + ); + this.reviewers = campaign.reviewers.map( + (reviewer: any) => new User(reviewer), + ); + } + + public toJSON(): CampaignSnapshot { + return { + campaignUUID: this.campaignUUID, + name: this.name, + description: this.description, + status: this.status, + datasets: this.datasets, + annotators: Object.values(this.annotators).map((annotator) => + annotator.toJSON(), + ), + reviewers: Object.values(this.reviewers).map((reviewer) => + reviewer.toJSON(), + ), + }; + } +} diff --git a/apps/editor/src/models/who/reviewer.ts b/apps/editor/src/models/who/reviewer.ts index ee3b4feb0..270b5bdb2 100644 --- a/apps/editor/src/models/who/reviewer.ts +++ b/apps/editor/src/models/who/reviewer.ts @@ -1,11 +1,10 @@ -export interface ReviewerSnapshot { - reviewerUUID: string; -} +import { IReviewer, ReviewerSnapshot } from "@visian/ui-shared"; -export class Reviewer { +export class Reviewer implements IReviewer { public reviewerUUID: string; // TODO: Properly type API response data + // TODO: Make observable constructor(reviewer: any) { this.reviewerUUID = reviewer.reviewerUUID; } diff --git a/apps/editor/src/models/who/sample.ts b/apps/editor/src/models/who/sample.ts index fd18647e6..936ad6292 100644 --- a/apps/editor/src/models/who/sample.ts +++ b/apps/editor/src/models/who/sample.ts @@ -1,15 +1,12 @@ -export interface SampleSnapshot { - sampleUUID: string; - title: string; - data: string; -} +import { ISample, SampleSnapshot } from "@visian/ui-shared"; -export class Sample { +export class Sample implements ISample { public sampleUUID: string; public title: string; public data: string; // TODO: Properly type API response data + // TODO: Make observable constructor(sample: any) { this.sampleUUID = sample.sampleUUID; this.title = sample.title; diff --git a/apps/editor/src/models/who/task.ts b/apps/editor/src/models/who/task.ts index 5745705cf..fc1e481b3 100644 --- a/apps/editor/src/models/who/task.ts +++ b/apps/editor/src/models/who/task.ts @@ -1,24 +1,16 @@ -import { Annotation, AnnotationSnapshot, AnnotationStatus } from "./annotation"; -import { AnnotationTask, AnnotationTaskSnapshot } from "./annotationTask"; +import { + AnnotationStatus, + ITask, + TaskSnapshot, + TaskType, +} from "@visian/ui-shared"; +import { Annotation } from "./annotation"; +import { AnnotationTask } from "./annotationTask"; +import { Campaign } from "./campaign"; import { Sample } from "./sample"; -import { User, UserSnapshot } from "./user"; +import { User } from "./user"; -export interface TaskSnapshot { - taskUUID: string; - kind: string; - readOnly: boolean; - annotationTasks: AnnotationTaskSnapshot[]; - annotations?: AnnotationSnapshot[]; - assignee: UserSnapshot; -} - -export enum TaskType { - Create = "create", - Correct = "correct", - Review = "review", -} - -export class Task { +export class Task implements ITask { public taskUUID: string; public kind: TaskType; public readOnly: boolean; @@ -26,8 +18,10 @@ export class Task { public samples: Sample[]; public annotations: Annotation[]; public assignee: User; + public campaign?: Campaign; // TODO: Properly type API response data + // TODO: Make observable constructor(task: any) { this.taskUUID = task.taskUUID; this.kind = task.kind; @@ -40,6 +34,9 @@ export class Task { (annotation: any) => new Annotation(annotation), ); this.assignee = new User(task.assignee); + if (task.campaign && Object.keys(task.campaign).length > 1) { + this.campaign = new Campaign(task.campaign); + } } public addNewAnnotation(): void { @@ -65,6 +62,7 @@ export class Task { annotation.toJSON(), ), assignee: this.assignee.toJSON(), + campaign: this.campaign ? this.campaign.toJSON() : {}, }; } } diff --git a/apps/editor/src/models/who/user.ts b/apps/editor/src/models/who/user.ts index e14818349..31b6126a1 100644 --- a/apps/editor/src/models/who/user.ts +++ b/apps/editor/src/models/who/user.ts @@ -1,18 +1,8 @@ -import { Annotator, AnnotatorSnapshot } from "./annotator"; -import { Reviewer, ReviewerSnapshot } from "./reviewer"; +import { IUser, UserRole, UserSnapshot } from "@visian/ui-shared"; +import { Annotator } from "./annotator"; +import { Reviewer } from "./reviewer"; -export interface UserSnapshot { - userUUID: string; - idpID: string; - username: string; - birthdate: string; - timezone: string; - email: string; - annotatorRole: AnnotatorSnapshot | Record; - reviewerRole: ReviewerSnapshot | Record; -} - -export class User { +export class User implements IUser { public userUUID: string; public idpID: string; public username: string; @@ -23,6 +13,7 @@ export class User { public reviewerRole?: Reviewer; // TODO: Properly type API response data + // TODO: Make observable constructor(user: any) { this.userUUID = user.userUUID; this.idpID = user.idpID; @@ -30,14 +21,21 @@ export class User { this.birthdate = user.birthdate; this.timezone = user.timezone; this.email = user.email; - if (user.annotatorRole && Object.keys(user.annotatorRole).length > 1) { + if (user.annotatorRole && "annotatorUUID" in user.annotatorRole) { this.annotatorRole = new Annotator(user.annotatorRole); } - if (user.reviewerRole && Object.keys(user.reviewerRole).length > 1) { + if (user.reviewerRole && "reviewerUUID" in user.reviewerRole) { this.reviewerRole = new Reviewer(user.reviewerRole); } } + // TODO: Return actual role name + public getRoleName(): UserRole { + if (this.reviewerRole) return "Reviewer"; + if (this.annotatorRole) return "Annotator"; + return "Supervisor"; + } + public toJSON(): UserSnapshot { return { userUUID: this.userUUID, diff --git a/libs/ui-shared/src/lib/types/editor/document.ts b/libs/ui-shared/src/lib/types/editor/document.ts index 8641392e5..86ee92d9a 100644 --- a/libs/ui-shared/src/lib/types/editor/document.ts +++ b/libs/ui-shared/src/lib/types/editor/document.ts @@ -1,5 +1,5 @@ import type * as THREE from "three"; -import { ITrackingData, TrackingLog } from "@visian/ui-shared"; +import { ITask, ITrackingData, TrackingLog } from "@visian/ui-shared"; import type { ISliceRenderer, IVolumeRenderer } from "../rendering"; import type { IHistory } from "./history"; @@ -74,6 +74,8 @@ export interface IDocument { theme: Theme; performanceMode: PerformanceMode; + currentTask: ITask | undefined; + /** Indicates wether the layer menu is open. */ showLayerMenu: boolean; diff --git a/libs/ui-shared/src/lib/types/index.ts b/libs/ui-shared/src/lib/types/index.ts index 49fae9e15..42ede455a 100644 --- a/libs/ui-shared/src/lib/types/index.ts +++ b/libs/ui-shared/src/lib/types/index.ts @@ -3,3 +3,4 @@ export * from "./error-notification"; export * from "./rendering"; export * from "./tracking"; export * from "./translation"; +export * from "./who"; diff --git a/libs/ui-shared/src/lib/types/who/annotation.ts b/libs/ui-shared/src/lib/types/who/annotation.ts new file mode 100644 index 000000000..e88a5e5e7 --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/annotation.ts @@ -0,0 +1,26 @@ +import { AnnotationDataSnapshot, IAnnotationData } from "./annotationData"; +import { IUser, UserSnapshot } from "./user"; + +export enum AnnotationStatus { + Pending = "PENDING", + Completed = "COMPLETED", + Rejected = "REJECTED", +} + +export interface AnnotationSnapshot { + annotationUUID: string; + status: AnnotationStatus; + annotationDataList: AnnotationDataSnapshot[]; + annotator: UserSnapshot; + submittedAt: string; +} + +export interface IAnnotation { + annotationUUID: string; + status: AnnotationStatus; + data: IAnnotationData[]; + annotator: IUser; + submittedAt: string; + + toJSON(): AnnotationSnapshot; +} diff --git a/libs/ui-shared/src/lib/types/who/annotationData.ts b/libs/ui-shared/src/lib/types/who/annotationData.ts new file mode 100644 index 000000000..46f03b103 --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/annotationData.ts @@ -0,0 +1,13 @@ +export interface AnnotationDataSnapshot { + annotationDataUUID: string; + data: string; +} + +export interface IAnnotationData { + annotationDataUUID: string; + data: string; + correspondingLayerId: string; + + setCorrespondingLayerId(layerId: string): void; + toJSON(): AnnotationDataSnapshot; +} diff --git a/libs/ui-shared/src/lib/types/who/annotationTask.ts b/libs/ui-shared/src/lib/types/who/annotationTask.ts new file mode 100644 index 000000000..6ffeacbf3 --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/annotationTask.ts @@ -0,0 +1,22 @@ +export interface AnnotationTaskSnapshot { + annotationTaskUUID: string; + kind: string; + title: string; + description: string; +} + +export enum AnnotationTaskType { + Classification = "classification", + ObjectDetection = "object_detection", + SemanticSegmentation = "semantic_segmentation", + InstanceSegmentation = "instance_segmentation", +} + +export interface IAnnotationTask { + annotationTaskUUID: string; + kind: AnnotationTaskType; + title: string; + description: string; + + toJSON(): AnnotationTaskSnapshot; +} diff --git a/libs/ui-shared/src/lib/types/who/annotator.ts b/libs/ui-shared/src/lib/types/who/annotator.ts new file mode 100644 index 000000000..6cb3952e9 --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/annotator.ts @@ -0,0 +1,23 @@ +export interface AnnotatorSnapshot { + annotatorUUID: string; + expertise: string; + yearsInPractice: number; + expectedSalary: number; + workCountry: string; + studyCountry: string; + selfAssessment: number; + degree: string; +} + +export interface IAnnotator { + annotatorUUID: string; + expertise: string; + yearsInPractice: number; + expectedSalary: number; + workCountry: string; + studyCountry: string; + selfAssessment: number; + degree: string; + + toJSON(): AnnotatorSnapshot; +} diff --git a/libs/ui-shared/src/lib/types/who/campaign.ts b/libs/ui-shared/src/lib/types/who/campaign.ts new file mode 100644 index 000000000..33f93fefe --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/campaign.ts @@ -0,0 +1,23 @@ +import { IUser, UserSnapshot } from "./user"; + +export interface CampaignSnapshot { + campaignUUID: string; + name: string; + description: string; + status: string; + datasets: string[]; + annotators: UserSnapshot[]; + reviewers: UserSnapshot[]; +} + +export interface ICampaign { + campaignUUID: string; + name: string; + description: string; + status: string; + datasets: string[]; + annotators: IUser[]; + reviewers: IUser[]; + + toJSON(): CampaignSnapshot; +} diff --git a/libs/ui-shared/src/lib/types/who/index.ts b/libs/ui-shared/src/lib/types/who/index.ts new file mode 100644 index 000000000..25c73fa3e --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/index.ts @@ -0,0 +1,9 @@ +export * from "./annotation"; +export * from "./annotationData"; +export * from "./annotationTask"; +export * from "./annotator"; +export * from "./campaign"; +export * from "./reviewer"; +export * from "./sample"; +export * from "./task"; +export * from "./user"; diff --git a/libs/ui-shared/src/lib/types/who/reviewer.ts b/libs/ui-shared/src/lib/types/who/reviewer.ts new file mode 100644 index 000000000..0a9b07464 --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/reviewer.ts @@ -0,0 +1,9 @@ +export interface ReviewerSnapshot { + reviewerUUID: string; +} + +export interface IReviewer { + reviewerUUID: string; + + toJSON(): ReviewerSnapshot; +} diff --git a/libs/ui-shared/src/lib/types/who/sample.ts b/libs/ui-shared/src/lib/types/who/sample.ts new file mode 100644 index 000000000..1dbca7e0f --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/sample.ts @@ -0,0 +1,13 @@ +export interface SampleSnapshot { + sampleUUID: string; + title: string; + data: string; +} + +export interface ISample { + sampleUUID: string; + title: string; + data: string; + + toJSON(): SampleSnapshot; +} diff --git a/libs/ui-shared/src/lib/types/who/task.ts b/libs/ui-shared/src/lib/types/who/task.ts new file mode 100644 index 000000000..25aa0ffc2 --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/task.ts @@ -0,0 +1,35 @@ +import { AnnotationSnapshot, IAnnotation } from "./annotation"; +import { AnnotationTaskSnapshot, IAnnotationTask } from "./annotationTask"; +import { CampaignSnapshot, ICampaign } from "./campaign"; +import { ISample } from "./sample"; +import { IUser, UserSnapshot } from "./user"; + +export interface TaskSnapshot { + taskUUID: string; + kind: string; + readOnly: boolean; + annotationTasks: AnnotationTaskSnapshot[]; + annotations?: AnnotationSnapshot[]; + assignee: UserSnapshot; + campaign: CampaignSnapshot | Record; +} + +export enum TaskType { + Create = "create", + Correct = "correct", + Review = "review", +} + +export interface ITask { + taskUUID: string; + kind: TaskType; + readOnly: boolean; + annotationTasks: IAnnotationTask[]; + samples: ISample[]; + annotations: IAnnotation[]; + assignee: IUser; + campaign?: ICampaign; + + addNewAnnotation(): void; + toJSON(): TaskSnapshot; +} diff --git a/libs/ui-shared/src/lib/types/who/user.ts b/libs/ui-shared/src/lib/types/who/user.ts new file mode 100644 index 000000000..59ba140e3 --- /dev/null +++ b/libs/ui-shared/src/lib/types/who/user.ts @@ -0,0 +1,29 @@ +import { AnnotatorSnapshot, IAnnotator } from "./annotator"; +import { IReviewer, ReviewerSnapshot } from "./reviewer"; + +export type UserRole = "Annotator" | "Reviewer" | "Supervisor"; + +export interface UserSnapshot { + userUUID: string; + idpID: string; + username: string; + birthdate: string; + timezone: string; + email: string; + annotatorRole: AnnotatorSnapshot | Record; + reviewerRole: ReviewerSnapshot | Record; +} + +export interface IUser { + userUUID: string; + idpID: string; + username: string; + birthdate: string; + timezone: string; + email: string; + annotatorRole?: IAnnotator; + reviewerRole?: IReviewer; + + getRoleName(): UserRole; + toJSON(): UserSnapshot; +}