Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cf2d072
refactor: remove external dependency for layer sorting
richartkeil Dec 9, 2023
edf8ab0
fix: Import annotation groups correctly
Tonybodo Dec 13, 2023
2dd3c82
fix: Import for single Images
Tonybodo Dec 14, 2023
be5dd36
fix: DnD for annotation groups and Image Layers
Tonybodo Dec 14, 2023
21139d0
Feat: Add new Annotation Group
Tonybodo Dec 16, 2023
9b49968
feat: Add Annotation Groups via UI
Tonybodo Dec 16, 2023
951ce82
Merge pull request #568 from HealthML/annotation-groups-only
Tonybodo Dec 16, 2023
d241ab4
Fix: Can not drag and drop after import
Tonybodo Jan 15, 2024
16940ae
Feat: Delete AnnotationsGroups
Tonybodo Jan 16, 2024
b96499c
Merge branch 'refactor-annotation-groups-ui' into add-and-delete-anno…
Tonybodo Jan 16, 2024
455f90a
Feat: Delete annotation references from database
Tonybodo Jan 20, 2024
bee5af2
Feat: Refactor saveAs method, so that its add metadata
Tonybodo Jan 20, 2024
aa379ae
Fix: First annotation-layer not draggable
Tonybodo Feb 4, 2024
3d9643f
Feat: Confirmation popups for delete annotation groups
Tonybodo Feb 6, 2024
d59c816
Fix: Review adjustments
Tonybodo Feb 9, 2024
58be698
Refactor: SaveAs changes metadata for existing group
Tonybodo Feb 10, 2024
079385b
Fix: Flanky import and imported group naming
Tonybodo Feb 12, 2024
01750d4
Fix: Updating hasChanges on annotation groups
Tonybodo Feb 12, 2024
79d7877
Fix: Create squashed Nifti and export popup
Tonybodo Feb 12, 2024
2c6a2c7
Fix: Fixes for presentation
Tonybodo Feb 16, 2024
8c0350f
Merge pull request #567 from HealthML/refactor-annotation-groups-ui
Tonybodo Feb 24, 2024
ed85c4d
Merge pull request #573 from HealthML/add-and-delete-annotation-groups
Tonybodo Feb 24, 2024
244efd9
Merge branch 'refactor-annotation-groups' into refactor-saving
Tonybodo Feb 24, 2024
6a9566c
Fix: Merge request
Tonybodo Feb 24, 2024
46f2f94
Refactor: Remove popup for groups without metadata
Tonybodo Feb 24, 2024
e66bd44
Merge pull request #577 from HealthML/refactor-saving
Tonybodo Feb 24, 2024
075847f
Merge branch 'develop-annotation-service' into refactor-annotation-gr…
Tonybodo Feb 24, 2024
70f3eaf
Fix: File endings corrupt saving
Tonybodo Feb 27, 2024
da4c03b
Fix: Review changes
Tonybodo Feb 27, 2024
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
14 changes: 13 additions & 1 deletion apps/editor/src/assets/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"layers": "Ebenen",
"main-image-layer": "Haupt-Bildebene",
"add-annotation-layer": "Neue Annotation hinzufügen",
"add-annotation-group": "Neue Annotationsgruppe hinzufügen",
"no-layers": "Keine Ebenen geladen.",
"untitled-layer": "Unbenannte Ebene",
"layer-settings": "Ebeneneinstellungen",
Expand Down Expand Up @@ -124,6 +125,7 @@
"export-tooltip": "Export (Strg + E)",
"exporting": "Exportieren",
"layers-to-export": "Annotationsebenen, die exportiert werden sollen",
"should-export-images": "Bildebenden mit exportieren",
"export-all-layers": "Alle Ebenen exportieren",
"export-annotation-group": "Gruppe exportieren",
"export-as": "Exportieren als",
Expand Down Expand Up @@ -439,5 +441,15 @@

"review": "Überprüfen",
"supervise": "Abnicken",
"review-description": "{{taskType}} der Annotationen des Bildes {{image}}."
"review-description": "{{taskType}} der Annotationen des Bildes {{image}}.",

"untitled-group": "Unbenannte Gruppe",
"rename-group": "Gruppe umbenennen",
"delete-group": "Gruppe löschen",
"delete-annotation-group-message": "Wollen Sie die Annotation \"{{name}}\" und die folgenden Ebenen wirklich löschen?",
"warning": "Warnung:",
"delete-backend-data-warning": "Durch das Löschen der Annotationsgruppe \"{{name}}\" wird auch die unter \"{{dataUri}}\" gespeicherte Annotation unwiderruflich gelöscht!",
"unsaved-backend-annotations": "Folgende Gruppen sind noch nicht gespeichert:",
"get-annotation-error": "Annotation konnte nicht gefunden werden",
"get-annotation-error-description": "Die Annotation mit der ID \"{{id}}\" konnte nicht gefunden werden."
}
14 changes: 13 additions & 1 deletion apps/editor/src/assets/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"layers": "Layers",
"main-image-layer": "Main Image Layer",
"add-annotation-layer": "Add new annotation",
"add-annotation-group": "Add new annotation group",
"no-layers": "No layers loaded.",
"untitled-layer": "Untitled layer",
"layer-settings": "Layer Settings",
Expand Down Expand Up @@ -124,6 +125,7 @@
"export-tooltip": "Export (Ctrl/Cmd + E)",
"exporting": "Exporting",
"layers-to-export": "Layers to export",
"should-export-images": "Include image layers",
"export-all-layers": "Export all layers",
"export-annotation-group": "Export annotation group",
"export-as": "Export as",
Expand Down Expand Up @@ -438,5 +440,15 @@

