Merge remote-tracking branch 'refs/remotes/origin/feat/datacenter-dev'

Conflicts:
	packages/app/src/components/create-workspace/index.tsx
	packages/data-center/package.json
	packages/data-center/src/index.ts
	pnpm-lock.yaml
This commit is contained in:
linonetwo
2023-01-12 17:11:27 +08:00
76 changed files with 1477 additions and 3796 deletions
+2
View File
@@ -15,6 +15,8 @@ const profileTarget = {
local: '127.0.0.1:3000',
};
// 100.77.180.48:11001
const getRedirectConfig = profile => {
const target = profileTarget[profile || 'dev'] || profileTarget['dev'];
+3 -3
View File
@@ -11,10 +11,10 @@
"dependencies": {
"@affine/datacenter": "workspace:*",
"@affine/i18n": "workspace:*",
"@blocksuite/blocks": "0.4.0-20230110112105-ef07332",
"@blocksuite/editor": "0.4.0-20230110112105-ef07332",
"@blocksuite/blocks": "0.3.1-20230109032243-37ad3ba",
"@blocksuite/editor": "0.3.1-20230109032243-37ad3ba",
"@blocksuite/icons": "^2.0.2",
"@blocksuite/store": "0.4.0-20230110112105-ef07332",
"@blocksuite/store": "0.3.1-20230109032243-37ad3ba",
"@emotion/css": "^11.10.0",
"@emotion/react": "^11.10.4",
"@emotion/server": "^11.10.0",
@@ -1,5 +1,4 @@
import { styled } from '@/styles';
import { useRouter } from 'next/router';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import { useState } from 'react';
@@ -7,6 +6,9 @@ import Input from '@/ui/input';
import { KeyboardEvent } from 'react';
import { useTranslation } from '@affine/i18n';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { useRouter } from 'next/router';
import { toast } from '@/ui/toast';
interface ModalProps {
open: boolean;
onClose: () => void;
@@ -18,11 +20,11 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
const router = useRouter();
const handleCreateWorkspace = async () => {
const workspace = await createWorkspace(workspaceName);
if (workspace && workspace.room) {
router.replace(`/workspace/${workspace.room}`);
if (workspace && workspace.id) {
router.replace(`/workspace/${workspace.id}`);
onClose();
} else {
console.log('create error');
toast('create error');
}
};
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
@@ -31,7 +31,6 @@ export const Editor = ({ page, workspace, setEditor }: Props) => {
const editor = new EditorContainer();
editor.page = page;
editorContainer.current?.appendChild(editor);
if (page.isEmpty) {
const isFirstPage = workspace?.meta.pageMetas.length === 1;
@@ -4,6 +4,20 @@ import { Tooltip } from '@/ui/tooltip';
import { ArrowDownIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/GlobalModalProvider';
import { useTranslation } from '@affine/i18n';
import { styled } from '@/styles';
const StyledIconButtonWithAnimate = styled(IconButton)(() => {
return {
svg: {
transition: 'transform 0.15s ease-in-out',
},
':hover': {
svg: {
transform: 'translateY(3px)',
},
},
};
});
export const QuickSearchButton = ({
onClick,
...props
@@ -12,7 +26,7 @@ export const QuickSearchButton = ({
const { t } = useTranslation();
return (
<Tooltip content={t('Switch to')} placement="bottom">
<IconButton
<StyledIconButtonWithAnimate
data-testid="header-quickSearchButton"
{...props}
onClick={e => {
@@ -21,7 +35,7 @@ export const QuickSearchButton = ({
}}
>
<ArrowDownIcon />
</IconButton>
</StyledIconButtonWithAnimate>
</Tooltip>
);
};
@@ -8,7 +8,7 @@ import { useTranslation } from '@affine/i18n';
export const TrashButtonGroup = () => {
const { permanentlyDeletePage } = usePageHelper();
const { currentWorkspaceId } = useAppState();
const { currentWorkspace } = useAppState();
const { toggleDeletePage } = usePageHelper();
const { confirm } = useConfirm();
const router = useRouter();
@@ -38,7 +38,7 @@ export const TrashButtonGroup = () => {
confirmType: 'danger',
}).then(confirm => {
if (confirm) {
router.push(`/workspace/${currentWorkspaceId}/all`);
router.push(`/workspace/${currentWorkspace?.id}/all`);
permanentlyDeletePage(id);
}
});
+2 -2
View File
@@ -22,11 +22,11 @@ export const ImportModal = ({ open, onClose }: ImportModalProps) => {
const { currentWorkspace } = useAppState();
const { t } = useTranslation();
const _applyTemplate = function (pageId: string, template: Template) {
const page = currentWorkspace?.getPage(pageId);
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId);
const title = template.name;
if (page) {
currentWorkspace?.setPageMeta(page.id, { title });
currentWorkspace?.blocksuiteWorkspace?.setPageMeta(page.id, { title });
if (page && page.root === null) {
setTimeout(() => {
const editor = document.querySelector('editor-container');
+13 -10
View File
@@ -1,19 +1,22 @@
import { styled } from '@/styles';
// Inspired by https://codepen.io/graphilla/pen/rNvBMYY
export const StyledLoadingWrapper = styled.div<{ size?: number }>(
({ size = 40 }) => {
return {
width: size * 4,
height: size * 4,
position: 'relative',
};
}
);
export const StyledLoadingWrapper = styled('div', {
shouldForwardProp: prop => {
return !['size'].includes(prop);
},
})<{ size?: number }>(({ size = 40 }) => {
return {
width: size * 4,
height: size * 4,
position: 'relative',
};
});
export const StyledLoading = styled.div`
position: absolute;
left: 25%;
top: 25%;
top: 50%;
transform: rotateX(55deg) rotateZ(-45deg);
@keyframes slide {
0% {
transform: translate(var(--sx), var(--sy));
+28 -18
View File
@@ -51,7 +51,7 @@ const FavoriteTag = ({
style={{
color: favorite ? theme.colors.primaryColor : theme.colors.iconColor,
}}
className="favorite-button"
className={favorite ? '' : 'favorite-button'}
>
{favorite ? (
<FavouritedIcon data-testid="favourited-icon" />
@@ -67,13 +67,15 @@ export const PageList = ({
pageList,
showFavoriteTag = false,
isTrash = false,
isPublic = false,
}: {
pageList: PageMeta[];
showFavoriteTag?: boolean;
isTrash?: boolean;
isPublic?: boolean;
}) => {
const router = useRouter();
const { currentWorkspaceId } = useAppState();
const { currentWorkspace } = useAppState();
const { t } = useTranslation();
if (pageList.length === 0) {
return <Empty />;
@@ -98,9 +100,15 @@ export const PageList = ({
<StyledTableRow
key={`${pageMeta.id}-${index}`}
onClick={() => {
router.push(
`/workspace/${currentWorkspaceId}/${pageMeta.id}`
);
if (isPublic) {
router.push(
`/public-workspace/${router.query.workspaceId}/${pageMeta.id}`
);
} else {
router.push(
`/workspace/${currentWorkspace?.id}/${pageMeta.id}`
);
}
}}
>
<TableCell>
@@ -124,19 +132,21 @@ export const PageList = ({
dateKey={isTrash ? 'trashDate' : 'updatedDate'}
backupKey={isTrash ? 'trashDate' : 'createDate'}
/>
<TableCell
style={{ padding: 0 }}
data-testid={`more-actions-${pageMeta.id}`}
onClick={e => {
e.stopPropagation();
}}
>
{isTrash ? (
<TrashOperationCell pageMeta={pageMeta} />
) : (
<OperationCell pageMeta={pageMeta} />
)}
</TableCell>
{!isPublic ? (
<TableCell
style={{ padding: 0 }}
data-testid={`more-actions-${pageMeta.id}`}
onClick={e => {
e.stopPropagation();
}}
>
{isTrash ? (
<TrashOperationCell pageMeta={pageMeta} />
) : (
<OperationCell pageMeta={pageMeta} />
)}
</TableCell>
) : null}
</StyledTableRow>
);
})}
@@ -17,10 +17,8 @@ export const Input = (props: {
const [isComposition, setIsComposition] = useState(false);
const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
const { currentWorkspaceId, workspaceList, currentWorkspace } = useAppState();
const isPublish = workspaceList.find(
meta => String(meta.id) === String(currentWorkspaceId)
)?.isPublish;
const { currentWorkspace } = useAppState();
useEffect(() => {
inputRef.current?.addEventListener(
'blur',
@@ -79,8 +77,8 @@ export const Input = (props: {
}
}}
placeholder={
isPublish
? `Search in ${currentWorkspace?.meta.name}`
currentWorkspace?.isPublish
? `Search in ${currentWorkspace?.blocksuiteWorkspace?.meta.name}`
: 'Quick Search...'
}
/>
@@ -9,7 +9,6 @@ import { useSwitchToConfig } from './config';
import { NoResultSVG } from './NoResultSVG';
import { useTranslation } from '@affine/i18n';
import usePageHelper from '@/hooks/use-page-helper';
import usePageMetaList from '@/hooks/use-page-meta-list';
export const Results = (props: {
query: string;
loading: boolean;
@@ -21,12 +20,11 @@ export const Results = (props: {
const setLoading = props.setLoading;
const setShowCreatePage = props.setShowCreatePage;
const { triggerQuickSearchModal } = useModal();
const pageMetaList = usePageMetaList();
const { openPage } = usePageHelper();
const router = useRouter();
const { currentWorkspaceId } = useAppState();
const { currentWorkspace, pageList } = useAppState();
const { search } = usePageHelper();
const List = useSwitchToConfig(currentWorkspaceId);
const List = useSwitchToConfig(currentWorkspace?.id);
const [results, setResults] = useState(new Map<string, string | undefined>());
const { t } = useTranslation();
useEffect(() => {
@@ -37,12 +35,12 @@ export const Results = (props: {
}, [query, setResults, setLoading]);
const pageIds = [...results.values()];
const resultsPageMeta = pageMetaList.filter(
const resultsPageMeta = pageList.filter(
page => pageIds.indexOf(page.id) > -1 && !page.trash
);
useEffect(() => {
setShowCreatePage(resultsPageMeta.length ? false : true);
setShowCreatePage(!resultsPageMeta.length);
//Determine whether to display the + New page
}, [resultsPageMeta, setShowCreatePage]);
return loading ? null : (
@@ -1,15 +1,16 @@
import { FC, SVGProps } from 'react';
import { AllPagesIcon, FavouritesIcon, TrashIcon } from '@blocksuite/icons';
import { useTranslation } from '@affine/i18n';
export const useSwitchToConfig = (
currentWorkspaceId: string
currentWorkspaceId?: string
): {
title: string;
href: string;
icon: React.FC<React.SVGProps<SVGSVGElement>>;
icon: FC<SVGProps<SVGSVGElement>>;
}[] => {
const { t } = useTranslation();
const List = [
return [
{
title: t('All pages'),
href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
@@ -28,5 +29,4 @@ export const useSwitchToConfig = (
icon: TrashIcon,
},
];
return List;
};
@@ -22,15 +22,13 @@ const isMac = () => {
return getUaHelper().isMacOs;
};
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
const { currentMetaWorkSpace } = useAppState();
const { currentWorkspace } = useAppState();
const [query, setQuery] = useState('');
const [loading, setLoading] = useState(true);
const [showCreatePage, setShowCreatePage] = useState(true);
const { triggerQuickSearchModal } = useModal();
const isPublish = currentMetaWorkSpace?.isPublish;
// Add ‘⌘+K shortcut keys as switches
useEffect(() => {
const down = (e: KeyboardEvent) => {
@@ -96,7 +94,7 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
setShowCreatePage={setShowCreatePage}
/>
</StyledContent>
{isPublish ? (
{currentWorkspace?.isPublish ? (
<></>
) : showCreatePage ? (
<>
@@ -1,7 +1,4 @@
import { useAppState } from '@/providers/app-state-provider';
import { stringToColour } from '@/utils';
import { useEffect, useState } from 'react';
interface IWorkspaceAvatar {
size: number;
name: string;
@@ -25,7 +22,9 @@ export const WorkspaceAvatar = (props: IWorkspaceAvatar) => {
overflow: 'hidden',
}}
>
<img src={props.avatar} alt="" />
<picture>
<img src={props.avatar} alt="" />
</picture>
</div>
) : (
<div
@@ -2,7 +2,6 @@ import { styled } from '@/styles';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import { useState } from 'react';
import { SignOut } from '@/hooks/mock-data/mock';
import { CreateWorkspaceModal } from '../create-workspace';
import {
CloudUnsyncedIcon,
@@ -10,12 +9,11 @@ import {
UsersIcon,
AddIcon,
} from '@blocksuite/icons';
// import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast';
import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
import { useUserHelper } from '@/hooks/use-user-helper';
import { useConfirm } from '@/providers/ConfirmProvider';
interface WorkspaceModalProps {
open: boolean;
@@ -24,9 +22,9 @@ interface WorkspaceModalProps {
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
// const { confirm } = useConfirm();
const { workspaceList, currentWorkspace } = useAppState();
const { login, user } = useUserHelper();
const { confirm } = useConfirm();
const { workspaceList, currentWorkspace, login, user, logout } =
useAppState();
const router = useRouter();
return (
<div>
@@ -55,7 +53,7 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
router.replace(`/workspace/${item.id}`);
onClose();
}}
active={item.id === currentWorkspace.room}
active={item.id === currentWorkspace?.id}
key={index}
>
<span style={{ width: '100px' }}>
@@ -101,7 +99,7 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
{item.provider === 'affine' && (
<CloudInsyncIcon fontSize={24} />
)}
{item.isPublish && <UsersIcon fontSize={24} />}
{item.published && <UsersIcon fontSize={24} />}
</span>
{/* {item.isLocal ? 'isLocal' : ''}/ */}
</WorkspaceItem>
@@ -137,9 +135,9 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
<Footer>
{!user ? (
<Button
onClick={() => {
login();
toast('login success');
onClick={async () => {
await login();
toast('Login success');
}}
>
Sign in AFFiNE Cloud
@@ -147,7 +145,20 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
) : (
<Button
onClick={() => {
SignOut();
confirm({
title: 'Sign out?',
content: `All data has been stored in the cloud. `,
confirmText: 'Sign out',
cancelText: 'Cancel',
}).then(async confirm => {
if (confirm) {
if (user) {
await logout();
router.replace(`/workspace`);
toast('Enabled success');
}
}
});
}}
>
Sign out of AFFiNE Cloud
@@ -1,5 +1,5 @@
import { styled } from '@/styles';
import { WorkspaceInfo } from '@affine/datacenter';
import { WorkspaceUnit } from '@affine/datacenter';
export const ExportPageTitleContainer = styled('div')(() => {
return {
@@ -9,7 +9,7 @@ export const ExportPageTitleContainer = styled('div')(() => {
flex: 1,
};
});
export const ExportPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
export const ExportPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
return (
<ExportPageTitleContainer>
Export Workspace{' '}
@@ -1,207 +0,0 @@
import {
StyledMemberAvatar,
StyledMemberButtonContainer,
StyledMemberEmail,
StyledMemberInfo,
StyledMemberListContainer,
StyledMemberListItem,
StyledMemberName,
StyledMemberNameContainer,
StyledMemberRoleContainer,
StyledMemberTitleContainer,
StyledMoreVerticalButton,
StyledPublishExplanation,
} from './style';
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
import { useEffect, useState } from 'react';
import { Button, IconButton } from '@/ui/button';
import { InviteMembers } from '../invite-members/index';
import { Menu, MenuItem } from '@/ui/menu';
import { Empty } from '@/ui/empty';
// import {
// deleteMember,
// getMembers,
// User,
// Workspace,
// } from '@/hooks/mock-data/mock';
import { WorkspaceInfo } from '@affine/datacenter';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { StyledMemberWarp } from './general/style';
import { useConfirm } from '@/providers/ConfirmProvider';
// import { useAppState } from '@/providers/app-state-provider';
export const MembersPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
const [members, setMembers] = useState<[{ name: string; email: string }]>([
{ name: 'affine', email: 'tttt' },
]);
console.log('setMembers: ', setMembers);
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
const { confirm } = useConfirm();
// const refreshMembers = useCallback(() => {
// getDataCenter()
// .then(dc =>
// dc.apis.getWorkspaceMembers({
// id: workspace.id,
// })
// )
// .then(data => {
// setMembers(data);
// })
// .catch(err => {
// console.log(err);
// });
// }, [workspace.id]);
const setMembersList = () => {
// setMembers([]);
// const members = getMembers(workspace.id);
// members && setMembers(members);
};
useEffect(() => {
setMembersList();
// refreshMembers();
});
return (
<div>
{workspace.provider === 'cloud' ? (
<>
<StyledMemberTitleContainer>
<StyledMemberNameContainer>
Users({members.length})
</StyledMemberNameContainer>
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
</StyledMemberTitleContainer>
<StyledMemberListContainer>
{members.length ? (
members.map((member, index) => {
return (
<StyledMemberListItem key={index}>
<StyledMemberNameContainer>
<StyledMemberAvatar alt="member avatar">
<EmailIcon></EmailIcon>
</StyledMemberAvatar>
<StyledMemberInfo>
<StyledMemberName>{member.name}</StyledMemberName>
<StyledMemberEmail>{member.email}</StyledMemberEmail>
</StyledMemberInfo>
</StyledMemberNameContainer>
<StyledMemberRoleContainer>
{/* {member.accepted
? member.type !== 99
? 'Member'
: 'Workspace Owner'
: 'Pending'} */}
Pending
</StyledMemberRoleContainer>
<StyledMoreVerticalButton>
<Menu
content={
<>
<MenuItem
onClick={() => {
// deleteMember(workspace.id, 0);
setMembersList();
// confirm({
// title: 'Delete Member?',
// content: `will delete member`,
// confirmText: 'Delete',
// confirmType: 'danger',
// }).then(confirm => {
// getDataCenter()
// .then(dc =>
// dc.apis.removeMember({
// permissionId: member.id,
// })
// )
// .then(() => {
// // console.log('data: ', data);
// toast('Moved to Trash');
// // refreshMembers();
// });
// });
}}
icon={<TrashIcon />}
>
Delete
</MenuItem>
</>
}
placement="bottom-end"
disablePortal={true}
>
<IconButton>
<MoreVerticalIcon />
</IconButton>
</Menu>
</StyledMoreVerticalButton>
</StyledMemberListItem>
);
})
) : (
<Empty
width={648}
sx={{ marginTop: '60px' }}
height={300}
></Empty>
)}
</StyledMemberListContainer>
<StyledMemberButtonContainer>
<Button
onClick={() => {
setIsInviteModalShow(true);
}}
type="primary"
shape="circle"
>
Invite Members
</Button>
<InviteMembers
onClose={() => {
setIsInviteModalShow(false);
}}
onInviteSuccess={() => {
setMembersList();
setIsInviteModalShow(false);
// refreshMembers();
}}
workspaceId={workspace.id}
open={isInviteModalShow}
></InviteMembers>
</StyledMemberButtonContainer>
</>
) : (
<StyledMemberWarp>
<>Collaborating with other members requires AFFiNE Cloud service.</>
<StyledPublishExplanation>
<Button
type="primary"
shape="circle"
onClick={() => {
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText: user ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(confirm => {
if (confirm) {
if (user) {
updateWorkspaceMeta(workspace.id, { isPublish: true });
} else {
login();
updateWorkspaceMeta(workspace.id, { isPublish: true });
}
}
});
}}
>
Enable AFFiNE Cloud
</Button>
</StyledPublishExplanation>
</StyledMemberWarp>
)}
</div>
);
};
@@ -9,20 +9,16 @@ import {
import { Button } from '@/ui/button';
import Input from '@/ui/input';
import { toast } from '@/ui/toast';
import { useConfirm } from '@/providers/ConfirmProvider';
// import { useAppState } from '@/providers/app-state-provider3';
import { WorkspaceUnit } from '@affine/datacenter';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { WorkspaceInfo } from '@affine/datacenter';
export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
const shareUrl =
window.location.host + '/workspace/' + workspace.id + '?share=true';
const { publishWorkspace } = useWorkspaceHelper();
export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const shareUrl = window.location.host + '/public-workspace/' + workspace.id;
const { publishWorkspace, enableWorkspace } = useWorkspaceHelper();
const { confirm } = useConfirm();
const togglePublic = (flag: boolean) => {
workspace.id && publishWorkspace(workspace?.id, flag);
const togglePublic = async (flag: boolean) => {
await publishWorkspace(workspace.id.toString(), flag);
};
const copyUrl = () => {
@@ -30,30 +26,15 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
toast('Copied url to clipboard');
};
const enableAffineCloud = () => {
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText:
workspace.provider === 'local' ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(confirm => {
if (confirm) {
// if (user) {
// updateWorkspaceMeta(workspace.id, { type: 'cloud' });
// } else {
// login();
// updateWorkspaceMeta(workspace.id, { type: 'cloud' });
// }
}
});
const enableAffineCloud = async () => {
await enableWorkspace();
};
return (
<>
{workspace.provider === 'cloud' ? (
{workspace.provider === 'affine' ? (
<div>
<StyledPublishContent>
{workspace?.isPublish ? (
{workspace?.published ? (
<>
<StyledPublishExplanation>
Publishing to web requires AFFiNE Cloud service .
@@ -75,7 +56,7 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
</StyledPublishExplanation>
)}
</StyledPublishContent>
{workspace.isPublish ? (
{workspace.published ? (
<Button
onClick={() => {
togglePublic(false);
@@ -6,20 +6,10 @@ import {
import { DownloadIcon } from '@blocksuite/icons';
import { Button } from '@/ui/button';
import { Menu, MenuItem } from '@/ui/menu';
import { WorkspaceInfo } from '@affine/datacenter';
import { WorkspaceUnit } from '@affine/datacenter';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { useAppState } from '@/providers/app-state-provider';
import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast';
import { useUserHelper } from '@/hooks/use-user-helper';
import { useRouter } from 'next/router';
export const SyncPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
// console.log('workspace: ', workspace);
export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const { enableWorkspace } = useWorkspaceHelper();
const { currentWorkspace } = useAppState();
const { confirm } = useConfirm();
const { user, login } = useUserHelper();
const router = useRouter();
return (
<div>
<StyledPublishContent>
@@ -33,22 +23,8 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
<StyledPublishCopyContainer>
<Button
onClick={() => {
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText: user ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(async confirm => {
if (confirm) {
// if (user) {
// await login();
// }
const id = await enableWorkspace(currentWorkspace);
router.push(`/workspace/${id}`);
toast('Enabled success');
}
});
onClick={async () => {
await enableWorkspace();
}}
type="primary"
shape="circle"
@@ -14,42 +14,29 @@ import { WorkspaceDelete } from './delete';
import { WorkspaceLeave } from './leave';
import { Upload } from '@/components/file-upload';
import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { WorkspaceInfo } from '@affine/datacenter';
import { WorkspaceUnit } from '@affine/datacenter';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const [showDelete, setShowDelete] = useState<boolean>(false);
const [showLeave, setShowLeave] = useState<boolean>(false);
const [uploading, setUploading] = useState<boolean>(false);
const [workspaceName, setWorkspaceName] = useState<string>(workspace.name);
const { currentWorkspace } = useAppState();
const { currentWorkspace, isOwner } = useAppState();
const { updateWorkspace } = useWorkspaceHelper();
const isOwner = true;
const handleChangeWorkSpaceName = (newName: string) => {
setWorkspaceName(newName);
};
const handleClickDelete = () => {
setShowDelete(true);
};
const handleCloseDelete = () => {
setShowDelete(false);
};
const handleClickLeave = () => {
setShowLeave(true);
};
const handleCloseLeave = () => {
setShowLeave(false);
};
const handleUpdateWorkspaceName = () => {
console.log('currentWorkspace: ', currentWorkspace);
updateWorkspace({ name: workspaceName }, currentWorkspace);
currentWorkspace &&
updateWorkspace({ name: workspaceName }, currentWorkspace);
};
const fileChange = async (file: File) => {
// console.log('file: ', file);
// setUploading(true);
setUploading(true);
const blob = new Blob([file], { type: file.type });
updateWorkspace({ avatarBlob: blob }, currentWorkspace);
currentWorkspace &&
(await updateWorkspace({ avatarBlob: blob }, currentWorkspace));
setUploading(false);
};
return workspace ? (
@@ -84,16 +71,19 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
placeholder="Workspace Name"
maxLength={14}
minLength={1}
disabled={!isOwner}
onChange={handleChangeWorkSpaceName}
></Input>
<TextButton
onClick={() => {
handleUpdateWorkspaceName();
}}
style={{ marginLeft: '0px' }}
>
</TextButton>
{isOwner ? (
<TextButton
onClick={() => {
handleUpdateWorkspaceName();
}}
style={{ marginLeft: '0px' }}
>
</TextButton>
) : null}
</StyledSettingInputContainer>
<StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2>
<StyledSettingInputContainer>
@@ -102,25 +92,39 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
<StyledDeleteButtonContainer>
{isOwner ? (
<>
<Button type="danger" shape="circle" onClick={handleClickDelete}>
<Button
type="danger"
shape="circle"
onClick={() => {
setShowDelete(true);
}}
>
Delete Workspace
</Button>
<WorkspaceDelete
open={showDelete}
onClose={handleCloseDelete}
onClose={() => {
setShowDelete(false);
}}
workspace={workspace}
/>
</>
) : (
<>
<Button type="danger" shape="circle" onClick={handleClickLeave}>
<Button
type="danger"
shape="circle"
onClick={() => {
setShowLeave(true);
}}
>
Leave Workspace
</Button>
<WorkspaceLeave
open={showLeave}
onClose={handleCloseLeave}
workspaceName={workspaceName}
workspaceId={workspace.id}
onClose={() => {
setShowLeave(false);
}}
/>
</>
)}
@@ -12,17 +12,14 @@ import { useState } from 'react';
import { ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import { useRouter } from 'next/router';
import {
deleteWorkspace,
getWorkspaces,
// Workspace,
} from '@/hooks/mock-data/mock';
import { WorkspaceInfo } from '@affine/datacenter';
import { WorkspaceUnit } from '@affine/datacenter';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
interface WorkspaceDeleteProps {
open: boolean;
onClose: () => void;
workspace: WorkspaceInfo;
workspace: WorkspaceUnit;
}
export const WorkspaceDelete = ({
@@ -32,23 +29,15 @@ export const WorkspaceDelete = ({
}: WorkspaceDeleteProps) => {
const [deleteStr, setDeleteStr] = useState<string>('');
const router = useRouter();
const { deleteWorkSpace } = useWorkspaceHelper();
const handlerInputChange = (workspaceName: string) => {
setDeleteStr(workspaceName);
};
const handleDelete = async () => {
// const dc = await getDataCenter();
// await dc.apis.deleteWorkspace({ id: workspaceId });
// router.push(`/workspace/${nextWorkSpaceId}`);
deleteWorkspace(workspace.id);
const workspaceList = getWorkspaces();
if (workspaceList.length) {
router.push(`/workspace/${workspaceList[0].id}`);
} else {
router.push(`/workspace`);
}
await deleteWorkSpace();
onClose();
router.push(`/workspace`);
};
return (
@@ -7,29 +7,19 @@ import {
} from './style';
import { ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
// import { getDataCenter } from '@affine/datacenter';
// import { useAppState } from '@/providers/app-state-provider';
interface WorkspaceDeleteProps {
open: boolean;
onClose: () => void;
workspaceName: string;
workspaceId: string;
}
export const WorkspaceLeave = ({
open,
onClose,
workspaceId,
}: WorkspaceDeleteProps) => {
console.log('workspaceId: ', workspaceId);
// const router = useRouter();
// const { refreshWorkspacesMeta } = useAppState();
export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
const { leaveWorkSpace } = useWorkspaceHelper();
const handleLeave = async () => {
// const dc = await getDataCenter();
// await dc.apis.leaveWorkspace({ id: workspaceId });
// // router.push(`/workspace/${nextWorkSpaceId}`);
// refreshWorkspacesMeta();
await leaveWorkSpace();
onClose();
};
@@ -26,11 +26,3 @@ export const StyledSettingAvatarContent = styled('div')(() => {
export const StyledSettingAvatar = styled(MuiAvatar)(() => {
return { height: '72px', width: '72px', marginRight: '24px' };
});
export const StyledMemberWarp = styled('div')(() => {
return {
display: 'flex',
flexDirection: 'column',
padding: '40px 0',
};
});
@@ -1,5 +1,5 @@
export * from './general';
export * from './ExportPage';
export * from './MembersPage';
export * from './member';
export * from './SyncPage';
export * from './PublishPage';
@@ -4,9 +4,9 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import Input from '@/ui/input';
import { useState } from 'react';
// import { getDataCenter } from '@affine/datacenter';
import { Avatar } from '@mui/material';
import { setMember } from '@/hooks/mock-data/mock';
import useMembers from '@/hooks/use-members';
import { User } from '@affine/datacenter';
interface LoginModalProps {
open: boolean;
onClose: () => void;
@@ -43,46 +43,27 @@ export const debounce = <T extends (...args: any) => any>(
};
const gmailReg = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@gmail\.com$/;
export const InviteMembers = ({
export const InviteMemberModal = ({
open,
onClose,
workspaceId,
onInviteSuccess,
}: LoginModalProps) => {
const [email, setEmail] = useState<string>('');
const [showMember, setShowMember] = useState<boolean>(false);
const [showTip, setShowTip] = useState<boolean>(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [userData, setUserData] = useState<any>({});
const [userData, setUserData] = useState<User | null>(null);
const { inviteMember, getUserByEmail } = useMembers();
const inputChange = (value: string) => {
setShowMember(true);
if (gmailReg.test(value)) {
setEmail(value);
setShowTip(false);
setUserData({
name: 'wxl',
avatar: 'https://avatars.githubusercontent.com/u/20501502?v=4',
email: value,
getUserByEmail(value).then(data => {
if (data?.name) {
setUserData(data);
setShowTip(false);
}
});
// debounce(
// () => {
// getDataCenter()
// .then(dc =>
// dc.apis.getUserByEmail({
// email: value,
// workspace_id: workspaceId,
// })
// )
// .then(data => {
// if (data?.name) {
// setUserData(data);
// setShowTip(false);
// }
// });
// },
// 300,
// true
// )();
} else {
setShowTip(true);
}
@@ -118,8 +99,8 @@ export const InviteMembers = ({
<NoFind>Non-Gmail is not supported</NoFind>
) : (
<Member>
{userData?.avatar_url ? (
<Avatar src={userData?.avatar_url}></Avatar>
{userData?.avatar ? (
<Avatar src={userData?.avatar}></Avatar>
) : (
<MemberIcon>
<EmailIcon></EmailIcon>
@@ -139,19 +120,9 @@ export const InviteMembers = ({
<Button
shape="circle"
type="primary"
onClick={() => {
setMember(workspaceId, userData);
onClick={async () => {
await inviteMember(email);
onInviteSuccess();
// getDataCenter()
// .then(dc => dc.apis.inviteMember({ id: workspaceId, email }))
// .then(() => {
// onClose();
// onInviteSuccess && onInviteSuccess();
// })
// .catch(err => {
// // toast('Invite failed');
// console.log(err);
// });
}}
>
Invite
@@ -0,0 +1,173 @@
import {
StyledMemberAvatar,
StyledMemberButtonContainer,
StyledMemberEmail,
StyledMemberInfo,
StyledMemberListContainer,
StyledMemberListItem,
StyledMemberName,
StyledMemberNameContainer,
StyledMemberRoleContainer,
StyledMemberTitleContainer,
StyledMoreVerticalButton,
StyledPublishExplanation,
StyledMemberWarp,
} from './style';
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
import { useState } from 'react';
import { Button, IconButton } from '@/ui/button';
import { InviteMemberModal } from './InviteMemberModal';
import { Menu, MenuItem } from '@/ui/menu';
import { Empty } from '@/ui/empty';
import { WorkspaceUnit } from '@affine/datacenter';
import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast';
import useMembers from '@/hooks/use-members';
import Loading from '@/components/loading';
import { Wrapper } from '@/ui/layout';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
const { members, removeMember, loaded } = useMembers();
const { enableWorkspace } = useWorkspaceHelper();
const { confirm } = useConfirm();
if (workspace.provider === 'affine') {
return (
<>
<StyledMemberListContainer>
{!loaded && (
<Wrapper justifyContent="center">
<Loading size={25} />
</Wrapper>
)}
{loaded && members.length === 0 && (
<Empty width={648} sx={{ marginTop: '60px' }} height={300} />
)}
{loaded && members.length && (
<>
<StyledMemberTitleContainer>
<StyledMemberNameContainer>
Users({members.length})
</StyledMemberNameContainer>
<StyledMemberRoleContainer>
Access level
</StyledMemberRoleContainer>
</StyledMemberTitleContainer>
{members.map((member, index) => {
const user = Object.assign(
{
avatar_url: '',
email: '',
id: '',
name: '',
},
member.user
);
return (
<StyledMemberListItem key={index}>
<StyledMemberNameContainer>
<StyledMemberAvatar
alt="member avatar"
src={user.avatar_url}
>
<EmailIcon />
</StyledMemberAvatar>
<StyledMemberInfo>
<StyledMemberName>{user.name}</StyledMemberName>
<StyledMemberEmail>
{member.user.email}
</StyledMemberEmail>
</StyledMemberInfo>
</StyledMemberNameContainer>
<StyledMemberRoleContainer>
{member.accepted
? member.type !== 99
? 'Member'
: 'Workspace Owner'
: 'Pending'}
</StyledMemberRoleContainer>
<StyledMoreVerticalButton>
<Menu
content={
<>
<MenuItem
onClick={async () => {
const confirmRemove = await confirm({
title: 'Delete Member?',
content: `will delete member`,
confirmText: 'Delete',
confirmType: 'danger',
});
if (!confirmRemove) {
return;
}
await removeMember(member.id);
toast(`${user.name} has been removed`);
}}
icon={<TrashIcon />}
>
Delete
</MenuItem>
</>
}
placement="bottom-end"
disablePortal={true}
>
<IconButton>
<MoreVerticalIcon />
</IconButton>
</Menu>
</StyledMoreVerticalButton>
</StyledMemberListItem>
);
})}
</>
)}
</StyledMemberListContainer>
<StyledMemberButtonContainer>
<Button
onClick={() => {
setIsInviteModalShow(true);
}}
type="primary"
shape="circle"
>
Invite Members
</Button>
<InviteMemberModal
onClose={() => {
setIsInviteModalShow(false);
}}
onInviteSuccess={() => {
setIsInviteModalShow(false);
// refreshMembers();
}}
workspaceId={workspace.id}
open={isInviteModalShow}
></InviteMemberModal>
</StyledMemberButtonContainer>
</>
);
}
return (
<StyledMemberWarp>
<>Collaborating with other members requires AFFiNE Cloud service.</>
<StyledPublishExplanation>
<Button
type="primary"
shape="circle"
onClick={async () => {
await enableWorkspace();
}}
>
Enable AFFiNE Cloud
</Button>
</StyledPublishExplanation>
</StyledMemberWarp>
);
};
@@ -0,0 +1 @@
export * from './MembersPage';
@@ -0,0 +1,106 @@
import { styled } from '@/styles';
import MuiAvatar from '@mui/material/Avatar';
export const StyledMemberTitleContainer = styled('div')(() => {
return {
display: 'flex',
marginTop: '60px',
fontWeight: '500',
flex: 1,
};
});
export const StyledMemberAvatar = styled(MuiAvatar)(() => {
return { height: '40px', width: '40px' };
});
export const StyledMemberNameContainer = styled('div')(() => {
return {
display: 'flex',
alignItems: 'center',
width: '402px',
};
});
export const StyledMemberRoleContainer = styled('div')(() => {
return {
display: 'flex',
alignItems: 'center',
width: '222px',
};
});
export const StyledMemberListContainer = styled('ul')(() => {
return {
marginTop: '15px',
overflowY: 'scroll',
};
});
export const StyledMemberListItem = styled('li')(() => {
return {
display: 'flex',
alignItems: 'center',
height: '72px',
};
});
export const StyledMemberInfo = styled('div')(() => {
return {
paddingLeft: '12px',
};
});
export const StyledMemberName = styled('div')(({ theme }) => {
return {
fontWeight: '400',
fontSize: '18px',
lineHeight: '16px',
color: theme.colors.textColor,
};
});
export const StyledMemberEmail = styled('div')(({ theme }) => {
return {
fontWeight: '400',
fontSize: '16px',
lineHeight: '22px',
color: theme.colors.iconColor,
};
});
export const StyledMemberButtonContainer = styled('div')(() => {
return {
marginTop: '14px',
};
});
export const StyledMoreVerticalButton = styled('button')(() => {
return {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '24px',
height: '24px',
cursor: 'pointer',
};
});
export const StyledPublishExplanation = styled('div')(() => {
return {
paddingRight: '48px',
fontWeight: '500',
fontSize: '18px',
lineHeight: '26px',
flex: 1,
marginTop: '60px',
};
});
export const StyledMemberWarp = styled('div')(() => {
return {
display: 'flex',
flexDirection: 'column',
padding: '40px 0',
};
});
@@ -1,8 +1,6 @@
import { styled } from '@/styles';
import { Button } from '@/ui/button';
import MuiAvatar from '@mui/material/Avatar';
export const StyledSettingContainer = styled('div')(({ theme }) => {
export const StyledSettingContainer = styled('div')(() => {
return {
display: 'flex',
padding: '50px',
@@ -12,7 +10,7 @@ export const StyledSettingContainer = styled('div')(({ theme }) => {
};
});
export const StyledSettingSidebar = styled('div')(({ theme }) => {
export const StyledSettingSidebar = styled('div')(() => {
{
return {
flexShrink: 0,
@@ -96,99 +94,6 @@ export const StyledSettingH2 = styled('h2')<{ marginTop?: number }>(
}
);
export const StyledAvatarUploadBtn = styled(Button)(({ theme }) => {
return {
backgroundColor: theme.colors.hoverBackground,
color: theme.colors.primaryColor,
margin: '0 12px 0 24px',
};
});
export const StyledMemberTitleContainer = styled('div')(() => {
return {
display: 'flex',
marginTop: '60px',
fontWeight: '500',
flex: 1,
};
});
export const StyledMemberAvatar = styled(MuiAvatar)(() => {
return { height: '40px', width: '40px' };
});
export const StyledMemberNameContainer = styled('div')(() => {
return {
display: 'flex',
alignItems: 'center',
width: '402px',
};
});
export const StyledMemberRoleContainer = styled('div')(() => {
return {
display: 'flex',
alignItems: 'center',
width: '222px',
};
});
export const StyledMemberListContainer = styled('ul')(() => {
return {
marginTop: '15px',
overflowY: 'scroll',
};
});
export const StyledMemberListItem = styled('li')(() => {
return {
display: 'flex',
alignItems: 'center',
height: '72px',
};
});
export const StyledMemberInfo = styled('div')(() => {
return {
paddingLeft: '12px',
};
});
export const StyledMemberName = styled('div')(({ theme }) => {
return {
fontWeight: '400',
fontSize: '18px',
lineHeight: '16px',
color: theme.colors.textColor,
};
});
export const StyledMemberEmail = styled('div')(({ theme }) => {
return {
fontWeight: '400',
fontSize: '16px',
lineHeight: '22px',
color: theme.colors.iconColor,
};
});
export const StyledMemberButtonContainer = styled('div')(() => {
return {
marginTop: '14px',
};
});
export const StyledMoreVerticalButton = styled('button')(() => {
return {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '24px',
height: '24px',
cursor: 'pointer',
};
});
export const StyledPublishExplanation = styled('div')(() => {
return {
paddingRight: '48px',
@@ -5,7 +5,7 @@ import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { useAppState } from '@/providers/app-state-provider';
export const WorkspaceSelector = () => {
const [workspaceListShow, setWorkspaceListShow] = useState(false);
const { currentMetaWorkSpace, workspaceList } = useAppState();
const { currentWorkspace, workspaceList } = useAppState();
useEffect(() => {
if (workspaceList.length === 0) {
@@ -23,7 +23,7 @@ export const WorkspaceSelector = () => {
<Avatar
alt="Affine"
data-testid="workspace-avatar"
src={currentMetaWorkSpace?.avatar}
src={currentWorkspace?.avatar}
>
<div
style={{
@@ -32,13 +32,13 @@ export const WorkspaceSelector = () => {
>
<WorkspaceAvatar
size={28}
name={currentMetaWorkSpace?.name ?? 'AFFiNE'}
avatar={currentMetaWorkSpace?.avatar ?? ''}
name={currentWorkspace?.name ?? 'AFFiNE'}
avatar={currentWorkspace?.avatar ?? ''}
/>
</div>
</Avatar>
<WorkspaceName data-testid="workspace-name">
{currentMetaWorkSpace?.name ?? 'AFFiNE'}
{currentWorkspace?.name ?? 'AFFiNE'}
</WorkspaceName>
</SelectorWrapper>
<WorkspaceModal
@@ -5,14 +5,12 @@ import {
StyledLink,
StyledListItem,
StyledListItemForWorkspace,
// StyledListItemForWorkspace,
StyledNewPageButton,
StyledSliderBar,
StyledSliderBarWrapper,
StyledSubListItem,
} from './style';
import { Arrow } from './icons';
// import { WorkspaceSelector } from './WorkspaceSelector';
import Collapse from '@mui/material/Collapse';
import {
ArrowDownIcon,
@@ -30,15 +28,13 @@ import { useModal } from '@/providers/GlobalModalProvider';
import { useAppState } from '@/providers/app-state-provider';
import { IconButton } from '@/ui/button';
import useLocalStorage from '@/hooks/use-local-storage';
import usePageMetaList from '@/hooks/use-page-meta-list';
import { usePageHelper } from '@/hooks/use-page-helper';
// import { WorkspaceSetting } from '@/components/workspace-setting';
import { useTranslation } from '@affine/i18n';
import { WorkspaceSelector } from './WorkspaceSelector/WorkspaceSelector';
const FavoriteList = ({ showList }: { showList: boolean }) => {
const { openPage } = usePageHelper();
const pageList = usePageMetaList();
const { pageList } = useAppState();
const router = useRouter();
const { t } = useTranslation();
const favoriteList = pageList.filter(p => p.favorite && !p.trash);
@@ -71,13 +67,13 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
export const WorkSpaceSliderBar = () => {
const { triggerQuickSearchModal, triggerImportModal } = useModal();
const [showSubFavorite, setShowSubFavorite] = useState(true);
const { currentWorkspaceId } = useAppState();
const { currentWorkspace } = useAppState();
const { openPage, createPage } = usePageHelper();
const router = useRouter();
const { t } = useTranslation();
const [showTip, setShowTip] = useState(false);
const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true);
const currentWorkspaceId = currentWorkspace?.id;
const paths = {
all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
favorite: currentWorkspaceId
-160
View File
@@ -1,160 +0,0 @@
export interface Workspace {
name: string; // 名称
id: string; //唯一标识
isPublish?: boolean; // 是否公开
isLocal?: boolean; // 是否全部数据都在本地
avatar?: string; // 封面
type: 'local' | 'cloud' | 'join'; // cloud: 云端(本次暂不支持),local: 本地,join : 加入别人的
workspaceOwner?: User; // 本地工作空间的拥有者
}
export interface User {
name: string;
id: string;
email: string;
avatar: string;
}
export function updateWorkspaceMeta(
workspaceId: string,
workspaceData: {
name?: string;
avatar?: string;
type?: 'local' | 'cloud' | 'join';
}
) {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
if (workspace.id === workspaceId) {
workspaceData.name && (workspace.name = workspaceData.name);
workspaceData.avatar && (workspace.avatar = workspaceData.avatar);
workspaceData.type && (workspace.type = workspaceData.type);
return workspaceData;
}
return workspace;
});
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
const activeWorkspace = getActiveWorkspace();
workspaceData.name && (activeWorkspace.name = workspaceData.name);
workspaceData.avatar && (activeWorkspace.avatar = workspaceData.avatar);
workspaceData.type && (activeWorkspace.type = workspaceData.type);
console.log(workspaceData);
setActiveWorkspace(activeWorkspace);
}
export function createWorkspace(workspaceName: string) {
const workspaceId = 'workspace-' + Date.now();
const workspaceData = {
name: workspaceName,
id: workspaceId,
isPublish: false,
isLocal: true,
avatar: '',
type: 'local',
} as Workspace;
const workspacesMeta = getWorkspaces();
workspacesMeta.push(workspaceData);
localStorage.setItem('affine-workspace', JSON.stringify(workspacesMeta));
setActiveWorkspace(workspaceData);
return { workspaceId };
}
export function getWorkspaces(): Workspace[] {
const workspacesMeta = JSON.parse(
localStorage.getItem('affine-workspace') ?? '[]'
);
return workspacesMeta;
}
export function deleteWorkspace(workspaceId: string) {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.filter(() => {
return workspaceId !== workspaceId;
});
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
}
export function getMembers(id: string): User[] {
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
return memberMap[id] || [];
}
export function setMember(workspaceId: string, member: User) {
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
memberMap[workspaceId] = memberMap[workspaceId] || [];
memberMap[workspaceId].push(member);
localStorage.setItem('affine-member', JSON.stringify(memberMap));
}
export function deleteMember(workspaceId: string, index: number) {
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
const memberList = memberMap[workspaceId];
memberList.splice(index, 1);
memberMap[workspaceId] = memberList;
localStorage.setItem('affine-member', JSON.stringify(memberMap));
}
export function leaveWorkspace() {
return true;
}
export function setWorkspacePublish(id: string, isPublish: boolean): boolean {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
if (workspace.id === id) {
workspace.isPublish = isPublish;
}
return workspace;
});
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
return isPublish;
}
export function getWorkspaceById(id: string) {
const workspacesMeta = getWorkspaces();
return workspacesMeta.find((workspace: Workspace) => {
return workspace.id === id;
});
}
export function getPagesByWorkspaceId(workspaceId: string) {
if (!workspaceId) return [];
const workspacesMeta = [];
for (let i = 0; i < 10; i++) {
workspacesMeta.push({
id: 'page-' + i,
name: 'page ' + i,
});
}
}
export function getActiveWorkspace(): Workspace {
return JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}');
}
export function setActiveWorkspace(workspaceData: Workspace) {
console.log('workspaceData: ', workspaceData);
localStorage.setItem(
'affine-active-workspace',
JSON.stringify(workspaceData)
);
}
export function getUserInfo(): User {
return JSON.parse(localStorage.getItem('affine-user') ?? 'null');
}
export function Login(): void {
localStorage.setItem(
'affine-user',
JSON.stringify({
name: 'Diamond',
id: 'ttt',
email: 'diamond.shx@gmail.com',
avatar: 'string',
})
);
}
export function SignOut(): void {
localStorage.removeItem('affine-user');
}
@@ -11,7 +11,7 @@ export const useChangePageMeta = () => {
return useCallback<ChangePageMeta>(
(pageId, pageMeta) => {
currentWorkspace?.setPageMeta(pageId, {
currentWorkspace?.blocksuiteWorkspace?.setPageMeta(pageId, {
...pageMeta,
});
},
@@ -9,7 +9,7 @@ export const useCurrentPageMeta = (): PageMeta | null => {
return null;
}
return (
(currentWorkspace.meta.pageMetas.find(
(currentWorkspace.blocksuiteWorkspace?.meta.pageMetas.find(
p => p.id === currentPage.id
) as PageMeta) ?? null
);
@@ -22,9 +22,11 @@ export const useCurrentPageMeta = (): PageMeta | null => {
useEffect(() => {
setCurrentPageMeta(pageMetaHandler);
const dispose = currentWorkspace?.meta.pagesUpdated.on(() => {
setCurrentPageMeta(pageMetaHandler);
}).dispose;
const dispose = currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on(
() => {
setCurrentPageMeta(pageMetaHandler);
}
).dispose;
return () => {
dispose?.();
+10 -10
View File
@@ -1,15 +1,15 @@
import { useState, useEffect } from 'react';
import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
const defaultOutLineWorkspaceId = 'affine';
// 'local-first-' + '85b4ca0b9081421d903bbc2501ea280f';
// It is a fully effective hook
// Cause it not just ensure workspace loaded, but also have router change.
export const useEnsureWorkspace = () => {
const [workspaceLoaded, setWorkspaceLoaded] = useState(false);
const { workspaceList, loadWorkspace, user } = useAppState();
console.log('workspaceList: ', workspaceList);
const router = useRouter();
const [activeWorkspaceId, setActiveWorkspaceId] = useState(
router.query.workspaceId as string
);
// const defaultOutLineWorkspaceId = '99ce7eb7';
// console.log(defaultOutLineWorkspaceId);
@@ -18,7 +18,8 @@ export const useEnsureWorkspace = () => {
// If workspaceList is empty, we need to create a default workspace but not jump to 404
if (
workspaceList.length &&
router.query.workspaceId &&
// FIXME: router is not ready when this hook is called
location.pathname.startsWith(`/workspace/${router.query.workspaceId}`) &&
workspaceList.findIndex(
meta => meta.id.toString() === router.query.workspaceId
) === -1
@@ -35,18 +36,17 @@ export const useEnsureWorkspace = () => {
// router.push('/404');
// return;
// }
const workspaceId = user
? (router.query.workspaceId as string) || workspaceList[0]?.id
: (router.query.workspaceId as string) || defaultOutLineWorkspaceId;
const workspaceId =
(router.query.workspaceId as string) || workspaceList[0]?.id;
loadWorkspace(workspaceId).finally(() => {
setWorkspaceLoaded(true);
setActiveWorkspaceId(activeWorkspaceId);
});
}, [loadWorkspace, router, user, workspaceList]);
}, [loadWorkspace, router, user, workspaceList, activeWorkspaceId]);
return {
workspaceLoaded,
activeWorkspaceId,
};
};
@@ -1,34 +0,0 @@
import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider';
import { useEffect, useRef, useState } from 'react';
export const useInitWorkspace = (disabled?: boolean) => {
const [loading, setLoading] = useState(true);
// Do not set as a constant, or it will trigger a hell of re-rendering
const defaultOutLineWorkspaceId = useRef(new Date().getTime().toString());
const router = useRouter();
const { workspaceList, loadWorkspace, currentWorkspace, currentWorkspaceId } =
useAppState();
const workspaceId =
(router.query.workspaceId as string) ||
workspaceList?.[0]?.id ||
defaultOutLineWorkspaceId.current;
useEffect(() => {
if (disabled) {
setLoading(false);
return;
}
setLoading(true);
loadWorkspace(workspaceId).finally(() => {
setLoading(false);
});
}, [workspaceId, disabled, loadWorkspace]);
return {
workspaceId,
workspace: workspaceId === currentWorkspaceId ? currentWorkspace : null,
loading,
};
};
+51
View File
@@ -0,0 +1,51 @@
import { useCallback, useEffect, useState } from 'react';
import { Member } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider';
export const useMembers = () => {
const { dataCenter, currentWorkspace } = useAppState();
const [members, setMembers] = useState<Member[]>([]);
const [loaded, setLoaded] = useState(false);
const refreshMembers = useCallback(async () => {
if (!currentWorkspace || !dataCenter) return;
const members = await dataCenter.getMembers(currentWorkspace.id);
setMembers(members);
}, [dataCenter, currentWorkspace]);
useEffect(() => {
const init = async () => {
await refreshMembers();
setLoaded(true);
};
init();
}, [refreshMembers]);
const inviteMember = async (email: string) => {
currentWorkspace &&
dataCenter &&
(await dataCenter.inviteMember(currentWorkspace?.id, email));
};
const removeMember = async (permissionId: number) => {
if (!currentWorkspace || !dataCenter) {
return;
}
setLoaded(false);
await dataCenter.removeMember(currentWorkspace.id, permissionId);
await refreshMembers();
setLoaded(true);
};
const getUserByEmail = async (email: string) => {
if (!currentWorkspace) return null;
return dataCenter?.getUserByEmail(currentWorkspace.id, email);
};
return {
members,
removeMember,
inviteMember,
getUserByEmail,
loaded,
};
};
export default useMembers;
+21 -14
View File
@@ -1,9 +1,10 @@
import { Workspace, uuidv4 } from '@blocksuite/store';
import { uuidv4 } from '@blocksuite/store';
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
import { PageMeta, useAppState } from '@/providers/app-state-provider';
import { EditorContainer } from '@blocksuite/editor';
import { useChangePageMeta } from '@/hooks/use-change-page-meta';
import { useRouter } from 'next/router';
import { WorkspaceUnit } from '@affine/datacenter';
export type EditorHandlers = {
createPage: (params?: {
@@ -27,13 +28,15 @@ export type EditorHandlers = {
) => Promise<EditorContainer['mode']>;
};
const getPageMeta = (workspace: Workspace | null, pageId: string) => {
return workspace?.meta.pageMetas.find(p => p.id === pageId);
const getPageMeta = (workspace: WorkspaceUnit | null, pageId: string) => {
return workspace?.blocksuiteWorkspace?.meta.pageMetas.find(
p => p.id === pageId
);
};
export const usePageHelper = (): EditorHandlers => {
const router = useRouter();
const changePageMeta = useChangePageMeta();
const { currentWorkspace, editor, currentWorkspaceId } = useAppState();
const { currentWorkspace, editor } = useAppState();
return {
createPage: ({
@@ -44,11 +47,15 @@ export const usePageHelper = (): EditorHandlers => {
if (!currentWorkspace) {
return resolve(null);
}
currentWorkspace.createPage(pageId);
currentWorkspace.signals.pageAdded.once(addedPageId => {
currentWorkspace.setPageMeta(addedPageId, { title });
resolve(addedPageId);
});
currentWorkspace.blocksuiteWorkspace?.createPage(pageId);
currentWorkspace.blocksuiteWorkspace?.signals.pageAdded.once(
addedPageId => {
currentWorkspace.blocksuiteWorkspace?.setPageMeta(addedPageId, {
title,
});
resolve(addedPageId);
}
);
});
},
toggleFavoritePage: async pageId => {
@@ -78,7 +85,7 @@ export const usePageHelper = (): EditorHandlers => {
},
search: (query: QueryContent) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return currentWorkspace!.search(query);
return currentWorkspace!.blocksuiteWorkspace!.search(query);
},
changePageMode: async (pageId, mode) => {
const pageMeta = getPageMeta(currentWorkspace, pageId);
@@ -96,17 +103,17 @@ export const usePageHelper = (): EditorHandlers => {
permanentlyDeletePage: pageId => {
// TODO: workspace.meta.removePage or workspace.removePage?
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
currentWorkspace!.meta.removePage(pageId);
currentWorkspace!.blocksuiteWorkspace?.meta.removePage(pageId);
},
openPage: (pageId, query = {}, newTab = false) => {
pageId = pageId.replace('space:', '');
if (newTab) {
window.open(`/workspace/${currentWorkspaceId}/${pageId}`, '_blank');
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
return Promise.resolve(true);
}
return router.push({
pathname: `/workspace/${currentWorkspaceId}/${pageId}`,
pathname: `/workspace/${currentWorkspace?.id}/${pageId}`,
query,
});
},
@@ -116,7 +123,7 @@ export const usePageHelper = (): EditorHandlers => {
}
return (
(currentWorkspace.meta.pageMetas.find(
(currentWorkspace.blocksuiteWorkspace?.meta.pageMetas.find(
page => page.id === pageId
) as PageMeta) || null
);
@@ -1,25 +0,0 @@
import { useEffect, useState } from 'react';
import { PageMeta } from '@/providers/app-state-provider';
import { useAppState } from '@/providers/app-state-provider';
export const usePageMetaList = () => {
const { currentWorkspace } = useAppState();
const [pageList, setPageList] = useState<PageMeta[]>([]);
useEffect(() => {
if (!currentWorkspace) {
return;
}
setPageList(currentWorkspace.meta.pageMetas as PageMeta[]);
const dispose = currentWorkspace.meta.pagesUpdated.on(() => {
setPageList(currentWorkspace.meta.pageMetas as PageMeta[]);
}).dispose;
return () => {
dispose();
};
}, [currentWorkspace]);
return pageList;
};
export default usePageMetaList;
-28
View File
@@ -1,28 +0,0 @@
import { useAppState } from '@/providers/app-state-provider';
import { User } from '@affine/datacenter';
import { useEffect, useState } from 'react';
export const useUserHelper = () => {
const { dataCenter } = useAppState();
const [user, setUser] = useState<User | undefined>(undefined);
useEffect(() => {
dataCenter
.getUserInfo()
.then(user => {
setUser(user);
})
.catch(e => {
setUser(undefined);
});
}, []);
const login = async () => {
await dataCenter.login();
const user = await dataCenter.getUserInfo();
setUser(user);
};
return {
user,
login,
};
};
+42 -13
View File
@@ -1,14 +1,18 @@
import { useAppState } from '@/providers/app-state-provider';
import { Workspace } from '@blocksuite/store';
import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast';
import { WorkspaceUnit } from '@affine/datacenter';
import router from 'next/router';
export const useWorkspaceHelper = () => {
const { dataCenter } = useAppState();
const { confirm } = useConfirm();
const { dataCenter, currentWorkspace, user, login } = useAppState();
const createWorkspace = async (name: string) => {
const workspaceInfo = await dataCenter.createWorkspace({
name: name,
});
if (workspaceInfo && workspaceInfo.room) {
const workspace = await dataCenter.loadWorkspace(workspaceInfo.room);
return workspace;
if (workspaceInfo && workspaceInfo.id) {
return await dataCenter.loadWorkspace(workspaceInfo.id);
}
return null;
};
@@ -16,31 +20,56 @@ export const useWorkspaceHelper = () => {
// const updateWorkspace = async (workspace: Workspace) => {};
const publishWorkspace = async (workspaceId: string, publish: boolean) => {
dataCenter.setWorkspacePublish(workspaceId, publish);
await dataCenter.setWorkspacePublish(workspaceId, publish);
};
const updateWorkspace = async (
{ name, avatarBlob }: { name?: string; avatarBlob?: Blob },
workspace: Workspace
workspace: WorkspaceUnit
) => {
if (name) {
await dataCenter.updateWorkspaceMeta({ name }, workspace);
}
if (avatarBlob) {
const blobId = await dataCenter.setBlob(workspace, avatarBlob);
await dataCenter.updateWorkspaceMeta({ avatar: blobId }, workspace);
// const blobId = await dataCenter.setBlob(workspace, avatarBlob);
// await dataCenter.updateWorkspaceMeta({ avatar: blobId }, workspace);
}
};
const enableWorkspace = async (workspace: Workspace) => {
const newWorkspaceId = await dataCenter.enableWorkspaceCloud(workspace);
// console.log('newWorkspace: ', newWorkspace);
return newWorkspaceId;
const enableWorkspace = async () => {
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText: user ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(async confirm => {
if (confirm && currentWorkspace) {
if (!user) {
await login();
}
const workspace = await dataCenter.enableWorkspaceCloud(
currentWorkspace
);
workspace && router.push(`/workspace/${workspace.id}/setting`);
toast('Enabled success');
}
});
};
const deleteWorkSpace = async () => {
currentWorkspace &&
(await dataCenter.deleteWorkspace(currentWorkspace?.id));
};
const leaveWorkSpace = async () => {
currentWorkspace && (await dataCenter.leaveWorkspace(currentWorkspace?.id));
};
return {
createWorkspace,
publishWorkspace,
updateWorkspace,
enableWorkspace,
deleteWorkSpace,
leaveWorkSpace,
};
};
-3
View File
@@ -21,7 +21,6 @@ import { useAppState } from '@/providers/app-state-provider';
import { PageLoading } from '@/components/loading';
import Head from 'next/head';
import '@affine/i18n';
import TemporaryHelperProvider from '@/providers/temporary-helper-provider';
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
ssr: false,
@@ -55,10 +54,8 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
<Logger />
<ProviderComposer
contexts={[
<TemporaryHelperProvider key="TemporaryHelperProvider" />,
<ThemeProvider key="ThemeProvider" />,
<AppStateProvider key="appStateProvider" />,
// <AppStateProvider2 key="appStateProvider2" />,
<ModalProvider key="ModalProvider" />,
<ConfirmProvider key="ConfirmProvider" />,
]}
@@ -1,47 +0,0 @@
import { WorkspaceModal } from '@/components/workspace-modal';
import { getWorkspaces } from '@/hooks/mock-data/mock';
import { useEffect, useState } from 'react';
import { styled } from '@/styles';
import Button from '@/ui/button/Button';
const Page = () => {
const [open, setOpen] = useState<boolean>(false);
useEffect(() => {
const data = getWorkspaces();
if (!data.length) {
setOpen(true);
}
}, []);
return (
<Workspace>
<h1>workspace</h1>
<div>
<Button
onClick={() => {
setOpen(true);
}}
>
View Workspace List
</Button>
</div>
<WorkspaceModal
open={open}
onClose={() => {
setOpen(false);
}}
></WorkspaceModal>
</Workspace>
);
};
export default Page;
const Workspace = styled.div(({ theme }) => {
return {
height: '100vh',
background: theme.colors.pageBackground,
color: '#FFFFFF',
fontSize: '18px',
fontWeight: 500,
};
});
@@ -37,11 +37,11 @@ const All = () => {
const { openPage, createPage } = usePageHelper();
const { currentWorkspace } = useAppState();
const _applyTemplate = function (pageId: string, template: Template) {
const page = currentWorkspace?.getPage(pageId);
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId);
const title = template.name;
if (page) {
currentWorkspace?.setPageMeta(page.id, { title });
currentWorkspace?.blocksuiteWorkspace?.setPageMeta(page.id, { title });
if (page && page.root === null) {
setTimeout(async () => {
const editor = document.querySelector('editor-container');
@@ -0,0 +1,69 @@
import { ReactElement, useEffect, useState } from 'react';
import { useAppState } from '@/providers/app-state-provider';
import type { NextPageWithLayout } from '../..//_app';
import { styled } from '@/styles';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
import { Page as PageStore, Workspace } from '@blocksuite/store';
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
ssr: false,
});
const Page: NextPageWithLayout = () => {
const [workspace, setWorkspace] = useState<Workspace>();
const [page, setPage] = useState<PageStore>();
const { dataCenter } = useAppState();
const router = useRouter();
useEffect(() => {
dataCenter
.loadPublicWorkspace(router.query.workspaceId as string)
.then(data => {
if (data && data.blocksuiteWorkspace) {
setWorkspace(data.blocksuiteWorkspace);
if (
router.query.pageId &&
data.blocksuiteWorkspace.meta.pageMetas.find(
p => p.id === router.query.pageId
)
) {
const page = data.blocksuiteWorkspace?.getPage(
router.query.pageId as string
);
page && setPage(page);
} else {
router.push('/404');
}
}
})
.catch(() => {
router.push('/404');
});
}, [router, dataCenter]);
return (
<PageContainer>
{workspace && page && (
<DynamicBlocksuite
page={page}
workspace={workspace}
setEditor={editor => {
editor.readonly = true;
}}
/>
)}
</PageContainer>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <div>{page}</div>;
};
export default Page;
export const PageContainer = styled.div(({ theme }) => {
return {
height: 'calc(100vh)',
padding: '78px 72px',
overflowY: 'auto',
backgroundColor: theme.colors.pageBackground,
};
});
@@ -0,0 +1,36 @@
import { PageList } from '@/components/page-list';
import { ReactElement, useEffect, useState } from 'react';
import { PageMeta, useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
import { PageContainer } from './[pageId]';
const All = () => {
const { dataCenter } = useAppState();
const router = useRouter();
const [pageList, setPageList] = useState<PageMeta[]>([]);
useEffect(() => {
dataCenter
.loadPublicWorkspace(router.query.workspaceId as string)
.then(data => {
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
})
.catch(() => {
router.push('/404');
});
}, [router, dataCenter]);
return (
<PageContainer>
<PageList
pageList={pageList.filter(p => !p.trash)}
showFavoriteTag={false}
isPublic={true}
/>
</PageContainer>
);
};
All.getLayout = function getLayout(page: ReactElement) {
return <div>{page}</div>;
};
export default All;
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {
PropsWithChildren,
ReactElement,
@@ -31,10 +30,10 @@ const Page: NextPageWithLayout = () => {
<EditorHeader />
<MobileModal />
{currentPage && (
{currentPage && currentWorkspace?.blocksuiteWorkspace && (
<DynamicBlocksuite
page={currentPage}
workspace={currentWorkspace}
workspace={currentWorkspace.blocksuiteWorkspace}
setEditor={setEditorHandler}
/>
)}
@@ -52,9 +51,10 @@ const PageDefender = ({ children }: PropsWithChildren) => {
useEffect(() => {
const initPage = async () => {
const pageId = router.query.pageId as string;
const isPageExist = !!currentWorkspace!.meta.pageMetas.find(
p => p.id === pageId
);
const isPageExist =
currentWorkspace?.blocksuiteWorkspace?.meta?.pageMetas.find(
p => p.id === pageId
);
if (!isPageExist) {
await createPage({ pageId });
}
@@ -1,18 +1,18 @@
import { PageList } from '@/components/page-list';
import { AllPagesIcon } from '@blocksuite/icons';
import usePageMetaList from '@/hooks/use-page-meta-list';
import { PageListHeader } from '@/components/header';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { useAppState } from '@/providers/app-state-provider';
const All = () => {
const pageMetaList = usePageMetaList();
const { pageList } = useAppState();
const { t } = useTranslation();
return (
<>
<PageListHeader icon={<AllPagesIcon />}>{t('All pages')}</PageListHeader>
<PageList
pageList={pageMetaList.filter(p => !p.trash)}
pageList={pageList.filter(p => !p.trash)}
showFavoriteTag={true}
/>
</>
@@ -1,19 +1,19 @@
import { PageListHeader } from '@/components/header';
import { PageList } from '@/components/page-list';
import { FavouritesIcon } from '@blocksuite/icons';
import usePageMetaList from '@/hooks/use-page-meta-list';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { useAppState } from '@/providers/app-state-provider';
export const Favorite = () => {
const pageMetaList = usePageMetaList();
const { pageList } = useAppState();
const { t } = useTranslation();
return (
<>
<PageListHeader icon={<FavouritesIcon />}>
{t('Favourites')}
</PageListHeader>
<PageList pageList={pageMetaList.filter(p => p.favorite && !p.trash)} />
<PageList pageList={pageList.filter(p => p.favorite && !p.trash)} />
</>
);
};
@@ -7,31 +7,32 @@ import usePageHelper from '@/hooks/use-page-helper';
const WorkspaceIndex = () => {
const router = useRouter();
const { currentWorkspaceId, currentWorkspace } = useAppState();
const { currentWorkspace } = useAppState();
const { createPage } = usePageHelper();
const { workspaceLoaded } = useEnsureWorkspace();
const { workspaceLoaded, activeWorkspaceId } = useEnsureWorkspace();
useEffect(() => {
const initPage = async () => {
if (!workspaceLoaded) {
return;
}
const savedPageId = currentWorkspace?.meta.pageMetas[0]?.id;
const savedPageId =
currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas[0]?.id;
if (savedPageId) {
router.replace(`/workspace/${currentWorkspaceId}/${savedPageId}`);
router.replace(`/workspace/${activeWorkspaceId}/${savedPageId}`);
return;
}
const pageId = await createPage();
router.replace(`/workspace/${currentWorkspaceId}/${pageId}`);
router.replace(`/workspace/${activeWorkspaceId}/${pageId}`);
};
initPage();
}, [
currentWorkspace,
currentWorkspaceId,
createPage,
router,
workspaceLoaded,
activeWorkspaceId,
]);
return <PageLoading />;
@@ -23,14 +23,14 @@ import {
} from '@/components/workspace-setting';
import { useAppState } from '@/providers/app-state-provider';
import WorkspaceLayout from '@/components/workspace-layout';
import { WorkspaceInfo } from '@affine/datacenter';
import { WorkspaceUnit } from '@affine/datacenter';
type TabNames = 'general' | 'members' | 'publish' | 'sync' | 'export';
const tabMap: {
name: TabNames;
icon: ReactNode;
panelRender: (workspace: WorkspaceInfo) => ReactNode;
panelRender: (workspace: WorkspaceUnit) => ReactNode;
}[] = [
{
name: 'general',
@@ -60,7 +60,7 @@ const tabMap: {
];
const WorkspaceSetting = () => {
const { currentMetaWorkSpace } = useAppState();
const { currentWorkspace, isOwner } = useAppState();
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
const handleTabChange = (tab: TabNames) => {
@@ -70,7 +70,20 @@ const WorkspaceSetting = () => {
const activeTabPanelRender = tabMap.find(
tab => tab.name === activeTab
)?.panelRender;
let tableArr: {
name: TabNames;
icon: ReactNode;
panelRender: (workspace: WorkspaceUnit) => ReactNode;
}[] = tabMap;
if (!isOwner) {
tableArr = [
{
name: 'general',
icon: <EditIcon />,
panelRender: workspace => <GeneralPage workspace={workspace} />,
},
];
}
return (
<StyledSettingContainer>
<StyledSettingSidebar>
@@ -78,7 +91,7 @@ const WorkspaceSetting = () => {
Workspace Settings
</StyledSettingSidebarHeader>
<StyledSettingTabContainer>
{tabMap.map(({ icon, name }) => {
{tableArr.map(({ icon, name }) => {
return (
<WorkspaceSettingTagItem
key={name}
@@ -98,7 +111,7 @@ const WorkspaceSetting = () => {
</StyledSettingSidebar>
<StyledSettingContent>
{currentMetaWorkSpace && activeTabPanelRender?.(currentMetaWorkSpace)}
{currentWorkspace && activeTabPanelRender?.(currentWorkspace)}
</StyledSettingContent>
</StyledSettingContainer>
);
@@ -1,17 +1,17 @@
import { PageListHeader } from '@/components/header';
import { PageList } from '@/components/page-list';
import { TrashIcon } from '@blocksuite/icons';
import usePageMetaList from '@/hooks/use-page-meta-list';
import { ReactElement } from 'react';
import WorkspaceLayout from '@/components/workspace-layout';
import { useTranslation } from '@affine/i18n';
import { useAppState } from '@/providers/app-state-provider';
export const Trash = () => {
const pageMetaList = usePageMetaList();
const { pageList } = useAppState();
const { t } = useTranslation();
return (
<>
<PageListHeader icon={<TrashIcon />}>{t('Trash')}</PageListHeader>
<PageList pageList={pageMetaList.filter(p => p.trash)} isTrash={true} />
<PageList pageList={pageList.filter(p => p.trash)} isTrash={true} />
</>
);
};
+3 -3
View File
@@ -6,14 +6,14 @@ import { PageLoading } from '@/components/loading';
export const WorkspaceIndex = () => {
const router = useRouter();
const { currentWorkspaceId } = useAppState();
const { currentWorkspace } = useAppState();
const { workspaceLoaded } = useEnsureWorkspace();
useEffect(() => {
if (workspaceLoaded) {
router.push(`/workspace/${currentWorkspaceId}`);
router.push(`/workspace/${currentWorkspace?.id}`);
}
}, [currentWorkspaceId, router, workspaceLoaded]);
}, [currentWorkspace, router, workspaceLoaded]);
return <PageLoading />;
};
@@ -8,7 +8,7 @@ import {
PageMeta,
} from './interface';
import { createDefaultWorkspace } from './utils';
import { WorkspaceInfo } from '@affine/datacenter';
import { User } from '@affine/datacenter';
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
@@ -20,63 +20,61 @@ export const AppStateProvider = ({
children,
}: PropsWithChildren<AppStateContextProps>) => {
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
useEffect(() => {
const init = async () => {
const initState = async () => {
const dataCenter = await getDataCenter();
// Ensure datacenter has at least one workspace
if (dataCenter.workspaces.length === 0) {
await createDefaultWorkspace(dataCenter);
}
const currentWorkspace = await dataCenter.loadWorkspace(
dataCenter.workspaces[0].id
);
const currentMetaWorkSpace = dataCenter.workspaces.find(item => {
return item.id === currentWorkspace.room;
});
setAppState({
dataCenter,
user: (await dataCenter.getUserInfo()) || null,
workspaceList: dataCenter.workspaces,
currentWorkspaceId: dataCenter.workspaces[0].id,
currentWorkspace,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
currentWorkspace: null,
pageList: [],
currentPage: null,
editor: null,
synced: true,
currentMetaWorkSpace: currentMetaWorkSpace ?? null,
isOwner: false,
});
};
init();
initState();
}, []);
useEffect(() => {
if (!appState?.currentWorkspace) {
if (!appState?.currentWorkspace?.blocksuiteWorkspace) {
return;
}
const currentWorkspace = appState.currentWorkspace;
const dispose = currentWorkspace.meta.pagesUpdated.on(() => {
setAppState({
...appState,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
});
}).dispose;
const dispose = currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on(
() => {
setAppState({
...appState,
pageList: currentWorkspace.blocksuiteWorkspace?.meta
.pageMetas as PageMeta[],
});
}
).dispose;
return () => {
dispose();
dispose && dispose();
};
}, [appState]);
useEffect(() => {
const { dataCenter } = appState;
// FIXME: onWorkspacesChange should have dispose function
dataCenter?.onWorkspacesChange(() => {
setAppState({
...appState,
workspaceList: dataCenter.workspaces,
});
});
dataCenter?.onWorkspacesChange(
() => {
setAppState({
...appState,
workspaceList: dataCenter.workspaces,
});
},
{ immediate: false }
);
}, [appState]);
const loadPage = useRef<AppStateFunction['loadPage']>();
@@ -85,7 +83,7 @@ export const AppStateProvider = ({
if (pageId === currentPage?.id) {
return;
}
const page = currentWorkspace?.getPage(pageId) || null;
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId) || null;
setAppState({
...appState,
currentPage: page,
@@ -94,28 +92,30 @@ export const AppStateProvider = ({
const loadWorkspace = useRef<AppStateFunction['loadWorkspace']>();
loadWorkspace.current = async (workspaceId: string) => {
const { dataCenter, workspaceList, currentWorkspaceId, currentWorkspace } =
appState;
if (!workspaceList.find(v => v.id === workspaceId)) {
const { dataCenter, workspaceList, currentWorkspace, user } = appState;
if (!workspaceList.find(v => v.id.toString() === workspaceId)) {
return null;
}
if (workspaceId === currentWorkspaceId) {
if (workspaceId === currentWorkspace?.id) {
return currentWorkspace;
}
const workspace = await dataCenter.loadWorkspace(workspaceId);
const currentMetaWorkSpace = dataCenter.workspaces.find(
(item: WorkspaceInfo) => {
return item.id === workspace.room;
}
);
const workspace = (await dataCenter.loadWorkspace(workspaceId)) ?? null;
let isOwner;
if (workspace.provider === 'local') {
// isOwner is useful only in the cloud
isOwner = true;
} else {
isOwner = workspace?.owner && user?.id === workspace?.owner?.id;
}
const pageList =
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
setAppState({
...appState,
currentWorkspace: workspace,
currentWorkspaceId: workspaceId,
currentMetaWorkSpace: currentMetaWorkSpace ?? null,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
pageList: pageList,
currentPage: null,
editor: null,
isOwner,
});
return workspace;
@@ -130,6 +130,26 @@ export const AppStateProvider = ({
});
};
const login = async () => {
const { dataCenter } = appState;
await dataCenter.login();
const user = (await dataCenter.getUserInfo()) as User;
setAppState({
...appState,
user,
});
return user;
};
const logout = async () => {
const { dataCenter } = appState;
await dataCenter.logout();
setAppState({
...appState,
user: null,
});
};
return (
<AppState.Provider
value={{
@@ -137,6 +157,8 @@ export const AppStateProvider = ({
setEditor,
loadPage: loadPage.current,
loadWorkspace: loadWorkspace.current,
login,
logout,
}}
>
{children}
@@ -1,9 +1,8 @@
import { DataCenter, User, WorkspaceInfo } from '@affine/datacenter';
import { DataCenter, User, WorkspaceUnit } from '@affine/datacenter';
import type { EditorContainer } from '@blocksuite/editor';
import type {
Page as StorePage,
Workspace as StoreWorkspace,
PageMeta as StorePageMeta,
} from '@blocksuite/store';
import { MutableRefObject } from 'react';
@@ -18,21 +17,23 @@ export interface PageMeta extends StorePageMeta {
export type AppStateValue = {
dataCenter: DataCenter;
user: User | null;
workspaceList: WorkspaceInfo[];
currentWorkspace: StoreWorkspace;
currentMetaWorkSpace: WorkspaceInfo | null;
currentWorkspaceId: string;
workspaceList: WorkspaceUnit[];
currentWorkspace: WorkspaceUnit | null;
pageList: PageMeta[];
currentPage: StorePage | null;
editor?: EditorContainer | null;
synced: boolean;
isOwner?: boolean;
};
export type AppStateFunction = {
setEditor: MutableRefObject<(page: EditorContainer) => void>;
loadWorkspace: (workspaceId: string) => Promise<StoreWorkspace | null>;
loadWorkspace: (workspaceId: string) => Promise<WorkspaceUnit | null>;
loadPage: (pageId: string) => void;
login: () => Promise<User>;
logout: () => Promise<void>;
};
export type AppStateContext = AppStateValue & AppStateFunction;
@@ -4,7 +4,6 @@ const DEFAULT_WORKSPACE_NAME = 'affine';
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
return dataCenter.createWorkspace({
avatar: 'test',
name: DEFAULT_WORKSPACE_NAME,
});
};
@@ -1,183 +0,0 @@
import { createContext, useContext, useEffect, useState } from 'react';
import type { PropsWithChildren } from 'react';
import { getWorkspaces, User, Workspace } from '@/hooks/mock-data/mock';
type TemporaryContextValue = {
workspaceMetaList: Workspace[];
currentWorkspace: Workspace | null;
user: User | null;
updateWorkspaceMeta: (
workspaceId: string,
workspaceData: {
name?: string;
avatar?: string;
isPublish?: boolean;
type?: 'local' | 'cloud' | 'join';
}
) => void;
createWorkspace: (workspaceName: string) => Workspace;
deleteWorkspace: (workspaceId: string) => void;
setWorkspacePublish: (id: string, isPublish: boolean) => void;
setActiveWorkspace: (workspaceData: Workspace) => void;
login: () => void;
signOut: () => void;
};
type TemporaryContextProps = PropsWithChildren<Record<string, unknown>>;
export const TemporaryContext = createContext<TemporaryContextValue>({
workspaceMetaList: [],
currentWorkspace: null,
user: null,
// eslint-disable-next-line @typescript-eslint/no-empty-function
updateWorkspaceMeta: () => {},
createWorkspace: () => {
return {} as Workspace;
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
deleteWorkspace: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
setWorkspacePublish: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
setActiveWorkspace: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
login: () => {},
// eslint-disable-next-line @typescript-eslint/no-empty-function
signOut: () => {},
});
export const useTemporaryHelper = () => useContext(TemporaryContext);
export const TemporaryHelperProvider = ({
children,
}: PropsWithChildren<TemporaryContextProps>) => {
const [workspaceMetaList, setWorkspaceMetaList] = useState<Workspace[]>([]);
const [currentWorkspace, setCurrentWorkspace] = useState<Workspace | null>(
null
);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
setWorkspaceMetaList(getWorkspaces());
setCurrentWorkspace(
JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}')
);
}, []);
return (
<TemporaryContext.Provider
value={{
workspaceMetaList,
currentWorkspace,
user,
updateWorkspaceMeta: (workspaceId, workspaceData) => {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.map(
(workspace: Workspace) => {
if (workspace.id === workspaceId) {
const workspaceObj = Object.assign(workspace, workspaceData);
return workspaceObj;
}
return workspace;
}
);
localStorage.setItem(
'affine-workspace',
JSON.stringify(newWorkspacesMeta)
);
setWorkspaceMetaList(newWorkspacesMeta);
if (workspaceId === currentWorkspace?.id) {
setCurrentWorkspace(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
newWorkspacesMeta.find(v => v.id === currentWorkspace.id)
);
}
},
createWorkspace: workspaceName => {
const workspaceData = {
name: workspaceName,
id: 'workspace-' + Date.now(),
isPublish: false,
isLocal: true,
avatar: '',
type: 'local',
} as Workspace;
const workspacesMeta = getWorkspaces();
workspacesMeta.push(workspaceData);
localStorage.setItem(
'affine-workspace',
JSON.stringify(workspacesMeta)
);
setWorkspaceMetaList([...workspacesMeta]);
return workspaceData;
},
deleteWorkspace: workspaceId => {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.filter(() => {
return workspaceId !== workspaceId;
});
localStorage.setItem(
'affine-workspace',
JSON.stringify(newWorkspacesMeta)
);
setWorkspaceMetaList(workspacesMeta);
},
setWorkspacePublish: (id, isPublish) => {
const workspacesMeta = getWorkspaces();
const newWorkspacesMeta = workspacesMeta.map(
(workspace: Workspace) => {
if (workspace.id === id) {
workspace.isPublish = isPublish;
}
return workspace;
}
);
localStorage.setItem(
'affine-workspace',
JSON.stringify(newWorkspacesMeta)
);
setWorkspaceMetaList(workspacesMeta);
},
setActiveWorkspace(workspaceData) {
localStorage.setItem(
'affine-active-workspace',
JSON.stringify(workspaceData)
);
setCurrentWorkspace(workspaceData);
},
login: () => {
const userInfo = {
name: 'Diamond',
id: 'ttt',
email: 'diamond.shx@gmail.com',
avatar: 'string',
};
localStorage.setItem('affine-user', JSON.stringify(userInfo));
setUser(userInfo);
},
signOut(): void {
localStorage.removeItem('affine-user');
setUser(null);
},
}}
>
{children}
</TemporaryContext.Provider>
);
};
export default TemporaryHelperProvider;
+1 -2
View File
@@ -9,7 +9,7 @@ export const StyledTable = styled.table<{ tableLayout: 'auto' | 'fixed' }>(
tableLayout,
width: '100%',
borderCollapse: 'separate',
borderSpacing: '0 25px',
borderSpacing: '0',
};
}
);
@@ -52,7 +52,6 @@ export const StyledTableHead = styled.thead(() => {
export const StyledTableRow = styled.tr(({ theme }) => {
return {
marginBottom: '25px',
td: {
transition: 'background .15s',
},