feat(component): support sort workspace card (#1837)
This commit is contained in:
@@ -37,11 +37,14 @@ __metadata:
|
|||||||
"@affine/debug": "workspace:*"
|
"@affine/debug": "workspace:*"
|
||||||
"@affine/i18n": "workspace:*"
|
"@affine/i18n": "workspace:*"
|
||||||
"@affine/jotai": "workspace:*"
|
"@affine/jotai": "workspace:*"
|
||||||
|
"@affine/workspace": "workspace:^"
|
||||||
"@blocksuite/blocks": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/blocks": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
"@blocksuite/editor": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/editor": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
"@blocksuite/global": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/global": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
"@blocksuite/icons": 2.1.5
|
"@blocksuite/icons": 2.1.5
|
||||||
"@blocksuite/store": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/store": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
|
"@dnd-kit/core": ^6.0.8
|
||||||
|
"@dnd-kit/sortable": ^7.0.2
|
||||||
"@emotion/cache": ^11.10.7
|
"@emotion/cache": ^11.10.7
|
||||||
"@emotion/react": ^11.10.6
|
"@emotion/react": ^11.10.6
|
||||||
"@emotion/server": ^11.10.0
|
"@emotion/server": ^11.10.0
|
||||||
@@ -178,7 +181,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@affine/workspace@workspace:../../packages/workspace":
|
"@affine/workspace@workspace:../../packages/workspace, @affine/workspace@workspace:^":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@affine/workspace@workspace:../../packages/workspace"
|
resolution: "@affine/workspace@workspace:../../packages/workspace"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1861,6 +1864,55 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/accessibility@npm:^3.0.0":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "@dnd-kit/accessibility@npm:3.0.1"
|
||||||
|
dependencies:
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: 0afc2c0fce9a1c107453620ca0da1778f182d340e74ffbc6e369ef0ac8943cafb929d3a6c0891d9b915aa23b2b92137ff4fad958f43118466586d8129a3359d5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/core@npm:^6.0.8":
|
||||||
|
version: 6.0.8
|
||||||
|
resolution: "@dnd-kit/core@npm:6.0.8"
|
||||||
|
dependencies:
|
||||||
|
"@dnd-kit/accessibility": ^3.0.0
|
||||||
|
"@dnd-kit/utilities": ^3.2.1
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
react-dom: ">=16.8.0"
|
||||||
|
checksum: abe48ff7395f84fd8c15e6c8b13da4df153dc1f1076096d783acd0c25539516c77e4854ea59be6621dde55739cb0df1d62924ad069df3267fe05ad90ef729b2f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/sortable@npm:^7.0.2":
|
||||||
|
version: 7.0.2
|
||||||
|
resolution: "@dnd-kit/sortable@npm:7.0.2"
|
||||||
|
dependencies:
|
||||||
|
"@dnd-kit/utilities": ^3.2.0
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
"@dnd-kit/core": ^6.0.7
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: 4ce705aceb15766a0deefe25a9d95a87e9413c3fb9088ea3eb0962e57f844895000117fcec7c0944a0d4ae4e1e889cfa69e3d3778164d4d23115fb1edb218283
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/utilities@npm:^3.2.0, @dnd-kit/utilities@npm:^3.2.1":
|
||||||
|
version: 3.2.1
|
||||||
|
resolution: "@dnd-kit/utilities@npm:3.2.1"
|
||||||
|
dependencies:
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: 038fd5cc1328bf4c9dca17cd48046e5a687bbf9d904c7197f851aab869ab52d9dee2734b2e255256fd6158245acd00063a23deed962c7673c0fadfbf061f04ca
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@electron-forge/cli@npm:^6.1.0":
|
"@electron-forge/cli@npm:^6.1.0":
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
resolution: "@electron-forge/cli@npm:6.1.0"
|
resolution: "@electron-forge/cli@npm:6.1.0"
|
||||||
@@ -16193,7 +16245,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0":
|
"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0":
|
||||||
version: 2.5.0
|
version: 2.5.0
|
||||||
resolution: "tslib@npm:2.5.0"
|
resolution: "tslib@npm:2.5.0"
|
||||||
checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1
|
checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
"@blocksuite/editor": "0.0.0-20230406032111-01cf598b-nightly",
|
"@blocksuite/editor": "0.0.0-20230406032111-01cf598b-nightly",
|
||||||
"@blocksuite/icons": "^2.1.5",
|
"@blocksuite/icons": "^2.1.5",
|
||||||
"@blocksuite/store": "0.0.0-20230406032111-01cf598b-nightly",
|
"@blocksuite/store": "0.0.0-20230406032111-01cf598b-nightly",
|
||||||
|
"@dnd-kit/core": "^6.0.8",
|
||||||
|
"@dnd-kit/sortable": "^7.0.2",
|
||||||
"@emotion/cache": "^11.10.7",
|
"@emotion/cache": "^11.10.7",
|
||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/server": "^11.10.0",
|
"@emotion/server": "^11.10.0",
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import {
|
|||||||
ModalWrapper,
|
ModalWrapper,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { WorkspaceCard } from '@affine/component/workspace-card';
|
import { WorkspaceList } from '@affine/component/workspace-list';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import type { AccessTokenMessage } from '@affine/workspace/affine/login';
|
import type { AccessTokenMessage } from '@affine/workspace/affine/login';
|
||||||
import { HelpIcon, PlusIcon } from '@blocksuite/icons';
|
import { HelpIcon, PlusIcon } from '@blocksuite/icons';
|
||||||
|
import type { DragEndEvent } from '@dnd-kit/core';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import type { AllWorkspace } from '../../../shared';
|
import type { AllWorkspace } from '../../../shared';
|
||||||
import { Footer } from '../footer';
|
import { Footer } from '../footer';
|
||||||
@@ -37,6 +39,7 @@ interface WorkspaceModalProps {
|
|||||||
onClickLogin: () => void;
|
onClickLogin: () => void;
|
||||||
onClickLogout: () => void;
|
onClickLogout: () => void;
|
||||||
onCreateWorkspace: () => void;
|
onCreateWorkspace: () => void;
|
||||||
|
onMoveWorkspace: (activeId: string, overId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceListModal = ({
|
export const WorkspaceListModal = ({
|
||||||
@@ -50,6 +53,7 @@ export const WorkspaceListModal = ({
|
|||||||
onClickWorkspaceSetting,
|
onClickWorkspaceSetting,
|
||||||
onCreateWorkspace,
|
onCreateWorkspace,
|
||||||
currentWorkspaceId,
|
currentWorkspaceId,
|
||||||
|
onMoveWorkspace,
|
||||||
}: WorkspaceModalProps) => {
|
}: WorkspaceModalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -91,18 +95,21 @@ export const WorkspaceListModal = ({
|
|||||||
</StyledModalHeader>
|
</StyledModalHeader>
|
||||||
|
|
||||||
<StyledModalContent>
|
<StyledModalContent>
|
||||||
{workspaces.map(workspace => {
|
<WorkspaceList
|
||||||
return (
|
items={workspaces}
|
||||||
<WorkspaceCard
|
currentWorkspaceId={currentWorkspaceId}
|
||||||
workspace={workspace}
|
onClick={onClickWorkspace}
|
||||||
currentWorkspaceId={currentWorkspaceId}
|
onSettingClick={onClickWorkspaceSetting}
|
||||||
onClick={onClickWorkspace}
|
onDragEnd={useCallback(
|
||||||
onSettingClick={onClickWorkspaceSetting}
|
(event: DragEndEvent) => {
|
||||||
key={workspace.id}
|
const { active, over } = event;
|
||||||
/>
|
if (active.id !== over?.id) {
|
||||||
);
|
onMoveWorkspace(active.id as string, over?.id as string);
|
||||||
})}
|
}
|
||||||
|
},
|
||||||
|
[onMoveWorkspace]
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<StyledCreateWorkspaceCard
|
<StyledCreateWorkspaceCard
|
||||||
data-testid="new-workspace"
|
data-testid="new-workspace"
|
||||||
onClick={onCreateWorkspace}
|
onClick={onCreateWorkspace}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DebugLogger } from '@affine/debug';
|
|||||||
import { config } from '@affine/env';
|
import { config } from '@affine/env';
|
||||||
import { setUpLanguage, useTranslation } from '@affine/i18n';
|
import { setUpLanguage, useTranslation } from '@affine/i18n';
|
||||||
import { createAffineGlobalChannel } from '@affine/workspace/affine/sync';
|
import { createAffineGlobalChannel } from '@affine/workspace/affine/sync';
|
||||||
import { jotaiWorkspacesAtom } from '@affine/workspace/atom';
|
import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom';
|
||||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||||
import { assertExists, nanoid } from '@blocksuite/store';
|
import { assertExists, nanoid } from '@blocksuite/store';
|
||||||
import { NoSsr } from '@mui/material';
|
import { NoSsr } from '@mui/material';
|
||||||
@@ -122,6 +122,8 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
|
|||||||
setUpLanguage(i18n);
|
setUpLanguage(i18n);
|
||||||
}, [i18n]);
|
}, [i18n]);
|
||||||
useCreateFirstWorkspace();
|
useCreateFirstWorkspace();
|
||||||
|
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
|
||||||
|
const jotaiWorkspaces = useAtomValue(jotaiWorkspacesAtom);
|
||||||
const set = useSetAtom(jotaiWorkspacesAtom);
|
const set = useSetAtom(jotaiWorkspacesAtom);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
logger.info('mount');
|
logger.info('mount');
|
||||||
@@ -131,10 +133,19 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
|
|||||||
.map(({ CRUD }) => CRUD.list);
|
.map(({ CRUD }) => CRUD.list);
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
|
const jotaiWorkspaces = jotaiStore.get(jotaiWorkspacesAtom);
|
||||||
const items = [];
|
const items = [];
|
||||||
for (const list of lists) {
|
for (const list of lists) {
|
||||||
try {
|
try {
|
||||||
const item = await list();
|
const item = await list();
|
||||||
|
if (jotaiWorkspaces.length) {
|
||||||
|
item.sort((a, b) => {
|
||||||
|
return (
|
||||||
|
jotaiWorkspaces.findIndex(x => x.id === a.id) -
|
||||||
|
jotaiWorkspaces.findIndex(x => x.id === b.id)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
items.push(...item.map(x => ({ id: x.id, flavour: x.flavour })));
|
items.push(...item.map(x => ({ id: x.id, flavour: x.flavour })));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('list data error:', e);
|
logger.error('list data error:', e);
|
||||||
@@ -153,8 +164,6 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
|
|||||||
logger.info('unmount');
|
logger.info('unmount');
|
||||||
};
|
};
|
||||||
}, [set]);
|
}, [set]);
|
||||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
|
|
||||||
const jotaiWorkspaces = useAtomValue(jotaiWorkspacesAtom);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const flavour = jotaiWorkspaces.find(
|
const flavour = jotaiWorkspaces.find(
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { jotaiWorkspacesAtom } from '@affine/workspace/atom';
|
||||||
|
import { arrayMove } from '@dnd-kit/sortable';
|
||||||
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
@@ -39,6 +41,7 @@ export function Modals() {
|
|||||||
const { jumpToSubPath } = useRouterHelper(router);
|
const { jumpToSubPath } = useRouterHelper(router);
|
||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useWorkspaces();
|
||||||
|
const setWorkspaces = useSetAtom(jotaiWorkspacesAtom);
|
||||||
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
|
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom);
|
||||||
const [, setCurrentWorkspace] = useCurrentWorkspace();
|
const [, setCurrentWorkspace] = useCurrentWorkspace();
|
||||||
const { createLocalWorkspace } = useWorkspacesHelper();
|
const { createLocalWorkspace } = useWorkspacesHelper();
|
||||||
@@ -53,6 +56,16 @@ export function Modals() {
|
|||||||
onClose={useCallback(() => {
|
onClose={useCallback(() => {
|
||||||
setOpenWorkspacesModal(false);
|
setOpenWorkspacesModal(false);
|
||||||
}, [setOpenWorkspacesModal])}
|
}, [setOpenWorkspacesModal])}
|
||||||
|
onMoveWorkspace={useCallback(
|
||||||
|
(activeId, overId) => {
|
||||||
|
const oldIndex = workspaces.findIndex(w => w.id === activeId);
|
||||||
|
const newIndex = workspaces.findIndex(w => w.id === overId);
|
||||||
|
setWorkspaces(workspaces =>
|
||||||
|
arrayMove(workspaces, oldIndex, newIndex)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[setWorkspaces, workspaces]
|
||||||
|
)}
|
||||||
onClickWorkspace={useCallback(
|
onClickWorkspace={useCallback(
|
||||||
workspace => {
|
workspace => {
|
||||||
setOpenWorkspacesModal(false);
|
setOpenWorkspacesModal(false);
|
||||||
|
|||||||
@@ -17,11 +17,14 @@
|
|||||||
"@affine/debug": "workspace:*",
|
"@affine/debug": "workspace:*",
|
||||||
"@affine/i18n": "workspace:*",
|
"@affine/i18n": "workspace:*",
|
||||||
"@affine/jotai": "workspace:*",
|
"@affine/jotai": "workspace:*",
|
||||||
|
"@affine/workspace": "workspace:^",
|
||||||
"@blocksuite/blocks": "0.0.0-20230406032111-01cf598b-nightly",
|
"@blocksuite/blocks": "0.0.0-20230406032111-01cf598b-nightly",
|
||||||
"@blocksuite/editor": "0.0.0-20230406032111-01cf598b-nightly",
|
"@blocksuite/editor": "0.0.0-20230406032111-01cf598b-nightly",
|
||||||
"@blocksuite/global": "0.0.0-20230406032111-01cf598b-nightly",
|
"@blocksuite/global": "0.0.0-20230406032111-01cf598b-nightly",
|
||||||
"@blocksuite/icons": "2.1.5",
|
"@blocksuite/icons": "2.1.5",
|
||||||
"@blocksuite/store": "0.0.0-20230406032111-01cf598b-nightly",
|
"@blocksuite/store": "0.0.0-20230406032111-01cf598b-nightly",
|
||||||
|
"@dnd-kit/core": "^6.0.8",
|
||||||
|
"@dnd-kit/sortable": "^7.0.2",
|
||||||
"@emotion/cache": "^11.10.7",
|
"@emotion/cache": "^11.10.7",
|
||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/server": "^11.10.0",
|
"@emotion/server": "^11.10.0",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
|
|||||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||||
import { SettingsIcon } from '@blocksuite/icons';
|
import { SettingsIcon } from '@blocksuite/icons';
|
||||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-blocksuite-workspace-name';
|
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-blocksuite-workspace-name';
|
||||||
import type React from 'react';
|
import type { FC, MouseEvent } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { WorkspaceAvatar } from '../workspace-avatar';
|
import { WorkspaceAvatar } from '../workspace-avatar';
|
||||||
@@ -45,7 +45,7 @@ const PublishIcon = () => {
|
|||||||
return <DefaultPublishIcon style={{ color: '#8699FF' }} />;
|
return <DefaultPublishIcon style={{ color: '#8699FF' }} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const WorkspaceType: React.FC<WorkspaceTypeProps> = ({ workspace }) => {
|
const WorkspaceType: FC<WorkspaceTypeProps> = ({ workspace }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
let isOwner = true;
|
let isOwner = true;
|
||||||
if (workspace.flavour === WorkspaceFlavour.AFFINE) {
|
if (workspace.flavour === WorkspaceFlavour.AFFINE) {
|
||||||
@@ -83,7 +83,7 @@ export type WorkspaceCardProps = {
|
|||||||
onSettingClick: (workspace: AffineWorkspace | LocalWorkspace) => void;
|
onSettingClick: (workspace: AffineWorkspace | LocalWorkspace) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkspaceCard: React.FC<WorkspaceCardProps> = ({
|
export const WorkspaceCard: FC<WorkspaceCardProps> = ({
|
||||||
workspace,
|
workspace,
|
||||||
onClick,
|
onClick,
|
||||||
onSettingClick,
|
onSettingClick,
|
||||||
@@ -95,9 +95,12 @@ export const WorkspaceCard: React.FC<WorkspaceCardProps> = ({
|
|||||||
return (
|
return (
|
||||||
<StyledCard
|
<StyledCard
|
||||||
data-testid="workspace-card"
|
data-testid="workspace-card"
|
||||||
onClick={useCallback(() => {
|
onClick={useCallback(
|
||||||
onClick(workspace);
|
(event: MouseEvent) => {
|
||||||
}, [onClick, workspace])}
|
onClick(workspace);
|
||||||
|
},
|
||||||
|
[onClick, workspace]
|
||||||
|
)}
|
||||||
active={workspace.id === currentWorkspaceId}
|
active={workspace.id === currentWorkspaceId}
|
||||||
>
|
>
|
||||||
<WorkspaceAvatar size={58} workspace={workspace} />
|
<WorkspaceAvatar size={58} workspace={workspace} />
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||||
|
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
|
||||||
|
import { arrayMove } from '@dnd-kit/sortable';
|
||||||
|
import type { Meta } from '@storybook/react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import type { WorkspaceListProps } from './index';
|
||||||
|
import { WorkspaceList } from './index';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'AFFiNE/WorkspaceList',
|
||||||
|
component: WorkspaceList,
|
||||||
|
} satisfies Meta<WorkspaceListProps>;
|
||||||
|
|
||||||
|
export const Default = () => {
|
||||||
|
const [items, setItems] = useState(() => {
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
flavour: WorkspaceFlavour.LOCAL,
|
||||||
|
blockSuiteWorkspace: createEmptyBlockSuiteWorkspace('1'),
|
||||||
|
providers: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
flavour: WorkspaceFlavour.LOCAL,
|
||||||
|
blockSuiteWorkspace: createEmptyBlockSuiteWorkspace('2'),
|
||||||
|
providers: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
flavour: WorkspaceFlavour.LOCAL,
|
||||||
|
blockSuiteWorkspace: createEmptyBlockSuiteWorkspace('3'),
|
||||||
|
providers: [],
|
||||||
|
},
|
||||||
|
] satisfies WorkspaceListProps['items'];
|
||||||
|
|
||||||
|
items.forEach(item => {
|
||||||
|
item.blockSuiteWorkspace.meta.setName(item.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<WorkspaceList
|
||||||
|
currentWorkspaceId={null}
|
||||||
|
items={items}
|
||||||
|
onClick={() => {}}
|
||||||
|
onSettingClick={() => {}}
|
||||||
|
onDragEnd={event => {
|
||||||
|
const { active, over } = event;
|
||||||
|
|
||||||
|
if (active.id !== over?.id) {
|
||||||
|
setItems(items => {
|
||||||
|
const oldIndex = items.findIndex(item => item.id === active.id);
|
||||||
|
const newIndex = items.findIndex(item => item.id === over?.id);
|
||||||
|
|
||||||
|
return arrayMove(items, oldIndex, newIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
70
packages/component/src/components/workspace-list/index.tsx
Normal file
70
packages/component/src/components/workspace-list/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import type { AffineWorkspace, LocalWorkspace } from '@affine/workspace/type';
|
||||||
|
import type { DragEndEvent } from '@dnd-kit/core';
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
PointerSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
} from '@dnd-kit/core';
|
||||||
|
import { SortableContext, useSortable } from '@dnd-kit/sortable';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
|
||||||
|
import { WorkspaceCard } from '../workspace-card';
|
||||||
|
|
||||||
|
export type WorkspaceListProps = {
|
||||||
|
currentWorkspaceId: string | null;
|
||||||
|
items: (AffineWorkspace | LocalWorkspace)[];
|
||||||
|
onClick: (workspace: AffineWorkspace | LocalWorkspace) => void;
|
||||||
|
onSettingClick: (workspace: AffineWorkspace | LocalWorkspace) => void;
|
||||||
|
onDragEnd: (event: DragEndEvent) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SortableWorkspaceItem: FC<
|
||||||
|
Omit<WorkspaceListProps, 'items'> & {
|
||||||
|
item: AffineWorkspace | LocalWorkspace;
|
||||||
|
}
|
||||||
|
> = props => {
|
||||||
|
const { setNodeRef, attributes, listeners, transform } = useSortable({
|
||||||
|
id: props.item.id,
|
||||||
|
});
|
||||||
|
const style = transform
|
||||||
|
? {
|
||||||
|
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-testid="draggable-item"
|
||||||
|
style={style}
|
||||||
|
ref={setNodeRef}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
>
|
||||||
|
<WorkspaceCard
|
||||||
|
currentWorkspaceId={props.currentWorkspaceId}
|
||||||
|
workspace={props.item}
|
||||||
|
onClick={props.onClick}
|
||||||
|
onSettingClick={props.onSettingClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceList: FC<WorkspaceListProps> = props => {
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(PointerSensor, {
|
||||||
|
activationConstraint: {
|
||||||
|
distance: 8,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<DndContext sensors={sensors} onDragEnd={props.onDragEnd}>
|
||||||
|
<SortableContext items={props.items}>
|
||||||
|
{props.items.map(item => (
|
||||||
|
<SortableWorkspaceItem {...props} item={item} key={item.id} />
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,13 +4,17 @@ import { expect } from '@playwright/test';
|
|||||||
interface CreateWorkspaceParams {
|
interface CreateWorkspaceParams {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function openWorkspaceListModal(page: Page) {
|
||||||
|
const workspaceName = page.getByTestId('workspace-name');
|
||||||
|
await workspaceName.click();
|
||||||
|
}
|
||||||
|
|
||||||
export async function createWorkspace(
|
export async function createWorkspace(
|
||||||
params: CreateWorkspaceParams,
|
params: CreateWorkspaceParams,
|
||||||
page: Page
|
page: Page
|
||||||
) {
|
) {
|
||||||
// open workspace list modal
|
await openWorkspaceListModal(page);
|
||||||
const workspaceName = page.getByTestId('workspace-name');
|
|
||||||
await workspaceName.click();
|
|
||||||
|
|
||||||
// open create workspace modal
|
// open create workspace modal
|
||||||
await page.locator('.add-icon').click();
|
await page.locator('.add-icon').click();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { openHomePage } from '../libs/load-page';
|
|||||||
import { waitMarkdownImported } from '../libs/page-logic';
|
import { waitMarkdownImported } from '../libs/page-logic';
|
||||||
import { test } from '../libs/playwright';
|
import { test } from '../libs/playwright';
|
||||||
import { clickSideBarAllPageButton } from '../libs/sidebar';
|
import { clickSideBarAllPageButton } from '../libs/sidebar';
|
||||||
import { createWorkspace } from '../libs/workspace';
|
import { createWorkspace, openWorkspaceListModal } from '../libs/workspace';
|
||||||
|
|
||||||
test.describe('Local first workspace list', () => {
|
test.describe('Local first workspace list', () => {
|
||||||
test('just one item in the workspace list at first', async ({ page }) => {
|
test('just one item in the workspace list at first', async ({ page }) => {
|
||||||
@@ -59,8 +59,61 @@ test.describe('Local first workspace list', () => {
|
|||||||
const workspaceName = page.getByTestId('workspace-name');
|
const workspaceName = page.getByTestId('workspace-name');
|
||||||
await workspaceName.click();
|
await workspaceName.click();
|
||||||
|
|
||||||
|
{
|
||||||
|
//check workspace list length
|
||||||
|
const workspaceCards = await page.$$('data-testid=workspace-card');
|
||||||
|
expect(workspaceCards.length).toBe(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
await openWorkspaceListModal(page);
|
||||||
|
await page.getByTestId('draggable-item').nth(1).click();
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
const currentId: string = await page.evaluate(() => currentWorkspace.id);
|
||||||
|
|
||||||
|
await openWorkspaceListModal(page);
|
||||||
|
const sourceElement = await page.getByTestId('draggable-item').nth(2);
|
||||||
|
const targetElement = await page.getByTestId('draggable-item').nth(1);
|
||||||
|
|
||||||
|
const sourceBox = await sourceElement.boundingBox();
|
||||||
|
const targetBox = await targetElement.boundingBox();
|
||||||
|
|
||||||
|
if (!sourceBox || !targetBox) {
|
||||||
|
throw new Error('sourceBox or targetBox is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.mouse.move(
|
||||||
|
sourceBox.x + sourceBox.width / 2,
|
||||||
|
sourceBox.y + sourceBox.height / 2,
|
||||||
|
{
|
||||||
|
steps: 5,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(
|
||||||
|
targetBox.x + targetBox.width / 2,
|
||||||
|
targetBox.y + targetBox.height / 2,
|
||||||
|
{
|
||||||
|
steps: 5,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await page.mouse.up();
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
await page.reload();
|
||||||
|
await openWorkspaceListModal(page);
|
||||||
|
|
||||||
//check workspace list length
|
//check workspace list length
|
||||||
const workspaceCards = await page.$$('data-testid=workspace-card');
|
{
|
||||||
expect(workspaceCards.length).toBe(3);
|
const workspaceCards1 = await page.$$('data-testid=workspace-card');
|
||||||
|
expect(workspaceCards1.length).toBe(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByTestId('draggable-item').nth(2).click();
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
const nextId: string = await page.evaluate(() => currentWorkspace.id);
|
||||||
|
expect(currentId).toBe(nextId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
56
yarn.lock
56
yarn.lock
@@ -37,11 +37,14 @@ __metadata:
|
|||||||
"@affine/debug": "workspace:*"
|
"@affine/debug": "workspace:*"
|
||||||
"@affine/i18n": "workspace:*"
|
"@affine/i18n": "workspace:*"
|
||||||
"@affine/jotai": "workspace:*"
|
"@affine/jotai": "workspace:*"
|
||||||
|
"@affine/workspace": "workspace:^"
|
||||||
"@blocksuite/blocks": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/blocks": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
"@blocksuite/editor": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/editor": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
"@blocksuite/global": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/global": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
"@blocksuite/icons": 2.1.5
|
"@blocksuite/icons": 2.1.5
|
||||||
"@blocksuite/store": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/store": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
|
"@dnd-kit/core": ^6.0.8
|
||||||
|
"@dnd-kit/sortable": ^7.0.2
|
||||||
"@emotion/cache": ^11.10.7
|
"@emotion/cache": ^11.10.7
|
||||||
"@emotion/react": ^11.10.6
|
"@emotion/react": ^11.10.6
|
||||||
"@emotion/server": ^11.10.0
|
"@emotion/server": ^11.10.0
|
||||||
@@ -170,6 +173,8 @@ __metadata:
|
|||||||
"@blocksuite/editor": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/editor": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
"@blocksuite/icons": ^2.1.5
|
"@blocksuite/icons": ^2.1.5
|
||||||
"@blocksuite/store": 0.0.0-20230406032111-01cf598b-nightly
|
"@blocksuite/store": 0.0.0-20230406032111-01cf598b-nightly
|
||||||
|
"@dnd-kit/core": ^6.0.8
|
||||||
|
"@dnd-kit/sortable": ^7.0.2
|
||||||
"@emotion/cache": ^11.10.7
|
"@emotion/cache": ^11.10.7
|
||||||
"@emotion/react": ^11.10.6
|
"@emotion/react": ^11.10.6
|
||||||
"@emotion/server": ^11.10.0
|
"@emotion/server": ^11.10.0
|
||||||
@@ -215,7 +220,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@affine/workspace@workspace:*, @affine/workspace@workspace:packages/workspace":
|
"@affine/workspace@workspace:*, @affine/workspace@workspace:^, @affine/workspace@workspace:packages/workspace":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@affine/workspace@workspace:packages/workspace"
|
resolution: "@affine/workspace@workspace:packages/workspace"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2095,6 +2100,55 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/accessibility@npm:^3.0.0":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "@dnd-kit/accessibility@npm:3.0.1"
|
||||||
|
dependencies:
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: 0afc2c0fce9a1c107453620ca0da1778f182d340e74ffbc6e369ef0ac8943cafb929d3a6c0891d9b915aa23b2b92137ff4fad958f43118466586d8129a3359d5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/core@npm:^6.0.8":
|
||||||
|
version: 6.0.8
|
||||||
|
resolution: "@dnd-kit/core@npm:6.0.8"
|
||||||
|
dependencies:
|
||||||
|
"@dnd-kit/accessibility": ^3.0.0
|
||||||
|
"@dnd-kit/utilities": ^3.2.1
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
react-dom: ">=16.8.0"
|
||||||
|
checksum: abe48ff7395f84fd8c15e6c8b13da4df153dc1f1076096d783acd0c25539516c77e4854ea59be6621dde55739cb0df1d62924ad069df3267fe05ad90ef729b2f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/sortable@npm:^7.0.2":
|
||||||
|
version: 7.0.2
|
||||||
|
resolution: "@dnd-kit/sortable@npm:7.0.2"
|
||||||
|
dependencies:
|
||||||
|
"@dnd-kit/utilities": ^3.2.0
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
"@dnd-kit/core": ^6.0.7
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: 4ce705aceb15766a0deefe25a9d95a87e9413c3fb9088ea3eb0962e57f844895000117fcec7c0944a0d4ae4e1e889cfa69e3d3778164d4d23115fb1edb218283
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@dnd-kit/utilities@npm:^3.2.0, @dnd-kit/utilities@npm:^3.2.1":
|
||||||
|
version: 3.2.1
|
||||||
|
resolution: "@dnd-kit/utilities@npm:3.2.1"
|
||||||
|
dependencies:
|
||||||
|
tslib: ^2.0.0
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8.0"
|
||||||
|
checksum: 038fd5cc1328bf4c9dca17cd48046e5a687bbf9d904c7197f851aab869ab52d9dee2734b2e255256fd6158245acd00063a23deed962c7673c0fadfbf061f04ca
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@emotion/babel-plugin@npm:^11.10.6":
|
"@emotion/babel-plugin@npm:^11.10.6":
|
||||||
version: 11.10.6
|
version: 11.10.6
|
||||||
resolution: "@emotion/babel-plugin@npm:11.10.6"
|
resolution: "@emotion/babel-plugin@npm:11.10.6"
|
||||||
|
|||||||
Reference in New Issue
Block a user