"review": "Review",
"supervise": "Supervise",
"review-description": "{{taskType}} the annotations of {{image}}"
"review-description": "{{taskType}} the annotations of {{image}}",

"untitled-group": "Untitled group",
"rename-group": "Rename Group",
"delete-group": "Delete Group",
"delete-annotation-group-message": "Do you really want to delete the annotation group \"{{name}}\" and following layers?",
"warning": "Warning:",
"delete-backend-data-warning": "Deleting the annotation group \"{{name}}\" also deletes the annotation saved under \"{{dataUri}}\"!",
"unsaved-backend-annotations": "The following annotation groups are not saved yet:",
"get-annotation-error": "Annotation cannot be found",
"get-annotation-error-description": "Annotation with the ID \"{{id}}\" cannot be found."
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { StatefulPopUpProps } from "@visian/ui-shared";
import { ReactNode } from "react";

export interface ConfirmationPopUpProps extends StatefulPopUpProps {
export interface ConfirmationPopUpProps<T = ReactNode>
extends StatefulPopUpProps {
title?: string;
titleTx?: string;
message?: string;
Expand All @@ -10,4 +12,5 @@ export interface ConfirmationPopUpProps extends StatefulPopUpProps {
cancel?: string;
cancelTx?: string;
onConfirm?: () => void;
children?: ReactNode;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ButtonParam, PopUp, Text } from "@visian/ui-shared";
import { observer } from "mobx-react-lite";
import { useCallback } from "react";
import { ReactNode, useCallback } from "react";
import styled from "styled-components";

import { ConfirmationPopUpProps } from "./confirmation-popup.props";
Expand All @@ -27,7 +27,7 @@ const StyledText = styled(Text)`
overflow-wrap: anywhere;
`;

export const ConfirmationPopup = observer<ConfirmationPopUpProps>(
export const ConfirmationPopup = observer<ConfirmationPopUpProps<ReactNode>>(
({
isOpen,
onClose,
Expand All @@ -40,6 +40,7 @@ export const ConfirmationPopup = observer<ConfirmationPopUpProps>(
confirmTx,
cancel,
cancelTx,
children,
}) => {
const handleConfirmation = useCallback(() => {
onConfirm?.();
Expand All @@ -55,6 +56,7 @@ export const ConfirmationPopup = observer<ConfirmationPopUpProps>(
shouldDismissOnOutsidePress
>
<StyledText tx={messageTx} text={message} />
{children}
<InlineRow>
<StyledTextButton
label={cancel}
Expand Down
98 changes: 89 additions & 9 deletions apps/editor/src/components/editor/export-popup/export-popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import {
Button,
DropDown,
ILayer,
InvisibleButton,
LayerList,
PopUp,
Switch,
Text,
} from "@visian/ui-shared";
import { observer } from "mobx-react-lite";
import path from "path";
import { useCallback, useEffect, useState } from "react";
import styled from "styled-components";

Expand Down Expand Up @@ -47,6 +49,15 @@ const StyledDropDown = styled(DropDown)`
background: none;
`;

export const SelectionCheckbox = styled(InvisibleButton)<{
emphasized?: boolean;
}>`
width: 18px;
margin-right: 8px;
opacity: ${({ emphasized }) => (emphasized ? 1 : 0.4)};
transition: opacity 0.1s ease-in-out;
`;

export const ExportPopUp = observer<ExportPopUpProps>(({ isOpen, onClose }) => {
const store = useStore();

Expand All @@ -60,31 +71,59 @@ export const ExportPopUp = observer<ExportPopUpProps>(({ isOpen, onClose }) => {
const [selectedExtension, setSelectedExtension] = useState(
fileExtensions[0].value,
);
const [shouldIncludeImages, setShouldIncludeImages] = useState(false);

useEffect(() => {
if (shouldExportAllLayers) {
setLayersToExport(
store?.editor?.activeDocument?.layers?.filter(
(layer) => layer.isAnnotation,
(layer) => layer.isAnnotation || shouldIncludeImages,
) ?? [],
);
} else if (shouldIncludeImages) {
const activeGroupLayers =
store?.editor?.activeDocument?.activeLayer?.getAnnotationGroupLayers();
const imageLayers =
store?.editor?.activeDocument?.layers?.filter(
(layer) => !layer.isAnnotation,
) ?? [];

const combinedLayers = activeGroupLayers?.concat(imageLayers) ?? [];
setLayersToExport(combinedLayers);
} else {
setLayersToExport(
store?.editor?.activeDocument?.activeLayer
?.getAnnotationGroupLayers()
.filter((layer) => layer.isAnnotation) ?? [],
store?.editor?.activeDocument?.activeLayer?.getAnnotationGroupLayers() ??
[],
);
}
}, [store, isOpen, shouldExportAllLayers]);
}, [store, isOpen, shouldExportAllLayers, shouldIncludeImages]);

const handleExport = useCallback(async () => {
store?.setProgress({ labelTx: "exporting" });

try {
const annotationGroupTitle =
store?.editor?.activeDocument?.activeLayer?.annotationGroup?.title;

let fileName;

if (shouldExportAllLayers) {
fileName = undefined;
} else {
fileName = annotationGroupTitle
? path.basename(
annotationGroupTitle,
path.extname(annotationGroupTitle),
)
: undefined;
}
if (selectedExtension === ".zip") {
await store?.editor.activeDocument?.exportZip(layersToExport, true);
await store?.editor.activeDocument?.exportZip(layersToExport, fileName);
} else {
await store?.editor?.activeDocument?.exportSquashedNii(layersToExport);
await store?.editor?.activeDocument?.exportSquashedNii(
layersToExport,
fileName,
);
}
} catch (error) {
store?.setError({
Expand All @@ -94,7 +133,36 @@ export const ExportPopUp = observer<ExportPopUpProps>(({ isOpen, onClose }) => {
} finally {
store?.setProgress();
}
}, [layersToExport, selectedExtension, store]);
}, [layersToExport, selectedExtension, shouldExportAllLayers, store]);

const handleCheckIncludeImageLayer = useCallback(
(value: boolean) => {
if (value) {
const imageLayers =
store?.editor?.activeDocument?.layers?.filter(
(layer) => !layer.isAnnotation,
) ?? [];
const newLayersToExport = layersToExport.concat(imageLayers);
setLayersToExport(newLayersToExport);
setShouldIncludeImages(true);
} else {
setLayersToExport(layersToExport.filter((layer) => layer.isAnnotation));
setShouldIncludeImages(false);
}
},
[layersToExport, store?.editor?.activeDocument?.layers],
);

const handleSelectExtension = useCallback((value: string) => {
if (value === ".nii.gz") {
setLayersToExport(
store?.editor?.activeDocument?.activeLayer?.getAnnotationGroupLayers() ??
[],
);
setShouldIncludeImages(false);
}
setSelectedExtension(value);
}, []);

return (
<ExportPopUpContainer
Expand All @@ -113,12 +181,24 @@ export const ExportPopUp = observer<ExportPopUpProps>(({ isOpen, onClose }) => {
value={shouldExportAllLayers}
onChange={setShouldExportAllLayers}
/>
{selectedExtension === ".zip" && (
<InlineRow>
<SectionLabel tx="should-export-images" />
<SelectionCheckbox
icon={shouldIncludeImages ? "checked" : "unchecked"}
onPointerDown={() =>
handleCheckIncludeImageLayer(!shouldIncludeImages)
}
emphasized={shouldIncludeImages}
/>
</InlineRow>
)}
<SectionLabel tx="export-as" />
<InlineRow>
<StyledDropDown
options={fileExtensions}
defaultValue={selectedExtension}
onChange={(value) => setSelectedExtension(value)}
onChange={(value) => handleSelectExtension(value)}
size="medium"
borderRadius="default"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { IAnnotationGroup, ILayer } from "@visian/ui-shared";
import { observer } from "mobx-react-lite";

import { AnnotationGroupListItem } from "./group-list-item";

export const DraggableAnnotationGroupListItem = observer<{
group: IAnnotationGroup;
isActive: boolean;
isLast?: boolean;
isDragged?: boolean;
draggedLayer?: ILayer;
}>(({ group, isActive, isLast, isDragged, draggedLayer }) => {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id: group.id, data: { annotationGroup: group } });

const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragged ? 0.3 : 1,
};

return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<AnnotationGroupListItem
group={group}
isActive={isActive}
isLast={isLast}
draggedLayer={draggedLayer}
/>
</div>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { ILayer } from "@visian/ui-shared";
import { observer } from "mobx-react-lite";

import { LayerListItem } from "./layer-list-item";

export const DraggableLayerListItem = observer<{
layer: ILayer;
isActive?: boolean;
isLast?: boolean;
isDragged?: boolean;
}>(({ layer, isActive, isLast, isDragged }) => {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({
id: layer.id,
data: { layer },
});

const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragged ? 0.3 : 1,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add the 0.3 as a variable draggedLayerListItemOpacity (or similar) to the theme.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here with the style?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is still open but maybe not necessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my point of view its fine, if you want to add it to the theme, feel free :D

};

return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<LayerListItem layer={layer} isActive={isActive} isLast={isLast} />
</div>
);
});
Loading