mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
feat: support remove user & workspace avatar (#4302)
This commit is contained in:
@@ -2,7 +2,7 @@ import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const header = style({
|
||||
position: 'relative',
|
||||
height: '44px',
|
||||
marginTop: '44px',
|
||||
});
|
||||
|
||||
export const content = style({
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { FlexWrapper, Input, Wrapper } from '@affine/component';
|
||||
import { pushNotificationAtom } from '@affine/component/notification-center';
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import type { AffineOfficialWorkspace } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CameraIcon } from '@blocksuite/icons';
|
||||
import { Avatar } from '@toeverything/components/avatar';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import clsx from 'clsx';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import {
|
||||
type KeyboardEvent,
|
||||
type MouseEvent,
|
||||
startTransition,
|
||||
useCallback,
|
||||
useState,
|
||||
@@ -29,7 +28,7 @@ export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const pushNotification = useSetAtom(pushNotificationAtom);
|
||||
|
||||
const [, update] = useBlockSuiteWorkspaceAvatarUrl(
|
||||
const [workspaceAvatar, update] = useBlockSuiteWorkspaceAvatarUrl(
|
||||
workspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
@@ -38,8 +37,6 @@ export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => {
|
||||
);
|
||||
|
||||
const [input, setInput] = useState<string>(name);
|
||||
const [tooltipContainer, setTooltipContainer] =
|
||||
useState<HTMLDivElement | null>(null);
|
||||
|
||||
const handleUpdateWorkspaceName = useCallback(
|
||||
(name: string) => {
|
||||
@@ -71,35 +68,38 @@ export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => {
|
||||
handleUpdateWorkspaceName(input);
|
||||
}, [handleUpdateWorkspaceName, input]);
|
||||
|
||||
const handleRemoveUserAvatar = useCallback(
|
||||
async (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
await update(null);
|
||||
},
|
||||
[update]
|
||||
);
|
||||
return (
|
||||
<div className={style.profileWrapper}>
|
||||
<Tooltip
|
||||
content={t['Click to replace photo']()}
|
||||
portalOptions={{
|
||||
container: tooltipContainer,
|
||||
}}
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={update}
|
||||
data-testid="upload-avatar"
|
||||
disabled={!isOwner}
|
||||
>
|
||||
<div
|
||||
className={clsx(style.avatarWrapper, { disable: !isOwner })}
|
||||
ref={setTooltipContainer}
|
||||
>
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={update}
|
||||
data-testid="upload-avatar"
|
||||
>
|
||||
<>
|
||||
<div className="camera-icon-wrapper">
|
||||
<CameraIcon />
|
||||
</div>
|
||||
<WorkspaceAvatar
|
||||
size={56}
|
||||
workspace={workspace.blockSuiteWorkspace}
|
||||
/>
|
||||
</>
|
||||
</Upload>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Avatar
|
||||
size={56}
|
||||
url={workspaceAvatar}
|
||||
name={name}
|
||||
colorfulFallback
|
||||
hoverIcon={isOwner ? <CameraIcon /> : undefined}
|
||||
onRemove={
|
||||
workspaceAvatar && isOwner ? handleRemoveUserAvatar : undefined
|
||||
}
|
||||
avatarTooltipOptions={{ content: t['Click to replace photo']() }}
|
||||
removeTooltipOptions={{ content: t['Remove photo']() }}
|
||||
data-testid="workspace-setting-avatar"
|
||||
removeButtonProps={{
|
||||
['data-testid' as string]: 'workspace-setting-remove-avatar-button',
|
||||
}}
|
||||
/>
|
||||
</Upload>
|
||||
|
||||
<Wrapper marginLeft={20}>
|
||||
<div className={style.label}>{t['Workspace Name']()}</div>
|
||||
|
||||
@@ -4,15 +4,24 @@ import {
|
||||
SettingRow,
|
||||
StorageProgress,
|
||||
} from '@affine/component/setting-components';
|
||||
import { UserAvatar } from '@affine/component/user-avatar';
|
||||
import { allBlobSizesQuery, uploadAvatarMutation } from '@affine/graphql';
|
||||
import {
|
||||
allBlobSizesQuery,
|
||||
removeAvatarMutation,
|
||||
uploadAvatarMutation,
|
||||
} from '@affine/graphql';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useMutation, useQuery } from '@affine/workspace/affine/gql';
|
||||
import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons';
|
||||
import { Avatar } from '@toeverything/components/avatar';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { type FC, Suspense, useCallback, useState } from 'react';
|
||||
import {
|
||||
type FC,
|
||||
type MouseEvent,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { authAtom } from '../../../../atoms';
|
||||
import { useCurrentUser } from '../../../../hooks/affine/use-current-user';
|
||||
@@ -21,24 +30,16 @@ import { signOutCloud } from '../../../../utils/cloud-utils';
|
||||
import { Upload } from '../../../pure/file-upload';
|
||||
import * as style from './style.css';
|
||||
|
||||
export const AvatarAndName = () => {
|
||||
export const UserAvatar = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const user = useCurrentUser();
|
||||
|
||||
const [tooltipContainer, setTooltipContainer] =
|
||||
useState<HTMLDivElement | null>(null);
|
||||
const [input, setInput] = useState<string>(user.name);
|
||||
|
||||
const { trigger: avatarTrigger } = useMutation({
|
||||
mutation: uploadAvatarMutation,
|
||||
});
|
||||
|
||||
const handleUpdateUserName = useCallback(
|
||||
(newName: string) => {
|
||||
user.update({ name: newName }).catch(console.error);
|
||||
},
|
||||
[user]
|
||||
);
|
||||
const { trigger: removeAvatarTrigger } = useMutation({
|
||||
mutation: removeAvatarMutation,
|
||||
});
|
||||
|
||||
const handleUpdateUserAvatar = useCallback(
|
||||
async (file: File) => {
|
||||
@@ -51,6 +52,50 @@ export const AvatarAndName = () => {
|
||||
},
|
||||
[avatarTrigger, user]
|
||||
);
|
||||
const handleRemoveUserAvatar = useCallback(
|
||||
async (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
await removeAvatarTrigger();
|
||||
// XXX: This is a hack to force the user to update, since next-auth can not only use update function without params
|
||||
user.update({ name: user.name }).catch(console.error);
|
||||
},
|
||||
[removeAvatarTrigger, user]
|
||||
);
|
||||
return (
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={handleUpdateUserAvatar}
|
||||
data-testid="upload-user-avatar"
|
||||
>
|
||||
<Avatar
|
||||
size={56}
|
||||
name={user.name}
|
||||
url={user.image}
|
||||
hoverIcon={<CameraIcon />}
|
||||
onRemove={user.image ? handleRemoveUserAvatar : undefined}
|
||||
avatarTooltipOptions={{ content: t['Click to replace photo']() }}
|
||||
removeTooltipOptions={{ content: t['Remove photo']() }}
|
||||
data-testid="user-setting-avatar"
|
||||
removeButtonProps={{
|
||||
['data-testid' as string]: 'user-setting-remove-avatar-button',
|
||||
}}
|
||||
/>
|
||||
</Upload>
|
||||
);
|
||||
};
|
||||
|
||||
export const AvatarAndName = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const user = useCurrentUser();
|
||||
const [input, setInput] = useState<string>(user.name);
|
||||
|
||||
const handleUpdateUserName = useCallback(
|
||||
(newName: string) => {
|
||||
user.update({ name: newName }).catch(console.error);
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow
|
||||
@@ -59,32 +104,9 @@ export const AvatarAndName = () => {
|
||||
spreadCol={false}
|
||||
>
|
||||
<FlexWrapper style={{ margin: '12px 0 24px 0' }} alignItems="center">
|
||||
<Tooltip
|
||||
content={t['Click to replace photo']()}
|
||||
portalOptions={{
|
||||
container: tooltipContainer,
|
||||
}}
|
||||
>
|
||||
<div className={style.avatarWrapper} ref={setTooltipContainer}>
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={handleUpdateUserAvatar}
|
||||
data-testid="upload-user-avatar"
|
||||
>
|
||||
<>
|
||||
<div className="camera-icon-wrapper">
|
||||
<CameraIcon />
|
||||
</div>
|
||||
<UserAvatar
|
||||
size={56}
|
||||
name={user.name}
|
||||
url={user.image}
|
||||
className="avatar"
|
||||
/>
|
||||
</>
|
||||
</Upload>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Suspense>
|
||||
<UserAvatar />
|
||||
</Suspense>
|
||||
|
||||
<div className={style.profileInputWrapper}>
|
||||
<label>{t['com.affine.settings.profile.name']()}</label>
|
||||
|
||||
@@ -2,14 +2,14 @@ import {
|
||||
WorkspaceListItemSkeleton,
|
||||
WorkspaceListSkeleton,
|
||||
} from '@affine/component/setting-components';
|
||||
import { UserAvatar } from '@affine/component/user-avatar';
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { Logo1Icon } from '@blocksuite/icons';
|
||||
import { Avatar } from '@toeverything/components/avatar';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { useStaticBlockSuiteWorkspace } from '@toeverything/infra/__internal__/react';
|
||||
import clsx from 'clsx';
|
||||
@@ -55,11 +55,13 @@ export const UserInfo = ({
|
||||
className={accountButton}
|
||||
onClick={onAccountSettingClick}
|
||||
>
|
||||
<UserAvatar
|
||||
<Avatar
|
||||
size={28}
|
||||
name={user.name}
|
||||
url={user.image}
|
||||
className="avatar"
|
||||
style={{
|
||||
marginRight: '10px',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="content">
|
||||
@@ -221,6 +223,7 @@ const WorkspaceListItem = ({
|
||||
isActive: boolean;
|
||||
}) => {
|
||||
const workspace = useStaticBlockSuiteWorkspace(meta.id);
|
||||
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(workspace);
|
||||
const [workspaceName] = useBlockSuiteWorkspaceName(workspace);
|
||||
const ref = useRef(null);
|
||||
|
||||
@@ -232,7 +235,15 @@ const WorkspaceListItem = ({
|
||||
data-testid="workspace-list-item"
|
||||
ref={ref}
|
||||
>
|
||||
<WorkspaceAvatar size={14} workspace={workspace} className="icon" />
|
||||
<Avatar
|
||||
size={14}
|
||||
url={workspaceAvatar}
|
||||
name={workspaceName}
|
||||
colorfulFallback
|
||||
style={{
|
||||
marginRight: '10px',
|
||||
}}
|
||||
/>
|
||||
<span className="setting-name">{workspaceName}</span>
|
||||
{isCurrent ? (
|
||||
<Tooltip
|
||||
|
||||
@@ -62,12 +62,6 @@ export const sidebarSelectItem = style({
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle(`${settingSlideBar} .icon`, {
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
marginRight: '10px',
|
||||
flexShrink: 0,
|
||||
});
|
||||
globalStyle(`${settingSlideBar} .setting-name`, {
|
||||
minWidth: 0,
|
||||
overflow: 'hidden',
|
||||
@@ -108,13 +102,6 @@ export const accountButton = style({
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle(`${accountButton} .avatar`, {
|
||||
border: '1px solid',
|
||||
borderColor: 'var(--affine-white)',
|
||||
marginRight: '10px',
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
globalStyle(`${accountButton} .avatar.not-sign`, {
|
||||
width: '28px',
|
||||
height: '28px',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UserAvatar } from '@affine/component/user-avatar';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloudWorkspaceIcon } from '@blocksuite/icons';
|
||||
import { Avatar } from '@toeverything/components/avatar';
|
||||
|
||||
import { useCurrentLoginStatus } from '../../hooks/affine/use-current-login-status';
|
||||
import { useCurrentUser } from '../../hooks/affine/use-current-user';
|
||||
@@ -37,12 +37,7 @@ const UserCard = () => {
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<UserAvatar
|
||||
size={28}
|
||||
name={user.name}
|
||||
url={user.image}
|
||||
className="avatar"
|
||||
/>
|
||||
<Avatar size={28} name={user.name} url={user.image} />
|
||||
<div style={{ marginLeft: '15px' }}>
|
||||
<div>{user.name}</div>
|
||||
<div>{user.email}</div>
|
||||
|
||||
@@ -8,12 +8,14 @@ export interface UploadProps {
|
||||
uploadType?: string;
|
||||
accept?: string;
|
||||
fileChange: (file: File) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Upload = ({
|
||||
fileChange,
|
||||
accept,
|
||||
children,
|
||||
disabled,
|
||||
...props
|
||||
}: PropsWithChildren<UploadProps>) => {
|
||||
const t = useAFFiNEI18N();
|
||||
@@ -35,6 +37,10 @@ export const Upload = ({
|
||||
}
|
||||
};
|
||||
|
||||
if (disabled) {
|
||||
return children ?? <Button>{t['Upload']()}</Button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<UploadStyle onClick={_chooseFile}>
|
||||
{children ?? <Button>{t['Upload']()}</Button>}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './workspace-selector';
|
||||
@@ -3,9 +3,10 @@ import type {
|
||||
AffineCloudWorkspace,
|
||||
LocalWorkspace,
|
||||
} from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { RootWorkspaceMetadata } from '@affine/workspace/atom';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
AccountIcon,
|
||||
ImportIcon,
|
||||
@@ -15,23 +16,28 @@ import {
|
||||
SignOutIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import type { DragEndEvent } from '@dnd-kit/core';
|
||||
import { Popover } from '@mui/material';
|
||||
import { arrayMove } from '@dnd-kit/sortable';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { Divider } from '@toeverything/components/divider';
|
||||
import { Menu, MenuIcon, MenuItem } from '@toeverything/components/menu';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { useCallback } from 'react';
|
||||
import { startTransition, useCallback, useMemo, useTransition } from 'react';
|
||||
|
||||
import {
|
||||
authAtom,
|
||||
openCreateWorkspaceModalAtom,
|
||||
openDisableCloudAlertModalAtom,
|
||||
openSettingModalAtom,
|
||||
} from '../../../atoms';
|
||||
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
|
||||
import type { AllWorkspace } from '../../../shared';
|
||||
import { signOutCloud } from '../../../utils/cloud-utils';
|
||||
} from '../../../../atoms';
|
||||
import type { AllWorkspace } from '../../../../shared';
|
||||
import { signOutCloud } from '../../../../utils/cloud-utils';
|
||||
import { useNavigateHelper } from '../.././../../hooks/use-navigate-helper';
|
||||
import {
|
||||
StyledCreateWorkspaceCardPill,
|
||||
StyledCreateWorkspaceCardPillContent,
|
||||
@@ -57,8 +63,6 @@ interface WorkspaceModalProps {
|
||||
disabled?: boolean;
|
||||
workspaces: RootWorkspaceMetadata[];
|
||||
currentWorkspaceId: AllWorkspace['id'] | null;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onClickWorkspace: (workspace: RootWorkspaceMetadata['id']) => void;
|
||||
onClickWorkspaceSetting: (workspace: RootWorkspaceMetadata['id']) => void;
|
||||
onNewWorkspace: () => void;
|
||||
@@ -66,10 +70,15 @@ interface WorkspaceModalProps {
|
||||
onMoveWorkspace: (activeId: string, overId: string) => void;
|
||||
}
|
||||
|
||||
const AccountMenu = () => {
|
||||
const AccountMenu = ({
|
||||
onOpenAccountSetting,
|
||||
onSignOut,
|
||||
}: {
|
||||
onOpenAccountSetting: () => void;
|
||||
onSignOut: () => void;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const setOpen = useSetAtom(openSettingModalAtom);
|
||||
const { jumpToIndex } = useNavigateHelper();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MenuItem
|
||||
@@ -79,9 +88,7 @@ const AccountMenu = () => {
|
||||
</MenuIcon>
|
||||
}
|
||||
data-testid="editor-option-menu-import"
|
||||
onClick={useCallback(() => {
|
||||
setOpen(prev => ({ ...prev, open: true, activeTab: 'account' }));
|
||||
}, [setOpen])}
|
||||
onClick={onOpenAccountSetting}
|
||||
>
|
||||
{t['com.affine.workspace.cloud.account.settings']()}
|
||||
</MenuItem>
|
||||
@@ -93,13 +100,7 @@ const AccountMenu = () => {
|
||||
</MenuIcon>
|
||||
}
|
||||
data-testid="editor-option-menu-import"
|
||||
onClick={useCallback(() => {
|
||||
signOutCloud()
|
||||
.then(() => {
|
||||
jumpToIndex();
|
||||
})
|
||||
.catch(console.error);
|
||||
}, [jumpToIndex])}
|
||||
onClick={onSignOut}
|
||||
>
|
||||
{t['com.affine.workspace.cloud.account.logout']()}
|
||||
</MenuItem>
|
||||
@@ -152,52 +153,108 @@ const CloudWorkSpaceList = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceListModal = ({
|
||||
disabled,
|
||||
open,
|
||||
onClose,
|
||||
workspaces,
|
||||
onClickWorkspace,
|
||||
onClickWorkspaceSetting,
|
||||
onNewWorkspace,
|
||||
onAddWorkspace,
|
||||
currentWorkspaceId,
|
||||
onMoveWorkspace,
|
||||
}: WorkspaceModalProps) => {
|
||||
export const UserWithWorkspaceList = ({
|
||||
onEventEnd,
|
||||
}: {
|
||||
onEventEnd?: () => void;
|
||||
}) => {
|
||||
const setOpenCreateWorkspaceModal = useSetAtom(openCreateWorkspaceModalAtom);
|
||||
|
||||
const { jumpToSubPath, jumpToIndex } = useNavigateHelper();
|
||||
const workspaces = useAtomValue(rootWorkspacesMetadataAtom, {
|
||||
delay: 0,
|
||||
});
|
||||
const setWorkspaces = useSetAtom(rootWorkspacesMetadataAtom);
|
||||
const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom(
|
||||
currentWorkspaceIdAtom
|
||||
);
|
||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
||||
const [, startCloseTransition] = useTransition();
|
||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
const setSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
|
||||
const t = useAFFiNEI18N();
|
||||
const setOpen = useSetAtom(authAtom);
|
||||
const setDisableCloudOpen = useSetAtom(openDisableCloudAlertModalAtom);
|
||||
// TODO: AFFiNE Cloud support
|
||||
const { data: session, status } = useSession();
|
||||
const isLoggedIn = status === 'authenticated' ? true : false;
|
||||
const anchorEl = document.getElementById('current-workspace');
|
||||
const cloudWorkspaces = workspaces.filter(
|
||||
({ flavour }) => flavour === WorkspaceFlavour.AFFINE_CLOUD
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[];
|
||||
const localWorkspaces = workspaces.filter(
|
||||
({ flavour }) => flavour === WorkspaceFlavour.LOCAL
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[];
|
||||
// FIXME: replace mui popover
|
||||
const isLoggedIn = useMemo(() => status === 'authenticated', [status]);
|
||||
const cloudWorkspaces = useMemo(
|
||||
() =>
|
||||
workspaces.filter(
|
||||
({ flavour }) => flavour === WorkspaceFlavour.AFFINE_CLOUD
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[],
|
||||
[workspaces]
|
||||
);
|
||||
const localWorkspaces = useMemo(
|
||||
() =>
|
||||
workspaces.filter(
|
||||
({ flavour }) => flavour === WorkspaceFlavour.LOCAL
|
||||
) as (AffineCloudWorkspace | LocalWorkspace)[],
|
||||
[workspaces]
|
||||
);
|
||||
|
||||
const onClickWorkspaceSetting = useCallback(
|
||||
(workspaceId: string) => {
|
||||
setOpenSettingModalAtom({
|
||||
open: true,
|
||||
activeTab: 'workspace',
|
||||
workspaceId,
|
||||
});
|
||||
onEventEnd?.();
|
||||
},
|
||||
[onEventEnd, setOpenSettingModalAtom]
|
||||
);
|
||||
|
||||
const onMoveWorkspace = useCallback(
|
||||
(activeId: string, overId: string) => {
|
||||
const oldIndex = workspaces.findIndex(w => w.id === activeId);
|
||||
const newIndex = workspaces.findIndex(w => w.id === overId);
|
||||
startTransition(() => {
|
||||
setWorkspaces(workspaces => arrayMove(workspaces, oldIndex, newIndex));
|
||||
});
|
||||
},
|
||||
[setWorkspaces, workspaces]
|
||||
);
|
||||
const onClickWorkspace = useCallback(
|
||||
(workspaceId: string) => {
|
||||
startCloseTransition(() => {
|
||||
setCurrentWorkspaceId(workspaceId);
|
||||
setCurrentPageId(null);
|
||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||
});
|
||||
onEventEnd?.();
|
||||
},
|
||||
[jumpToSubPath, onEventEnd, setCurrentPageId, setCurrentWorkspaceId]
|
||||
);
|
||||
const onNewWorkspace = useCallback(() => {
|
||||
setOpenCreateWorkspaceModal('new');
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||
const onAddWorkspace = useCallback(async () => {
|
||||
setOpenCreateWorkspaceModal('add');
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, setOpenCreateWorkspaceModal]);
|
||||
|
||||
const onOpenAccountSetting = useCallback(() => {
|
||||
setSettingModalAtom(prev => ({
|
||||
...prev,
|
||||
open: true,
|
||||
activeTab: 'account',
|
||||
}));
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, setSettingModalAtom]);
|
||||
const onSignOut = useCallback(async () => {
|
||||
signOutCloud()
|
||||
.then(() => {
|
||||
jumpToIndex();
|
||||
})
|
||||
.catch(console.error);
|
||||
onEventEnd?.();
|
||||
}, [onEventEnd, jumpToIndex]);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
sx={{
|
||||
color: 'success.main',
|
||||
zIndex: 999,
|
||||
'& .MuiPopover-paper': {
|
||||
borderRadius: '8px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
boxShadow: 'var(--affine-shadow-2)',
|
||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||
padding: '16px 12px',
|
||||
},
|
||||
maxHeight: '90vh',
|
||||
}}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onClose}
|
||||
disableEnforceFocus
|
||||
>
|
||||
<>
|
||||
{!isLoggedIn ? (
|
||||
<StyledModalHeaderContent>
|
||||
<StyledSignInCardPill>
|
||||
@@ -211,7 +268,6 @@ export const WorkspaceListModal = ({
|
||||
openModal: true,
|
||||
}));
|
||||
}
|
||||
onClose();
|
||||
}}
|
||||
data-testid="cloud-signin-button"
|
||||
>
|
||||
@@ -242,7 +298,12 @@ export const WorkspaceListModal = ({
|
||||
<StyledModalTitle>{session?.user.email}</StyledModalTitle>
|
||||
<StyledOperationWrapper>
|
||||
<Menu
|
||||
items={<AccountMenu />}
|
||||
items={
|
||||
<AccountMenu
|
||||
onOpenAccountSetting={onOpenAccountSetting}
|
||||
onSignOut={onSignOut}
|
||||
/>
|
||||
}
|
||||
contentOptions={{
|
||||
side: 'right',
|
||||
sideOffset: 30,
|
||||
@@ -263,9 +324,6 @@ export const WorkspaceListModal = ({
|
||||
{isLoggedIn && cloudWorkspaces.length !== 0 ? (
|
||||
<>
|
||||
<CloudWorkSpaceList
|
||||
disabled={disabled}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
workspaces={workspaces}
|
||||
onClickWorkspace={onClickWorkspace}
|
||||
onClickWorkspaceSetting={onClickWorkspaceSetting}
|
||||
@@ -288,7 +346,6 @@ export const WorkspaceListModal = ({
|
||||
</StyledModalHeader>
|
||||
<StyledModalContent>
|
||||
<WorkspaceList
|
||||
disabled={disabled}
|
||||
items={localWorkspaces}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
onClick={onClickWorkspace}
|
||||
@@ -335,6 +392,6 @@ export const WorkspaceListModal = ({
|
||||
</StyledItem>
|
||||
</StyledCreateWorkspaceCardPill>
|
||||
</StyledModalFooterContent>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,4 @@
|
||||
import { createVar, keyframes, style } from '@vanilla-extract/css';
|
||||
export const workspaceAvatarStyle = style({
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
export const speedVar = createVar('speedVar');
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
|
||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import {
|
||||
CloudWorkspaceIcon,
|
||||
@@ -6,11 +5,14 @@ import {
|
||||
NoNetworkIcon,
|
||||
UnsyncIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { Avatar } from '@toeverything/components/avatar';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
|
||||
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { atom, useSetAtom } from 'jotai';
|
||||
import {
|
||||
type KeyboardEvent,
|
||||
forwardRef,
|
||||
type HTMLAttributes,
|
||||
type MouseEvent,
|
||||
useCallback,
|
||||
useMemo,
|
||||
@@ -20,7 +22,6 @@ import {
|
||||
import { useDatasourceSync } from '../../../../hooks/use-datasource-sync';
|
||||
import { useSystemOnline } from '../../../../hooks/use-system-online';
|
||||
import type { AllWorkspace } from '../../../../shared';
|
||||
import { workspaceAvatarStyle } from './index.css';
|
||||
import { Loading } from './loading-icon';
|
||||
import {
|
||||
StyledSelectorContainer,
|
||||
@@ -29,13 +30,10 @@ import {
|
||||
StyledWorkspaceStatus,
|
||||
} from './styles';
|
||||
|
||||
export interface WorkspaceSelectorProps {
|
||||
currentWorkspace: AllWorkspace;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const hoverAtom = atom(false);
|
||||
|
||||
// FIXME:
|
||||
// 1. Remove mui style
|
||||
// 2. Refactor the code to improve readability
|
||||
const CloudWorkspaceStatus = () => {
|
||||
return (
|
||||
<>
|
||||
@@ -161,47 +159,37 @@ const WorkspaceStatus = ({
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @todo-Doma Co-locate WorkspaceListModal with {@link WorkspaceSelector},
|
||||
* because it's never used elsewhere.
|
||||
*/
|
||||
export const WorkspaceSelector = ({
|
||||
currentWorkspace,
|
||||
onClick,
|
||||
}: WorkspaceSelectorProps) => {
|
||||
export const WorkspaceCard = forwardRef<
|
||||
HTMLDivElement,
|
||||
{
|
||||
currentWorkspace: AllWorkspace;
|
||||
} & HTMLAttributes<HTMLDivElement>
|
||||
>(({ currentWorkspace, ...props }, ref) => {
|
||||
const [name] = useBlockSuiteWorkspaceName(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
// Open dialog when `Enter` or `Space` pressed
|
||||
// TODO-Doma Refactor with `@radix-ui/react-dialog` or other libraries that handle these out of the box and be accessible by default
|
||||
// TODO: Delete this?
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
// TODO-Doma Rename this callback to `onOpenDialog` or something to reduce ambiguity.
|
||||
onClick();
|
||||
}
|
||||
},
|
||||
[onClick]
|
||||
const [workspaceAvatar] = useBlockSuiteWorkspaceAvatarUrl(
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
const isHovered = useAtomValue(hoverAtom);
|
||||
|
||||
return (
|
||||
<StyledSelectorContainer
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
disableHoverBackground={isHovered}
|
||||
data-testid="current-workspace"
|
||||
id="current-workspace"
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<WorkspaceAvatar
|
||||
<Avatar
|
||||
data-testid="workspace-avatar"
|
||||
className={workspaceAvatarStyle}
|
||||
size={40}
|
||||
workspace={currentWorkspace.blockSuiteWorkspace}
|
||||
url={workspaceAvatar}
|
||||
name={name}
|
||||
colorfulFallback
|
||||
style={{
|
||||
marginRight: '10px',
|
||||
}}
|
||||
/>
|
||||
<StyledSelectorWrapper>
|
||||
<StyledWorkspaceName data-testid="workspace-name">
|
||||
@@ -211,4 +199,6 @@ export const WorkspaceSelector = ({
|
||||
</StyledSelectorWrapper>
|
||||
</StyledSelectorContainer>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
WorkspaceCard.displayName = 'WorkspaceCard';
|
||||
@@ -1,22 +1,16 @@
|
||||
import { displayFlex, textEllipsis } from '@affine/component';
|
||||
import { styled } from '@affine/component';
|
||||
export const StyledSelectorContainer = styled('div')(({
|
||||
disableHoverBackground,
|
||||
}: {
|
||||
disableHoverBackground: boolean;
|
||||
}) => {
|
||||
return {
|
||||
height: '58px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 6px',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
background: disableHoverBackground ? '' : 'var(--affine-hover-color)',
|
||||
},
|
||||
};
|
||||
export const StyledSelectorContainer = styled('div')({
|
||||
height: '58px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 6px',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
background: 'var(--affine-hover-color)',
|
||||
},
|
||||
});
|
||||
|
||||
export const StyledSelectorWrapper = styled('div')(() => {
|
||||
@@ -20,10 +20,17 @@ import {
|
||||
} from '@blocksuite/icons';
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import { NoSsr } from '@mui/material';
|
||||
import { Popover } from '@toeverything/components/popover';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import type { HTMLAttributes, ReactElement } from 'react';
|
||||
import {
|
||||
forwardRef,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { useHistoryAtom } from '../../atoms/history';
|
||||
import { useAppSetting } from '../../atoms/settings';
|
||||
@@ -33,14 +40,14 @@ import { CollectionsList } from '../pure/workspace-slider-bar/collections';
|
||||
import { AddCollectionButton } from '../pure/workspace-slider-bar/collections/add-collection-button';
|
||||
import { AddFavouriteButton } from '../pure/workspace-slider-bar/favorite/add-favourite-button';
|
||||
import FavoriteList from '../pure/workspace-slider-bar/favorite/favorite-list';
|
||||
import { WorkspaceSelector } from '../pure/workspace-slider-bar/WorkspaceSelector';
|
||||
import { UserWithWorkspaceList } from '../pure/workspace-slider-bar/user-with-workspace-list';
|
||||
import { WorkspaceCard } from '../pure/workspace-slider-bar/workspace-card';
|
||||
import ImportPage from './import-page';
|
||||
|
||||
export type RootAppSidebarProps = {
|
||||
isPublicWorkspace: boolean;
|
||||
onOpenQuickSearchModal: () => void;
|
||||
onOpenSettingModal: () => void;
|
||||
onOpenWorkspaceListModal: () => void;
|
||||
currentWorkspace: AllWorkspace;
|
||||
openPage: (pageId: string) => void;
|
||||
createPage: () => Page;
|
||||
@@ -52,7 +59,7 @@ export type RootAppSidebarProps = {
|
||||
};
|
||||
};
|
||||
|
||||
const RouteMenuLinkItem = React.forwardRef<
|
||||
const RouteMenuLinkItem = forwardRef<
|
||||
HTMLDivElement,
|
||||
{
|
||||
currentPath: string; // todo: pass through useRouter?
|
||||
@@ -60,7 +67,7 @@ const RouteMenuLinkItem = React.forwardRef<
|
||||
icon: ReactElement;
|
||||
children?: ReactElement;
|
||||
isDraggedOver?: boolean;
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
} & HTMLAttributes<HTMLDivElement>
|
||||
>(({ currentPath, path, icon, children, isDraggedOver, ...props }, ref) => {
|
||||
// Force active style when a page is dragged over
|
||||
const active = isDraggedOver || currentPath === path;
|
||||
@@ -94,7 +101,6 @@ export const RootAppSidebar = ({
|
||||
currentPath,
|
||||
paths,
|
||||
onOpenQuickSearchModal,
|
||||
onOpenWorkspaceListModal,
|
||||
onOpenSettingModal,
|
||||
}: RootAppSidebarProps): ReactElement => {
|
||||
const currentWorkspaceId = currentWorkspace.id;
|
||||
@@ -102,6 +108,7 @@ export const RootAppSidebar = ({
|
||||
const { backToAll } = useCollectionManager(currentCollectionsAtom);
|
||||
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
|
||||
const t = useAFFiNEI18N();
|
||||
const [openUserWorkspaceList, setOpenUserWorkspaceList] = useState(false);
|
||||
const onClickNewPage = useCallback(async () => {
|
||||
const page = createPage();
|
||||
await page.waitForLoaded();
|
||||
@@ -152,6 +159,9 @@ export const RootAppSidebar = ({
|
||||
const trashDroppable = useDroppable({
|
||||
id: DROPPABLE_SIDEBAR_TRASH,
|
||||
});
|
||||
const closeUserWorkspaceList = useCallback(() => {
|
||||
setOpenUserWorkspaceList(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -166,12 +176,27 @@ export const RootAppSidebar = ({
|
||||
}
|
||||
>
|
||||
<SidebarContainer>
|
||||
<NoSsr>
|
||||
<WorkspaceSelector
|
||||
<Popover
|
||||
open={openUserWorkspaceList}
|
||||
content={
|
||||
<Suspense>
|
||||
<UserWithWorkspaceList onEventEnd={closeUserWorkspaceList} />
|
||||
</Suspense>
|
||||
}
|
||||
contentOptions={{
|
||||
// hide trigger
|
||||
sideOffset: -58,
|
||||
onInteractOutside: closeUserWorkspaceList,
|
||||
onEscapeKeyDown: closeUserWorkspaceList,
|
||||
}}
|
||||
>
|
||||
<WorkspaceCard
|
||||
currentWorkspace={currentWorkspace}
|
||||
onClick={onOpenWorkspaceListModal}
|
||||
onClick={useCallback(() => {
|
||||
setOpenUserWorkspaceList(true);
|
||||
}, [])}
|
||||
/>
|
||||
</NoSsr>
|
||||
</Popover>
|
||||
<QuickSearchInput
|
||||
data-testid="slider-bar-quick-search-button"
|
||||
onClick={onOpenQuickSearchModal}
|
||||
|
||||
@@ -36,11 +36,7 @@ import { lazy, Suspense, useCallback, useEffect } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Map as YMap } from 'yjs';
|
||||
|
||||
import {
|
||||
openQuickSearchModalAtom,
|
||||
openSettingModalAtom,
|
||||
openWorkspacesModalAtom,
|
||||
} from '../atoms';
|
||||
import { openQuickSearchModalAtom, openSettingModalAtom } from '../atoms';
|
||||
import { mainContainerAtom } from '../atoms/element';
|
||||
import { useAppSetting } from '../atoms/settings';
|
||||
import { AdapterProviderWrapper } from '../components/adapter-worksapce-wrapper';
|
||||
@@ -167,7 +163,6 @@ export const WorkspaceLayoutInner = ({
|
||||
|
||||
usePassiveWorkspaceEffect(currentWorkspace.blockSuiteWorkspace);
|
||||
|
||||
const [, setOpenWorkspacesModal] = useAtom(openWorkspacesModalAtom);
|
||||
const helper = usePageHelper(currentWorkspace.blockSuiteWorkspace);
|
||||
|
||||
const handleCreatePage = useCallback(() => {
|
||||
@@ -178,10 +173,6 @@ export const WorkspaceLayoutInner = ({
|
||||
return page;
|
||||
}, [currentWorkspace.blockSuiteWorkspace, helper]);
|
||||
|
||||
const handleOpenWorkspaceListModal = useCallback(() => {
|
||||
setOpenWorkspacesModal(true);
|
||||
}, [setOpenWorkspacesModal]);
|
||||
|
||||
const [, setOpenQuickSearchModalAtom] = useAtom(openQuickSearchModalAtom);
|
||||
const handleOpenQuickSearchModal = useCallback(() => {
|
||||
setOpenQuickSearchModalAtom(true);
|
||||
@@ -255,7 +246,6 @@ export const WorkspaceLayoutInner = ({
|
||||
onOpenQuickSearchModal={handleOpenQuickSearchModal}
|
||||
onOpenSettingModal={handleOpenSettingModal}
|
||||
currentWorkspace={currentWorkspace}
|
||||
onOpenWorkspaceListModal={handleOpenWorkspaceListModal}
|
||||
openPage={useCallback(
|
||||
(pageId: string) => {
|
||||
assertExists(currentWorkspace);
|
||||
|
||||
@@ -7,6 +7,8 @@ import { lazy } from 'react';
|
||||
import type { LoaderFunction } from 'react-router-dom';
|
||||
import { redirect } from 'react-router-dom';
|
||||
|
||||
import { UserWithWorkspaceList } from '../components/pure/workspace-slider-bar/user-with-workspace-list';
|
||||
|
||||
const AllWorkspaceModals = lazy(() =>
|
||||
import('../providers/modal-provider').then(({ AllWorkspaceModals }) => ({
|
||||
default: AllWorkspaceModals,
|
||||
@@ -54,5 +56,22 @@ export const loader: LoaderFunction = async () => {
|
||||
};
|
||||
|
||||
export const Component = () => {
|
||||
return <AllWorkspaceModals />;
|
||||
// TODO: We need a no workspace page
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: 300,
|
||||
margin: '80px auto',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'var(--affine-shadow-2)',
|
||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||
padding: '16px 12px',
|
||||
}}
|
||||
>
|
||||
<UserWithWorkspaceList />
|
||||
</div>
|
||||
<AllWorkspaceModals />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import { WorkspaceSubPath } from '@affine/env/workspace';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { arrayMove } from '@dnd-kit/sortable';
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
} from '@toeverything/infra/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { ReactElement } from 'react';
|
||||
import {
|
||||
lazy,
|
||||
startTransition,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useTransition,
|
||||
} from 'react';
|
||||
import { lazy, Suspense, useCallback } from 'react';
|
||||
|
||||
import type { SettingAtom } from '../atoms';
|
||||
import {
|
||||
@@ -22,7 +10,6 @@ import {
|
||||
openCreateWorkspaceModalAtom,
|
||||
openDisableCloudAlertModalAtom,
|
||||
openSettingModalAtom,
|
||||
openWorkspacesModalAtom,
|
||||
} from '../atoms';
|
||||
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
|
||||
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
@@ -38,12 +25,6 @@ const Auth = lazy(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
const WorkspaceListModal = lazy(() =>
|
||||
import('../components/pure/workspace-list-modal').then(module => ({
|
||||
default: module.WorkspaceListModal,
|
||||
}))
|
||||
);
|
||||
|
||||
const CreateWorkspaceModal = lazy(() =>
|
||||
import('../components/affine/create-workspace-modal').then(module => ({
|
||||
default: module.CreateWorkspaceModal,
|
||||
@@ -161,90 +142,14 @@ export function CurrentWorkspaceModals() {
|
||||
}
|
||||
|
||||
export const AllWorkspaceModals = (): ReactElement => {
|
||||
const [openWorkspacesModal, setOpenWorkspacesModal] = useAtom(
|
||||
openWorkspacesModalAtom
|
||||
);
|
||||
const [isOpenCreateWorkspaceModal, setOpenCreateWorkspaceModal] = useAtom(
|
||||
openCreateWorkspaceModalAtom
|
||||
);
|
||||
|
||||
const { jumpToSubPath } = useNavigateHelper();
|
||||
const workspaces = useAtomValue(rootWorkspacesMetadataAtom, {
|
||||
delay: 0,
|
||||
});
|
||||
const setWorkspaces = useSetAtom(rootWorkspacesMetadataAtom);
|
||||
const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom(
|
||||
currentWorkspaceIdAtom
|
||||
);
|
||||
const setCurrentPageId = useSetAtom(currentPageIdAtom);
|
||||
const [, startCloseTransition] = useTransition();
|
||||
const [, setOpenSettingModalAtom] = useAtom(openSettingModalAtom);
|
||||
|
||||
const handleOpenSettingModal = useCallback(
|
||||
(workspaceId: string) => {
|
||||
setOpenWorkspacesModal(false);
|
||||
|
||||
setOpenSettingModalAtom({
|
||||
open: true,
|
||||
activeTab: 'workspace',
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
[setOpenSettingModalAtom, setOpenWorkspacesModal]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Suspense>
|
||||
<WorkspaceListModal
|
||||
workspaces={workspaces}
|
||||
currentWorkspaceId={currentWorkspaceId}
|
||||
open={
|
||||
(openWorkspacesModal || workspaces.length === 0) &&
|
||||
isOpenCreateWorkspaceModal === false
|
||||
}
|
||||
onClose={useCallback(() => {
|
||||
startCloseTransition(() => {
|
||||
setOpenWorkspacesModal(false);
|
||||
});
|
||||
}, [setOpenWorkspacesModal])}
|
||||
onMoveWorkspace={useCallback(
|
||||
(activeId, overId) => {
|
||||
const oldIndex = workspaces.findIndex(w => w.id === activeId);
|
||||
const newIndex = workspaces.findIndex(w => w.id === overId);
|
||||
startTransition(() => {
|
||||
setWorkspaces(workspaces =>
|
||||
arrayMove(workspaces, oldIndex, newIndex)
|
||||
);
|
||||
});
|
||||
},
|
||||
[setWorkspaces, workspaces]
|
||||
)}
|
||||
onClickWorkspace={useCallback(
|
||||
workspaceId => {
|
||||
startCloseTransition(() => {
|
||||
setOpenWorkspacesModal(false);
|
||||
setCurrentWorkspaceId(workspaceId);
|
||||
setCurrentPageId(null);
|
||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||
});
|
||||
},
|
||||
[
|
||||
jumpToSubPath,
|
||||
setCurrentPageId,
|
||||
setCurrentWorkspaceId,
|
||||
setOpenWorkspacesModal,
|
||||
]
|
||||
)}
|
||||
onClickWorkspaceSetting={handleOpenSettingModal}
|
||||
onNewWorkspace={useCallback(() => {
|
||||
setOpenCreateWorkspaceModal('new');
|
||||
}, [setOpenCreateWorkspaceModal])}
|
||||
onAddWorkspace={useCallback(async () => {
|
||||
setOpenCreateWorkspaceModal('add');
|
||||
}, [setOpenCreateWorkspaceModal])}
|
||||
/>
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<CreateWorkspaceModal
|
||||
mode={isOpenCreateWorkspaceModal}
|
||||
@@ -254,14 +159,13 @@ export const AllWorkspaceModals = (): ReactElement => {
|
||||
onCreate={useCallback(
|
||||
id => {
|
||||
setOpenCreateWorkspaceModal(false);
|
||||
setOpenWorkspacesModal(false);
|
||||
// if jumping immediately, the page may stuck in loading state
|
||||
// not sure why yet .. here is a workaround
|
||||
setTimeout(() => {
|
||||
jumpToSubPath(id, WorkspaceSubPath.ALL);
|
||||
});
|
||||
},
|
||||
[jumpToSubPath, setOpenCreateWorkspaceModal, setOpenWorkspacesModal]
|
||||
[jumpToSubPath, setOpenCreateWorkspaceModal]
|
||||
)}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
Reference in New Issue
Block a user