mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
refactor(group): split group menu
This commit is contained in:
@@ -8,7 +8,7 @@ import { styled } from '@toeverything/components/ui';
|
|||||||
import type { CreateView } from '@toeverything/framework/virgo';
|
import type { CreateView } from '@toeverything/framework/virgo';
|
||||||
import type { ComponentType, FC } from 'react';
|
import type { ComponentType, FC } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { GroupMenuWrapper } from './GroupMenu';
|
import { GroupMenuWrapper } from './group-menu';
|
||||||
import { SceneKanban } from './scene-kanban';
|
import { SceneKanban } from './scene-kanban';
|
||||||
import { ScenePage } from './ScenePage';
|
import { ScenePage } from './ScenePage';
|
||||||
import { SceneTable } from './SceneTable';
|
import { SceneTable } from './SceneTable';
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import {
|
||||||
|
RecastScene,
|
||||||
|
useRecastView,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
|
import { AddViewIcon, DoneIcon } from '@toeverything/components/icons';
|
||||||
|
import { Input, MuiClickAwayListener } from '@toeverything/components/ui';
|
||||||
|
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { IconButton } from '../components/IconButton';
|
||||||
|
import { Panel } from '../components/Panel';
|
||||||
|
import { VIEW_ICON_MAP } from './constant';
|
||||||
|
import { PanelItem } from './styles';
|
||||||
|
|
||||||
|
export const AddViewMenu = () => {
|
||||||
|
const [viewName, setViewName] = useState('');
|
||||||
|
const [viewType, setViewType] = useState<RecastScene>(RecastScene.Page);
|
||||||
|
const [activePanel, setActivePanel] = useState(false);
|
||||||
|
const { addView, setCurrentView } = useRecastView();
|
||||||
|
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setViewName(e.target.value.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (event.key !== 'Enter') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleAddView();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddView = async () => {
|
||||||
|
const newView = await addView({ name: viewName, type: viewType });
|
||||||
|
await setCurrentView(newView);
|
||||||
|
setActivePanel(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MuiClickAwayListener onClickAway={() => setActivePanel(false)}>
|
||||||
|
<IconButton
|
||||||
|
active={activePanel}
|
||||||
|
onClick={() => setActivePanel(!activePanel)}
|
||||||
|
>
|
||||||
|
<AddViewIcon fontSize="small" />
|
||||||
|
<span>Add View</span>
|
||||||
|
{activePanel && (
|
||||||
|
<Panel>
|
||||||
|
<PanelItem>
|
||||||
|
<Input
|
||||||
|
placeholder="View Name"
|
||||||
|
autoFocus
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
aria-label="done"
|
||||||
|
onClick={handleAddView}
|
||||||
|
>
|
||||||
|
<DoneIcon />
|
||||||
|
</IconButton>
|
||||||
|
</PanelItem>
|
||||||
|
|
||||||
|
<PanelItem>
|
||||||
|
{Object.entries(VIEW_ICON_MAP).map(
|
||||||
|
([name, icon]) => (
|
||||||
|
<IconButton
|
||||||
|
key={name}
|
||||||
|
active={
|
||||||
|
viewType === (name as RecastScene)
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (name === 'table') {
|
||||||
|
// The table view is under progress
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setViewType(name as RecastScene);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{VIEW_ICON_MAP[name as RecastScene]}
|
||||||
|
{name.toUpperCase()}
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</PanelItem>
|
||||||
|
</Panel>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</MuiClickAwayListener>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { CommingSoon } from '@toeverything/components/common';
|
import { CommingSoon } from '@toeverything/components/common';
|
||||||
|
import type {
|
||||||
|
AsyncBlock,
|
||||||
|
BlockEditor,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
import {
|
import {
|
||||||
mergeToPreviousGroup,
|
mergeToPreviousGroup,
|
||||||
RecastScene,
|
RecastScene,
|
||||||
RecastView,
|
|
||||||
useCurrentView,
|
useCurrentView,
|
||||||
useRecastView,
|
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import {
|
import {
|
||||||
AddViewIcon,
|
|
||||||
FilterIcon,
|
FilterIcon,
|
||||||
FullScreenIcon,
|
FullScreenIcon,
|
||||||
GroupByIcon,
|
GroupByIcon,
|
||||||
@@ -15,45 +16,21 @@ import {
|
|||||||
KanBanIcon,
|
KanBanIcon,
|
||||||
SortIcon,
|
SortIcon,
|
||||||
TableIcon,
|
TableIcon,
|
||||||
TodoListIcon,
|
|
||||||
} from '@toeverything/components/icons';
|
} from '@toeverything/components/icons';
|
||||||
import { Popover, useTheme } from '@toeverything/components/ui';
|
import { Popover, useTheme } from '@toeverything/components/ui';
|
||||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||||
import type { AsyncBlock, BlockEditor } from '@toeverything/framework/virgo';
|
import type { ReactNode } from 'react';
|
||||||
import type { ReactElement, ReactNode } from 'react';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Filter } from './components/filter';
|
import { Filter } from '../components/filter';
|
||||||
import { GroupBy } from './components/group-by/GroupBy';
|
import { GroupBy } from '../components/group-by/GroupBy';
|
||||||
import { GroupPanel } from './components/group-panel/GroupPanel';
|
import { GroupPanel } from '../components/group-panel/GroupPanel';
|
||||||
import { IconButton } from './components/IconButton';
|
import { IconButton } from '../components/IconButton';
|
||||||
import { Line } from './components/Line';
|
import { Line } from '../components/Line';
|
||||||
import { Sorter } from './components/sorter';
|
import { Sorter } from '../components/sorter';
|
||||||
import { PANEL_CONFIG } from './config';
|
import { PANEL_CONFIG } from '../config';
|
||||||
import type { ActivePanel } from './types';
|
import type { ActivePanel } from '../types';
|
||||||
|
import { AddViewMenu } from './AddViewMenu';
|
||||||
const VIEW_ICON_MAP: Record<RecastView['type'], ReactElement> = {
|
import { ViewsMenu } from './ViewsMenu';
|
||||||
page: <TodoListIcon fontSize="small" />,
|
|
||||||
kanban: <KanBanIcon fontSize="small" />,
|
|
||||||
table: <TableIcon fontSize="small" />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ViewsMenu = () => {
|
|
||||||
const { currentView, recastViews, setCurrentView } = useRecastView();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{recastViews.map(view => (
|
|
||||||
<IconButton
|
|
||||||
key={view.id}
|
|
||||||
active={view.id === currentView.id}
|
|
||||||
onClick={() => setCurrentView(view)}
|
|
||||||
>
|
|
||||||
{VIEW_ICON_MAP[view.type]}
|
|
||||||
{view.name}
|
|
||||||
</IconButton>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const GroupMenuWrapper = ({
|
const GroupMenuWrapper = ({
|
||||||
block,
|
block,
|
||||||
@@ -105,21 +82,7 @@ const GroupMenuWrapper = ({
|
|||||||
content={
|
content={
|
||||||
<GroupPanel>
|
<GroupPanel>
|
||||||
<ViewsMenu />
|
<ViewsMenu />
|
||||||
|
<AddViewMenu />
|
||||||
{filterSorterFlag && (
|
|
||||||
<IconButton
|
|
||||||
active={activePanel === PANEL_CONFIG.ADD_VIEW}
|
|
||||||
onClick={() =>
|
|
||||||
setActivePanel(
|
|
||||||
PANEL_CONFIG.ADD_VIEW as ActivePanel
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AddViewIcon fontSize="small" />
|
|
||||||
Add View
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
// // Closed beta period temporarily
|
// // Closed beta period temporarily
|
||||||
// filterSorterFlag && (
|
// filterSorterFlag && (
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import {
|
||||||
|
RecastScene,
|
||||||
|
RecastView,
|
||||||
|
useRecastView,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
|
import { DeleteCashBinIcon, DoneIcon } from '@toeverything/components/icons';
|
||||||
|
import { Input } from '@toeverything/components/ui';
|
||||||
|
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { IconButton } from '../components/IconButton';
|
||||||
|
import { Panel } from '../components/Panel';
|
||||||
|
import { VIEW_ICON_MAP } from './constant';
|
||||||
|
import { PanelItem } from './styles';
|
||||||
|
|
||||||
|
export const ViewsMenu = () => {
|
||||||
|
const [viewName, setViewName] = useState('');
|
||||||
|
const [viewType, setViewType] = useState<RecastScene>(RecastScene.Page);
|
||||||
|
const [activeView, setActiveView] = useState<RecastView | null>(null);
|
||||||
|
const { currentView, recastViews, setCurrentView, updateView, removeView } =
|
||||||
|
useRecastView();
|
||||||
|
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setViewName(e.target.value.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (event.key !== 'Enter') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleUpdateView();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateView = async () => {
|
||||||
|
if (!activeView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updateView({
|
||||||
|
...activeView,
|
||||||
|
name: viewName,
|
||||||
|
type: viewType,
|
||||||
|
});
|
||||||
|
setActiveView(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (!activeView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await removeView(activeView.id);
|
||||||
|
|
||||||
|
setActiveView(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{recastViews.map(view => (
|
||||||
|
<IconButton
|
||||||
|
key={view.id}
|
||||||
|
active={view.id === currentView.id}
|
||||||
|
onClick={() => setCurrentView(view)}
|
||||||
|
onContextMenu={e => {
|
||||||
|
setViewName(view.name);
|
||||||
|
setViewType(view.type);
|
||||||
|
setActiveView(view);
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{VIEW_ICON_MAP[view.type]}
|
||||||
|
<span style={{ userSelect: 'none' }}>{view.name}</span>
|
||||||
|
|
||||||
|
{activeView === view && (
|
||||||
|
<Panel>
|
||||||
|
<PanelItem>
|
||||||
|
<Input
|
||||||
|
placeholder="View Name"
|
||||||
|
autoFocus
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
value={viewName}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
aria-label="delete"
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
<DeleteCashBinIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
aria-label="done"
|
||||||
|
onClick={handleUpdateView}
|
||||||
|
>
|
||||||
|
<DoneIcon />
|
||||||
|
</IconButton>
|
||||||
|
</PanelItem>
|
||||||
|
|
||||||
|
<PanelItem>
|
||||||
|
{Object.entries(VIEW_ICON_MAP).map(
|
||||||
|
([name, icon]) => (
|
||||||
|
<IconButton
|
||||||
|
key={name}
|
||||||
|
active={
|
||||||
|
viewType ===
|
||||||
|
(name as RecastScene)
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if (name === 'table') {
|
||||||
|
// The table view is under progress
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setViewType(
|
||||||
|
name as RecastScene
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{VIEW_ICON_MAP[name as RecastScene]}
|
||||||
|
{name.toUpperCase()}
|
||||||
|
</IconButton>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</PanelItem>
|
||||||
|
</Panel>
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { RecastView } from '@toeverything/components/editor-core';
|
||||||
|
import {
|
||||||
|
KanBanIcon,
|
||||||
|
TableIcon,
|
||||||
|
TodoListIcon,
|
||||||
|
} from '@toeverything/components/icons';
|
||||||
|
import type { ReactElement } from 'react';
|
||||||
|
|
||||||
|
export const VIEW_ICON_MAP: Record<RecastView['type'], ReactElement> = {
|
||||||
|
page: <TodoListIcon fontSize="small" />,
|
||||||
|
kanban: <KanBanIcon fontSize="small" />,
|
||||||
|
table: <TableIcon fontSize="small" />,
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { GroupMenuWrapper } from './MainMenu';
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { styled } from '@toeverything/components/ui';
|
||||||
|
|
||||||
|
export const PanelItem = styled('div')({
|
||||||
|
display: 'flex',
|
||||||
|
userSelect: 'none',
|
||||||
|
|
||||||
|
'& + &': {
|
||||||
|
marginTop: '8px',
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user