Skip to content

Commit

Permalink
Merge pull request pixano#41 from pixano/fix/improve-multiview-and-to…
Browse files Browse the repository at this point in the history
…ol-selection

fix(front): Improve multi-view and tool selection
  • Loading branch information
timothee-LJN authored Jan 18, 2024
2 parents 74aa206 + af1c446 commit 666e92c
Show file tree
Hide file tree
Showing 14 changed files with 163 additions and 46 deletions.
39 changes: 36 additions & 3 deletions ui/apps/pixano/src/components/layout/DatasetHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@
import pixanoLogo from "@pixano/core/src/assets/pixano.png";
import { IconButton, PrimaryButton, type DatasetInfo } from "@pixano/core/src";
import { IconButton, PrimaryButton, ConfirmModal, type DatasetInfo } from "@pixano/core/src";
import { findSelectedItem } from "$lib/api/navigationApi";
import { datasetsStore, isLoadingNewItemStore } from "$lib/stores/datasetStores";
import {
datasetsStore,
isLoadingNewItemStore,
saveCurrentItemStore,
} from "$lib/stores/datasetStores";
import { navItems } from "$lib/constants/headerConstants";
export let datasetName: string;
Expand All @@ -35,6 +39,11 @@
let datasets: DatasetInfo[];
let currentItemId: string;
let isLoading: boolean;
let canSaveCurrentItem: boolean;
type Direction = "previous" | "next" | "none";
let showConfirmModal: Direction = "none";
saveCurrentItemStore.subscribe((value) => (canSaveCurrentItem = value.canSave));
isLoadingNewItemStore.subscribe((value) => {
isLoading = value;
Expand All @@ -48,14 +57,23 @@
currentItemId = value.params.itemId;
});
const goToNeighborItem = async (direction: "previous" | "next") => {
const findAndNavigateToNeighbor = async (direction: Direction) => {
if (direction === "none") return;
const currentDataset = datasets.find((dataset) => dataset.name === currentDatasetName);
const datasetItems = Object.values(currentDataset?.page?.items || {});
const selectedId = findSelectedItem(direction, datasetItems, currentItemId);
if (selectedId) {
currentItemId = selectedId;
await goto(`/${currentDatasetName}/dataset/${selectedId}`);
showConfirmModal = "none";
}
};
const goToNeighborItem = async (direction: "previous" | "next") => {
if (canSaveCurrentItem) {
return (showConfirmModal = direction);
}
await findAndNavigateToNeighbor(direction);
};
const onKeyDown = async (event: KeyboardEvent) => {
Expand All @@ -67,6 +85,11 @@
return event.key;
};
const handleConfirmClick = async () => {
saveCurrentItemStore.update((old) => ({ ...old, shouldSave: true }));
await findAndNavigateToNeighbor(showConfirmModal);
};
async function navigateTo(route: string) {
await goto(route);
}
Expand Down Expand Up @@ -116,4 +139,14 @@
</div>
</div>
</header>
{#if showConfirmModal !== "none"}
<ConfirmModal
message="You have unsaved changes"
confirm="Save and continue"
alternativeAction="Continue without saving"
on:confirm={handleConfirmClick}
on:alternative={() => findAndNavigateToNeighbor(showConfirmModal)}
on:cancel={() => (showConfirmModal = "none")}
/>
{/if}
<svelte:window on:keyup={onKeyDown} />
5 changes: 5 additions & 0 deletions ui/apps/pixano/src/lib/stores/datasetStores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ export const datasetsStore = writable<DatasetInfo[]>();
export const modelsStore = writable<string[]>([]);
export const isLoadingNewItemStore = writable<boolean>(false);
export const datasetTableStore = writable<DatasetTableStore>(defaultDatasetTableValues);
export const canSaveCurrentItemStore = writable<boolean>();
export const saveCurrentItemStore = writable<{ shouldSave: boolean; canSave: boolean }>({
shouldSave: false,
canSave: false,
});
26 changes: 24 additions & 2 deletions ui/apps/pixano/src/routes/[dataset]/dataset/[itemId]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
<script lang="ts">
import { page } from "$app/stores";
import type { DatasetItem, DatasetInfo } from "@pixano/core/src";
import { type DatasetItem, type DatasetInfo, PrimaryButton } from "@pixano/core/src";
import ImageWorkspace from "@pixano/imageworkspace/src/ImageWorkspace.svelte";
import { api } from "@pixano/core/src";
import {
datasetsStore,
isLoadingNewItemStore,
modelsStore,
saveCurrentItemStore,
} from "../../../../lib/stores/datasetStores";
import { goto } from "$app/navigation";
let selectedItem: DatasetItem;
let selectedDataset: DatasetInfo;
let models: Array<string>;
let currentDatasetName: string;
let currentItemId: string;
let isLoadingNewItem: boolean = false;
let canSaveCurrentItem: boolean = false;
let shouldSaveCurrentItem: boolean = false;
modelsStore.subscribe((value) => {
models = value;
});
saveCurrentItemStore.subscribe((value) => {
canSaveCurrentItem = value.canSave;
shouldSaveCurrentItem = value.shouldSave;
});
$: saveCurrentItemStore.update((old) => ({ ...old, canSave: canSaveCurrentItem }));
const handleSelectItem = (dataset: DatasetInfo, id: string) => {
api
.getDatasetItem(dataset.id, encodeURIComponent(id))
Expand Down Expand Up @@ -54,15 +65,26 @@
async function handleSaveItem(savedItem: DatasetItem) {
await api.postDatasetItem(selectedDataset.id, savedItem);
handleSelectItem(selectedDataset, currentItemId);
saveCurrentItemStore.update((old) => ({ ...old, shouldSave: false }));
}
</script>

{#if selectedItem && selectedDataset}
<ImageWorkspace
{selectedItem}
currentDatasetId={selectedDataset.id}
{models}
currentDatasetId={selectedDataset.id}
{handleSaveItem}
isLoading={isLoadingNewItem}
bind:canSaveCurrentItem
{shouldSaveCurrentItem}
/>
{/if}
{#if !selectedItem && !isLoadingNewItem}
<div class="w-full pt-40 text-center flex flex-col gap-5 items-center">
<p>Current item could not be loaded</p>
<PrimaryButton on:click={() => goto(`/${currentDatasetName}/dataset`)}
>Back to dataset</PrimaryButton
>
</div>
{/if}
49 changes: 27 additions & 22 deletions ui/components/canvas2d/src/Canvas2D.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@
// Find existing Konva elements in case a previous item was already loaded
if (currentId) {
const viewLayer: Konva.Layer = stage.findOne(`#${view.id}`);
const konvaImg: Konva.Image = viewLayer.findOne("#image");
const konvaImg: Konva.Image = viewLayer.findOne(`#image-${view.id}`);
konvaImg.image(images[view.id]);
}
scaleView(view);
Expand Down Expand Up @@ -330,7 +330,7 @@
if (viewLayer) {
const bboxGroup: Konva.Group = viewLayer.findOne("#bboxes");
const image: Konva.Image = viewLayer.findOne("#image");
const image: Konva.Image = viewLayer.findOne(`#image-${selectedItem.views[viewId].id}`);
const bboxIds: Array<string> = [];
if (!bboxGroup) return;
Expand Down Expand Up @@ -412,7 +412,7 @@
const currentMaskGroup = findOrCreateCurrentMask(viewId, stage);
const viewLayer: Konva.Layer = stage.findOne(`#${viewId}`);
const image: Konva.Image = viewLayer.findOne("#image");
const image: Konva.Image = viewLayer.findOne(`#image-${viewId}`);
// always clean existing masks before adding a new currentAnn
currentMaskGroup.removeChildren();
Expand Down Expand Up @@ -508,7 +508,7 @@
manualMasks = manualMasks.map((mask) =>
mask.status === "created"
? mask
: { ...mask, points: [...mask.points, { x, y, id: mask.points.length }] },
: { ...mask, points: [...mask.points, { x, y, id: mask.points.length }], viewId },
);
}
Expand Down Expand Up @@ -581,10 +581,10 @@
function findOrCreateInputPointPointer(id: string, viewId: string = null): Konva.Circle {
let pointer: Konva.Circle = stage.findOne(`#${id}`);
if (!pointer) {
let zoomF = 1.0; //in some cases we aren't in a view, so we use default scaling
let zoomF = 1.0; // in some cases we aren't in a view, so we use default scaling
if (viewId) zoomF = zoomFactor[viewId];
pointer = new Konva.Circle({
id: id,
id,
x: 0,
y: 0,
radius: INPUTPOINT_RADIUS / zoomF,
Expand Down Expand Up @@ -636,7 +636,7 @@
stage.container().style.cursor = "grabbing";
const viewLayer: Konva.Layer = stage.findOne(`#${viewId}`);
const image: Konva.Image = viewLayer.findOne("#image");
const image: Konva.Image = viewLayer.findOne(`#image-${viewId}`);
const img_size = image.getSize();
if (drag_point.x() < 0) {
drag_point.x(0);
Expand Down Expand Up @@ -680,10 +680,9 @@
function displayInputRectTool(tool: SelectionTool) {
if (toolsLayer) {
//clean other tools
//TODO: etre générique sur l'ensemble des outils != Rectangle
// TODO: être générique sur l'ensemble des outils != Rectangle
const pointer = stage.findOne(`#${POINT_SELECTION}`);
if (pointer) pointer.destroy();
if (!highlighted_point) {
stage.container().style.cursor = tool.cursor;
}
Expand Down Expand Up @@ -845,6 +844,10 @@
if (selectedTool) {
stage.container().style.cursor = selectedTool.cursor;
}
const pointer: Konva.Circle = stage.findOne(`#${POINT_SELECTION}`);
if (pointer) pointer.destroy();
const crossline = toolsLayer?.findOne("#crossline");
if (crossline) crossline.destroy();
currentAnn = null;
}
Expand Down Expand Up @@ -963,9 +966,8 @@
y: pos.y + 1,
width: 0,
height: 0,
stroke: "white",
dash: [10, 5],
fill: "rgba(255, 255, 255, 0.15)",
stroke: "hsl(316deg 60% 29.41%)",
fill: "#f9f4f773",
strokeWidth: INPUTRECT_STROKEWIDTH / zoomFactor[viewId],
listening: false,
});
Expand Down Expand Up @@ -1090,7 +1092,7 @@
on:wheel={(event) => handleWheelOnImage(event.detail.evt, view)}
>
<KonvaImage
config={{ image: images[view.id], id: "image" }}
config={{ image: images[view.id], id: `image-${view.id}` }}
on:pointerdown={(event) => handleClickOnImage(event.detail.evt, view.id)}
on:pointerup={() => handlePointerUpOnImage(view.id)}
on:dblclick={() => handleDoubleClickOnImage(view.id)}
Expand All @@ -1101,15 +1103,18 @@
<Group config={{ id: "input" }} />
{#each manualMasks as manualMask}
{#key manualMask.id}
<PolygonGroup
viewId={view.id}
selectedItemId={selectedItem.id}
bind:newShape
{stage}
{images}
polygonDetails={manualMask}
color={colorScale(manualMask.id)}
/>
{#if manualMask.viewId === view.id}
<PolygonGroup
viewId={view.id}
selectedItemId={selectedItem.id}
bind:newShape
{stage}
{images}
polygonDetails={manualMask}
color={colorScale(manualMask.id)}
{zoomFactor}
/>
{/if}
{/key}
{/each}
</Layer>
Expand Down
3 changes: 2 additions & 1 deletion ui/components/canvas2d/src/api/boundingBoxesApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ export function mapMaskPointsToLineCoordinates(masks: Mask[]): PolygonGroupDetai
id: mask.id,
status: mask?.id ? "created" : "creating",
svg: mask?.svg,
viewId: mask.viewId,
points: points.reduce((acc, val, i) => {
if (i % 2 === 0) {
acc.push({
Expand All @@ -311,7 +312,6 @@ export function mapMaskPointsToLineCoordinates(masks: Mask[]): PolygonGroupDetai
.map((mask) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const simplifiedPoints = simplify(mask.points, 4, false);
// console.log({ mask, simplifiedPoints });
mask.points = simplifiedPoints as PolygonGroupPoint[];
return mask as PolygonGroupDetails;
});
Expand All @@ -322,6 +322,7 @@ export function mapMaskPointsToLineCoordinates(masks: Mask[]): PolygonGroupDetai
svg: [],
editing: false,
id: "creating",
viewId: undefined,
};
mappedMasks.push(emptyMask);
return mappedMasks;
Expand Down
Loading

0 comments on commit 666e92c

Please sign in to comment.