Skip to content

Commit

Permalink
feat: change the way images are listed (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
moonayyur authored Apr 3, 2024
1 parent 7515e5b commit be37f5d
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 110 deletions.
2 changes: 0 additions & 2 deletions src/components/Pixelium.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import CenterPanel from './layout/CenterPanel';
import Header from './layout/Header';
import Sidebar from './layout/Sidebar';
import ModalContainer from './modal/ModalContainer';
import CloseTool from './tool/CloseTool';
import ExportTool from './tool/ExportTool';
import GreyTool from './tool/FilterTool';
import GeometryTool from './tool/GeometryTool';
Expand Down Expand Up @@ -113,7 +112,6 @@ function Pixelium({ data, preferences, view, webSource }: PixeliumProps) {
<MorphologyTool />
<GeometryTool />
<ROITool />
<CloseTool />
<ModalContainer />
</Toolbar>
<SplitPane
Expand Down
93 changes: 93 additions & 0 deletions src/components/images/ImagesPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import styled from '@emotion/styled';
import { useCallback, useMemo } from 'react';
import { Button } from 'react-science/ui';

import useCurrentTab from '../../hooks/useCurrentTab';
import useData from '../../hooks/useData';
import useDataDispatch from '../../hooks/useDataDispatch';
import useViewDispatch from '../../hooks/useViewDispatch';
import { CLOSE_IMAGE } from '../../state/data/DataActionTypes';
import { CLOSE_TAB, OPEN_TAB } from '../../state/view/ViewActionTypes';

const TabTitle = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
`;

const TabItem = styled.div<{ current: boolean }>`
cursor: default;
display: flex;
align-items: center;
border-bottom: 1px solid #e0e0e0;
&:hover {
background-color: #f0f0f0;
}
background-color: ${(props) => (props.current ? '#f0f0f0' : 'white')};
`;

export default function ImagesPanel() {
const { images } = useData();
const viewDispatch = useViewDispatch();
const dataDispatch = useDataDispatch();

const tabsItems = useMemo(
() =>
Object.keys(images).map((identifier) => ({
id: identifier,
title: <TabTitle>{images[identifier].metadata.name}</TabTitle>,
})),
[images],
);

const currentTab = useCurrentTab();

const openTab = useCallback(
(identifier: string) => {
viewDispatch({ type: OPEN_TAB, payload: identifier });
},
[viewDispatch],
);

const closeImage = useCallback(
(closeId: string) => {
dataDispatch({ type: CLOSE_IMAGE, payload: closeId });
if (tabsItems.length === 1) {
viewDispatch({ type: CLOSE_TAB });
} else if (currentTab === closeId) {
const lastTab = tabsItems.at(-1)?.id;
const id = lastTab === currentTab ? tabsItems.at(-2)?.id : lastTab;
if (id) viewDispatch({ type: OPEN_TAB, payload: id });
}
},
[dataDispatch, tabsItems, currentTab, viewDispatch],
);

return (
<>
{tabsItems.length > 0 ? (
<div>
{tabsItems.map((item) => (
<TabItem
key={item.id}
current={currentTab === item.id}
onClick={() => openTab(item.id)}
>
{item.title}
<Button
minimal
icon="cross"
onClick={(e) => {
e.stopPropagation();
closeImage(item.id);
}}
/>
</TabItem>
))}
</div>
) : null}
</>
);
}
70 changes: 3 additions & 67 deletions src/components/layout/CenterPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,19 @@
/** @jsxImportSource @emotion/react */
import { Tabs, Tab } from '@blueprintjs/core';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { memo, useCallback, useEffect, useMemo } from 'react';
import { memo } from 'react';
import { DropZoneContainer } from 'react-science/ui';

import useCurrentTab from '../../hooks/useCurrentTab';
import useData from '../../hooks/useData';
import useFileLoader from '../../hooks/useFileLoader';
import useViewDispatch from '../../hooks/useViewDispatch';
import { OPEN_TAB } from '../../state/view/ViewActionTypes';
import ImageViewer from '../ImageViewer';

const StyledCenterPanel = styled.div`
width: 100%;
`;

const TabTitle = styled.div`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
`;

function CenterPanel() {
const { images } = useData();
const viewDispatch = useViewDispatch();

const tabsItems = useMemo(
() =>
Object.keys(images).map((identifier) => ({
id: identifier,
title: <TabTitle>{images[identifier].metadata.name}</TabTitle>,
content: (
<ImageViewer key={identifier} identifier={identifier} annotable />
),
})),
[images],
);

const currentTab = useCurrentTab();

const openTab = useCallback(
(identifier: string) => {
viewDispatch({ type: OPEN_TAB, payload: identifier });
},
[viewDispatch],
);

useEffect(() => {
if (!currentTab && tabsItems.length > 0) {
openTab(tabsItems[0].id);
}
}, [openTab, currentTab, tabsItems]);

const { handleFileLoad: handleOnDrop } = useFileLoader();

return (
Expand All @@ -63,31 +22,8 @@ function CenterPanel() {
emptyDescription="Drag and drop here either an image or a Pixelium file."
onDrop={handleOnDrop}
>
{tabsItems.length > 0 ? (
<Tabs
selectedTabId={currentTab}
onChange={openTab}
css={css`
height: 100%;
div[role='tablist'] {
overflow-x: auto;
overflow-y: hidden;
}
div[role='tabpanel'] {
height: calc(100% - 30px);
margin-top: 0;
}
`}
>
{tabsItems.map((item) => (
<Tab
id={item.id}
key={item.id}
title={item.title}
panel={item.content}
/>
))}
</Tabs>
{currentTab ? (
<ImageViewer key={currentTab} identifier={currentTab} annotable />
) : null}
</DropZoneContainer>
</StyledCenterPanel>
Expand Down
4 changes: 4 additions & 0 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Accordion } from 'react-science/ui';

import useCurrentTab from '../../hooks/useCurrentTab';
import Histograms from '../histogram/Histograms';
import ImagesPanel from '../images/ImagesPanel';
import InformationPanel from '../information/InformationPanel';
import PipelineTable from '../pipeline/PipelineTable';
import ROIAccordion from '../roi/ROIAccordion';
Expand All @@ -20,6 +21,9 @@ function Sidebar() {
return (
<StyledSidebar>
<Accordion>
<Accordion.Item title="Images" defaultOpened>
<ImagesPanel />
</Accordion.Item>
<Accordion.Item title="Informations">
<InformationPanel />
</Accordion.Item>
Expand Down
34 changes: 0 additions & 34 deletions src/components/tool/CloseTool.tsx

This file was deleted.

18 changes: 16 additions & 2 deletions src/hooks/useFileLoader.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { v4 as uuid } from '@lukeed/uuid';
import {
fileCollectionFromFiles,
FileCollection,
Expand All @@ -14,7 +15,7 @@ import {
} from '../state/data/DataActionTypes';
import { DataFile } from '../state/data/DataReducer';
import { INITIALIZE_PREFERENCES } from '../state/preferences/PreferenceActionTypes';
import { LOAD_VIEW_STATE } from '../state/view/ViewActionTypes';
import { LOAD_VIEW_STATE, OPEN_TAB } from '../state/view/ViewActionTypes';
import { loadPixeliumBundle } from '../utils/export';

import useDataDispatch from './useDataDispatch';
Expand Down Expand Up @@ -77,7 +78,20 @@ export default function useFileLoader() {
}
}
}
dataDispatch({ type: LOAD_DROP, payload: dataFiles });

const ids = dataFiles.map(() => uuid());
if (dataFiles.length !== ids.length) {
logger.error('The number of files and ids must be the same');
return 0;
}
const files = Object.fromEntries(
ids.map((id, index) => [id, dataFiles[index]]),
);

dataDispatch({ type: LOAD_DROP, payload: files });

const newId = ids.at(-1);
if (newId) viewDispatch({ type: OPEN_TAB, payload: newId });
return dataFiles.length;
},
[dataDispatch, logger, preferencesDispatch, viewDispatch],
Expand Down
15 changes: 10 additions & 5 deletions src/state/data/actions/LoadActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { v4 as uuid } from '@lukeed/uuid';
import { Draft } from 'immer';

import {
Expand All @@ -11,7 +10,10 @@ import {
import { DataState, DataFile } from '../DataReducer';

export type SetLoadingAction = DataActionType<typeof SET_LOADING, boolean>;
export type LoadDropAction = DataActionType<typeof LOAD_DROP, DataFile[]>;
export type LoadDropAction = DataActionType<
typeof LOAD_DROP,
Record<string, DataFile>
>;
export type LoadPixeliumAction = DataActionType<
typeof LOAD_PIXELIUM,
DataState
Expand All @@ -25,9 +27,12 @@ export function setLoading(
draft.isLoading = payload;
}

export function loadDrop(draft: Draft<DataState>, payload: DataFile[]) {
for (const file of payload) {
draft.images[uuid()] = file;
export function loadDrop(
draft: Draft<DataState>,
payload: Record<string, DataFile>,
) {
for (const id in payload) {
draft.images[id] = payload[id];
}
}

Expand Down

0 comments on commit be37f5d

Please sign in to comment.