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

Conflicts:
	package.json
	packages/app/src/components/create-workspace/index.tsx
	packages/app/src/components/workspace-modal/index.tsx
	packages/app/src/components/workspace-modal/languageMenu.tsx
	packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx
	packages/app/src/components/workspace-slider-bar/index.tsx
	packages/app/src/pages/_app.tsx
	pnpm-lock.yaml
This commit is contained in:
linonetwo
2023-01-10 18:23:25 +08:00
58 changed files with 1188 additions and 3572 deletions

View File

@@ -46,7 +46,6 @@
"jest": "^29.3.1", "jest": "^29.3.1",
"lint-staged": "^13.1.0", "lint-staged": "^13.1.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"ts-jest": "^29.0.3",
"typescript": "^4.9.3" "typescript": "^4.9.3"
}, },
"eslintConfig": { "eslintConfig": {

View File

@@ -1,26 +1,29 @@
import { styled } from '@/styles'; import { styled } from '@/styles';
import { useRouter } from 'next/router';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
import { useState } from 'react'; import { useState } from 'react';
import Input from '@/ui/input'; import Input from '@/ui/input';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { KeyboardEvent } from 'react'; import { KeyboardEvent } from 'react';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
interface ICloseParams { import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
workspaceId?: string;
}
interface ModalProps { interface ModalProps {
open: boolean; open: boolean;
onClose: (opts: ICloseParams) => void; onClose: () => void;
} }
export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => { export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
const [workspaceName, setWorkspaceName] = useState(''); const [workspaceName, setWorkspaceName] = useState('');
const { createWorkspace, setActiveWorkspace } = useTemporaryHelper(); const { createWorkspace } = useWorkspaceHelper();
const handleCreateWorkspace = () => { const router = useRouter();
const workspace = createWorkspace(workspaceName); const handleCreateWorkspace = async () => {
onClose({ workspaceId: workspace.id }); const workspace = await createWorkspace(workspaceName);
setActiveWorkspace(workspace); if (workspace && workspace.room) {
router.replace(`/workspace/${workspace.room}`);
onClose();
} else {
console.log('create error');
}
}; };
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => { const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
@@ -39,7 +42,7 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
top={6} top={6}
right={6} right={6}
onClick={() => { onClick={() => {
onClose({}); onClose();
}} }}
/> />
</Header> </Header>

View File

@@ -0,0 +1,71 @@
import { useEffect, useRef } from 'react';
import type { Page, Workspace } from '@blocksuite/store';
import '@blocksuite/blocks';
import { EditorContainer } from '@blocksuite/editor';
import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-v2.0.md';
import { styled } from '@/styles';
const StyledEditorContainer = styled('div')(() => {
return {
height: 'calc(100vh - 60px)',
padding: '0 32px',
};
});
type Props = {
page: Page;
workspace: Workspace;
setEditor: (editor: EditorContainer) => void;
};
export const Editor = ({ page, workspace, setEditor }: Props) => {
const editorContainer = useRef<HTMLDivElement>(null);
// const { currentWorkspace, currentPage, setEditor } = useAppState();
useEffect(() => {
const ret = () => {
const node = editorContainer.current;
while (node?.firstChild) {
node.removeChild(node.firstChild);
}
};
const editor = new EditorContainer();
editor.page = page;
editorContainer.current?.appendChild(editor);
if (page.isEmpty) {
const isFirstPage = workspace?.meta.pageMetas.length === 1;
// Can not use useCurrentPageMeta to get new title, cause meta title will trigger rerender, but the second time can not remove title
const { title: metaTitle } = page.meta;
const title = metaTitle
? metaTitle
: isFirstPage
? 'Welcome to AFFiNE Alpha "Abbey Wood"'
: '';
workspace?.setPageMeta(page.id, { title });
const pageId = page.addBlock({
flavour: 'affine:page',
title,
});
page.addBlock({ flavour: 'affine:surface' });
const frameId = page.addBlock({ flavour: 'affine:frame' }, pageId);
page.addBlock({ flavour: 'affine:frame' }, pageId);
// If this is a first page in workspace, init an introduction markdown
if (isFirstPage) {
editor.clipboard.importMarkdown(exampleMarkdown, `${frameId}`);
workspace.setPageMeta(page.id, { title });
page.resetHistory();
}
page.resetHistory();
}
setEditor(editor);
document.title = page?.meta.title || 'Untitled';
return ret;
}, [workspace, page, setEditor]);
return <StyledEditorContainer ref={editorContainer} />;
};
export default Editor;

View File

@@ -6,7 +6,7 @@ import {
StyledTitleWrapper, StyledTitleWrapper,
} from './styles'; } from './styles';
import { Content } from '@/ui/layout'; import { Content } from '@/ui/layout';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import EditorModeSwitch from '@/components/editor-mode-switch'; import EditorModeSwitch from '@/components/editor-mode-switch';
import QuickSearchButton from './QuickSearchButton'; import QuickSearchButton from './QuickSearchButton';
import Header from './Header'; import Header from './Header';

View File

@@ -1,6 +1,6 @@
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons'; import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
import { useModal } from '@/providers/GlobalModalProvider'; import { useModal } from '@/providers/GlobalModalProvider';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import { IconButton } from '@/ui/button'; import { IconButton } from '@/ui/button';
export const SyncUser = () => { export const SyncUser = () => {

View File

@@ -4,7 +4,7 @@ import { Button } from '@/ui/button';
import { Wrapper, Content } from '@/ui/layout'; import { Wrapper, Content } from '@/ui/layout';
import Loading from '@/components/loading'; import Loading from '@/components/loading';
import { usePageHelper } from '@/hooks/use-page-helper'; import { usePageHelper } from '@/hooks/use-page-helper';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
// import { Tooltip } from '@/ui/tooltip'; // import { Tooltip } from '@/ui/tooltip';

View File

@@ -1,11 +1,11 @@
import { getDataCenter } from '@affine/datacenter'; // import { getDataCenter } from '@affine/datacenter';
import { styled } from '@/styles'; import { styled } from '@/styles';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
import { useModal } from '@/providers/GlobalModalProvider'; // import { useModal } from '@/providers/GlobalModalProvider';
import { GoogleIcon, StayLogOutIcon } from './Icons'; import { GoogleIcon, StayLogOutIcon } from './Icons';
export const GoogleLoginButton = () => { export const GoogleLoginButton = () => {
const { triggerLoginModal } = useModal(); // const { triggerLoginModal } = useModal();
return ( return (
<StyledGoogleButton <StyledGoogleButton
onClick={() => { onClick={() => {

View File

@@ -20,7 +20,7 @@ import DateCell from '@/components/page-list/DateCell';
import { IconButton } from '@/ui/button'; import { IconButton } from '@/ui/button';
import { Tooltip } from '@/ui/tooltip'; import { Tooltip } from '@/ui/tooltip';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import { toast } from '@/ui/toast'; import { toast } from '@/ui/toast';
import { usePageHelper } from '@/hooks/use-page-helper'; import { usePageHelper } from '@/hooks/use-page-helper';
import { useTheme } from '@/providers/ThemeProvider'; import { useTheme } from '@/providers/ThemeProvider';

View File

@@ -17,11 +17,10 @@ export const Input = (props: {
const [isComposition, setIsComposition] = useState(false); const [isComposition, setIsComposition] = useState(false);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const { currentWorkspaceId, workspacesMeta, currentWorkspace } = const { currentWorkspaceId, workspaceList, currentWorkspace } = useAppState();
useAppState(); const isPublish = workspaceList.find(
const isPublic = workspacesMeta.find(
meta => String(meta.id) === String(currentWorkspaceId) meta => String(meta.id) === String(currentWorkspaceId)
)?.public; )?.isPublish;
useEffect(() => { useEffect(() => {
inputRef.current?.addEventListener( inputRef.current?.addEventListener(
'blur', 'blur',
@@ -80,7 +79,7 @@ export const Input = (props: {
} }
}} }}
placeholder={ placeholder={
isPublic isPublish
? `Search in ${currentWorkspace?.meta.name}` ? `Search in ${currentWorkspace?.meta.name}`
: 'Quick Search...' : 'Quick Search...'
} }

View File

@@ -22,16 +22,14 @@ const isMac = () => {
return getUaHelper().isMacOs; return getUaHelper().isMacOs;
}; };
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
const { currentMetaWorkSpace } = useAppState();
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [showCreatePage, setShowCreatePage] = useState(true); const [showCreatePage, setShowCreatePage] = useState(true);
const { triggerQuickSearchModal } = useModal(); const { triggerQuickSearchModal } = useModal();
const { currentWorkspaceId, workspacesMeta } = useAppState();
const currentWorkspace = workspacesMeta.find( const isPublish = currentMetaWorkSpace?.isPublish;
meta => String(meta.id) === String(currentWorkspaceId)
);
const isPublic = currentWorkspace?.public;
// Add ‘⌘+K’ shortcut keys as switches // Add ‘⌘+K’ shortcut keys as switches
useEffect(() => { useEffect(() => {
@@ -98,7 +96,7 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
setShowCreatePage={setShowCreatePage} setShowCreatePage={setShowCreatePage}
/> />
</StyledContent> </StyledContent>
{isPublic ? ( {isPublish ? (
<></> <></>
) : showCreatePage ? ( ) : showCreatePage ? (
<> <>

View File

@@ -1,15 +1,33 @@
import { useAppState } from '@/providers/app-state-provider';
import { stringToColour } from '@/utils'; import { stringToColour } from '@/utils';
import { useEffect, useState } from 'react';
interface IWorkspaceAvatar { interface IWorkspaceAvatar {
size: number; size: number;
name: string; name: string;
avatar: string;
} }
export const WorkspaceAvatar = (props: IWorkspaceAvatar) => { export const WorkspaceAvatar = (props: IWorkspaceAvatar) => {
const size = props.size || 20; const size = props.size || 20;
const sizeStr = size + 'px'; const sizeStr = size + 'px';
return ( return (
<> <>
{props.avatar ? (
<div
style={{
width: sizeStr,
height: sizeStr,
border: '1px solid #fff',
color: '#fff',
borderRadius: '50%',
overflow: 'hidden',
}}
>
<img src={props.avatar} alt="" />
</div>
) : (
<div <div
style={{ style={{
width: sizeStr, width: sizeStr,
@@ -25,6 +43,7 @@ export const WorkspaceAvatar = (props: IWorkspaceAvatar) => {
> >
{(props.name || 'AFFiNE').substring(0, 1)} {(props.name || 'AFFiNE').substring(0, 1)}
</div> </div>
)}
</> </>
); );
}; };

View File

@@ -10,27 +10,24 @@ import {
UsersIcon, UsersIcon,
AddIcon, AddIcon,
} from '@blocksuite/icons'; } from '@blocksuite/icons';
import { useConfirm } from '@/providers/ConfirmProvider'; // import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast'; import { toast } from '@/ui/toast';
import { WorkspaceAvatar } from '@/components/workspace-avatar'; import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { useAppState } from '@/providers/app-state-provider';
import { useRouter } from 'next/router';
import { useUserHelper } from '@/hooks/use-user-helper';
interface LoginModalProps { interface WorkspaceModalProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
} }
export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => { export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false); const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
const { confirm } = useConfirm(); // const { confirm } = useConfirm();
const { const { workspaceList, currentWorkspace } = useAppState();
user, const { login, user } = useUserHelper();
login, const router = useRouter();
workspaceMetaList,
setActiveWorkspace,
updateWorkspaceMeta,
} = useTemporaryHelper();
return ( return (
<div> <div>
<Modal open={open} onClose={onClose}> <Modal open={open} onClose={onClose}>
@@ -51,13 +48,14 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
</Header> </Header>
<Content> <Content>
<WorkspaceList> <WorkspaceList>
{workspaceMetaList.map((item, index) => { {workspaceList.map((item, index) => {
return ( return (
<WorkspaceItem <WorkspaceItem
onClick={() => { onClick={() => {
setActiveWorkspace(item); router.replace(`/workspace/${item.id}`);
onClose(); onClose();
}} }}
active={item.id === currentWorkspace.room}
key={index} key={index}
> >
<span style={{ width: '100px' }}> <span style={{ width: '100px' }}>
@@ -69,7 +67,11 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
marginRight: '10px', marginRight: '10px',
}} }}
> >
<WorkspaceAvatar size={50} name={item.name} /> <WorkspaceAvatar
size={50}
name={item.name}
avatar={item.avatar ?? ''}
/>
</div> </div>
<span <span
@@ -93,10 +95,10 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
top: '20px', top: '20px',
}} }}
> >
{(item.type === 'local' || !item.type) && ( {(item.provider === 'local' || !item.provider) && (
<CloudUnsyncedIcon fontSize={24} /> <CloudUnsyncedIcon fontSize={24} />
)} )}
{item.type === 'cloud' && ( {item.provider === 'affine' && (
<CloudInsyncIcon fontSize={24} /> <CloudInsyncIcon fontSize={24} />
)} )}
{item.isPublish && <UsersIcon fontSize={24} />} {item.isPublish && <UsersIcon fontSize={24} />}
@@ -154,26 +156,26 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
</Footer> </Footer>
<CreateWorkspaceModal <CreateWorkspaceModal
open={createWorkspaceOpen} open={createWorkspaceOpen}
onClose={({ workspaceId }) => { onClose={() => {
setCreateWorkspaceOpen(false); setCreateWorkspaceOpen(false);
onClose(); onClose();
confirm({ // confirm({
title: 'Enable AFFiNE Cloud?', // title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via 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', // confirmText: user ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip', // cancelText: 'Skip',
}).then(confirm => { // }).then(confirm => {
if (confirm) { // if (confirm) {
if (user) { // if (user) {
workspaceId && // // workspaceId &&
updateWorkspaceMeta(workspaceId, { isPublish: true }); // // updateWorkspaceMeta(workspaceId, { isPublish: true });
} else { // } else {
login(); // // login();
workspaceId && // // workspaceId &&
updateWorkspaceMeta(workspaceId, { isPublish: true }); // // updateWorkspaceMeta(workspaceId, { isPublish: true });
} // }
} // }
}); // });
}} }}
></CreateWorkspaceModal> ></CreateWorkspaceModal>
</ModalWrapper> </ModalWrapper>
@@ -218,11 +220,17 @@ const WorkspaceList = styled('div')({
gridTemplateColumns: 'repeat(2, 1fr)', gridTemplateColumns: 'repeat(2, 1fr)',
}); });
const WorkspaceItem = styled('div')({ export const WorkspaceItem = styled.div<{
active: boolean;
}>(({ theme, active }) => {
const backgroundColor = active ? theme.colors.hoverBackground : 'transparent';
return {
cursor: 'pointer', cursor: 'pointer',
padding: '8px', padding: '8px',
border: '1px solid #eee', border: '1px solid #eee',
backgroundColor: backgroundColor,
':hover': { ':hover': {
background: '#eee', background: theme.colors.hoverBackground,
}, },
};
}); });

View File

@@ -1,5 +1,5 @@
import { Workspace } from '@/hooks/mock-data/mock';
import { styled } from '@/styles'; import { styled } from '@/styles';
import { WorkspaceInfo } from '@affine/datacenter';
export const ExportPageTitleContainer = styled('div')(() => { export const ExportPageTitleContainer = styled('div')(() => {
return { return {
@@ -9,7 +9,7 @@ export const ExportPageTitleContainer = styled('div')(() => {
flex: 1, flex: 1,
}; };
}); });
export const ExportPage = ({ workspace }: { workspace: Workspace }) => { export const ExportPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
return ( return (
<ExportPageTitleContainer> <ExportPageTitleContainer>
Export Workspace{' '} Export Workspace{' '}

View File

@@ -10,6 +10,7 @@ import {
StyledMemberRoleContainer, StyledMemberRoleContainer,
StyledMemberTitleContainer, StyledMemberTitleContainer,
StyledMoreVerticalButton, StyledMoreVerticalButton,
StyledPublishExplanation,
} from './style'; } from './style';
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons'; import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -17,20 +18,24 @@ import { Button, IconButton } from '@/ui/button';
import { InviteMembers } from '../invite-members/index'; import { InviteMembers } from '../invite-members/index';
import { Menu, MenuItem } from '@/ui/menu'; import { Menu, MenuItem } from '@/ui/menu';
import { Empty } from '@/ui/empty'; import { Empty } from '@/ui/empty';
import { // import {
deleteMember, // deleteMember,
getMembers, // getMembers,
User, // User,
Workspace, // Workspace,
} from '@/hooks/mock-data/mock'; // } from '@/hooks/mock-data/mock';
import { WorkspaceInfo } from '@affine/datacenter';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { StyledMemberWarp } from './general/style'; import { StyledMemberWarp } from './general/style';
import { useConfirm } from '@/providers/ConfirmProvider'; import { useConfirm } from '@/providers/ConfirmProvider';
// import { useAppState } from '@/providers/app-state-provider'; // import { useAppState } from '@/providers/app-state-provider';
export const MembersPage = ({ workspace }: { workspace: Workspace }) => { export const MembersPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
const [isInviteModalShow, setIsInviteModalShow] = useState(false); const [isInviteModalShow, setIsInviteModalShow] = useState(false);
const [members, setMembers] = useState<User[]>([]); const [members, setMembers] = useState<[{ name: string; email: string }]>([
{ name: 'affine', email: 'tttt' },
]);
console.log('setMembers: ', setMembers);
const { user, login, updateWorkspaceMeta } = useTemporaryHelper(); const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
const { confirm } = useConfirm(); const { confirm } = useConfirm();
// const refreshMembers = useCallback(() => { // const refreshMembers = useCallback(() => {
@@ -48,8 +53,9 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
// }); // });
// }, [workspace.id]); // }, [workspace.id]);
const setMembersList = () => { const setMembersList = () => {
const members = getMembers(workspace.id); // setMembers([]);
members && setMembers(members); // const members = getMembers(workspace.id);
// members && setMembers(members);
}; };
useEffect(() => { useEffect(() => {
setMembersList(); setMembersList();
@@ -58,7 +64,7 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
return ( return (
<div> <div>
{workspace.type === 'cloud' ? ( {workspace.provider === 'cloud' ? (
<> <>
<StyledMemberTitleContainer> <StyledMemberTitleContainer>
<StyledMemberNameContainer> <StyledMemberNameContainer>
@@ -67,13 +73,6 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer> <StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
</StyledMemberTitleContainer> </StyledMemberTitleContainer>
<StyledMemberListContainer> <StyledMemberListContainer>
{members.length === 0 && (
<Empty
width={648}
sx={{ marginTop: '60px' }}
height={300}
></Empty>
)}
{members.length ? ( {members.length ? (
members.map((member, index) => { members.map((member, index) => {
return ( return (
@@ -103,7 +102,7 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
<> <>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
deleteMember(workspace.id, 0); // deleteMember(workspace.id, 0);
setMembersList(); setMembersList();
// confirm({ // confirm({
// title: 'Delete Member?', // title: 'Delete Member?',
@@ -142,7 +141,11 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
); );
}) })
) : ( ) : (
<></> <Empty
width={648}
sx={{ marginTop: '60px' }}
height={300}
></Empty>
)} )}
</StyledMemberListContainer> </StyledMemberListContainer>
<StyledMemberButtonContainer> <StyledMemberButtonContainer>
@@ -171,10 +174,8 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
</> </>
) : ( ) : (
<StyledMemberWarp> <StyledMemberWarp>
<div style={{ flex: 1 }}> <>Collaborating with other members requires AFFiNE Cloud service.</>
Collaborating with other members requires AFFiNE Cloud service. <StyledPublishExplanation>
</div>
<div style={{ height: '40px' }}>
<Button <Button
type="primary" type="primary"
shape="circle" shape="circle"
@@ -198,7 +199,7 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
> >
Enable AFFiNE Cloud Enable AFFiNE Cloud
</Button> </Button>
</div> </StyledPublishExplanation>
</StyledMemberWarp> </StyledMemberWarp>
)} )}
</div> </div>

View File

@@ -9,20 +9,20 @@ import {
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
import Input from '@/ui/input'; import Input from '@/ui/input';
import { toast } from '@/ui/toast'; import { toast } from '@/ui/toast';
import { Workspace } from '@/hooks/mock-data/mock';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { useConfirm } from '@/providers/ConfirmProvider'; import { useConfirm } from '@/providers/ConfirmProvider';
// import { useAppState } from '@/providers/app-state-provider3';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { WorkspaceInfo } from '@affine/datacenter';
export const PublishPage = ({ workspace }: { workspace: Workspace }) => { export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
console.log('workspace: ', workspace);
const shareUrl = const shareUrl =
window.location.host + '/workspace/' + workspace.id + '?share=true'; window.location.host + '/workspace/' + workspace.id + '?share=true';
const { publishWorkspace } = useWorkspaceHelper();
const { login, updateWorkspaceMeta, user } = useTemporaryHelper();
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const togglePublic = (flag: boolean) => { const togglePublic = (flag: boolean) => {
updateWorkspaceMeta(workspace.id, { isPublish: flag }); workspace.id && publishWorkspace(workspace?.id, flag);
}; };
const copyUrl = () => { const copyUrl = () => {
@@ -34,22 +34,23 @@ export const PublishPage = ({ workspace }: { workspace: Workspace }) => {
confirm({ confirm({
title: 'Enable AFFiNE Cloud?', title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via 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', confirmText:
workspace.provider === 'local' ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip', cancelText: 'Skip',
}).then(confirm => { }).then(confirm => {
if (confirm) { if (confirm) {
if (user) { // if (user) {
updateWorkspaceMeta(workspace.id, { type: 'cloud' }); // updateWorkspaceMeta(workspace.id, { type: 'cloud' });
} else { // } else {
login(); // login();
updateWorkspaceMeta(workspace.id, { type: 'cloud' }); // updateWorkspaceMeta(workspace.id, { type: 'cloud' });
} // }
} }
}); });
}; };
return ( return (
<> <>
{workspace.type === 'cloud' ? ( {workspace.provider === 'cloud' ? (
<div> <div>
<StyledPublishContent> <StyledPublishContent>
{workspace?.isPublish ? ( {workspace?.isPublish ? (

View File

@@ -6,28 +6,48 @@ import {
import { DownloadIcon } from '@blocksuite/icons'; import { DownloadIcon } from '@blocksuite/icons';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
import { Menu, MenuItem } from '@/ui/menu'; import { Menu, MenuItem } from '@/ui/menu';
import { deleteMember, Workspace } from '@/hooks/mock-data/mock'; import { WorkspaceInfo } from '@affine/datacenter';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { useAppState } from '@/providers/app-state-provider';
export const SyncPage = ({ workspace }: { workspace: Workspace }) => { import { useConfirm } from '@/providers/ConfirmProvider';
const { currentWorkspace, updateWorkspaceMeta } = useTemporaryHelper(); 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);
const { enableWorkspace } = useWorkspaceHelper();
const { currentWorkspace } = useAppState();
const { confirm } = useConfirm();
const { user, login } = useUserHelper();
const router = useRouter();
return ( return (
<div> <div>
<StyledPublishContent> <StyledPublishContent>
{currentWorkspace?.type === 'local' ? ( {workspace?.provider === 'local' ? (
<> <>
<StyledPublishExplanation> <StyledPublishExplanation>
{currentWorkspace.name} is Local Workspace. All data is stored on {workspace.name ?? 'Affine'} is Local Workspace. All data is
the current device. You can enable AFFiNE Cloud for this workspace stored on the current device. You can enable AFFiNE Cloud for this
to keep data in sync with the cloud. workspace to keep data in sync with the cloud.
</StyledPublishExplanation> </StyledPublishExplanation>
<StyledPublishCopyContainer> <StyledPublishCopyContainer>
<Button <Button
onClick={() => { onClick={() => {
updateWorkspaceMeta(currentWorkspace.id, { confirm({
type: 'cloud', 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');
}
}); });
}} }}
type="primary" type="primary"
@@ -40,8 +60,8 @@ export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
) : ( ) : (
<> <>
<StyledPublishExplanation> <StyledPublishExplanation>
<code>{currentWorkspace && currentWorkspace.name}</code> is Cloud <code>{workspace.name ?? 'Affine'}</code> is Cloud Workspace. All
Workspace. All data will be synchronized and saved to the AFFiNE data will be synchronized and saved to the AFFiNE
</StyledPublishExplanation> </StyledPublishExplanation>
<StyledPublishCopyContainer> <StyledPublishCopyContainer>
<Menu <Menu
@@ -49,7 +69,7 @@ export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
<> <>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
deleteMember(workspace.id, 0); // deleteMember(workspace.id, 0);
}} }}
icon={<DownloadIcon />} icon={<DownloadIcon />}
> >
@@ -57,7 +77,7 @@ export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
deleteMember(workspace.id, 0); // deleteMember(workspace.id, 0);
}} }}
icon={<DownloadIcon />} icon={<DownloadIcon />}
> >

View File

@@ -1,164 +0,0 @@
import Modal, { ModalCloseButton } from '@/ui/modal';
import {
StyledSettingContainer,
StyledSettingContent,
StyledSettingSidebar,
StyledSettingSidebarHeader,
StyledSettingTabContainer,
StyledSettingTagIconContainer,
WorkspaceSettingTagItem,
} from './style';
import {
EditIcon,
UsersIcon,
PublishIcon,
CloudInsyncIcon,
} from '@blocksuite/icons';
import { useEffect, useState } from 'react';
import { GeneralPage } from './general';
import { MembersPage } from './MembersPage';
import { PublishPage } from './PublishPage';
import { ExportPage } from './ExportPage';
import { SyncPage } from './SyncPage';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
enum ActiveTab {
'general' = 'general',
'members' = 'members',
'publish' = 'publish',
'sync' = 'sync',
'export' = 'export',
}
type SettingTabProps = {
activeTab: ActiveTab;
onTabChange?: (tab: ActiveTab) => void;
};
type WorkspaceSettingProps = {
isShow: boolean;
onClose?: () => void;
};
const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => {
return (
<StyledSettingTabContainer>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.general}
onClick={() => {
onTabChange && onTabChange(ActiveTab.general);
}}
>
<StyledSettingTagIconContainer>
<EditIcon />
</StyledSettingTagIconContainer>
General
</WorkspaceSettingTagItem>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.sync}
onClick={() => {
onTabChange && onTabChange(ActiveTab.sync);
}}
>
<StyledSettingTagIconContainer>
<CloudInsyncIcon />
</StyledSettingTagIconContainer>
Sync
</WorkspaceSettingTagItem>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.members}
onClick={() => {
onTabChange && onTabChange(ActiveTab.members);
}}
>
<StyledSettingTagIconContainer>
<UsersIcon />
</StyledSettingTagIconContainer>
Collaboration
</WorkspaceSettingTagItem>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.publish}
onClick={() => {
onTabChange && onTabChange(ActiveTab.publish);
}}
>
<StyledSettingTagIconContainer>
<PublishIcon />
</StyledSettingTagIconContainer>
Publish
</WorkspaceSettingTagItem>
<WorkspaceSettingTagItem
isActive={activeTab === ActiveTab.export}
onClick={() => {
onTabChange && onTabChange(ActiveTab.export);
}}
>
<StyledSettingTagIconContainer>
<PublishIcon />
</StyledSettingTagIconContainer>
Export
</WorkspaceSettingTagItem>
</StyledSettingTabContainer>
);
};
export const WorkspaceSetting = ({
isShow,
onClose,
}: WorkspaceSettingProps) => {
// const { workspaces } = useAppState();
const [activeTab, setActiveTab] = useState<ActiveTab>(ActiveTab.general);
const handleTabChange = (tab: ActiveTab) => {
setActiveTab(tab);
};
const { currentWorkspace } = useTemporaryHelper();
const handleClickClose = () => {
onClose && onClose();
};
const isOwner = true;
useEffect(() => {
// reset tab when modal is closed
if (!isShow) {
setActiveTab(ActiveTab.general);
}
}, [isShow]);
return (
<Modal open={isShow}>
<StyledSettingContainer>
<ModalCloseButton onClick={handleClickClose} />
{isOwner ? (
<StyledSettingSidebar>
<StyledSettingSidebarHeader>
Workspace Settings
</StyledSettingSidebarHeader>
<WorkspaceSettingTab
activeTab={activeTab}
onTabChange={handleTabChange}
/>
</StyledSettingSidebar>
) : null}
<StyledSettingContent>
{activeTab === ActiveTab.general && currentWorkspace && (
<GeneralPage workspace={currentWorkspace} />
)}
{activeTab === ActiveTab.sync && currentWorkspace && (
<SyncPage workspace={currentWorkspace} />
)}
{activeTab === ActiveTab.members && currentWorkspace && (
<MembersPage workspace={currentWorkspace} />
)}
{activeTab === ActiveTab.publish && currentWorkspace && (
<PublishPage workspace={currentWorkspace} />
)}
{activeTab === ActiveTab.export && currentWorkspace && (
<ExportPage workspace={currentWorkspace} />
)}
</StyledSettingContent>
</StyledSettingContainer>
</Modal>
);
};

View File

@@ -9,26 +9,21 @@ import { StyledSettingH2 } from '../style';
import { useState } from 'react'; import { useState } from 'react';
import { Button, TextButton } from '@/ui/button'; import { Button, TextButton } from '@/ui/button';
import Input from '@/ui/input'; import Input from '@/ui/input';
// import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceDelete } from './delete'; import { WorkspaceDelete } from './delete';
// import { debounce } from '@/utils';
import { WorkspaceLeave } from './leave'; import { WorkspaceLeave } from './leave';
import { Upload } from '@/components/file-upload'; import { Upload } from '@/components/file-upload';
import { Workspace } from '@/hooks/mock-data/mock';
import { WorkspaceAvatar } from '@/components/workspace-avatar'; import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { WorkspaceInfo } from '@affine/datacenter';
export const GeneralPage = ({ workspace }: { workspace: Workspace }) => { import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
// const { refreshWorkspacesMeta } = useAppState(); export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
const { updateWorkspaceMeta } = useTemporaryHelper();
const [showDelete, setShowDelete] = useState<boolean>(false); const [showDelete, setShowDelete] = useState<boolean>(false);
const [showLeave, setShowLeave] = useState<boolean>(false); const [showLeave, setShowLeave] = useState<boolean>(false);
const [uploading, setUploading] = useState<boolean>(false); const [uploading, setUploading] = useState<boolean>(false);
const [workspaceName, setWorkspaceName] = useState<string>(''); const [workspaceName, setWorkspaceName] = useState<string>(workspace.name);
// const debouncedRefreshWorkspacesMeta = debounce(() => { const { currentWorkspace } = useAppState();
// refreshWorkspacesMeta(); const { updateWorkspace } = useWorkspaceHelper();
// }, 100);
const isOwner = true; const isOwner = true;
const handleChangeWorkSpaceName = (newName: string) => { const handleChangeWorkSpaceName = (newName: string) => {
setWorkspaceName(newName); setWorkspaceName(newName);
}; };
@@ -46,24 +41,15 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
setShowLeave(false); setShowLeave(false);
}; };
const handleUpdateWorkspaceName = () => { const handleUpdateWorkspaceName = () => {
workspace && updateWorkspaceMeta(workspace.id, { name: workspaceName }); console.log('currentWorkspace: ', currentWorkspace);
updateWorkspace({ name: workspaceName }, currentWorkspace);
}; };
const fileChange = async (file: File) => { const fileChange = async (file: File) => {
console.log('file: ', file); // console.log('file: ', file);
setUploading(true); // setUploading(true);
// const blob = new Blob([file], { type: file.type }); const blob = new Blob([file], { type: file.type });
// const blobId = await getDataCenter() updateWorkspace({ avatarBlob: blob }, currentWorkspace);
// .then(dc => dc.apis.uploadBlob({ blob }))
// .finally(() => {
// setUploading(false);
// });
// if (blobId) {
// currentWorkspace?.meta.setAvatar(blobId);
// // workspaces[workspace.id]?.meta.setAvatar(blobId);
// setUploading(false);
// debouncedRefreshWorkspacesMeta();
// }
}; };
return workspace ? ( return workspace ? (
@@ -73,11 +59,14 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
<div <div
style={{ style={{
float: 'left', float: 'left',
marginRight: '20px',
marginRight: '5px',
}} }}
> >
<WorkspaceAvatar size={60} name={workspace.name} /> <WorkspaceAvatar
size={60}
name={workspace.name}
avatar={workspace.avatar ?? ''}
/>
</div> </div>
<Upload <Upload
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg" accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
@@ -101,30 +90,14 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
onClick={() => { onClick={() => {
handleUpdateWorkspaceName(); handleUpdateWorkspaceName();
}} }}
style={{ marginLeft: '10px' }} style={{ marginLeft: '0px' }}
> >
✔️ ✔️
</TextButton> </TextButton>
</StyledSettingInputContainer> </StyledSettingInputContainer>
{/* {userInfo ? (
<div>
<StyledSettingH2 marginTop={20}>Workspace Owner</StyledSettingH2>
<StyledSettingInputContainer>
<Input
width={327}
disabled
value={userInfo?.name}
placeholder="Workspace Owner"
></Input>
</StyledSettingInputContainer>
</div>
) : (
''
)} */}
<StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2> <StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2>
<StyledSettingInputContainer> <StyledSettingInputContainer>
<code>{workspace.type} </code> <code>{workspace.provider} </code>
</StyledSettingInputContainer> </StyledSettingInputContainer>
<StyledDeleteButtonContainer> <StyledDeleteButtonContainer>
{isOwner ? ( {isOwner ? (

View File

@@ -15,13 +15,14 @@ import { useRouter } from 'next/router';
import { import {
deleteWorkspace, deleteWorkspace,
getWorkspaces, getWorkspaces,
Workspace, // Workspace,
} from '@/hooks/mock-data/mock'; } from '@/hooks/mock-data/mock';
import { WorkspaceInfo } from '@affine/datacenter';
interface WorkspaceDeleteProps { interface WorkspaceDeleteProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
workspace: Workspace; workspace: WorkspaceInfo;
} }
export const WorkspaceDelete = ({ export const WorkspaceDelete = ({
@@ -55,7 +56,7 @@ export const WorkspaceDelete = ({
<StyledModalWrapper> <StyledModalWrapper>
<ModalCloseButton onClick={onClose} /> <ModalCloseButton onClick={onClose} />
<StyledModalHeader>Delete Workspace</StyledModalHeader> <StyledModalHeader>Delete Workspace</StyledModalHeader>
{workspace.type === 'local' ? ( {workspace.provider === 'local' ? (
<StyledTextContent> <StyledTextContent>
Deleting ( Deleting (
<StyledWorkspaceName>{workspace.name}</StyledWorkspaceName>) cannot <StyledWorkspaceName>{workspace.name}</StyledWorkspaceName>) cannot

View File

@@ -1 +1 @@
export * from './Delete'; export { WorkspaceDelete } from './Delete';

View File

@@ -1 +1 @@
export * from './General'; export { GeneralPage } from './General';

View File

@@ -8,7 +8,7 @@ import {
import { ModalCloseButton } from '@/ui/modal'; import { ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
// import { getDataCenter } from '@affine/datacenter'; // import { getDataCenter } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider'; // import { useAppState } from '@/providers/app-state-provider';
interface WorkspaceDeleteProps { interface WorkspaceDeleteProps {
open: boolean; open: boolean;
@@ -22,8 +22,9 @@ export const WorkspaceLeave = ({
onClose, onClose,
workspaceId, workspaceId,
}: WorkspaceDeleteProps) => { }: WorkspaceDeleteProps) => {
console.log('workspaceId: ', workspaceId);
// const router = useRouter(); // const router = useRouter();
const { refreshWorkspacesMeta } = useAppState(); // const { refreshWorkspacesMeta } = useAppState();
const handleLeave = async () => { const handleLeave = async () => {
// const dc = await getDataCenter(); // const dc = await getDataCenter();
// await dc.apis.leaveWorkspace({ id: workspaceId }); // await dc.apis.leaveWorkspace({ id: workspaceId });

View File

@@ -1 +1 @@
export * from './Leave'; export { WorkspaceLeave } from './Leave';

View File

@@ -30,8 +30,7 @@ export const StyledSettingAvatar = styled(MuiAvatar)(() => {
export const StyledMemberWarp = styled('div')(() => { export const StyledMemberWarp = styled('div')(() => {
return { return {
display: 'flex', display: 'flex',
height: '500px',
flexDirection: 'column', flexDirection: 'column',
padding: '60px 0', padding: '40px 0',
}; };
}); });

View File

@@ -1 +1,5 @@
export * from './WorkspaceSetting'; export * from './general';
export * from './ExportPage';
export * from './MembersPage';
export * from './SyncPage';
export * from './PublishPage';

View File

@@ -4,22 +4,17 @@ import MuiAvatar from '@mui/material/Avatar';
export const StyledSettingContainer = styled('div')(({ theme }) => { export const StyledSettingContainer = styled('div')(({ theme }) => {
return { return {
position: 'relative',
display: 'flex', display: 'flex',
padding: '0px', padding: '50px',
width: '961px', flexDirection: 'column',
background: theme.colors.popoverBackground, height: '100vh',
borderRadius: '12px', overflow: 'auto',
overflow: 'hidden',
}; };
}); });
export const StyledSettingSidebar = styled('div')(({ theme }) => { export const StyledSettingSidebar = styled('div')(({ theme }) => {
{ {
return { return {
width: '212px',
height: '620px',
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
flexShrink: 0, flexShrink: 0,
flexGrow: 0, flexGrow: 0,
}; };
@@ -29,7 +24,6 @@ export const StyledSettingSidebar = styled('div')(({ theme }) => {
export const StyledSettingContent = styled('div')(() => { export const StyledSettingContent = styled('div')(() => {
return { return {
paddingLeft: '48px', paddingLeft: '48px',
height: '620px',
}; };
}); });
@@ -37,7 +31,6 @@ export const StyledSetting = styled('div')(({ theme }) => {
{ {
return { return {
width: '236px', width: '236px',
height: '620px',
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC', background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
}; };
} }
@@ -49,7 +42,7 @@ export const StyledSettingSidebarHeader = styled('div')(() => {
fontWeight: '500', fontWeight: '500',
fontSize: '18px', fontSize: '18px',
lineHeight: '26px', lineHeight: '26px',
textAlign: 'center', textAlign: 'left',
marginTop: '37px', marginTop: '37px',
}; };
} }
@@ -59,7 +52,6 @@ export const StyledSettingTabContainer = styled('ul')(() => {
{ {
return { return {
display: 'flex', display: 'flex',
flexDirection: 'column',
marginTop: '25px', marginTop: '25px',
}; };
} }
@@ -144,7 +136,6 @@ export const StyledMemberRoleContainer = styled('div')(() => {
export const StyledMemberListContainer = styled('ul')(() => { export const StyledMemberListContainer = styled('ul')(() => {
return { return {
marginTop: '15px', marginTop: '15px',
height: '432px',
overflowY: 'scroll', overflowY: 'scroll',
}; };
}); });
@@ -228,7 +219,6 @@ export const StyledCopyButtonContainer = styled('div')(() => {
export const StyledPublishContent = styled('div')(() => { export const StyledPublishContent = styled('div')(() => {
return { return {
height: '494px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
}; };

View File

@@ -1,180 +1,183 @@
import { InformationIcon, LogOutIcon } from '@blocksuite/icons'; // import { InformationIcon, LogOutIcon } from '@blocksuite/icons';
import { styled } from '@/styles'; // import { styled } from '@/styles';
import { Divider } from '@/ui/divider'; // import { Divider } from '@/ui/divider';
import { useAppState } from '@/providers/app-state-provider/context'; // import { useAppState } from '@/providers/app-state-provider';
import { SelectorPopperContainer } from './styles'; // import { SelectorPopperContainer } from './styles';
import { // import {
PrivateWorkspaceItem, // PrivateWorkspaceItem,
WorkspaceItem, // WorkspaceItem,
CreateWorkspaceItem, // CreateWorkspaceItem,
ListItem, // ListItem,
LoginItem, // LoginItem,
} from './WorkspaceItem'; // } from './WorkspaceItem';
import { WorkspaceSetting } from '@/components/workspace-setting'; // import { WorkspaceSetting } from '@/components/workspace-setting';
import { useCallback, useEffect, useState } from 'react'; // import { useCallback, useEffect, useState } from 'react';
import { WorkspaceType } from '@affine/datacenter'; // import { WorkspaceInfo } from '@affine/datacenter';
import { useModal } from '@/providers/GlobalModalProvider'; // import { useModal } from '@/providers/GlobalModalProvider';
export type WorkspaceDetails = Record< // export type WorkspaceDetails = Record<
string, // string,
{ memberCount: number; owner: { id: string; name: string } } // { memberCount: number; owner: { id: string; name: string } }
>; // >;
type SelectorPopperContentProps = { // type SelectorPopperContentProps = {
isShow: boolean; // isShow: boolean;
// };
// export const SelectorPopperContent = ({
// isShow,
// }: SelectorPopperContentProps) => {
// const { user, workspacesMeta, refreshWorkspacesMeta } = useAppState();
// const [settingWorkspaceId, setSettingWorkspaceId] = useState<string | null>(
// null
// );
// const [workSpaceDetails, setWorkSpaceDetails] = useState<WorkspaceDetails>(
// {}
// );
// const { triggerContactModal } = useModal();
// const handleClickSettingWorkspace = (workspaceId: string) => {
// setSettingWorkspaceId(workspaceId);
// };
// const handleCloseWorkSpace = () => {
// setSettingWorkspaceId(null);
// };
// const settingWorkspace = settingWorkspaceId
// ? workspacesMeta.find(workspace => workspace.id === settingWorkspaceId)
// : undefined;
// const refreshDetails = useCallback(async () => {
// const workspaceDetailList = await Promise.all(
// workspacesMeta.map(async ({ id, type }) => {
// if (user) {
// if (type === WorkspaceType.Private) {
// return { id, member_count: 1, owner: user };
// } else {
// // const dc = await getDataCenter();
// // const data = await dc.apis.getWorkspaceDetail({ id });
// // return { id, ...data } || { id, member_count: 0, owner: user };
// }
// }
// })
// );
// const workSpaceDetails: WorkspaceDetails = {};
// workspaceDetailList.forEach(details => {
// if (details) {
// const { id, member_count, owner } = details;
// if (!owner) return;
// workSpaceDetails[id] = {
// memberCount: member_count || 1,
// owner: {
// id: owner.id,
// name: owner.name,
// },
// };
// }
// });
// setWorkSpaceDetails(workSpaceDetails);
// }, [user, workspacesMeta]);
// useEffect(() => {
// if (isShow) {
// setSettingWorkspaceId(null);
// refreshWorkspacesMeta();
// refreshDetails();
// }
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [isShow]);
// return !user ? (
// <SelectorPopperContainer placement="bottom-start">
// <LoginItem />
// <StyledDivider />
// <ListItem
// icon={<InformationIcon />}
// name="About AFFiNE"
// onClick={() => triggerContactModal()}
// />
// </SelectorPopperContainer>
// ) : (
// <SelectorPopperContainer placement="bottom-start">
// <PrivateWorkspaceItem
// privateWorkspaceId={
// workspacesMeta.find(
// workspace => workspace.type === WorkspaceType.Private
// )?.id
// }
// />
// <StyledDivider />
// <WorkspaceGroupTitle>Workspace</WorkspaceGroupTitle>
// <WorkspaceWrapper>
// {workspacesMeta.map(workspace => {
// return workspace.type !== WorkspaceType.Private ? (
// <WorkspaceItem
// type={workspace.type}
// key={workspace.id}
// id={workspace.id}
// icon={`loading...`}
// onClickSetting={handleClickSettingWorkspace}
// name={`loading...`}
// memberCount={workSpaceDetails[workspace.id]?.memberCount || 1}
// />
// ) : null;
// })}
// </WorkspaceWrapper>
// <CreateWorkspaceItem />
// {settingWorkspace ? (
// <WorkspaceSetting
// isShow={false}
// onClose={handleCloseWorkSpace}
// // workspace={settingWorkspace}
// // owner={
// // (settingWorkspaceId &&
// // workSpaceDetails[settingWorkspaceId]?.owner) || {
// // id: user.id,
// // name: user.name,
// // }
// // }
// />
// ) : null}
// <StyledDivider />
// <ListItem
// icon={<InformationIcon />}
// name="About AFFiNE"
// onClick={() => triggerContactModal()}
// />
// <ListItem
// icon={<LogOutIcon />}
// name="Sign out"
// onClick={() => {
// console.log('Sign out');
// // FIXME: remove token from local storage and reload the page
// localStorage.removeItem('affine_token');
// window.location.reload();
// }}
// />
// </SelectorPopperContainer>
// );
// };
// const StyledDivider = styled(Divider)({
// margin: '8px 12px',
// width: 'calc(100% - 24px)',
// });
// const WorkspaceGroupTitle = styled('div')(({ theme }) => {
// return {
// color: theme.colors.iconColor,
// fontSize: theme.font.sm,
// lineHeight: '30px',
// height: '30px',
// padding: '0 12px',
// };
// });
// const WorkspaceWrapper = styled('div')(() => {
// return {
// maxHeight: '200px',
// overflow: 'auto',
// };
// });
export const test = () => {
return <></>;
}; };
export const SelectorPopperContent = ({
isShow,
}: SelectorPopperContentProps) => {
const { user, workspacesMeta, refreshWorkspacesMeta } = useAppState();
const [settingWorkspaceId, setSettingWorkspaceId] = useState<string | null>(
null
);
const [workSpaceDetails, setWorkSpaceDetails] = useState<WorkspaceDetails>(
{}
);
const { triggerContactModal } = useModal();
const handleClickSettingWorkspace = (workspaceId: string) => {
setSettingWorkspaceId(workspaceId);
};
const handleCloseWorkSpace = () => {
setSettingWorkspaceId(null);
};
const settingWorkspace = settingWorkspaceId
? workspacesMeta.find(workspace => workspace.id === settingWorkspaceId)
: undefined;
const refreshDetails = useCallback(async () => {
const workspaceDetailList = await Promise.all(
workspacesMeta.map(async ({ id, type }) => {
if (user) {
if (type === WorkspaceType.Private) {
return { id, member_count: 1, owner: user };
} else {
// const dc = await getDataCenter();
// const data = await dc.apis.getWorkspaceDetail({ id });
// return { id, ...data } || { id, member_count: 0, owner: user };
}
}
})
);
const workSpaceDetails: WorkspaceDetails = {};
workspaceDetailList.forEach(details => {
if (details) {
const { id, member_count, owner } = details;
if (!owner) return;
workSpaceDetails[id] = {
memberCount: member_count || 1,
owner: {
id: owner.id,
name: owner.name,
},
};
}
});
setWorkSpaceDetails(workSpaceDetails);
}, [user, workspacesMeta]);
useEffect(() => {
if (isShow) {
setSettingWorkspaceId(null);
refreshWorkspacesMeta();
refreshDetails();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isShow]);
return !user ? (
<SelectorPopperContainer placement="bottom-start">
<LoginItem />
<StyledDivider />
<ListItem
icon={<InformationIcon />}
name="About AFFiNE"
onClick={() => triggerContactModal()}
/>
</SelectorPopperContainer>
) : (
<SelectorPopperContainer placement="bottom-start">
<PrivateWorkspaceItem
privateWorkspaceId={
workspacesMeta.find(
workspace => workspace.type === WorkspaceType.Private
)?.id
}
/>
<StyledDivider />
<WorkspaceGroupTitle>Workspace</WorkspaceGroupTitle>
<WorkspaceWrapper>
{workspacesMeta.map(workspace => {
return workspace.type !== WorkspaceType.Private ? (
<WorkspaceItem
type={workspace.type}
key={workspace.id}
id={workspace.id}
icon={`loading...`}
onClickSetting={handleClickSettingWorkspace}
name={`loading...`}
memberCount={workSpaceDetails[workspace.id]?.memberCount || 1}
/>
) : null;
})}
</WorkspaceWrapper>
<CreateWorkspaceItem />
{settingWorkspace ? (
<WorkspaceSetting
isShow={false}
onClose={handleCloseWorkSpace}
// workspace={settingWorkspace}
// owner={
// (settingWorkspaceId &&
// workSpaceDetails[settingWorkspaceId]?.owner) || {
// id: user.id,
// name: user.name,
// }
// }
/>
) : null}
<StyledDivider />
<ListItem
icon={<InformationIcon />}
name="About AFFiNE"
onClick={() => triggerContactModal()}
/>
<ListItem
icon={<LogOutIcon />}
name="Sign out"
onClick={() => {
console.log('Sign out');
// FIXME: remove token from local storage and reload the page
localStorage.removeItem('affine_token');
window.location.reload();
}}
/>
</SelectorPopperContainer>
);
};
const StyledDivider = styled(Divider)({
margin: '8px 12px',
width: 'calc(100% - 24px)',
});
const WorkspaceGroupTitle = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
lineHeight: '30px',
height: '30px',
padding: '0 12px',
};
});
const WorkspaceWrapper = styled('div')(() => {
return {
maxHeight: '200px',
overflow: 'auto',
};
});

View File

@@ -1,4 +1,4 @@
import { getDataCenter } from '@affine/datacenter'; // import { getDataCenter } from '@affine/datacenter';
import Modal from '@/ui/modal'; import Modal from '@/ui/modal';
import Input from '@/ui/input'; import Input from '@/ui/input';
import { import {
@@ -11,8 +11,8 @@ import {
} from './style'; } from './style';
import { useState } from 'react'; import { useState } from 'react';
import { ModalCloseButton } from '@/ui/modal'; import { ModalCloseButton } from '@/ui/modal';
import router from 'next/router'; // import router from 'next/router';
import { useAppState } from '@/providers/app-state-provider'; // import { useAppState } from '@/providers/app-state-provider';
interface WorkspaceCreateProps { interface WorkspaceCreateProps {
open: boolean; open: boolean;
@@ -30,7 +30,7 @@ const DefaultHeadImgColors = [
export const WorkspaceCreate = ({ open, onClose }: WorkspaceCreateProps) => { export const WorkspaceCreate = ({ open, onClose }: WorkspaceCreateProps) => {
const [workspaceName, setWorkspaceId] = useState<string>(''); const [workspaceName, setWorkspaceId] = useState<string>('');
const [creating, setCreating] = useState<boolean>(false); const [creating, setCreating] = useState<boolean>(false);
const { refreshWorkspacesMeta } = useAppState(); // const { refreshWorkspacesMeta } = useAppState();
const handlerInputChange = (workspaceName: string) => { const handlerInputChange = (workspaceName: string) => {
setWorkspaceId(workspaceName); setWorkspaceId(workspaceName);
}; };

View File

@@ -1,60 +1,63 @@
import { styled } from '@/styles'; // import { styled } from '@/styles';
import { useAppState } from '@/providers/app-state-provider/context'; // import { useAppState } from '@/providers/app-state-provider';
import { // import {
WorkspaceItemAvatar, // WorkspaceItemAvatar,
PrivateWorkspaceWrapper, // PrivateWorkspaceWrapper,
WorkspaceItemContent, // WorkspaceItemContent,
} from './styles'; // } from './styles';
import { useRouter } from 'next/router'; // import { useRouter } from 'next/router';
type PrivateWorkspaceItemProps = { // type PrivateWorkspaceItemProps = {
privateWorkspaceId?: string; // privateWorkspaceId?: string;
// };
// export const PrivateWorkspaceItem = ({
// privateWorkspaceId,
// }: PrivateWorkspaceItemProps) => {
// const { user } = useAppState();
// const router = useRouter();
// const handleClick = () => {
// if (privateWorkspaceId) {
// router.push(`/workspace/${privateWorkspaceId}`);
// }
// };
// if (user) {
// const Username = user.name;
// return (
// <PrivateWorkspaceWrapper onClick={handleClick}>
// <WorkspaceItemAvatar alt={Username} src={user.avatar_url}>
// {Username}
// </WorkspaceItemAvatar>
// <WorkspaceItemContent>
// <Name title={Username}>{Username}</Name>
// <Email title={user.email}>{user.email}</Email>
// </WorkspaceItemContent>
// </PrivateWorkspaceWrapper>
// );
// }
// return null;
// };
// const Name = styled('div')(({ theme }) => {
// return {
// color: theme.colors.quoteColor,
// fontSize: theme.font.base,
// fontWeight: 500,
// overflow: 'hidden',
// textOverflow: 'ellipsis',
// whiteSpace: 'nowrap',
// };
// });
// const Email = styled('div')(({ theme }) => {
// return {
// color: theme.colors.iconColor,
// fontSize: theme.font.sm,
// overflow: 'hidden',
// textOverflow: 'ellipsis',
// whiteSpace: 'nowrap',
// };
// });
export const test = () => {
return <></>;
}; };
export const PrivateWorkspaceItem = ({
privateWorkspaceId,
}: PrivateWorkspaceItemProps) => {
const { user } = useAppState();
const router = useRouter();
const handleClick = () => {
if (privateWorkspaceId) {
router.push(`/workspace/${privateWorkspaceId}`);
}
};
if (user) {
const Username = user.name;
return (
<PrivateWorkspaceWrapper onClick={handleClick}>
<WorkspaceItemAvatar alt={Username} src={user.avatar_url}>
{Username}
</WorkspaceItemAvatar>
<WorkspaceItemContent>
<Name title={Username}>{Username}</Name>
<Email title={user.email}>{user.email}</Email>
</WorkspaceItemContent>
</PrivateWorkspaceWrapper>
);
}
return null;
};
const Name = styled('div')(({ theme }) => {
return {
color: theme.colors.quoteColor,
fontSize: theme.font.base,
fontWeight: 500,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
const Email = styled('div')(({ theme }) => {
return {
color: theme.colors.iconColor,
fontSize: theme.font.sm,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});

View File

@@ -1,96 +1,100 @@
import { useRouter } from 'next/router'; // import { useRouter } from 'next/router';
import { styled } from '@/styles'; // import { styled } from '@/styles';
import { // import {
WorkspaceItemAvatar, // WorkspaceItemAvatar,
WorkspaceItemWrapper, // WorkspaceItemWrapper,
WorkspaceItemContent, // WorkspaceItemContent,
} from '../styles'; // } from '../styles';
import { FooterSetting } from './FooterSetting'; // import { FooterSetting } from './FooterSetting';
import { FooterUsers } from './FooterUsers'; // import { FooterUsers } from './FooterUsers';
import { WorkspaceType } from '@affine/datacenter'; // import { WorkspaceType } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider'; // import { useAppState } from '@/providers/app-state-provider';
interface WorkspaceItemProps { // interface WorkspaceItemProps {
id: string; // id: string;
name: string; // name: string;
icon: string; // icon: string;
type: WorkspaceType; // type: WorkspaceType;
memberCount: number; // memberCount: number;
onClickSetting?: (workspaceId: string) => void; // onClickSetting?: (workspaceId: string) => void;
} // }
export const WorkspaceItem = ({ // export const WorkspaceItem = ({
id, // id,
name, // name,
icon, // icon,
type, // type,
onClickSetting, // onClickSetting,
memberCount, // memberCount,
}: WorkspaceItemProps) => { // }: WorkspaceItemProps) => {
const router = useRouter(); // const router = useRouter();
const { currentWorkspaceId } = useAppState(); // const { currentWorkspaceId } = useAppState();
const handleClickSetting = async () => { // const handleClickSetting = async () => {
onClickSetting && onClickSetting(id); // onClickSetting && onClickSetting(id);
}; // };
return ( // return (
<StyledWrapper // <StyledWrapper
onClick={() => { // onClick={() => {
router.push(`/workspace/${id}`); // router.push(`/workspace/${id}`);
}} // }}
canSet={ // canSet={
type !== WorkspaceType.Private && currentWorkspaceId === String(id) // type !== WorkspaceType.Private && currentWorkspaceId === String(id)
} // }
> // >
<WorkspaceItemAvatar alt={name} src={icon}> // <WorkspaceItemAvatar alt={name} src={icon}>
{name.charAt(0)} // {name.charAt(0)}
</WorkspaceItemAvatar> // </WorkspaceItemAvatar>
<WorkspaceItemContent> // <WorkspaceItemContent>
<Name title={name}>{name}</Name> // <Name title={name}>{name}</Name>
</WorkspaceItemContent> // </WorkspaceItemContent>
<Footer> // <Footer>
<FooterUsers memberCount={memberCount} /> // <FooterUsers memberCount={memberCount} />
<FooterSetting onClick={handleClickSetting} /> // <FooterSetting onClick={handleClickSetting} />
</Footer> // </Footer>
</StyledWrapper> // </StyledWrapper>
); // );
// };
// const Name = styled('div')(({ theme }) => {
// return {
// color: theme.colors.quoteColor,
// fontSize: theme.font.sm,
// fontWeight: 400,
// overflow: 'hidden',
// textOverflow: 'ellipsis',
// whiteSpace: 'nowrap',
// };
// });
// const StyledWrapper = styled(WorkspaceItemWrapper)<{ canSet: boolean }>(
// ({ canSet }) => {
// return {
// '& .footer-setting': {
// display: 'none',
// },
// ':hover .footer-users': {
// display: canSet ? 'none' : '',
// },
// ':hover .footer-setting': {
// display: canSet ? 'block' : 'none',
// },
// };
// }
// );
// const Footer = styled('div')({
// width: '42px',
// flex: '0 42px',
// fontSize: '20px',
// display: 'flex',
// alignItems: 'center',
// justifyContent: 'center',
// marginLeft: '12px',
// });
export const test = () => {
return <></>;
}; };
const Name = styled('div')(({ theme }) => {
return {
color: theme.colors.quoteColor,
fontSize: theme.font.sm,
fontWeight: 400,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
};
});
const StyledWrapper = styled(WorkspaceItemWrapper)<{ canSet: boolean }>(
({ canSet }) => {
return {
'& .footer-setting': {
display: 'none',
},
':hover .footer-users': {
display: canSet ? 'none' : '',
},
':hover .footer-setting': {
display: canSet ? 'block' : 'none',
},
};
}
);
const Footer = styled('div')({
width: '42px',
flex: '0 42px',
fontSize: '20px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginLeft: '12px',
});

View File

@@ -1,5 +1,5 @@
export * from './PrivateWorkspaceItem'; export * from './PrivateWorkspaceItem';
export * from './WorkspaceItem'; // export * from './WorkspaceItem';
export * from './CreateWorkspaceItem'; export * from './CreateWorkspaceItem';
export * from './ListItem'; export * from './ListItem';
export * from './LoginItem'; export * from './LoginItem';

View File

@@ -2,17 +2,16 @@ import { Avatar, WorkspaceName, SelectorWrapper } from './styles';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { WorkspaceModal } from '@/components/workspace-modal'; import { WorkspaceModal } from '@/components/workspace-modal';
import { WorkspaceAvatar } from '@/components/workspace-avatar'; import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider'; import { useAppState } from '@/providers/app-state-provider';
export const WorkspaceSelector = () => { export const WorkspaceSelector = () => {
const [workspaceListShow, setWorkspaceListShow] = useState(false); const [workspaceListShow, setWorkspaceListShow] = useState(false);
const { currentWorkspace, workspaceMetaList } = useTemporaryHelper(); const { currentMetaWorkSpace, workspaceList } = useAppState();
useEffect(() => { useEffect(() => {
if (workspaceMetaList.length === 0) { if (workspaceList.length === 0) {
setWorkspaceListShow(true); setWorkspaceListShow(true);
} }
}, [workspaceMetaList]); }, [workspaceList]);
return ( return (
<> <>
<SelectorWrapper <SelectorWrapper
@@ -24,7 +23,7 @@ export const WorkspaceSelector = () => {
<Avatar <Avatar
alt="Affine" alt="Affine"
data-testid="workspace-avatar" data-testid="workspace-avatar"
src={currentWorkspace?.avatar} src={currentMetaWorkSpace?.avatar}
> >
<div <div
style={{ style={{
@@ -33,12 +32,13 @@ export const WorkspaceSelector = () => {
> >
<WorkspaceAvatar <WorkspaceAvatar
size={28} size={28}
name={currentWorkspace?.name ?? 'AFFiNE'} name={currentMetaWorkSpace?.name ?? 'AFFiNE'}
avatar={currentMetaWorkSpace?.avatar ?? ''}
/> />
</div> </div>
</Avatar> </Avatar>
<WorkspaceName data-testid="workspace-name"> <WorkspaceName data-testid="workspace-name">
{currentWorkspace?.name ?? 'AFFiNE'} {currentMetaWorkSpace?.name ?? 'AFFiNE'}
</WorkspaceName> </WorkspaceName>
</SelectorWrapper> </SelectorWrapper>
<WorkspaceModal <WorkspaceModal

View File

@@ -4,6 +4,7 @@ import {
StyledArrowButton, StyledArrowButton,
StyledLink, StyledLink,
StyledListItem, StyledListItem,
StyledListItemForWorkspace,
// StyledListItemForWorkspace, // StyledListItemForWorkspace,
StyledNewPageButton, StyledNewPageButton,
StyledSliderBar, StyledSliderBar,
@@ -26,13 +27,14 @@ import {
import Link from 'next/link'; import Link from 'next/link';
import { Tooltip } from '@/ui/tooltip'; import { Tooltip } from '@/ui/tooltip';
import { useModal } from '@/providers/GlobalModalProvider'; import { useModal } from '@/providers/GlobalModalProvider';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import { IconButton } from '@/ui/button'; import { IconButton } from '@/ui/button';
import useLocalStorage from '@/hooks/use-local-storage'; import useLocalStorage from '@/hooks/use-local-storage';
import usePageMetaList from '@/hooks/use-page-meta-list'; import usePageMetaList from '@/hooks/use-page-meta-list';
import { usePageHelper } from '@/hooks/use-page-helper'; import { usePageHelper } from '@/hooks/use-page-helper';
import { WorkspaceSetting } from '@/components/workspace-setting'; // import { WorkspaceSetting } from '@/components/workspace-setting';
import { useTranslation } from '@affine/i18n'; import { useTranslation } from '@affine/i18n';
import { WorkspaceSelector } from './WorkspaceSelector/WorkspaceSelector';
const FavoriteList = ({ showList }: { showList: boolean }) => { const FavoriteList = ({ showList }: { showList: boolean }) => {
const { openPage } = usePageHelper(); const { openPage } = usePageHelper();
@@ -76,14 +78,15 @@ export const WorkSpaceSliderBar = () => {
const [showTip, setShowTip] = useState(false); const [showTip, setShowTip] = useState(false);
const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true); const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true);
const [showWorkspaceSetting, setShowWorkspaceSetting] = useState(false);
const paths = { const paths = {
all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '', all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
favorite: currentWorkspaceId favorite: currentWorkspaceId
? `/workspace/${currentWorkspaceId}/favorite` ? `/workspace/${currentWorkspaceId}/favorite`
: '', : '',
trash: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/trash` : '', trash: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/trash` : '',
setting: currentWorkspaceId
? `/workspace/${currentWorkspaceId}/setting`
: '',
}; };
return ( return (
@@ -113,9 +116,9 @@ export const WorkSpaceSliderBar = () => {
</Tooltip> </Tooltip>
<StyledSliderBarWrapper data-testid="sliderBar"> <StyledSliderBarWrapper data-testid="sliderBar">
{/* <StyledListItemForWorkspace> <StyledListItemForWorkspace>
<WorkspaceSelector /> <WorkspaceSelector />
</StyledListItemForWorkspace> */} </StyledListItemForWorkspace>
<StyledListItem <StyledListItem
data-testid="sliderBar-quickSearchButton" data-testid="sliderBar-quickSearchButton"
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
@@ -150,20 +153,19 @@ export const WorkSpaceSliderBar = () => {
</IconButton> </IconButton>
</StyledListItem> </StyledListItem>
<FavoriteList showList={showSubFavorite} /> <FavoriteList showList={showSubFavorite} />
<StyledListItem <StyledListItem active={router.asPath === paths.setting}>
onClick={() => { <StyledLink href={{ pathname: paths.setting }}>
setShowWorkspaceSetting(true); <SettingsIcon />
}} Settings
> </StyledLink>
<SettingsIcon /> Setting
</StyledListItem> </StyledListItem>
<WorkspaceSetting {/* <WorkspaceSetting
isShow={showWorkspaceSetting} isShow={showWorkspaceSetting}
onClose={() => { onClose={() => {
setShowWorkspaceSetting(false); setShowWorkspaceSetting(false);
}} }}
/> /> */}
<StyledListItem <StyledListItem
onClick={() => { onClick={() => {

View File

@@ -7,22 +7,19 @@ const defaultOutLineWorkspaceId = 'affine';
// Cause it not just ensure workspace loaded, but also have router change. // Cause it not just ensure workspace loaded, but also have router change.
export const useEnsureWorkspace = () => { export const useEnsureWorkspace = () => {
const [workspaceLoaded, setWorkspaceLoaded] = useState(false); const [workspaceLoaded, setWorkspaceLoaded] = useState(false);
const { workspacesMeta, loadWorkspace, synced, user } = useAppState(); const { workspaceList, loadWorkspace, user } = useAppState();
console.log('workspaceList: ', workspaceList);
const router = useRouter(); const router = useRouter();
// const defaultOutLineWorkspaceId = '99ce7eb7'; // const defaultOutLineWorkspaceId = '99ce7eb7';
// console.log(defaultOutLineWorkspaceId); // console.log(defaultOutLineWorkspaceId);
useEffect(() => { useEffect(() => {
if (!synced) {
setWorkspaceLoaded(false);
return;
}
// If router.query.workspaceId is not in workspace list, jump to 404 page // If router.query.workspaceId is not in workspace list, jump to 404 page
// If workspacesMeta is empty, we need to create a default workspace but not jump to 404 // If workspaceList is empty, we need to create a default workspace but not jump to 404
if ( if (
workspacesMeta.length && workspaceList.length &&
router.query.workspaceId && router.query.workspaceId &&
workspacesMeta.findIndex( workspaceList.findIndex(
meta => meta.id.toString() === router.query.workspaceId meta => meta.id.toString() === router.query.workspaceId
) === -1 ) === -1
) { ) {
@@ -40,13 +37,13 @@ export const useEnsureWorkspace = () => {
// } // }
const workspaceId = user const workspaceId = user
? (router.query.workspaceId as string) || workspacesMeta[0]?.id ? (router.query.workspaceId as string) || workspaceList[0]?.id
: (router.query.workspaceId as string) || defaultOutLineWorkspaceId; : (router.query.workspaceId as string) || defaultOutLineWorkspaceId;
loadWorkspace(workspaceId).finally(() => { loadWorkspace(workspaceId).finally(() => {
setWorkspaceLoaded(true); setWorkspaceLoaded(true);
}); });
}, [loadWorkspace, router, synced, user, workspacesMeta]); }, [loadWorkspace, router, user, workspaceList]);
return { return {
workspaceLoaded, workspaceLoaded,

View File

@@ -1,5 +1,5 @@
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
export const useInitWorkspace = (disabled?: boolean) => { export const useInitWorkspace = (disabled?: boolean) => {
@@ -8,15 +8,11 @@ export const useInitWorkspace = (disabled?: boolean) => {
const defaultOutLineWorkspaceId = useRef(new Date().getTime().toString()); const defaultOutLineWorkspaceId = useRef(new Date().getTime().toString());
const router = useRouter(); const router = useRouter();
const { const { workspaceList, loadWorkspace, currentWorkspace, currentWorkspaceId } =
workspacesMeta, useAppState();
loadWorkspace,
currentWorkspace,
currentWorkspaceId,
} = useAppState();
const workspaceId = const workspaceId =
(router.query.workspaceId as string) || (router.query.workspaceId as string) ||
workspacesMeta?.[0]?.id || workspaceList?.[0]?.id ||
defaultOutLineWorkspaceId.current; defaultOutLineWorkspaceId.current;
useEffect(() => { useEffect(() => {

View File

@@ -0,0 +1,28 @@
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,
};
};

View File

@@ -0,0 +1,46 @@
import { useAppState } from '@/providers/app-state-provider';
import { Workspace } from '@blocksuite/store';
export const useWorkspaceHelper = () => {
const { dataCenter } = 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;
}
return null;
};
// const updateWorkspace = async (workspace: Workspace) => {};
const publishWorkspace = async (workspaceId: string, publish: boolean) => {
dataCenter.setWorkspacePublish(workspaceId, publish);
};
const updateWorkspace = async (
{ name, avatarBlob }: { name?: string; avatarBlob?: Blob },
workspace: Workspace
) => {
if (name) {
await dataCenter.updateWorkspaceMeta({ name }, workspace);
}
if (avatarBlob) {
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;
};
return {
createWorkspace,
publishWorkspace,
updateWorkspace,
enableWorkspace,
};
};

View File

@@ -1,4 +1,9 @@
import NotfoundPage from '@/components/404'; import NotfoundPage from '@/components/404';
import { useAppState } from '@/providers/app-state-provider';
export default function Custom404() { export default function Custom404() {
const { workspaceList } = useAppState();
console.log('workspaceList: ', workspaceList);
return <NotfoundPage></NotfoundPage>; return <NotfoundPage></NotfoundPage>;
} }

View File

@@ -10,16 +10,18 @@ import '../utils/print-build-info';
import ProviderComposer from '@/components/provider-composer'; import ProviderComposer from '@/components/provider-composer';
import type { PropsWithChildren, ReactElement, ReactNode } from 'react'; import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import { AppStateProvider } from '@/providers/app-state-provider/Provider'; import { AppStateProvider } from '@/providers/app-state-provider';
import ConfirmProvider from '@/providers/ConfirmProvider'; import ConfirmProvider from '@/providers/ConfirmProvider';
import { ModalProvider } from '@/providers/GlobalModalProvider'; import { ModalProvider } from '@/providers/GlobalModalProvider';
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useAppState } from '@/providers/app-state-provider'; import { useAppState } from '@/providers/app-state-provider';
import { PageLoading } from '@/components/loading'; import { PageLoading } from '@/components/loading';
import Head from 'next/head'; import Head from 'next/head';
import TemporaryHelperProvider from '@/providers/temporary-helper-provider';
import '@affine/i18n'; import '@affine/i18n';
import TemporaryHelperProvider from '@/providers/temporary-helper-provider';
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), { const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
ssr: false, ssr: false,
@@ -56,6 +58,7 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
<TemporaryHelperProvider key="TemporaryHelperProvider" />, <TemporaryHelperProvider key="TemporaryHelperProvider" />,
<ThemeProvider key="ThemeProvider" />, <ThemeProvider key="ThemeProvider" />,
<AppStateProvider key="appStateProvider" />, <AppStateProvider key="appStateProvider" />,
// <AppStateProvider2 key="appStateProvider2" />,
<ModalProvider key="ModalProvider" />, <ModalProvider key="ModalProvider" />,
<ConfirmProvider key="ConfirmProvider" />, <ConfirmProvider key="ConfirmProvider" />,
]} ]}
@@ -67,9 +70,8 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
}; };
const AppDefender = ({ children }: PropsWithChildren) => { const AppDefender = ({ children }: PropsWithChildren) => {
const router = useRouter();
const { synced } = useAppState(); const { synced } = useAppState();
const router = useRouter();
useEffect(() => { useEffect(() => {
if (router.pathname === '/') { if (router.pathname === '/') {

View File

@@ -1,7 +1,7 @@
import { styled } from '@/styles'; import { styled } from '@/styles';
import { Empty } from '@/ui/empty'; import { Empty } from '@/ui/empty';
import { Avatar } from '@mui/material'; import { Avatar } from '@mui/material';
import { getDataCenter } from '@affine/datacenter'; // import { getDataCenter } from '@affine/datacenter';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -37,6 +37,8 @@ export default function DevPage() {
// .catch(err => { // .catch(err => {
// console.log('err: ', err); // console.log('err: ', err);
// }); // });
setSuccessInvited(true);
setInviteData(null);
}, [router.query.invite_code]); }, [router.query.invite_code]);
return ( return (

View File

@@ -4,7 +4,7 @@ import exampleMarkdown1 from '@/templates/Welcome-to-the-AFFiNE-Alpha.md';
import exampleMarkdown2 from '@/templates/AFFiNE-Docs.md'; import exampleMarkdown2 from '@/templates/AFFiNE-Docs.md';
import { usePageHelper } from '@/hooks/use-page-helper'; import { usePageHelper } from '@/hooks/use-page-helper';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import { Button } from '@/ui/button'; import { Button } from '@/ui/button';
interface Template { interface Template {
name: string; name: string;

View File

@@ -2,88 +2,42 @@
import { import {
PropsWithChildren, PropsWithChildren,
ReactElement, ReactElement,
useCallback,
useEffect, useEffect,
useRef,
useState, useState,
} from 'react'; } from 'react';
import { styled } from '@/styles';
import { EditorHeader } from '@/components/header'; import { EditorHeader } from '@/components/header';
import EdgelessToolbar from '@/components/edgeless-toolbar'; import EdgelessToolbar from '@/components/edgeless-toolbar';
import MobileModal from '@/components/mobile-modal'; import MobileModal from '@/components/mobile-modal';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-v2.0.md';
import type { NextPageWithLayout } from '../..//_app'; import type { NextPageWithLayout } from '../..//_app';
import WorkspaceLayout from '@/components/workspace-layout'; import WorkspaceLayout from '@/components/workspace-layout';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { usePageHelper } from '@/hooks/use-page-helper'; import { usePageHelper } from '@/hooks/use-page-helper';
import dynamic from 'next/dynamic';
const StyledEditorContainer = styled('div')(() => { import { EditorContainer } from '@blocksuite/editor';
return { const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
height: 'calc(100vh - 60px)', ssr: false,
padding: '0 32px',
};
}); });
const Page: NextPageWithLayout = () => { const Page: NextPageWithLayout = () => {
const editorContainer = useRef<HTMLDivElement>(null); const { currentPage, currentWorkspace, setEditor } = useAppState();
const { createEditor, setEditor, currentPage, currentWorkspace } =
useAppState();
useEffect(() => { const setEditorHandler = useCallback(
const ret = () => { (editor: EditorContainer) => setEditor.current(editor),
const node = editorContainer.current; [setEditor]
while (node?.firstChild) {
node.removeChild(node.firstChild);
}
};
const editor = createEditor?.current?.(currentPage!);
if (editor) {
editorContainer.current?.appendChild(editor);
setEditor?.current?.(editor);
if (currentPage!.isEmpty) {
const isFirstPage = currentWorkspace?.meta.pageMetas.length === 1;
// Can not use useCurrentPageMeta to get new title, cause meta title will trigger rerender, but the second time can not remove title
const { title: metaTitle } = currentPage!.meta;
const title = metaTitle
? metaTitle
: isFirstPage
? 'Welcome to AFFiNE Alpha "Abbey Wood"'
: '';
currentWorkspace?.setPageMeta(currentPage!.id, { title });
const pageId = currentPage!.addBlock({
flavour: 'affine:page',
title,
});
currentPage!.addBlock({ flavour: 'affine:surface' }, null);
const frameId = currentPage!.addBlock(
{ flavour: 'affine:frame' },
pageId
); );
currentPage!.addBlock({ flavour: 'affine:frame' }, pageId);
// If this is a first page in workspace, init an introduction markdown
if (isFirstPage) {
editor.clipboard
.importMarkdown(exampleMarkdown, `${frameId}`)
.then(() => {
currentWorkspace!.setPageMeta(currentPage!.id, { title });
currentPage!.resetHistory();
});
}
currentPage!.resetHistory();
}
}
document.title = currentPage?.meta.title || 'Untitled';
return ret;
}, [currentWorkspace, currentPage, createEditor, setEditor]);
return ( return (
<> <>
<EditorHeader /> <EditorHeader />
<MobileModal /> <MobileModal />
<StyledEditorContainer ref={editorContainer} />
{currentPage && (
<DynamicBlocksuite
page={currentPage}
workspace={currentWorkspace}
setEditor={setEditorHandler}
/>
)}
<EdgelessToolbar /> <EdgelessToolbar />
</> </>
); );

View File

@@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useAppState } from '@/providers/app-state-provider/context'; import { useAppState } from '@/providers/app-state-provider';
import useEnsureWorkspace from '@/hooks/use-ensure-workspace'; import useEnsureWorkspace from '@/hooks/use-ensure-workspace';
import { PageLoading } from '@/components/loading'; import { PageLoading } from '@/components/loading';
import usePageHelper from '@/hooks/use-page-helper'; import usePageHelper from '@/hooks/use-page-helper';

View File

@@ -0,0 +1,110 @@
import {
StyledSettingContainer,
StyledSettingContent,
StyledSettingSidebar,
StyledSettingSidebarHeader,
StyledSettingTabContainer,
StyledSettingTagIconContainer,
WorkspaceSettingTagItem,
} from '@/components/workspace-setting/style';
import {
EditIcon,
UsersIcon,
PublishIcon,
CloudInsyncIcon,
} from '@blocksuite/icons';
import { ReactElement, ReactNode, useState } from 'react';
import {
GeneralPage,
MembersPage,
PublishPage,
ExportPage,
SyncPage,
} from '@/components/workspace-setting';
import { useAppState } from '@/providers/app-state-provider';
import WorkspaceLayout from '@/components/workspace-layout';
import { WorkspaceInfo } from '@affine/datacenter';
type TabNames = 'general' | 'members' | 'publish' | 'sync' | 'export';
const tabMap: {
name: TabNames;
icon: ReactNode;
panelRender: (workspace: WorkspaceInfo) => ReactNode;
}[] = [
{
name: 'general',
icon: <EditIcon />,
panelRender: workspace => <GeneralPage workspace={workspace} />,
},
{
name: 'members',
icon: <CloudInsyncIcon />,
panelRender: workspace => <MembersPage workspace={workspace} />,
},
{
name: 'publish',
icon: <UsersIcon />,
panelRender: workspace => <PublishPage workspace={workspace} />,
},
{
name: 'sync',
icon: <PublishIcon />,
panelRender: workspace => <SyncPage workspace={workspace} />,
},
{
name: 'export',
icon: <PublishIcon />,
panelRender: workspace => <ExportPage workspace={workspace} />,
},
];
const WorkspaceSetting = () => {
const { currentMetaWorkSpace } = useAppState();
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
const handleTabChange = (tab: TabNames) => {
setActiveTab(tab);
};
const activeTabPanelRender = tabMap.find(
tab => tab.name === activeTab
)?.panelRender;
return (
<StyledSettingContainer>
<StyledSettingSidebar>
<StyledSettingSidebarHeader>
Workspace Settings
</StyledSettingSidebarHeader>
<StyledSettingTabContainer>
{tabMap.map(({ icon, name }) => {
return (
<WorkspaceSettingTagItem
key={name}
isActive={activeTab === name}
onClick={() => {
handleTabChange(name);
}}
>
<StyledSettingTagIconContainer>
{icon}
</StyledSettingTagIconContainer>
{name}
</WorkspaceSettingTagItem>
);
})}
</StyledSettingTabContainer>
</StyledSettingSidebar>
<StyledSettingContent>
{currentMetaWorkSpace && activeTabPanelRender?.(currentMetaWorkSpace)}
</StyledSettingContent>
</StyledSettingContainer>
);
};
WorkspaceSetting.getLayout = function getLayout(page: ReactElement) {
return <WorkspaceLayout>{page}</WorkspaceLayout>;
};
export default WorkspaceSetting;

View File

@@ -1,25 +0,0 @@
import { useEffect } from 'react';
import type { Page } from '@blocksuite/store';
import '@blocksuite/blocks';
import { EditorContainer } from '@blocksuite/editor';
import type { CreateEditorHandler } from './context';
interface Props {
setCreateEditorHandler: (handler: CreateEditorHandler) => void;
}
const DynamicBlocksuite = ({ setCreateEditorHandler }: Props) => {
useEffect(() => {
const createEditorHandler: CreateEditorHandler = (page: Page) => {
const editor = new EditorContainer();
editor.page = page;
return editor;
};
setCreateEditorHandler(createEditorHandler);
}, [setCreateEditorHandler]);
return <></>;
};
export default DynamicBlocksuite;

View File

@@ -1,143 +1,144 @@
import { useMemo, useState, useCallback, useRef } from 'react'; import { createContext, useContext, useEffect, useState, useRef } from 'react';
import type { ReactNode } from 'react'; import type { PropsWithChildren } from 'react';
import dynamic from 'next/dynamic';
import { getDataCenter } from '@affine/datacenter'; import { getDataCenter } from '@affine/datacenter';
import { AppState, AppStateContext } from './context'; import {
import type { AppStateContext,
AppStateFunction,
AppStateValue, AppStateValue,
CreateEditorHandler, PageMeta,
LoadWorkspaceHandler, } from './interface';
} from './context'; import { createDefaultWorkspace } from './utils';
import { Page } from '@blocksuite/store'; import { WorkspaceInfo } from '@affine/datacenter';
import { EditorContainer } from '@blocksuite/editor';
const DynamicBlocksuite = dynamic(() => import('./DynamicBlocksuite'), {
ssr: false,
});
const loadWorkspaceHandler: LoadWorkspaceHandler = async workspaceId => { type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
if (workspaceId) {
const dc = await getDataCenter(); export const AppState = createContext<AppStateContext>({} as AppStateContext);
return dc.load(workspaceId, { providerId: 'affine' });
} else { export const useAppState = () => useContext(AppState);
return null;
export const AppStateProvider = ({
children,
}: PropsWithChildren<AppStateContextProps>) => {
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
useEffect(() => {
const init = async () => {
const dataCenter = await getDataCenter();
if (dataCenter.workspaces.length === 0) {
await createDefaultWorkspace(dataCenter);
} }
}; const currentWorkspace = await dataCenter.loadWorkspace(
export const AppStateProvider = ({ children }: { children?: ReactNode }) => { dataCenter.workspaces[0].id
const refreshWorkspacesMeta = async () => { );
const dc = await getDataCenter(); const currentMetaWorkSpace = dataCenter.workspaces.find(item => {
const workspacesMeta = await dc.apis.getWorkspaces().catch(() => { return item.id === currentWorkspace.room;
return [];
}); });
setState(state => ({ ...state, workspacesMeta }));
};
const [state, setState] = useState<AppStateValue>({ setAppState({
user: null, dataCenter,
workspacesMeta: [], user: (await dataCenter.getUserInfo()) || null,
currentWorkspaceId: '', workspaceList: dataCenter.workspaces,
currentWorkspace: null, currentWorkspaceId: dataCenter.workspaces[0].id,
currentWorkspace,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
currentPage: null, currentPage: null,
editor: null, editor: null,
refreshWorkspacesMeta,
synced: true, synced: true,
currentMetaWorkSpace: currentMetaWorkSpace ?? null,
}); });
};
const [createEditorHandler, _setCreateEditorHandler] = init();
useState<CreateEditorHandler>(); }, []);
const setCreateEditorHandler = useCallback( useEffect(() => {
(handler: CreateEditorHandler) => { if (!appState?.currentWorkspace) {
_setCreateEditorHandler(() => handler); return;
},
[_setCreateEditorHandler]
);
const loadWorkspace = useRef<AppStateContext['loadWorkspace']>(() =>
Promise.resolve(null)
);
loadWorkspace.current = async (workspaceId: string) => {
if (state.currentWorkspaceId === workspaceId) {
return state.currentWorkspace;
} }
const workspace = const currentWorkspace = appState.currentWorkspace;
(await loadWorkspaceHandler(workspaceId, state.user)) || null; const dispose = currentWorkspace.meta.pagesUpdated.on(() => {
setAppState({
...appState,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
});
}).dispose;
return () => {
dispose();
};
}, [appState]);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment useEffect(() => {
// @ts-expect-error const { dataCenter } = appState;
window.workspace = workspace; // FIXME: onWorkspacesChange should have dispose function
// FIXME: there needs some method to destroy websocket. dataCenter?.onWorkspacesChange(() => {
// Or we need a manager to manage websocket. setAppState({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment ...appState,
// @ts-expect-error workspaceList: dataCenter.workspaces,
state.currentWorkspace?.__ws__?.destroy(); });
});
}, [appState]);
setState(state => ({ const loadPage = useRef<AppStateFunction['loadPage']>();
...state, loadPage.current = (pageId: string) => {
const { currentWorkspace, currentPage } = appState;
if (pageId === currentPage?.id) {
return;
}
const page = currentWorkspace?.getPage(pageId) || null;
setAppState({
...appState,
currentPage: page,
});
};
const loadWorkspace = useRef<AppStateFunction['loadWorkspace']>();
loadWorkspace.current = async (workspaceId: string) => {
const { dataCenter, workspaceList, currentWorkspaceId, currentWorkspace } =
appState;
if (!workspaceList.find(v => v.id === workspaceId)) {
return null;
}
if (workspaceId === currentWorkspaceId) {
return currentWorkspace;
}
const workspace = await dataCenter.loadWorkspace(workspaceId);
const currentMetaWorkSpace = dataCenter.workspaces.find(
(item: WorkspaceInfo) => {
return item.id === workspace.room;
}
);
setAppState({
...appState,
currentWorkspace: workspace, currentWorkspace: workspace,
currentWorkspaceId: workspaceId, currentWorkspaceId: workspaceId,
})); currentMetaWorkSpace: currentMetaWorkSpace ?? null,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
currentPage: null,
editor: null,
});
return workspace; return workspace;
}; };
const loadPage = useRef<AppStateContext['loadPage']>(() => const setEditor: AppStateFunction['setEditor'] =
Promise.resolve(null) useRef() as AppStateFunction['setEditor'];
); setEditor.current = editor => {
loadPage.current = async (pageId: string) => { setAppState({
const { currentWorkspace, currentPage } = state; ...appState,
if (pageId === currentPage?.id) { editor,
return currentPage; });
}
const page = (pageId ? currentWorkspace?.getPage(pageId) : null) || null;
setState(state => ({ ...state, currentPage: page }));
return page;
}; };
const createEditor = useRef<
((page: Page) => EditorContainer | null) | undefined
>();
createEditor.current = () => {
const { currentPage, currentWorkspace } = state;
if (!currentPage || !currentWorkspace) {
return null;
}
const editor = createEditorHandler?.(currentPage) || null;
if (editor) {
const pageMeta = currentWorkspace.meta.pageMetas.find(
p => p.id === currentPage.id
);
if (pageMeta?.mode) {
editor.mode = pageMeta.mode as 'page' | 'edgeless' | undefined;
}
if (pageMeta?.trash) {
editor.readonly = true;
}
}
return editor;
};
const setEditor = useRef<(editor: AppStateValue['editor']) => void>();
setEditor.current = (editor: AppStateValue['editor']) => {
setState(state => ({ ...state, editor }));
};
const context = useMemo(
() => ({
...state,
setState,
createEditor,
setEditor,
loadWorkspace: loadWorkspace.current,
loadPage: loadPage.current,
}),
[state, setState, loadPage, loadWorkspace]
);
return ( return (
<AppState.Provider value={context}> <AppState.Provider
<DynamicBlocksuite setCreateEditorHandler={setCreateEditorHandler} /> value={{
...appState,
setEditor,
loadPage: loadPage.current,
loadWorkspace: loadWorkspace.current,
}}
>
{children} {children}
</AppState.Provider> </AppState.Provider>
); );

View File

@@ -1,61 +0,0 @@
import { createContext, MutableRefObject, useContext } from 'react';
import type { AccessTokenMessage, Workspace } from '@affine/datacenter';
import type {
Page as StorePage,
Workspace as StoreWorkspace,
} from '@blocksuite/store';
import type { EditorContainer } from '@blocksuite/editor';
export type LoadWorkspaceHandler = (
workspaceId: string,
user?: AccessTokenMessage | null
) => Promise<StoreWorkspace | null> | null;
export type CreateEditorHandler = (page: StorePage) => EditorContainer | null;
export interface AppStateValue {
user: AccessTokenMessage | null;
workspacesMeta: Workspace[];
currentWorkspaceId: string;
currentWorkspace: StoreWorkspace | null;
currentPage: StorePage | null;
// workspaces: Record<string, StoreWorkspace | null>;
editor: EditorContainer | null;
synced: boolean;
refreshWorkspacesMeta: () => void;
}
export interface AppStateContext extends AppStateValue {
setState: (state: AppStateValue) => void;
createEditor?: MutableRefObject<
((page: StorePage) => EditorContainer | null) | undefined
>;
setEditor?: MutableRefObject<((page: EditorContainer) => void) | undefined>;
loadWorkspace: (workspaceId: string) => Promise<StoreWorkspace | null>;
loadPage: (pageId: string) => Promise<StorePage | null>;
}
export const AppState = createContext<AppStateContext>({
user: null,
workspacesMeta: [],
currentWorkspaceId: '',
currentWorkspace: null,
currentPage: null,
editor: null,
// eslint-disable-next-line @typescript-eslint/no-empty-function
setState: () => {},
createEditor: undefined,
setEditor: undefined,
loadWorkspace: () => Promise.resolve(null),
loadPage: () => Promise.resolve(null),
synced: false,
// eslint-disable-next-line @typescript-eslint/no-empty-function
refreshWorkspacesMeta: () => {},
});
export const useAppState = () => {
return useContext(AppState);
};

View File

@@ -1,2 +1,2 @@
export * from './context'; export * from './Provider';
export * from './interface'; export * from './interface';

View File

@@ -1,17 +1,40 @@
import { PageMeta as OriginalPageMeta } from '@blocksuite/store/dist/workspace/workspace'; import { DataCenter, User, WorkspaceInfo } from '@affine/datacenter';
import type { EditorContainer } from '@blocksuite/editor';
// export type PageMeta = { import type {
// favorite: boolean; Page as StorePage,
// trash: boolean; Workspace as StoreWorkspace,
// trashDate: number | void; PageMeta as StorePageMeta,
// updatedDate: number | void; } from '@blocksuite/store';
// mode: EditorContainer['mode']; import { MutableRefObject } from 'react';
// } & OriginalPageMeta; export interface PageMeta extends StorePageMeta {
export interface PageMeta extends OriginalPageMeta {
favorite: boolean; favorite: boolean;
trash: boolean; trash: boolean;
trashDate: number; trashDate: number;
updatedDate: number; updatedDate: number;
mode: 'edgeless' | 'page'; mode: 'edgeless' | 'page';
} }
export type AppStateValue = {
dataCenter: DataCenter;
user: User | null;
workspaceList: WorkspaceInfo[];
currentWorkspace: StoreWorkspace;
currentMetaWorkSpace: WorkspaceInfo | null;
currentWorkspaceId: string;
pageList: PageMeta[];
currentPage: StorePage | null;
editor?: EditorContainer | null;
synced: boolean;
};
export type AppStateFunction = {
setEditor: MutableRefObject<(page: EditorContainer) => void>;
loadWorkspace: (workspaceId: string) => Promise<StoreWorkspace | null>;
loadPage: (pageId: string) => void;
};
export type AppStateContext = AppStateValue & AppStateFunction;
export type CreateEditorHandler = (page: StorePage) => EditorContainer | null;

View File

@@ -0,0 +1,10 @@
import { DataCenter } from '@affine/datacenter';
const DEFAULT_WORKSPACE_NAME = 'affine';
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
return dataCenter.createWorkspace({
avatar: 'test',
name: DEFAULT_WORKSPACE_NAME,
});
};

View File

@@ -37,8 +37,11 @@
"ky-universal": "^0.11.0", "ky-universal": "^0.11.0",
"lib0": "^0.2.58", "lib0": "^0.2.58",
"swr": "^2.0.0", "swr": "^2.0.0",
"vitest": "^0.26.3", "yjs": "^13.5.44",
"y-protocols": "^1.0.5", "y-protocols": "^1.0.5"
"yjs": "^13.5.44" },
"peerDependencies": {
"@blocksuite/blocks": "0.3.1-*",
"@blocksuite/store": "0.3.1-*"
} }
} }

View File

@@ -10,7 +10,7 @@ import { getLogger } from './logger';
import { applyUpdate, encodeStateAsUpdate } from 'yjs'; import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { TauriIPCProvider } from './provider/tauri-ipc'; import { TauriIPCProvider } from './provider/tauri-ipc';
import { createBlocksuiteWorkspace } from './utils/index.js'; import { createBlocksuiteWorkspace } from './utils/index.js';
import { MessageCenter } from './message/message'; import { MessageCenter } from './message';
/** /**
* @class DataCenter * @class DataCenter
@@ -313,6 +313,7 @@ export class DataCenter {
applyUpdate(newWorkspace.doc, encodeStateAsUpdate(workspace.doc)); applyUpdate(newWorkspace.doc, encodeStateAsUpdate(workspace.doc));
assert(newWorkspace, 'Create workspace failed'); assert(newWorkspace, 'Create workspace failed');
await currentProvider.deleteWorkspace(workspace.room); await currentProvider.deleteWorkspace(workspace.room);
return newWorkspace.room;
} }
/** /**

View File

@@ -25,7 +25,8 @@ const _initializeDataCenter = () => {
export const getDataCenter = _initializeDataCenter(); export const getDataCenter = _initializeDataCenter();
export type { DataCenter };
export type { AccessTokenMessage } from './provider/affine/apis'; export type { AccessTokenMessage } from './provider/affine/apis';
export type { WorkspaceInfo } from './types'; export * from './types';
export { getLogger } from './logger'; export { getLogger } from './logger';
export * from './message'; export * from './message';

View File

@@ -1,5 +1,5 @@
import { Observable } from 'lib0/observable'; import { Observable } from 'lib0/observable';
import { Message } from 'src/types'; import { Message } from '../types';
import { MessageCode } from './code'; import { MessageCode } from './code';
export class MessageCenter extends Observable<string> { export class MessageCenter extends Observable<string> {

View File

@@ -11,7 +11,7 @@ import { WebsocketProvider } from './sync.js';
import { getApis } from './apis/index.js'; import { getApis } from './apis/index.js';
import type { Apis, WorkspaceDetail, Callback } from './apis'; import type { Apis, WorkspaceDetail, Callback } from './apis';
import { setDefaultAvatar } from '../utils.js'; import { setDefaultAvatar } from '../utils.js';
import { MessageCode } from 'src/message/code.js'; import { MessageCode } from '../../message';
export interface AffineProviderConstructorParams export interface AffineProviderConstructorParams
extends ProviderConstructorParams { extends ProviderConstructorParams {
@@ -146,8 +146,8 @@ export class AffineProvider extends BaseProvider {
if (workspace) { if (workspace) {
workspaces[i] = { workspaces[i] = {
...workspaces[i], ...workspaces[i],
name: workspace.doc.meta.name, name: workspace.meta.name,
avatar: workspace.doc.meta.avatar, avatar: workspace.meta.avatar,
}; };
} }
}); });

View File

@@ -1,5 +1,5 @@
import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store'; import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store';
import { MessageCenter } from 'src/message'; import { MessageCenter } from '../message';
import { Logger, User, WorkspaceInfo, WorkspaceMeta } from '../types'; import { Logger, User, WorkspaceInfo, WorkspaceMeta } from '../types';
import type { WorkspaceMetaCollectionScope } from '../workspace-meta-collection'; import type { WorkspaceMetaCollectionScope } from '../workspace-meta-collection';

View File

@@ -25,8 +25,9 @@ export class LocalProvider extends BaseProvider {
public override async linkLocal(workspace: BlocksuiteWorkspace) { public override async linkLocal(workspace: BlocksuiteWorkspace) {
assert(workspace.room); assert(workspace.room);
let idb = this._idbMap.get(workspace.room); let idb = this._idbMap.get(workspace.room);
idb?.destroy(); if (!idb) {
idb = new IndexedDBProvider(workspace.room, workspace.doc); idb = new IndexedDBProvider(workspace.room, workspace.doc);
}
this._idbMap.set(workspace.room, idb); this._idbMap.set(workspace.room, idb);
this._logger('Local data loaded'); this._logger('Local data loaded');
return workspace; return workspace;

2615
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff