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
-1
View File
@@ -46,7 +46,6 @@
"jest": "^29.3.1",
"lint-staged": "^13.1.0",
"prettier": "^2.7.1",
"ts-jest": "^29.0.3",
"typescript": "^4.9.3"
},
"eslintConfig": {
@@ -1,26 +1,29 @@
import { styled } from '@/styles';
import { useRouter } from 'next/router';
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
import { useState } from 'react';
import Input from '@/ui/input';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { KeyboardEvent } from 'react';
import { useTranslation } from '@affine/i18n';
interface ICloseParams {
workspaceId?: string;
}
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
interface ModalProps {
open: boolean;
onClose: (opts: ICloseParams) => void;
onClose: () => void;
}
export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
const [workspaceName, setWorkspaceName] = useState('');
const { createWorkspace, setActiveWorkspace } = useTemporaryHelper();
const handleCreateWorkspace = () => {
const workspace = createWorkspace(workspaceName);
onClose({ workspaceId: workspace.id });
setActiveWorkspace(workspace);
const { createWorkspace } = useWorkspaceHelper();
const router = useRouter();
const handleCreateWorkspace = async () => {
const workspace = await createWorkspace(workspaceName);
if (workspace && workspace.room) {
router.replace(`/workspace/${workspace.room}`);
onClose();
} else {
console.log('create error');
}
};
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
@@ -39,7 +42,7 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
top={6}
right={6}
onClick={() => {
onClose({});
onClose();
}}
/>
</Header>
@@ -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;
@@ -6,7 +6,7 @@ import {
StyledTitleWrapper,
} from './styles';
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 QuickSearchButton from './QuickSearchButton';
import Header from './Header';
@@ -1,6 +1,6 @@
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
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';
export const SyncUser = () => {
+1 -1
View File
@@ -4,7 +4,7 @@ import { Button } from '@/ui/button';
import { Wrapper, Content } from '@/ui/layout';
import Loading from '@/components/loading';
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 { useTranslation } from '@affine/i18n';
// import { Tooltip } from '@/ui/tooltip';
@@ -1,11 +1,11 @@
import { getDataCenter } from '@affine/datacenter';
// import { getDataCenter } from '@affine/datacenter';
import { styled } from '@/styles';
import { Button } from '@/ui/button';
import { useModal } from '@/providers/GlobalModalProvider';
// import { useModal } from '@/providers/GlobalModalProvider';
import { GoogleIcon, StayLogOutIcon } from './Icons';
export const GoogleLoginButton = () => {
const { triggerLoginModal } = useModal();
// const { triggerLoginModal } = useModal();
return (
<StyledGoogleButton
onClick={() => {
@@ -20,7 +20,7 @@ import DateCell from '@/components/page-list/DateCell';
import { IconButton } from '@/ui/button';
import { Tooltip } from '@/ui/tooltip';
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 { usePageHelper } from '@/hooks/use-page-helper';
import { useTheme } from '@/providers/ThemeProvider';
@@ -17,11 +17,10 @@ export const Input = (props: {
const [isComposition, setIsComposition] = useState(false);
const [inputValue, setInputValue] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
const { currentWorkspaceId, workspacesMeta, currentWorkspace } =
useAppState();
const isPublic = workspacesMeta.find(
const { currentWorkspaceId, workspaceList, currentWorkspace } = useAppState();
const isPublish = workspaceList.find(
meta => String(meta.id) === String(currentWorkspaceId)
)?.public;
)?.isPublish;
useEffect(() => {
inputRef.current?.addEventListener(
'blur',
@@ -80,7 +79,7 @@ export const Input = (props: {
}
}}
placeholder={
isPublic
isPublish
? `Search in ${currentWorkspace?.meta.name}`
: 'Quick Search...'
}
@@ -22,16 +22,14 @@ const isMac = () => {
return getUaHelper().isMacOs;
};
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
const { currentMetaWorkSpace } = useAppState();
const [query, setQuery] = useState('');
const [loading, setLoading] = useState(true);
const [showCreatePage, setShowCreatePage] = useState(true);
const { triggerQuickSearchModal } = useModal();
const { currentWorkspaceId, workspacesMeta } = useAppState();
const currentWorkspace = workspacesMeta.find(
meta => String(meta.id) === String(currentWorkspaceId)
);
const isPublic = currentWorkspace?.public;
const isPublish = currentMetaWorkSpace?.isPublish;
// Add ‘⌘+K shortcut keys as switches
useEffect(() => {
@@ -98,7 +96,7 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
setShowCreatePage={setShowCreatePage}
/>
</StyledContent>
{isPublic ? (
{isPublish ? (
<></>
) : showCreatePage ? (
<>
@@ -1,30 +1,49 @@
import { useAppState } from '@/providers/app-state-provider';
import { stringToColour } from '@/utils';
import { useEffect, useState } from 'react';
interface IWorkspaceAvatar {
size: number;
name: string;
avatar: string;
}
export const WorkspaceAvatar = (props: IWorkspaceAvatar) => {
const size = props.size || 20;
const sizeStr = size + 'px';
return (
<>
<div
style={{
width: sizeStr,
height: sizeStr,
border: '1px solid #fff',
color: '#fff',
fontSize: Math.ceil(0.5 * size) + 'px',
background: stringToColour(props.name || 'AFFiNE'),
borderRadius: '50%',
textAlign: 'center',
lineHeight: size + 'px',
}}
>
{(props.name || 'AFFiNE').substring(0, 1)}
</div>
{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
style={{
width: sizeStr,
height: sizeStr,
border: '1px solid #fff',
color: '#fff',
fontSize: Math.ceil(0.5 * size) + 'px',
background: stringToColour(props.name || 'AFFiNE'),
borderRadius: '50%',
textAlign: 'center',
lineHeight: size + 'px',
}}
>
{(props.name || 'AFFiNE').substring(0, 1)}
</div>
)}
</>
);
};
@@ -10,27 +10,24 @@ import {
UsersIcon,
AddIcon,
} from '@blocksuite/icons';
import { useConfirm } from '@/providers/ConfirmProvider';
// import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast';
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;
onClose: () => void;
}
export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
const { confirm } = useConfirm();
const {
user,
login,
workspaceMetaList,
setActiveWorkspace,
updateWorkspaceMeta,
} = useTemporaryHelper();
// const { confirm } = useConfirm();
const { workspaceList, currentWorkspace } = useAppState();
const { login, user } = useUserHelper();
const router = useRouter();
return (
<div>
<Modal open={open} onClose={onClose}>
@@ -51,13 +48,14 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
</Header>
<Content>
<WorkspaceList>
{workspaceMetaList.map((item, index) => {
{workspaceList.map((item, index) => {
return (
<WorkspaceItem
onClick={() => {
setActiveWorkspace(item);
router.replace(`/workspace/${item.id}`);
onClose();
}}
active={item.id === currentWorkspace.room}
key={index}
>
<span style={{ width: '100px' }}>
@@ -69,7 +67,11 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
marginRight: '10px',
}}
>
<WorkspaceAvatar size={50} name={item.name} />
<WorkspaceAvatar
size={50}
name={item.name}
avatar={item.avatar ?? ''}
/>
</div>
<span
@@ -93,10 +95,10 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
top: '20px',
}}
>
{(item.type === 'local' || !item.type) && (
{(item.provider === 'local' || !item.provider) && (
<CloudUnsyncedIcon fontSize={24} />
)}
{item.type === 'cloud' && (
{item.provider === 'affine' && (
<CloudInsyncIcon fontSize={24} />
)}
{item.isPublish && <UsersIcon fontSize={24} />}
@@ -154,26 +156,26 @@ export const WorkspaceModal = ({ open, onClose }: LoginModalProps) => {
</Footer>
<CreateWorkspaceModal
open={createWorkspaceOpen}
onClose={({ workspaceId }) => {
onClose={() => {
setCreateWorkspaceOpen(false);
onClose();
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText: user ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(confirm => {
if (confirm) {
if (user) {
workspaceId &&
updateWorkspaceMeta(workspaceId, { isPublish: true });
} else {
login();
workspaceId &&
updateWorkspaceMeta(workspaceId, { isPublish: true });
}
}
});
// confirm({
// title: 'Enable AFFiNE Cloud?',
// content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
// confirmText: user ? 'Enable' : 'Sign in and Enable',
// cancelText: 'Skip',
// }).then(confirm => {
// if (confirm) {
// if (user) {
// // workspaceId &&
// // updateWorkspaceMeta(workspaceId, { isPublish: true });
// } else {
// // login();
// // workspaceId &&
// // updateWorkspaceMeta(workspaceId, { isPublish: true });
// }
// }
// });
}}
></CreateWorkspaceModal>
</ModalWrapper>
@@ -218,11 +220,17 @@ const WorkspaceList = styled('div')({
gridTemplateColumns: 'repeat(2, 1fr)',
});
const WorkspaceItem = styled('div')({
cursor: 'pointer',
padding: '8px',
border: '1px solid #eee',
':hover': {
background: '#eee',
},
export const WorkspaceItem = styled.div<{
active: boolean;
}>(({ theme, active }) => {
const backgroundColor = active ? theme.colors.hoverBackground : 'transparent';
return {
cursor: 'pointer',
padding: '8px',
border: '1px solid #eee',
backgroundColor: backgroundColor,
':hover': {
background: theme.colors.hoverBackground,
},
};
});
@@ -1,5 +1,5 @@
import { Workspace } from '@/hooks/mock-data/mock';
import { styled } from '@/styles';
import { WorkspaceInfo } from '@affine/datacenter';
export const ExportPageTitleContainer = styled('div')(() => {
return {
@@ -9,7 +9,7 @@ export const ExportPageTitleContainer = styled('div')(() => {
flex: 1,
};
});
export const ExportPage = ({ workspace }: { workspace: Workspace }) => {
export const ExportPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
return (
<ExportPageTitleContainer>
Export Workspace{' '}
@@ -10,6 +10,7 @@ import {
StyledMemberRoleContainer,
StyledMemberTitleContainer,
StyledMoreVerticalButton,
StyledPublishExplanation,
} from './style';
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
import { useEffect, useState } from 'react';
@@ -17,20 +18,24 @@ import { Button, IconButton } from '@/ui/button';
import { InviteMembers } from '../invite-members/index';
import { Menu, MenuItem } from '@/ui/menu';
import { Empty } from '@/ui/empty';
import {
deleteMember,
getMembers,
User,
Workspace,
} from '@/hooks/mock-data/mock';
// import {
// deleteMember,
// getMembers,
// User,
// Workspace,
// } from '@/hooks/mock-data/mock';
import { WorkspaceInfo } from '@affine/datacenter';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { StyledMemberWarp } from './general/style';
import { useConfirm } from '@/providers/ConfirmProvider';
// import { useAppState } from '@/providers/app-state-provider';
export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
export const MembersPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
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 { confirm } = useConfirm();
// const refreshMembers = useCallback(() => {
@@ -48,8 +53,9 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
// });
// }, [workspace.id]);
const setMembersList = () => {
const members = getMembers(workspace.id);
members && setMembers(members);
// setMembers([]);
// const members = getMembers(workspace.id);
// members && setMembers(members);
};
useEffect(() => {
setMembersList();
@@ -58,7 +64,7 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
return (
<div>
{workspace.type === 'cloud' ? (
{workspace.provider === 'cloud' ? (
<>
<StyledMemberTitleContainer>
<StyledMemberNameContainer>
@@ -67,13 +73,6 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
</StyledMemberTitleContainer>
<StyledMemberListContainer>
{members.length === 0 && (
<Empty
width={648}
sx={{ marginTop: '60px' }}
height={300}
></Empty>
)}
{members.length ? (
members.map((member, index) => {
return (
@@ -103,7 +102,7 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
<>
<MenuItem
onClick={() => {
deleteMember(workspace.id, 0);
// deleteMember(workspace.id, 0);
setMembersList();
// confirm({
// title: 'Delete Member?',
@@ -142,7 +141,11 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
);
})
) : (
<></>
<Empty
width={648}
sx={{ marginTop: '60px' }}
height={300}
></Empty>
)}
</StyledMemberListContainer>
<StyledMemberButtonContainer>
@@ -171,10 +174,8 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
</>
) : (
<StyledMemberWarp>
<div style={{ flex: 1 }}>
Collaborating with other members requires AFFiNE Cloud service.
</div>
<div style={{ height: '40px' }}>
<>Collaborating with other members requires AFFiNE Cloud service.</>
<StyledPublishExplanation>
<Button
type="primary"
shape="circle"
@@ -198,7 +199,7 @@ export const MembersPage = ({ workspace }: { workspace: Workspace }) => {
>
Enable AFFiNE Cloud
</Button>
</div>
</StyledPublishExplanation>
</StyledMemberWarp>
)}
</div>
@@ -9,20 +9,20 @@ import {
import { Button } from '@/ui/button';
import Input from '@/ui/input';
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 { useAppState } from '@/providers/app-state-provider3';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { WorkspaceInfo } from '@affine/datacenter';
export const PublishPage = ({ workspace }: { workspace: Workspace }) => {
console.log('workspace: ', workspace);
export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
const shareUrl =
window.location.host + '/workspace/' + workspace.id + '?share=true';
const { publishWorkspace } = useWorkspaceHelper();
const { login, updateWorkspaceMeta, user } = useTemporaryHelper();
const { confirm } = useConfirm();
const togglePublic = (flag: boolean) => {
updateWorkspaceMeta(workspace.id, { isPublish: flag });
workspace.id && publishWorkspace(workspace?.id, flag);
};
const copyUrl = () => {
@@ -34,22 +34,23 @@ export const PublishPage = ({ workspace }: { workspace: Workspace }) => {
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText: user ? 'Enable' : 'Sign in and Enable',
confirmText:
workspace.provider === 'local' ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(confirm => {
if (confirm) {
if (user) {
updateWorkspaceMeta(workspace.id, { type: 'cloud' });
} else {
login();
updateWorkspaceMeta(workspace.id, { type: 'cloud' });
}
// if (user) {
// updateWorkspaceMeta(workspace.id, { type: 'cloud' });
// } else {
// login();
// updateWorkspaceMeta(workspace.id, { type: 'cloud' });
// }
}
});
};
return (
<>
{workspace.type === 'cloud' ? (
{workspace.provider === 'cloud' ? (
<div>
<StyledPublishContent>
{workspace?.isPublish ? (
@@ -6,28 +6,48 @@ import {
import { DownloadIcon } from '@blocksuite/icons';
import { Button } from '@/ui/button';
import { Menu, MenuItem } from '@/ui/menu';
import { deleteMember, Workspace } from '@/hooks/mock-data/mock';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
const { currentWorkspace, updateWorkspaceMeta } = useTemporaryHelper();
import { WorkspaceInfo } from '@affine/datacenter';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
import { useAppState } from '@/providers/app-state-provider';
import { useConfirm } from '@/providers/ConfirmProvider';
import { toast } from '@/ui/toast';
import { useUserHelper } from '@/hooks/use-user-helper';
import { useRouter } from 'next/router';
export const SyncPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
// console.log('workspace: ', workspace);
const { enableWorkspace } = useWorkspaceHelper();
const { currentWorkspace } = useAppState();
const { confirm } = useConfirm();
const { user, login } = useUserHelper();
const router = useRouter();
return (
<div>
<StyledPublishContent>
{currentWorkspace?.type === 'local' ? (
{workspace?.provider === 'local' ? (
<>
<StyledPublishExplanation>
{currentWorkspace.name} is Local Workspace. All data is stored on
the current device. You can enable AFFiNE Cloud for this workspace
to keep data in sync with the cloud.
{workspace.name ?? 'Affine'} is Local Workspace. All data is
stored on the current device. You can enable AFFiNE Cloud for this
workspace to keep data in sync with the cloud.
</StyledPublishExplanation>
<StyledPublishCopyContainer>
<Button
onClick={() => {
updateWorkspaceMeta(currentWorkspace.id, {
type: 'cloud',
confirm({
title: 'Enable AFFiNE Cloud?',
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
confirmText: user ? 'Enable' : 'Sign in and Enable',
cancelText: 'Skip',
}).then(async confirm => {
if (confirm) {
// if (user) {
// await login();
// }
const id = await enableWorkspace(currentWorkspace);
router.push(`/workspace/${id}`);
toast('Enabled success');
}
});
}}
type="primary"
@@ -40,8 +60,8 @@ export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
) : (
<>
<StyledPublishExplanation>
<code>{currentWorkspace && currentWorkspace.name}</code> is Cloud
Workspace. All data will be synchronized and saved to the AFFiNE
<code>{workspace.name ?? 'Affine'}</code> is Cloud Workspace. All
data will be synchronized and saved to the AFFiNE
</StyledPublishExplanation>
<StyledPublishCopyContainer>
<Menu
@@ -49,7 +69,7 @@ export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
<>
<MenuItem
onClick={() => {
deleteMember(workspace.id, 0);
// deleteMember(workspace.id, 0);
}}
icon={<DownloadIcon />}
>
@@ -57,7 +77,7 @@ export const SyncPage = ({ workspace }: { workspace: Workspace }) => {
</MenuItem>
<MenuItem
onClick={() => {
deleteMember(workspace.id, 0);
// deleteMember(workspace.id, 0);
}}
icon={<DownloadIcon />}
>
@@ -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>
);
};
@@ -9,26 +9,21 @@ import { StyledSettingH2 } from '../style';
import { useState } from 'react';
import { Button, TextButton } from '@/ui/button';
import Input from '@/ui/input';
// import { useAppState } from '@/providers/app-state-provider';
import { useAppState } from '@/providers/app-state-provider';
import { WorkspaceDelete } from './delete';
// import { debounce } from '@/utils';
import { WorkspaceLeave } from './leave';
import { Upload } from '@/components/file-upload';
import { Workspace } from '@/hooks/mock-data/mock';
import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
// const { refreshWorkspacesMeta } = useAppState();
const { updateWorkspaceMeta } = useTemporaryHelper();
import { WorkspaceInfo } from '@affine/datacenter';
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
const [showDelete, setShowDelete] = useState<boolean>(false);
const [showLeave, setShowLeave] = useState<boolean>(false);
const [uploading, setUploading] = useState<boolean>(false);
const [workspaceName, setWorkspaceName] = useState<string>('');
// const debouncedRefreshWorkspacesMeta = debounce(() => {
// refreshWorkspacesMeta();
// }, 100);
const [workspaceName, setWorkspaceName] = useState<string>(workspace.name);
const { currentWorkspace } = useAppState();
const { updateWorkspace } = useWorkspaceHelper();
const isOwner = true;
const handleChangeWorkSpaceName = (newName: string) => {
setWorkspaceName(newName);
};
@@ -46,24 +41,15 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
setShowLeave(false);
};
const handleUpdateWorkspaceName = () => {
workspace && updateWorkspaceMeta(workspace.id, { name: workspaceName });
console.log('currentWorkspace: ', currentWorkspace);
updateWorkspace({ name: workspaceName }, currentWorkspace);
};
const fileChange = async (file: File) => {
console.log('file: ', file);
setUploading(true);
// const blob = new Blob([file], { type: file.type });
// const blobId = await getDataCenter()
// .then(dc => dc.apis.uploadBlob({ blob }))
// .finally(() => {
// setUploading(false);
// });
// if (blobId) {
// currentWorkspace?.meta.setAvatar(blobId);
// // workspaces[workspace.id]?.meta.setAvatar(blobId);
// setUploading(false);
// debouncedRefreshWorkspacesMeta();
// }
// console.log('file: ', file);
// setUploading(true);
const blob = new Blob([file], { type: file.type });
updateWorkspace({ avatarBlob: blob }, currentWorkspace);
};
return workspace ? (
@@ -73,11 +59,14 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
<div
style={{
float: 'left',
marginRight: '5px',
marginRight: '20px',
}}
>
<WorkspaceAvatar size={60} name={workspace.name} />
<WorkspaceAvatar
size={60}
name={workspace.name}
avatar={workspace.avatar ?? ''}
/>
</div>
<Upload
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
@@ -101,30 +90,14 @@ export const GeneralPage = ({ workspace }: { workspace: Workspace }) => {
onClick={() => {
handleUpdateWorkspaceName();
}}
style={{ marginLeft: '10px' }}
style={{ marginLeft: '0px' }}
>
</TextButton>
</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>
<StyledSettingInputContainer>
<code>{workspace.type} </code>
<code>{workspace.provider} </code>
</StyledSettingInputContainer>
<StyledDeleteButtonContainer>
{isOwner ? (
@@ -15,13 +15,14 @@ import { useRouter } from 'next/router';
import {
deleteWorkspace,
getWorkspaces,
Workspace,
// Workspace,
} from '@/hooks/mock-data/mock';
import { WorkspaceInfo } from '@affine/datacenter';
interface WorkspaceDeleteProps {
open: boolean;
onClose: () => void;
workspace: Workspace;
workspace: WorkspaceInfo;
}
export const WorkspaceDelete = ({
@@ -55,7 +56,7 @@ export const WorkspaceDelete = ({
<StyledModalWrapper>
<ModalCloseButton onClick={onClose} />
<StyledModalHeader>Delete Workspace</StyledModalHeader>
{workspace.type === 'local' ? (
{workspace.provider === 'local' ? (
<StyledTextContent>
Deleting (
<StyledWorkspaceName>{workspace.name}</StyledWorkspaceName>) cannot
@@ -1 +1 @@
export * from './Delete';
export { WorkspaceDelete } from './Delete';
@@ -1 +1 @@
export * from './General';
export { GeneralPage } from './General';
@@ -8,7 +8,7 @@ import {
import { ModalCloseButton } from '@/ui/modal';
import { Button } from '@/ui/button';
// import { getDataCenter } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider';
// import { useAppState } from '@/providers/app-state-provider';
interface WorkspaceDeleteProps {
open: boolean;
@@ -22,8 +22,9 @@ export const WorkspaceLeave = ({
onClose,
workspaceId,
}: WorkspaceDeleteProps) => {
console.log('workspaceId: ', workspaceId);
// const router = useRouter();
const { refreshWorkspacesMeta } = useAppState();
// const { refreshWorkspacesMeta } = useAppState();
const handleLeave = async () => {
// const dc = await getDataCenter();
// await dc.apis.leaveWorkspace({ id: workspaceId });
@@ -1 +1 @@
export * from './Leave';
export { WorkspaceLeave } from './Leave';
@@ -30,8 +30,7 @@ export const StyledSettingAvatar = styled(MuiAvatar)(() => {
export const StyledMemberWarp = styled('div')(() => {
return {
display: 'flex',
height: '500px',
flexDirection: 'column',
padding: '60px 0',
padding: '40px 0',
};
});
@@ -1 +1,5 @@
export * from './WorkspaceSetting';
export * from './general';
export * from './ExportPage';
export * from './MembersPage';
export * from './SyncPage';
export * from './PublishPage';
@@ -4,22 +4,17 @@ import MuiAvatar from '@mui/material/Avatar';
export const StyledSettingContainer = styled('div')(({ theme }) => {
return {
position: 'relative',
display: 'flex',
padding: '0px',
width: '961px',
background: theme.colors.popoverBackground,
borderRadius: '12px',
overflow: 'hidden',
padding: '50px',
flexDirection: 'column',
height: '100vh',
overflow: 'auto',
};
});
export const StyledSettingSidebar = styled('div')(({ theme }) => {
{
return {
width: '212px',
height: '620px',
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
flexShrink: 0,
flexGrow: 0,
};
@@ -29,7 +24,6 @@ export const StyledSettingSidebar = styled('div')(({ theme }) => {
export const StyledSettingContent = styled('div')(() => {
return {
paddingLeft: '48px',
height: '620px',
};
});
@@ -37,7 +31,6 @@ export const StyledSetting = styled('div')(({ theme }) => {
{
return {
width: '236px',
height: '620px',
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
};
}
@@ -49,7 +42,7 @@ export const StyledSettingSidebarHeader = styled('div')(() => {
fontWeight: '500',
fontSize: '18px',
lineHeight: '26px',
textAlign: 'center',
textAlign: 'left',
marginTop: '37px',
};
}
@@ -59,7 +52,6 @@ export const StyledSettingTabContainer = styled('ul')(() => {
{
return {
display: 'flex',
flexDirection: 'column',
marginTop: '25px',
};
}
@@ -144,7 +136,6 @@ export const StyledMemberRoleContainer = styled('div')(() => {
export const StyledMemberListContainer = styled('ul')(() => {
return {
marginTop: '15px',
height: '432px',
overflowY: 'scroll',
};
});
@@ -228,7 +219,6 @@ export const StyledCopyButtonContainer = styled('div')(() => {
export const StyledPublishContent = styled('div')(() => {
return {
height: '494px',
display: 'flex',
flexDirection: 'column',
};
@@ -1,180 +1,183 @@
import { InformationIcon, LogOutIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { Divider } from '@/ui/divider';
import { useAppState } from '@/providers/app-state-provider/context';
import { SelectorPopperContainer } from './styles';
import {
PrivateWorkspaceItem,
WorkspaceItem,
CreateWorkspaceItem,
ListItem,
LoginItem,
} from './WorkspaceItem';
import { WorkspaceSetting } from '@/components/workspace-setting';
import { useCallback, useEffect, useState } from 'react';
import { WorkspaceType } from '@affine/datacenter';
import { useModal } from '@/providers/GlobalModalProvider';
// import { InformationIcon, LogOutIcon } from '@blocksuite/icons';
// import { styled } from '@/styles';
// import { Divider } from '@/ui/divider';
// import { useAppState } from '@/providers/app-state-provider';
// import { SelectorPopperContainer } from './styles';
// import {
// PrivateWorkspaceItem,
// WorkspaceItem,
// CreateWorkspaceItem,
// ListItem,
// LoginItem,
// } from './WorkspaceItem';
// import { WorkspaceSetting } from '@/components/workspace-setting';
// import { useCallback, useEffect, useState } from 'react';
// import { WorkspaceInfo } from '@affine/datacenter';
// import { useModal } from '@/providers/GlobalModalProvider';
export type WorkspaceDetails = Record<
string,
{ memberCount: number; owner: { id: string; name: string } }
>;
// export type WorkspaceDetails = Record<
// string,
// { memberCount: number; owner: { id: string; name: string } }
// >;
type SelectorPopperContentProps = {
isShow: boolean;
// type SelectorPopperContentProps = {
// 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',
};
});
@@ -1,4 +1,4 @@
import { getDataCenter } from '@affine/datacenter';
// import { getDataCenter } from '@affine/datacenter';
import Modal from '@/ui/modal';
import Input from '@/ui/input';
import {
@@ -11,8 +11,8 @@ import {
} from './style';
import { useState } from 'react';
import { ModalCloseButton } from '@/ui/modal';
import router from 'next/router';
import { useAppState } from '@/providers/app-state-provider';
// import router from 'next/router';
// import { useAppState } from '@/providers/app-state-provider';
interface WorkspaceCreateProps {
open: boolean;
@@ -30,7 +30,7 @@ const DefaultHeadImgColors = [
export const WorkspaceCreate = ({ open, onClose }: WorkspaceCreateProps) => {
const [workspaceName, setWorkspaceId] = useState<string>('');
const [creating, setCreating] = useState<boolean>(false);
const { refreshWorkspacesMeta } = useAppState();
// const { refreshWorkspacesMeta } = useAppState();
const handlerInputChange = (workspaceName: string) => {
setWorkspaceId(workspaceName);
};
@@ -1,60 +1,63 @@
import { styled } from '@/styles';
import { useAppState } from '@/providers/app-state-provider/context';
import {
WorkspaceItemAvatar,
PrivateWorkspaceWrapper,
WorkspaceItemContent,
} from './styles';
import { useRouter } from 'next/router';
// import { styled } from '@/styles';
// import { useAppState } from '@/providers/app-state-provider';
// import {
// WorkspaceItemAvatar,
// PrivateWorkspaceWrapper,
// WorkspaceItemContent,
// } from './styles';
// import { useRouter } from 'next/router';
type PrivateWorkspaceItemProps = {
privateWorkspaceId?: string;
// type PrivateWorkspaceItemProps = {
// 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',
};
});
@@ -1,96 +1,100 @@
import { useRouter } from 'next/router';
import { styled } from '@/styles';
import {
WorkspaceItemAvatar,
WorkspaceItemWrapper,
WorkspaceItemContent,
} from '../styles';
import { FooterSetting } from './FooterSetting';
import { FooterUsers } from './FooterUsers';
import { WorkspaceType } from '@affine/datacenter';
import { useAppState } from '@/providers/app-state-provider';
// import { useRouter } from 'next/router';
// import { styled } from '@/styles';
// import {
// WorkspaceItemAvatar,
// WorkspaceItemWrapper,
// WorkspaceItemContent,
// } from '../styles';
// import { FooterSetting } from './FooterSetting';
// import { FooterUsers } from './FooterUsers';
// import { WorkspaceType } from '@affine/datacenter';
// import { useAppState } from '@/providers/app-state-provider';
interface WorkspaceItemProps {
id: string;
name: string;
icon: string;
type: WorkspaceType;
memberCount: number;
onClickSetting?: (workspaceId: string) => void;
}
// interface WorkspaceItemProps {
// id: string;
// name: string;
// icon: string;
// type: WorkspaceType;
// memberCount: number;
// onClickSetting?: (workspaceId: string) => void;
// }
export const WorkspaceItem = ({
id,
name,
icon,
type,
onClickSetting,
memberCount,
}: WorkspaceItemProps) => {
const router = useRouter();
// export const WorkspaceItem = ({
// id,
// name,
// icon,
// type,
// onClickSetting,
// memberCount,
// }: WorkspaceItemProps) => {
// const router = useRouter();
const { currentWorkspaceId } = useAppState();
// const { currentWorkspaceId } = useAppState();
const handleClickSetting = async () => {
onClickSetting && onClickSetting(id);
};
// const handleClickSetting = async () => {
// onClickSetting && onClickSetting(id);
// };
return (
<StyledWrapper
onClick={() => {
router.push(`/workspace/${id}`);
}}
canSet={
type !== WorkspaceType.Private && currentWorkspaceId === String(id)
}
>
<WorkspaceItemAvatar alt={name} src={icon}>
{name.charAt(0)}
</WorkspaceItemAvatar>
<WorkspaceItemContent>
<Name title={name}>{name}</Name>
</WorkspaceItemContent>
<Footer>
<FooterUsers memberCount={memberCount} />
<FooterSetting onClick={handleClickSetting} />
</Footer>
</StyledWrapper>
);
// return (
// <StyledWrapper
// onClick={() => {
// router.push(`/workspace/${id}`);
// }}
// canSet={
// type !== WorkspaceType.Private && currentWorkspaceId === String(id)
// }
// >
// <WorkspaceItemAvatar alt={name} src={icon}>
// {name.charAt(0)}
// </WorkspaceItemAvatar>
// <WorkspaceItemContent>
// <Name title={name}>{name}</Name>
// </WorkspaceItemContent>
// <Footer>
// <FooterUsers memberCount={memberCount} />
// <FooterSetting onClick={handleClickSetting} />
// </Footer>
// </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',
});
@@ -1,5 +1,5 @@
export * from './PrivateWorkspaceItem';
export * from './WorkspaceItem';
// export * from './WorkspaceItem';
export * from './CreateWorkspaceItem';
export * from './ListItem';
export * from './LoginItem';
@@ -2,17 +2,16 @@ import { Avatar, WorkspaceName, SelectorWrapper } from './styles';
import { useEffect, useState } from 'react';
import { WorkspaceModal } from '@/components/workspace-modal';
import { WorkspaceAvatar } from '@/components/workspace-avatar';
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
import { useAppState } from '@/providers/app-state-provider';
export const WorkspaceSelector = () => {
const [workspaceListShow, setWorkspaceListShow] = useState(false);
const { currentWorkspace, workspaceMetaList } = useTemporaryHelper();
const { currentMetaWorkSpace, workspaceList } = useAppState();
useEffect(() => {
if (workspaceMetaList.length === 0) {
if (workspaceList.length === 0) {
setWorkspaceListShow(true);
}
}, [workspaceMetaList]);
}, [workspaceList]);
return (
<>
<SelectorWrapper
@@ -24,7 +23,7 @@ export const WorkspaceSelector = () => {
<Avatar
alt="Affine"
data-testid="workspace-avatar"
src={currentWorkspace?.avatar}
src={currentMetaWorkSpace?.avatar}
>
<div
style={{
@@ -33,12 +32,13 @@ export const WorkspaceSelector = () => {
>
<WorkspaceAvatar
size={28}
name={currentWorkspace?.name ?? 'AFFiNE'}
name={currentMetaWorkSpace?.name ?? 'AFFiNE'}
avatar={currentMetaWorkSpace?.avatar ?? ''}
/>
</div>
</Avatar>
<WorkspaceName data-testid="workspace-name">
{currentWorkspace?.name ?? 'AFFiNE'}
{currentMetaWorkSpace?.name ?? 'AFFiNE'}
</WorkspaceName>
</SelectorWrapper>
<WorkspaceModal
@@ -4,6 +4,7 @@ import {
StyledArrowButton,
StyledLink,
StyledListItem,
StyledListItemForWorkspace,
// StyledListItemForWorkspace,
StyledNewPageButton,
StyledSliderBar,
@@ -26,13 +27,14 @@ import {
import Link from 'next/link';
import { Tooltip } from '@/ui/tooltip';
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 useLocalStorage from '@/hooks/use-local-storage';
import usePageMetaList from '@/hooks/use-page-meta-list';
import { usePageHelper } from '@/hooks/use-page-helper';
import { WorkspaceSetting } from '@/components/workspace-setting';
// import { WorkspaceSetting } from '@/components/workspace-setting';
import { useTranslation } from '@affine/i18n';
import { WorkspaceSelector } from './WorkspaceSelector/WorkspaceSelector';
const FavoriteList = ({ showList }: { showList: boolean }) => {
const { openPage } = usePageHelper();
@@ -76,14 +78,15 @@ export const WorkSpaceSliderBar = () => {
const [showTip, setShowTip] = useState(false);
const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true);
const [showWorkspaceSetting, setShowWorkspaceSetting] = useState(false);
const paths = {
all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
favorite: currentWorkspaceId
? `/workspace/${currentWorkspaceId}/favorite`
: '',
trash: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/trash` : '',
setting: currentWorkspaceId
? `/workspace/${currentWorkspaceId}/setting`
: '',
};
return (
@@ -113,9 +116,9 @@ export const WorkSpaceSliderBar = () => {
</Tooltip>
<StyledSliderBarWrapper data-testid="sliderBar">
{/* <StyledListItemForWorkspace>
<StyledListItemForWorkspace>
<WorkspaceSelector />
</StyledListItemForWorkspace> */}
</StyledListItemForWorkspace>
<StyledListItem
data-testid="sliderBar-quickSearchButton"
style={{ cursor: 'pointer' }}
@@ -150,20 +153,19 @@ export const WorkSpaceSliderBar = () => {
</IconButton>
</StyledListItem>
<FavoriteList showList={showSubFavorite} />
<StyledListItem
onClick={() => {
setShowWorkspaceSetting(true);
}}
>
<SettingsIcon /> Setting
<StyledListItem active={router.asPath === paths.setting}>
<StyledLink href={{ pathname: paths.setting }}>
<SettingsIcon />
Settings
</StyledLink>
</StyledListItem>
<WorkspaceSetting
{/* <WorkspaceSetting
isShow={showWorkspaceSetting}
onClose={() => {
setShowWorkspaceSetting(false);
}}
/>
/> */}
<StyledListItem
onClick={() => {
+7 -10
View File
@@ -7,22 +7,19 @@ const defaultOutLineWorkspaceId = 'affine';
// Cause it not just ensure workspace loaded, but also have router change.
export const useEnsureWorkspace = () => {
const [workspaceLoaded, setWorkspaceLoaded] = useState(false);
const { workspacesMeta, loadWorkspace, synced, user } = useAppState();
const { workspaceList, loadWorkspace, user } = useAppState();
console.log('workspaceList: ', workspaceList);
const router = useRouter();
// const defaultOutLineWorkspaceId = '99ce7eb7';
// console.log(defaultOutLineWorkspaceId);
useEffect(() => {
if (!synced) {
setWorkspaceLoaded(false);
return;
}
// 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 (
workspacesMeta.length &&
workspaceList.length &&
router.query.workspaceId &&
workspacesMeta.findIndex(
workspaceList.findIndex(
meta => meta.id.toString() === router.query.workspaceId
) === -1
) {
@@ -40,13 +37,13 @@ export const useEnsureWorkspace = () => {
// }
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;
loadWorkspace(workspaceId).finally(() => {
setWorkspaceLoaded(true);
});
}, [loadWorkspace, router, synced, user, workspacesMeta]);
}, [loadWorkspace, router, user, workspaceList]);
return {
workspaceLoaded,
+4 -8
View File
@@ -1,5 +1,5 @@
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';
export const useInitWorkspace = (disabled?: boolean) => {
@@ -8,15 +8,11 @@ export const useInitWorkspace = (disabled?: boolean) => {
const defaultOutLineWorkspaceId = useRef(new Date().getTime().toString());
const router = useRouter();
const {
workspacesMeta,
loadWorkspace,
currentWorkspace,
currentWorkspaceId,
} = useAppState();
const { workspaceList, loadWorkspace, currentWorkspace, currentWorkspaceId } =
useAppState();
const workspaceId =
(router.query.workspaceId as string) ||
workspacesMeta?.[0]?.id ||
workspaceList?.[0]?.id ||
defaultOutLineWorkspaceId.current;
useEffect(() => {
+28
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,
};
};
@@ -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,
};
};
+5
View File
@@ -1,4 +1,9 @@
import NotfoundPage from '@/components/404';
import { useAppState } from '@/providers/app-state-provider';
export default function Custom404() {
const { workspaceList } = useAppState();
console.log('workspaceList: ', workspaceList);
return <NotfoundPage></NotfoundPage>;
}
+6 -4
View File
@@ -10,16 +10,18 @@ import '../utils/print-build-info';
import ProviderComposer from '@/components/provider-composer';
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
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 { ModalProvider } from '@/providers/GlobalModalProvider';
// import AppStateProvider2 from '@/providers/app-state-provider2/provider';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { useAppState } from '@/providers/app-state-provider';
import { PageLoading } from '@/components/loading';
import Head from 'next/head';
import TemporaryHelperProvider from '@/providers/temporary-helper-provider';
import '@affine/i18n';
import TemporaryHelperProvider from '@/providers/temporary-helper-provider';
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
ssr: false,
@@ -56,6 +58,7 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
<TemporaryHelperProvider key="TemporaryHelperProvider" />,
<ThemeProvider key="ThemeProvider" />,
<AppStateProvider key="appStateProvider" />,
// <AppStateProvider2 key="appStateProvider2" />,
<ModalProvider key="ModalProvider" />,
<ConfirmProvider key="ConfirmProvider" />,
]}
@@ -67,9 +70,8 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
};
const AppDefender = ({ children }: PropsWithChildren) => {
const router = useRouter();
const { synced } = useAppState();
const router = useRouter();
useEffect(() => {
if (router.pathname === '/') {
@@ -1,7 +1,7 @@
import { styled } from '@/styles';
import { Empty } from '@/ui/empty';
import { Avatar } from '@mui/material';
import { getDataCenter } from '@affine/datacenter';
// import { getDataCenter } from '@affine/datacenter';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
@@ -37,6 +37,8 @@ export default function DevPage() {
// .catch(err => {
// console.log('err: ', err);
// });
setSuccessInvited(true);
setInviteData(null);
}, [router.query.invite_code]);
return (
@@ -4,7 +4,7 @@ import exampleMarkdown1 from '@/templates/Welcome-to-the-AFFiNE-Alpha.md';
import exampleMarkdown2 from '@/templates/AFFiNE-Docs.md';
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';
interface Template {
name: string;
@@ -2,88 +2,42 @@
import {
PropsWithChildren,
ReactElement,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { styled } from '@/styles';
import { EditorHeader } from '@/components/header';
import EdgelessToolbar from '@/components/edgeless-toolbar';
import MobileModal from '@/components/mobile-modal';
import { useAppState } from '@/providers/app-state-provider/context';
import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-v2.0.md';
import { useAppState } from '@/providers/app-state-provider';
import type { NextPageWithLayout } from '../..//_app';
import WorkspaceLayout from '@/components/workspace-layout';
import { useRouter } from 'next/router';
import { usePageHelper } from '@/hooks/use-page-helper';
const StyledEditorContainer = styled('div')(() => {
return {
height: 'calc(100vh - 60px)',
padding: '0 32px',
};
import dynamic from 'next/dynamic';
import { EditorContainer } from '@blocksuite/editor';
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
ssr: false,
});
const Page: NextPageWithLayout = () => {
const editorContainer = useRef<HTMLDivElement>(null);
const { createEditor, setEditor, currentPage, currentWorkspace } =
useAppState();
useEffect(() => {
const ret = () => {
const node = editorContainer.current;
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]);
const { currentPage, currentWorkspace, setEditor } = useAppState();
const setEditorHandler = useCallback(
(editor: EditorContainer) => setEditor.current(editor),
[setEditor]
);
return (
<>
<EditorHeader />
<MobileModal />
<StyledEditorContainer ref={editorContainer} />
{currentPage && (
<DynamicBlocksuite
page={currentPage}
workspace={currentWorkspace}
setEditor={setEditorHandler}
/>
)}
<EdgelessToolbar />
</>
);
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
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 { PageLoading } from '@/components/loading';
import usePageHelper from '@/hooks/use-page-helper';
@@ -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;
@@ -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;
@@ -1,143 +1,144 @@
import { useMemo, useState, useCallback, useRef } from 'react';
import type { ReactNode } from 'react';
import dynamic from 'next/dynamic';
import { createContext, useContext, useEffect, useState, useRef } from 'react';
import type { PropsWithChildren } from 'react';
import { getDataCenter } from '@affine/datacenter';
import { AppState, AppStateContext } from './context';
import type {
import {
AppStateContext,
AppStateFunction,
AppStateValue,
CreateEditorHandler,
LoadWorkspaceHandler,
} from './context';
import { Page } from '@blocksuite/store';
import { EditorContainer } from '@blocksuite/editor';
const DynamicBlocksuite = dynamic(() => import('./DynamicBlocksuite'), {
ssr: false,
});
PageMeta,
} from './interface';
import { createDefaultWorkspace } from './utils';
import { WorkspaceInfo } from '@affine/datacenter';
const loadWorkspaceHandler: LoadWorkspaceHandler = async workspaceId => {
if (workspaceId) {
const dc = await getDataCenter();
return dc.load(workspaceId, { providerId: 'affine' });
} else {
return null;
}
};
export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
const refreshWorkspacesMeta = async () => {
const dc = await getDataCenter();
const workspacesMeta = await dc.apis.getWorkspaces().catch(() => {
return [];
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
export const AppState = createContext<AppStateContext>({} as AppStateContext);
export const useAppState = () => useContext(AppState);
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(
dataCenter.workspaces[0].id
);
const currentMetaWorkSpace = dataCenter.workspaces.find(item => {
return item.id === currentWorkspace.room;
});
setAppState({
dataCenter,
user: (await dataCenter.getUserInfo()) || null,
workspaceList: dataCenter.workspaces,
currentWorkspaceId: dataCenter.workspaces[0].id,
currentWorkspace,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
currentPage: null,
editor: null,
synced: true,
currentMetaWorkSpace: currentMetaWorkSpace ?? null,
});
};
init();
}, []);
useEffect(() => {
if (!appState?.currentWorkspace) {
return;
}
const currentWorkspace = appState.currentWorkspace;
const dispose = currentWorkspace.meta.pagesUpdated.on(() => {
setAppState({
...appState,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
});
}).dispose;
return () => {
dispose();
};
}, [appState]);
useEffect(() => {
const { dataCenter } = appState;
// FIXME: onWorkspacesChange should have dispose function
dataCenter?.onWorkspacesChange(() => {
setAppState({
...appState,
workspaceList: dataCenter.workspaces,
});
});
}, [appState]);
const loadPage = useRef<AppStateFunction['loadPage']>();
loadPage.current = (pageId: string) => {
const { currentWorkspace, currentPage } = appState;
if (pageId === currentPage?.id) {
return;
}
const page = currentWorkspace?.getPage(pageId) || null;
setAppState({
...appState,
currentPage: page,
});
setState(state => ({ ...state, workspacesMeta }));
};
const [state, setState] = useState<AppStateValue>({
user: null,
workspacesMeta: [],
currentWorkspaceId: '',
currentWorkspace: null,
currentPage: null,
editor: null,
refreshWorkspacesMeta,
synced: true,
});
const [createEditorHandler, _setCreateEditorHandler] =
useState<CreateEditorHandler>();
const setCreateEditorHandler = useCallback(
(handler: CreateEditorHandler) => {
_setCreateEditorHandler(() => handler);
},
[_setCreateEditorHandler]
);
const loadWorkspace = useRef<AppStateContext['loadWorkspace']>(() =>
Promise.resolve(null)
);
const loadWorkspace = useRef<AppStateFunction['loadWorkspace']>();
loadWorkspace.current = async (workspaceId: string) => {
if (state.currentWorkspaceId === workspaceId) {
return state.currentWorkspace;
const { dataCenter, workspaceList, currentWorkspaceId, currentWorkspace } =
appState;
if (!workspaceList.find(v => v.id === workspaceId)) {
return null;
}
const workspace =
(await loadWorkspaceHandler(workspaceId, state.user)) || null;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
window.workspace = workspace;
// FIXME: there needs some method to destroy websocket.
// Or we need a manager to manage websocket.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
state.currentWorkspace?.__ws__?.destroy();
setState(state => ({
...state,
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,
currentWorkspaceId: workspaceId,
}));
currentMetaWorkSpace: currentMetaWorkSpace ?? null,
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
currentPage: null,
editor: null,
});
return workspace;
};
const loadPage = useRef<AppStateContext['loadPage']>(() =>
Promise.resolve(null)
);
loadPage.current = async (pageId: string) => {
const { currentWorkspace, currentPage } = state;
if (pageId === currentPage?.id) {
return currentPage;
}
const page = (pageId ? currentWorkspace?.getPage(pageId) : null) || null;
setState(state => ({ ...state, currentPage: page }));
return page;
const setEditor: AppStateFunction['setEditor'] =
useRef() as AppStateFunction['setEditor'];
setEditor.current = editor => {
setAppState({
...appState,
editor,
});
};
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 (
<AppState.Provider value={context}>
<DynamicBlocksuite setCreateEditorHandler={setCreateEditorHandler} />
<AppState.Provider
value={{
...appState,
setEditor,
loadPage: loadPage.current,
loadWorkspace: loadWorkspace.current,
}}
>
{children}
</AppState.Provider>
);
@@ -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);
};
@@ -1,2 +1,2 @@
export * from './context';
export * from './Provider';
export * from './interface';
@@ -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 = {
// favorite: boolean;
// trash: boolean;
// trashDate: number | void;
// updatedDate: number | void;
// mode: EditorContainer['mode'];
// } & OriginalPageMeta;
export interface PageMeta extends OriginalPageMeta {
import type {
Page as StorePage,
Workspace as StoreWorkspace,
PageMeta as StorePageMeta,
} from '@blocksuite/store';
import { MutableRefObject } from 'react';
export interface PageMeta extends StorePageMeta {
favorite: boolean;
trash: boolean;
trashDate: number;
updatedDate: number;
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;
@@ -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,
});
};
+6 -3
View File
@@ -37,8 +37,11 @@
"ky-universal": "^0.11.0",
"lib0": "^0.2.58",
"swr": "^2.0.0",
"vitest": "^0.26.3",
"y-protocols": "^1.0.5",
"yjs": "^13.5.44"
"yjs": "^13.5.44",
"y-protocols": "^1.0.5"
},
"peerDependencies": {
"@blocksuite/blocks": "0.3.1-*",
"@blocksuite/store": "0.3.1-*"
}
}
+2 -1
View File
@@ -10,7 +10,7 @@ import { getLogger } from './logger';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { TauriIPCProvider } from './provider/tauri-ipc';
import { createBlocksuiteWorkspace } from './utils/index.js';
import { MessageCenter } from './message/message';
import { MessageCenter } from './message';
/**
* @class DataCenter
@@ -313,6 +313,7 @@ export class DataCenter {
applyUpdate(newWorkspace.doc, encodeStateAsUpdate(workspace.doc));
assert(newWorkspace, 'Create workspace failed');
await currentProvider.deleteWorkspace(workspace.room);
return newWorkspace.room;
}
/**
+2 -1
View File
@@ -25,7 +25,8 @@ const _initializeDataCenter = () => {
export const getDataCenter = _initializeDataCenter();
export type { DataCenter };
export type { AccessTokenMessage } from './provider/affine/apis';
export type { WorkspaceInfo } from './types';
export * from './types';
export { getLogger } from './logger';
export * from './message';
+1 -1
View File
@@ -1,5 +1,5 @@
import { Observable } from 'lib0/observable';
import { Message } from 'src/types';
import { Message } from '../types';
import { MessageCode } from './code';
export class MessageCenter extends Observable<string> {
@@ -11,7 +11,7 @@ import { WebsocketProvider } from './sync.js';
import { getApis } from './apis/index.js';
import type { Apis, WorkspaceDetail, Callback } from './apis';
import { setDefaultAvatar } from '../utils.js';
import { MessageCode } from 'src/message/code.js';
import { MessageCode } from '../../message';
export interface AffineProviderConstructorParams
extends ProviderConstructorParams {
@@ -146,8 +146,8 @@ export class AffineProvider extends BaseProvider {
if (workspace) {
workspaces[i] = {
...workspaces[i],
name: workspace.doc.meta.name,
avatar: workspace.doc.meta.avatar,
name: workspace.meta.name,
avatar: workspace.meta.avatar,
};
}
});
+1 -1
View File
@@ -1,5 +1,5 @@
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 type { WorkspaceMetaCollectionScope } from '../workspace-meta-collection';
@@ -25,8 +25,9 @@ export class LocalProvider extends BaseProvider {
public override async linkLocal(workspace: BlocksuiteWorkspace) {
assert(workspace.room);
let idb = this._idbMap.get(workspace.room);
idb?.destroy();
idb = new IndexedDBProvider(workspace.room, workspace.doc);
if (!idb) {
idb = new IndexedDBProvider(workspace.room, workspace.doc);
}
this._idbMap.set(workspace.room, idb);
this._logger('Local data loaded');
return workspace;
+101 -2514
View File
File diff suppressed because it is too large Load Diff