Skip to content

Commit

Permalink
feat(quicklabel): update quicklabel component
Browse files Browse the repository at this point in the history
  • Loading branch information
cefeng06 committed Dec 29, 2023
1 parent af938e9 commit 957c327
Show file tree
Hide file tree
Showing 27 changed files with 2,094 additions and 162 deletions.
141 changes: 5 additions & 136 deletions packages/app/src/pages/Annotator/index.tsx
Original file line number Diff line number Diff line change
@@ -1,141 +1,10 @@
import React, { useEffect, useState } from 'react';
import { history, useModel } from '@umijs/max';
import styles from './index.less';
import { AnnotateEditor, EditorMode } from 'dds-components/Annotator';
import { ImageList } from './components/ImageList';
import { Button } from 'antd';
import { SettingOutlined } from '@ant-design/icons';
import { FormModal } from './components/FormModal';
import { useLocale } from 'dds-utils/locale';
import { useKeyPress } from 'ahooks';
import { BaseObject } from '@/types';
import React from 'react';
import QuickLabel from 'dds-components/QuickLabel';
import { useModel } from '@umijs/max';

const Page: React.FC = () => {
const {
images,
setImages,
current,
setCurrent,
categories,
setCategories,
exportAnnotations,
} = useModel('Annotator.model');

const { localeText } = useLocale();
const [openModal, setModalOpen] = useState(true);

useEffect(() => {
// const handleBeforeUnload = (event: BeforeUnloadEvent) => {
// event.preventDefault();
// event.returnValue =
// 'The current changes will not be saved. Please export before leaving.';
// };
// window.addEventListener('beforeunload', handleBeforeUnload);
// return () => {
// window.removeEventListener('beforeunload', handleBeforeUnload);
// };
}, []);

// local test
useEffect(
() => {
// if(images.length > 0 && categories.length > 0) {
// localStorage.setItem('images', JSON.stringify(images));
// localStorage.setItem('categories', JSON.stringify(categories));
// console.log('>>> save localStorage');
// }
const images = localStorage.getItem('images');
const categories = localStorage.getItem('categories');
if (images && categories) {
setImages(JSON.parse(images));
setCategories(JSON.parse(categories));
setModalOpen(false);
}
},
// [images, categories]
[],
);

useKeyPress(
'uparrow',
() => {
setCurrent(Math.max(0, current - 1));
},
{ exactMatch: true },
);

useKeyPress(
'downarrow',
() => {
setCurrent(Math.min(current + 1, images.length - 1));
},
{ exactMatch: true },
);

return (
<div className={styles.container}>
<div
className={styles.left}
onMouseDown={(event) => {
event.stopPropagation();
}}
onMouseUp={(event) => {
event.stopPropagation();
}}
>
<Button
type="primary"
icon={<SettingOutlined />}
onClick={() => {
setModalOpen(true);
}}
>
{localeText('annotator.setting')}
</Button>
<ImageList
images={images}
selected={current}
onImageSelected={(index) => {
setCurrent(index);
}}
/>
</div>
<div className={styles.right}>
<AnnotateEditor
isOldMode
isSeperate={true}
visible={true}
mode={EditorMode.Edit}
categories={categories}
setCategories={setCategories}
list={images}
current={current}
actionElements={[
<Button type="primary" key={'export'} onClick={exportAnnotations}>
{localeText('annotator.export')}
</Button>,
]}
onAutoSave={(annos: BaseObject[], naturalSize: ISize) => {
setImages((images) => {
if (images[current]) {
images[current].objects = annos;
images[current].width = naturalSize.width;
images[current].height = naturalSize.height;
}
});
}}
onCancel={() => history.push('/')}
/>
</div>
<div
className={styles.modal}
onMouseDown={(e) => e.stopPropagation()}
onMouseUp={(e) => e.stopPropagation()}
>
<FormModal open={openModal} setOpen={setModalOpen} />
</div>
</div>
);
const props = useModel('Annotator.model');
return <QuickLabel {...props} />;
};

export default Page;
28 changes: 2 additions & 26 deletions packages/app/src/pages/Annotator/model.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,5 @@
import { useImmer } from 'use-immer';
import { useState } from 'react';
import { genFileNameByTimestamp, saveObejctToJsonFile } from 'dds-utils/file';
import { convertToCocoDateset } from '@/utils/adapter';
import { LabelImageFile } from '@/types/annotator';
import { Category } from '@/types';
import useQuickLabelModel from 'dds-components/QuickLabel/hooks/useQuickLabelModel';

export default () => {
const [images, setImages] = useImmer<LabelImageFile[]>([]);
const [current, setCurrent] = useState(0);
const [categories, setCategories] = useImmer<Category[]>([]);

/** Export with COCO formats*/
const exportAnnotations = () => {
const dataset = convertToCocoDateset(images, categories);
const fileName = genFileNameByTimestamp(Date.now(), 'Annotations');
saveObejctToJsonFile(dataset, fileName);
};

return {
images,
setImages,
current,
setCurrent,
categories,
setCategories,
exportAnnotations,
};
return useQuickLabelModel();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.dds-quicklabel-image-filter {
display: flex;
align-items: center;
gap: 10px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ClearOutlined } from '@ant-design/icons';
import { Button, Select } from 'antd';
import { Category } from '@/Annotator/type';
import { globalLocaleText } from 'dds-utils/locale';
import './index.less';

interface IProps {
categories: Category[];
filterCategoryName: string | null;
onSelectFilter: (name: string) => void;
onClearFilter: () => void;
}

const ImageFilter: React.FC<IProps> = ({
categories,
filterCategoryName,
onSelectFilter,
onClearFilter,
}) => {
return (
<div className="dds-quicklabel-image-filter">
<div>{globalLocaleText('quicklabel.imageFilter')}</div>
<Select
style={{ width: 150 }}
showSearch
placeholder={globalLocaleText('quicklabel.allCategories')}
size="middle"
value={filterCategoryName}
onChange={onSelectFilter}
popupClassName="filter-options-popup"
onClick={(event) => event.stopPropagation()}
onKeyUp={(event) => event.stopPropagation()}
onInputKeyDown={(event) => {
if (event.code !== 'Enter') {
event.stopPropagation();
}
}}
dropdownRender={(menu) => (
<>
{menu}
{
<Button
type="text"
icon={<ClearOutlined />}
onClick={onClearFilter}
>
{globalLocaleText('quicklabel.clearFilter')}
</Button>
}
</>
)}
>
{categories?.map((category) => (
<Select.Option key={category.name} value={category.name}>
{category.name}
</Select.Option>
))}
</Select>
</div>
);
};

export default ImageFilter;
28 changes: 28 additions & 0 deletions packages/components/src/QuickLabel/components/ImageList/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.dds-quicklabel-options-list {
height: 100vh;

&-virtual {
border-radius: 8px;
}

&-image {
margin: 8px 0;
width: 100%;
height: 120px;
box-sizing: border-box;
object-fit: cover;
border-radius: 8px;
background-color: #fff;
cursor: pointer;
transition: transform 0.3s ease;
}

&-image:hover {
transform: scale(0.95);
}

&-image-selected {
border: 3px solid #fff;
border-radius: 8px;
}
}
70 changes: 70 additions & 0 deletions packages/components/src/QuickLabel/components/ImageList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import VirtualList from 'rc-virtual-list';
import { useCallback, useEffect, useState } from 'react';
import { QsAnnotatorFile } from '../../type';
import './index.less';

interface IProps {
images: QsAnnotatorFile[];
selected: number;
onImageSelected: (index: number) => void;
}

export const ImageList: React.FC<IProps> = ({
images,
selected,
onImageSelected,
}: IProps) => {
const [containerHeight, setContainerHeight] = useState(0);
const itemHeight = 120;

const handleResize = useCallback(() => {
const container = document.getElementById('image-options-container');
if (container) {
const height = container.offsetHeight || 0;
setContainerHeight(height - 56);
}
}, []);

useEffect(() => {
handleResize();
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [handleResize]);

const handleImageSelect = (index: number) => {
if (index < 0 || index >= images.length) return;
onImageSelected(index);
};

return (
<div id="image-options-container" className="dds-quicklabel-options-list">
<VirtualList
className="dds-quicklabel-options-list-virtual"
data={images}
height={containerHeight}
fullHeight={true}
itemHeight={itemHeight}
itemKey="id"
>
{(item, index) => {
const selectedClassName =
index === selected
? 'dds-quicklabel-options-list-image-selected'
: '';
return (
<div>
<img
className={`dds-quicklabel-options-list-image ${selectedClassName}`}
src={item.url}
key={item.id}
onClick={() => handleImageSelect(index)}
/>
</div>
);
}}
</VirtualList>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.dds-quicklabel-subtitle {
font-size: 16px;
font-weight: 500;
margin: 20px 0 10px;
}

.dds-quicklabel-upload {
width: 100%;
height: 360px;
}

.dds-quicklabel-upload-tip {
margin: 10px 0 0;
background-color: transparent;
border-width: 0;
}

.dds-quicklabel-upload-preannot-btn {
width: 100%;
height: 42px;
font-weight: 600;
border-radius: 5px;
background: #fff;
}
Loading

0 comments on commit 957c327

Please sign in to comment.