Merge remote-tracking branch 'refs/remotes/origin/feat/datacenter-dev'
Conflicts: packages/app/src/components/create-workspace/index.tsx packages/data-center/package.json packages/data-center/src/index.ts pnpm-lock.yaml
This commit is contained in:
@@ -15,6 +15,8 @@ const profileTarget = {
|
|||||||
local: '127.0.0.1:3000',
|
local: '127.0.0.1:3000',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 100.77.180.48:11001
|
||||||
|
|
||||||
const getRedirectConfig = profile => {
|
const getRedirectConfig = profile => {
|
||||||
const target = profileTarget[profile || 'dev'] || profileTarget['dev'];
|
const target = profileTarget[profile || 'dev'] || profileTarget['dev'];
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@affine/datacenter": "workspace:*",
|
"@affine/datacenter": "workspace:*",
|
||||||
"@affine/i18n": "workspace:*",
|
"@affine/i18n": "workspace:*",
|
||||||
"@blocksuite/blocks": "0.4.0-20230110112105-ef07332",
|
"@blocksuite/blocks": "0.3.1-20230109032243-37ad3ba",
|
||||||
"@blocksuite/editor": "0.4.0-20230110112105-ef07332",
|
"@blocksuite/editor": "0.3.1-20230109032243-37ad3ba",
|
||||||
"@blocksuite/icons": "^2.0.2",
|
"@blocksuite/icons": "^2.0.2",
|
||||||
"@blocksuite/store": "0.4.0-20230110112105-ef07332",
|
"@blocksuite/store": "0.3.1-20230109032243-37ad3ba",
|
||||||
"@emotion/css": "^11.10.0",
|
"@emotion/css": "^11.10.0",
|
||||||
"@emotion/react": "^11.10.4",
|
"@emotion/react": "^11.10.4",
|
||||||
"@emotion/server": "^11.10.0",
|
"@emotion/server": "^11.10.0",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
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';
|
||||||
@@ -7,6 +6,9 @@ import Input from '@/ui/input';
|
|||||||
import { KeyboardEvent } from 'react';
|
import { KeyboardEvent } from 'react';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { toast } from '@/ui/toast';
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -18,11 +20,11 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const handleCreateWorkspace = async () => {
|
const handleCreateWorkspace = async () => {
|
||||||
const workspace = await createWorkspace(workspaceName);
|
const workspace = await createWorkspace(workspaceName);
|
||||||
if (workspace && workspace.room) {
|
if (workspace && workspace.id) {
|
||||||
router.replace(`/workspace/${workspace.room}`);
|
router.replace(`/workspace/${workspace.id}`);
|
||||||
onClose();
|
onClose();
|
||||||
} else {
|
} else {
|
||||||
console.log('create error');
|
toast('create error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export const Editor = ({ page, workspace, setEditor }: Props) => {
|
|||||||
|
|
||||||
const editor = new EditorContainer();
|
const editor = new EditorContainer();
|
||||||
editor.page = page;
|
editor.page = page;
|
||||||
|
|
||||||
editorContainer.current?.appendChild(editor);
|
editorContainer.current?.appendChild(editor);
|
||||||
if (page.isEmpty) {
|
if (page.isEmpty) {
|
||||||
const isFirstPage = workspace?.meta.pageMetas.length === 1;
|
const isFirstPage = workspace?.meta.pageMetas.length === 1;
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ import { Tooltip } from '@/ui/tooltip';
|
|||||||
import { ArrowDownIcon } from '@blocksuite/icons';
|
import { ArrowDownIcon } from '@blocksuite/icons';
|
||||||
import { useModal } from '@/providers/GlobalModalProvider';
|
import { useModal } from '@/providers/GlobalModalProvider';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
import { styled } from '@/styles';
|
||||||
|
|
||||||
|
const StyledIconButtonWithAnimate = styled(IconButton)(() => {
|
||||||
|
return {
|
||||||
|
svg: {
|
||||||
|
transition: 'transform 0.15s ease-in-out',
|
||||||
|
},
|
||||||
|
':hover': {
|
||||||
|
svg: {
|
||||||
|
transform: 'translateY(3px)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
export const QuickSearchButton = ({
|
export const QuickSearchButton = ({
|
||||||
onClick,
|
onClick,
|
||||||
...props
|
...props
|
||||||
@@ -12,7 +26,7 @@ export const QuickSearchButton = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Tooltip content={t('Switch to')} placement="bottom">
|
<Tooltip content={t('Switch to')} placement="bottom">
|
||||||
<IconButton
|
<StyledIconButtonWithAnimate
|
||||||
data-testid="header-quickSearchButton"
|
data-testid="header-quickSearchButton"
|
||||||
{...props}
|
{...props}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
@@ -21,7 +35,7 @@ export const QuickSearchButton = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArrowDownIcon />
|
<ArrowDownIcon />
|
||||||
</IconButton>
|
</StyledIconButtonWithAnimate>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useTranslation } from '@affine/i18n';
|
|||||||
|
|
||||||
export const TrashButtonGroup = () => {
|
export const TrashButtonGroup = () => {
|
||||||
const { permanentlyDeletePage } = usePageHelper();
|
const { permanentlyDeletePage } = usePageHelper();
|
||||||
const { currentWorkspaceId } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const { toggleDeletePage } = usePageHelper();
|
const { toggleDeletePage } = usePageHelper();
|
||||||
const { confirm } = useConfirm();
|
const { confirm } = useConfirm();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -38,7 +38,7 @@ export const TrashButtonGroup = () => {
|
|||||||
confirmType: 'danger',
|
confirmType: 'danger',
|
||||||
}).then(confirm => {
|
}).then(confirm => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
router.push(`/workspace/${currentWorkspaceId}/all`);
|
router.push(`/workspace/${currentWorkspace?.id}/all`);
|
||||||
permanentlyDeletePage(id);
|
permanentlyDeletePage(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ export const ImportModal = ({ open, onClose }: ImportModalProps) => {
|
|||||||
const { currentWorkspace } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const _applyTemplate = function (pageId: string, template: Template) {
|
const _applyTemplate = function (pageId: string, template: Template) {
|
||||||
const page = currentWorkspace?.getPage(pageId);
|
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId);
|
||||||
|
|
||||||
const title = template.name;
|
const title = template.name;
|
||||||
if (page) {
|
if (page) {
|
||||||
currentWorkspace?.setPageMeta(page.id, { title });
|
currentWorkspace?.blocksuiteWorkspace?.setPageMeta(page.id, { title });
|
||||||
if (page && page.root === null) {
|
if (page && page.root === null) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const editor = document.querySelector('editor-container');
|
const editor = document.querySelector('editor-container');
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import { styled } from '@/styles';
|
import { styled } from '@/styles';
|
||||||
|
|
||||||
// Inspired by https://codepen.io/graphilla/pen/rNvBMYY
|
// Inspired by https://codepen.io/graphilla/pen/rNvBMYY
|
||||||
export const StyledLoadingWrapper = styled.div<{ size?: number }>(
|
export const StyledLoadingWrapper = styled('div', {
|
||||||
({ size = 40 }) => {
|
shouldForwardProp: prop => {
|
||||||
return {
|
return !['size'].includes(prop);
|
||||||
width: size * 4,
|
},
|
||||||
height: size * 4,
|
})<{ size?: number }>(({ size = 40 }) => {
|
||||||
position: 'relative',
|
return {
|
||||||
};
|
width: size * 4,
|
||||||
}
|
height: size * 4,
|
||||||
);
|
position: 'relative',
|
||||||
|
};
|
||||||
|
});
|
||||||
export const StyledLoading = styled.div`
|
export const StyledLoading = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 25%;
|
left: 25%;
|
||||||
top: 25%;
|
top: 50%;
|
||||||
|
transform: rotateX(55deg) rotateZ(-45deg);
|
||||||
@keyframes slide {
|
@keyframes slide {
|
||||||
0% {
|
0% {
|
||||||
transform: translate(var(--sx), var(--sy));
|
transform: translate(var(--sx), var(--sy));
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const FavoriteTag = ({
|
|||||||
style={{
|
style={{
|
||||||
color: favorite ? theme.colors.primaryColor : theme.colors.iconColor,
|
color: favorite ? theme.colors.primaryColor : theme.colors.iconColor,
|
||||||
}}
|
}}
|
||||||
className="favorite-button"
|
className={favorite ? '' : 'favorite-button'}
|
||||||
>
|
>
|
||||||
{favorite ? (
|
{favorite ? (
|
||||||
<FavouritedIcon data-testid="favourited-icon" />
|
<FavouritedIcon data-testid="favourited-icon" />
|
||||||
@@ -67,13 +67,15 @@ export const PageList = ({
|
|||||||
pageList,
|
pageList,
|
||||||
showFavoriteTag = false,
|
showFavoriteTag = false,
|
||||||
isTrash = false,
|
isTrash = false,
|
||||||
|
isPublic = false,
|
||||||
}: {
|
}: {
|
||||||
pageList: PageMeta[];
|
pageList: PageMeta[];
|
||||||
showFavoriteTag?: boolean;
|
showFavoriteTag?: boolean;
|
||||||
isTrash?: boolean;
|
isTrash?: boolean;
|
||||||
|
isPublic?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { currentWorkspaceId } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
if (pageList.length === 0) {
|
if (pageList.length === 0) {
|
||||||
return <Empty />;
|
return <Empty />;
|
||||||
@@ -98,9 +100,15 @@ export const PageList = ({
|
|||||||
<StyledTableRow
|
<StyledTableRow
|
||||||
key={`${pageMeta.id}-${index}`}
|
key={`${pageMeta.id}-${index}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push(
|
if (isPublic) {
|
||||||
`/workspace/${currentWorkspaceId}/${pageMeta.id}`
|
router.push(
|
||||||
);
|
`/public-workspace/${router.query.workspaceId}/${pageMeta.id}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
router.push(
|
||||||
|
`/workspace/${currentWorkspace?.id}/${pageMeta.id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -124,19 +132,21 @@ export const PageList = ({
|
|||||||
dateKey={isTrash ? 'trashDate' : 'updatedDate'}
|
dateKey={isTrash ? 'trashDate' : 'updatedDate'}
|
||||||
backupKey={isTrash ? 'trashDate' : 'createDate'}
|
backupKey={isTrash ? 'trashDate' : 'createDate'}
|
||||||
/>
|
/>
|
||||||
<TableCell
|
{!isPublic ? (
|
||||||
style={{ padding: 0 }}
|
<TableCell
|
||||||
data-testid={`more-actions-${pageMeta.id}`}
|
style={{ padding: 0 }}
|
||||||
onClick={e => {
|
data-testid={`more-actions-${pageMeta.id}`}
|
||||||
e.stopPropagation();
|
onClick={e => {
|
||||||
}}
|
e.stopPropagation();
|
||||||
>
|
}}
|
||||||
{isTrash ? (
|
>
|
||||||
<TrashOperationCell pageMeta={pageMeta} />
|
{isTrash ? (
|
||||||
) : (
|
<TrashOperationCell pageMeta={pageMeta} />
|
||||||
<OperationCell pageMeta={pageMeta} />
|
) : (
|
||||||
)}
|
<OperationCell pageMeta={pageMeta} />
|
||||||
</TableCell>
|
)}
|
||||||
|
</TableCell>
|
||||||
|
) : null}
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -17,10 +17,8 @@ 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, workspaceList, currentWorkspace } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const isPublish = workspaceList.find(
|
|
||||||
meta => String(meta.id) === String(currentWorkspaceId)
|
|
||||||
)?.isPublish;
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current?.addEventListener(
|
inputRef.current?.addEventListener(
|
||||||
'blur',
|
'blur',
|
||||||
@@ -79,8 +77,8 @@ export const Input = (props: {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder={
|
placeholder={
|
||||||
isPublish
|
currentWorkspace?.isPublish
|
||||||
? `Search in ${currentWorkspace?.meta.name}`
|
? `Search in ${currentWorkspace?.blocksuiteWorkspace?.meta.name}`
|
||||||
: 'Quick Search...'
|
: 'Quick Search...'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { useSwitchToConfig } from './config';
|
|||||||
import { NoResultSVG } from './NoResultSVG';
|
import { NoResultSVG } from './NoResultSVG';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import usePageHelper from '@/hooks/use-page-helper';
|
import usePageHelper from '@/hooks/use-page-helper';
|
||||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
|
||||||
export const Results = (props: {
|
export const Results = (props: {
|
||||||
query: string;
|
query: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@@ -21,12 +20,11 @@ export const Results = (props: {
|
|||||||
const setLoading = props.setLoading;
|
const setLoading = props.setLoading;
|
||||||
const setShowCreatePage = props.setShowCreatePage;
|
const setShowCreatePage = props.setShowCreatePage;
|
||||||
const { triggerQuickSearchModal } = useModal();
|
const { triggerQuickSearchModal } = useModal();
|
||||||
const pageMetaList = usePageMetaList();
|
|
||||||
const { openPage } = usePageHelper();
|
const { openPage } = usePageHelper();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { currentWorkspaceId } = useAppState();
|
const { currentWorkspace, pageList } = useAppState();
|
||||||
const { search } = usePageHelper();
|
const { search } = usePageHelper();
|
||||||
const List = useSwitchToConfig(currentWorkspaceId);
|
const List = useSwitchToConfig(currentWorkspace?.id);
|
||||||
const [results, setResults] = useState(new Map<string, string | undefined>());
|
const [results, setResults] = useState(new Map<string, string | undefined>());
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -37,12 +35,12 @@ export const Results = (props: {
|
|||||||
}, [query, setResults, setLoading]);
|
}, [query, setResults, setLoading]);
|
||||||
const pageIds = [...results.values()];
|
const pageIds = [...results.values()];
|
||||||
|
|
||||||
const resultsPageMeta = pageMetaList.filter(
|
const resultsPageMeta = pageList.filter(
|
||||||
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
page => pageIds.indexOf(page.id) > -1 && !page.trash
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowCreatePage(resultsPageMeta.length ? false : true);
|
setShowCreatePage(!resultsPageMeta.length);
|
||||||
//Determine whether to display the ‘+ New page’
|
//Determine whether to display the ‘+ New page’
|
||||||
}, [resultsPageMeta, setShowCreatePage]);
|
}, [resultsPageMeta, setShowCreatePage]);
|
||||||
return loading ? null : (
|
return loading ? null : (
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
|
import { FC, SVGProps } from 'react';
|
||||||
import { AllPagesIcon, FavouritesIcon, TrashIcon } from '@blocksuite/icons';
|
import { AllPagesIcon, FavouritesIcon, TrashIcon } from '@blocksuite/icons';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
|
||||||
export const useSwitchToConfig = (
|
export const useSwitchToConfig = (
|
||||||
currentWorkspaceId: string
|
currentWorkspaceId?: string
|
||||||
): {
|
): {
|
||||||
title: string;
|
title: string;
|
||||||
href: string;
|
href: string;
|
||||||
icon: React.FC<React.SVGProps<SVGSVGElement>>;
|
icon: FC<SVGProps<SVGSVGElement>>;
|
||||||
}[] => {
|
}[] => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const List = [
|
return [
|
||||||
{
|
{
|
||||||
title: t('All pages'),
|
title: t('All pages'),
|
||||||
href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
|
href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
|
||||||
@@ -28,5 +29,4 @@ export const useSwitchToConfig = (
|
|||||||
icon: TrashIcon,
|
icon: TrashIcon,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return List;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,15 +22,13 @@ const isMac = () => {
|
|||||||
return getUaHelper().isMacOs;
|
return getUaHelper().isMacOs;
|
||||||
};
|
};
|
||||||
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
||||||
const { currentMetaWorkSpace } = useAppState();
|
const { currentWorkspace } = 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 isPublish = currentMetaWorkSpace?.isPublish;
|
|
||||||
|
|
||||||
// Add ‘⌘+K’ shortcut keys as switches
|
// Add ‘⌘+K’ shortcut keys as switches
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const down = (e: KeyboardEvent) => {
|
const down = (e: KeyboardEvent) => {
|
||||||
@@ -96,7 +94,7 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => {
|
|||||||
setShowCreatePage={setShowCreatePage}
|
setShowCreatePage={setShowCreatePage}
|
||||||
/>
|
/>
|
||||||
</StyledContent>
|
</StyledContent>
|
||||||
{isPublish ? (
|
{currentWorkspace?.isPublish ? (
|
||||||
<></>
|
<></>
|
||||||
) : showCreatePage ? (
|
) : showCreatePage ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
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;
|
||||||
@@ -25,7 +22,9 @@ export const WorkspaceAvatar = (props: IWorkspaceAvatar) => {
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src={props.avatar} alt="" />
|
<picture>
|
||||||
|
<img src={props.avatar} alt="" />
|
||||||
|
</picture>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { styled } from '@/styles';
|
|||||||
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 { SignOut } from '@/hooks/mock-data/mock';
|
|
||||||
import { CreateWorkspaceModal } from '../create-workspace';
|
import { CreateWorkspaceModal } from '../create-workspace';
|
||||||
import {
|
import {
|
||||||
CloudUnsyncedIcon,
|
CloudUnsyncedIcon,
|
||||||
@@ -10,12 +9,11 @@ import {
|
|||||||
UsersIcon,
|
UsersIcon,
|
||||||
AddIcon,
|
AddIcon,
|
||||||
} from '@blocksuite/icons';
|
} from '@blocksuite/icons';
|
||||||
// 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 { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useUserHelper } from '@/hooks/use-user-helper';
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
|
|
||||||
interface WorkspaceModalProps {
|
interface WorkspaceModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -24,9 +22,9 @@ interface WorkspaceModalProps {
|
|||||||
|
|
||||||
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
||||||
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
|
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
|
||||||
// const { confirm } = useConfirm();
|
const { confirm } = useConfirm();
|
||||||
const { workspaceList, currentWorkspace } = useAppState();
|
const { workspaceList, currentWorkspace, login, user, logout } =
|
||||||
const { login, user } = useUserHelper();
|
useAppState();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -55,7 +53,7 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
|||||||
router.replace(`/workspace/${item.id}`);
|
router.replace(`/workspace/${item.id}`);
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
active={item.id === currentWorkspace.room}
|
active={item.id === currentWorkspace?.id}
|
||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
<span style={{ width: '100px' }}>
|
<span style={{ width: '100px' }}>
|
||||||
@@ -101,7 +99,7 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
|||||||
{item.provider === 'affine' && (
|
{item.provider === 'affine' && (
|
||||||
<CloudInsyncIcon fontSize={24} />
|
<CloudInsyncIcon fontSize={24} />
|
||||||
)}
|
)}
|
||||||
{item.isPublish && <UsersIcon fontSize={24} />}
|
{item.published && <UsersIcon fontSize={24} />}
|
||||||
</span>
|
</span>
|
||||||
{/* {item.isLocal ? 'isLocal' : ''}/ */}
|
{/* {item.isLocal ? 'isLocal' : ''}/ */}
|
||||||
</WorkspaceItem>
|
</WorkspaceItem>
|
||||||
@@ -137,9 +135,9 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
|||||||
<Footer>
|
<Footer>
|
||||||
{!user ? (
|
{!user ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
login();
|
await login();
|
||||||
toast('login success');
|
toast('Login success');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sign in AFFiNE Cloud
|
Sign in AFFiNE Cloud
|
||||||
@@ -147,7 +145,20 @@ export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
|||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
SignOut();
|
confirm({
|
||||||
|
title: 'Sign out?',
|
||||||
|
content: `All data has been stored in the cloud. `,
|
||||||
|
confirmText: 'Sign out',
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
}).then(async confirm => {
|
||||||
|
if (confirm) {
|
||||||
|
if (user) {
|
||||||
|
await logout();
|
||||||
|
router.replace(`/workspace`);
|
||||||
|
toast('Enabled success');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sign out of AFFiNE Cloud
|
Sign out of AFFiNE Cloud
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { styled } from '@/styles';
|
import { styled } from '@/styles';
|
||||||
import { WorkspaceInfo } from '@affine/datacenter';
|
import { WorkspaceUnit } 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: WorkspaceInfo }) => {
|
export const ExportPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||||
return (
|
return (
|
||||||
<ExportPageTitleContainer>
|
<ExportPageTitleContainer>
|
||||||
Export Workspace{' '}
|
Export Workspace{' '}
|
||||||
|
|||||||
@@ -1,207 +0,0 @@
|
|||||||
import {
|
|
||||||
StyledMemberAvatar,
|
|
||||||
StyledMemberButtonContainer,
|
|
||||||
StyledMemberEmail,
|
|
||||||
StyledMemberInfo,
|
|
||||||
StyledMemberListContainer,
|
|
||||||
StyledMemberListItem,
|
|
||||||
StyledMemberName,
|
|
||||||
StyledMemberNameContainer,
|
|
||||||
StyledMemberRoleContainer,
|
|
||||||
StyledMemberTitleContainer,
|
|
||||||
StyledMoreVerticalButton,
|
|
||||||
StyledPublishExplanation,
|
|
||||||
} from './style';
|
|
||||||
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { Button, IconButton } from '@/ui/button';
|
|
||||||
import { InviteMembers } from '../invite-members/index';
|
|
||||||
import { Menu, MenuItem } from '@/ui/menu';
|
|
||||||
import { Empty } from '@/ui/empty';
|
|
||||||
// import {
|
|
||||||
// deleteMember,
|
|
||||||
// getMembers,
|
|
||||||
// User,
|
|
||||||
// Workspace,
|
|
||||||
// } from '@/hooks/mock-data/mock';
|
|
||||||
import { WorkspaceInfo } from '@affine/datacenter';
|
|
||||||
import { useTemporaryHelper } from '@/providers/temporary-helper-provider';
|
|
||||||
import { StyledMemberWarp } from './general/style';
|
|
||||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
|
||||||
|
|
||||||
// import { useAppState } from '@/providers/app-state-provider';
|
|
||||||
export const MembersPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
|
|
||||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
|
||||||
const [members, setMembers] = useState<[{ name: string; email: string }]>([
|
|
||||||
{ name: 'affine', email: 'tttt' },
|
|
||||||
]);
|
|
||||||
console.log('setMembers: ', setMembers);
|
|
||||||
const { user, login, updateWorkspaceMeta } = useTemporaryHelper();
|
|
||||||
const { confirm } = useConfirm();
|
|
||||||
// const refreshMembers = useCallback(() => {
|
|
||||||
// getDataCenter()
|
|
||||||
// .then(dc =>
|
|
||||||
// dc.apis.getWorkspaceMembers({
|
|
||||||
// id: workspace.id,
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// .then(data => {
|
|
||||||
// setMembers(data);
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
// }, [workspace.id]);
|
|
||||||
const setMembersList = () => {
|
|
||||||
// setMembers([]);
|
|
||||||
// const members = getMembers(workspace.id);
|
|
||||||
// members && setMembers(members);
|
|
||||||
};
|
|
||||||
useEffect(() => {
|
|
||||||
setMembersList();
|
|
||||||
// refreshMembers();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{workspace.provider === 'cloud' ? (
|
|
||||||
<>
|
|
||||||
<StyledMemberTitleContainer>
|
|
||||||
<StyledMemberNameContainer>
|
|
||||||
Users({members.length})
|
|
||||||
</StyledMemberNameContainer>
|
|
||||||
<StyledMemberRoleContainer>Access level</StyledMemberRoleContainer>
|
|
||||||
</StyledMemberTitleContainer>
|
|
||||||
<StyledMemberListContainer>
|
|
||||||
{members.length ? (
|
|
||||||
members.map((member, index) => {
|
|
||||||
return (
|
|
||||||
<StyledMemberListItem key={index}>
|
|
||||||
<StyledMemberNameContainer>
|
|
||||||
<StyledMemberAvatar alt="member avatar">
|
|
||||||
<EmailIcon></EmailIcon>
|
|
||||||
</StyledMemberAvatar>
|
|
||||||
|
|
||||||
<StyledMemberInfo>
|
|
||||||
<StyledMemberName>{member.name}</StyledMemberName>
|
|
||||||
|
|
||||||
<StyledMemberEmail>{member.email}</StyledMemberEmail>
|
|
||||||
</StyledMemberInfo>
|
|
||||||
</StyledMemberNameContainer>
|
|
||||||
<StyledMemberRoleContainer>
|
|
||||||
{/* {member.accepted
|
|
||||||
? member.type !== 99
|
|
||||||
? 'Member'
|
|
||||||
: 'Workspace Owner'
|
|
||||||
: 'Pending'} */}
|
|
||||||
Pending
|
|
||||||
</StyledMemberRoleContainer>
|
|
||||||
<StyledMoreVerticalButton>
|
|
||||||
<Menu
|
|
||||||
content={
|
|
||||||
<>
|
|
||||||
<MenuItem
|
|
||||||
onClick={() => {
|
|
||||||
// deleteMember(workspace.id, 0);
|
|
||||||
setMembersList();
|
|
||||||
// confirm({
|
|
||||||
// title: 'Delete Member?',
|
|
||||||
// content: `will delete member`,
|
|
||||||
// confirmText: 'Delete',
|
|
||||||
// confirmType: 'danger',
|
|
||||||
// }).then(confirm => {
|
|
||||||
// getDataCenter()
|
|
||||||
// .then(dc =>
|
|
||||||
// dc.apis.removeMember({
|
|
||||||
// permissionId: member.id,
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// .then(() => {
|
|
||||||
// // console.log('data: ', data);
|
|
||||||
// toast('Moved to Trash');
|
|
||||||
// // refreshMembers();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
}}
|
|
||||||
icon={<TrashIcon />}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</MenuItem>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
placement="bottom-end"
|
|
||||||
disablePortal={true}
|
|
||||||
>
|
|
||||||
<IconButton>
|
|
||||||
<MoreVerticalIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Menu>
|
|
||||||
</StyledMoreVerticalButton>
|
|
||||||
</StyledMemberListItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<Empty
|
|
||||||
width={648}
|
|
||||||
sx={{ marginTop: '60px' }}
|
|
||||||
height={300}
|
|
||||||
></Empty>
|
|
||||||
)}
|
|
||||||
</StyledMemberListContainer>
|
|
||||||
<StyledMemberButtonContainer>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsInviteModalShow(true);
|
|
||||||
}}
|
|
||||||
type="primary"
|
|
||||||
shape="circle"
|
|
||||||
>
|
|
||||||
Invite Members
|
|
||||||
</Button>
|
|
||||||
<InviteMembers
|
|
||||||
onClose={() => {
|
|
||||||
setIsInviteModalShow(false);
|
|
||||||
}}
|
|
||||||
onInviteSuccess={() => {
|
|
||||||
setMembersList();
|
|
||||||
setIsInviteModalShow(false);
|
|
||||||
// refreshMembers();
|
|
||||||
}}
|
|
||||||
workspaceId={workspace.id}
|
|
||||||
open={isInviteModalShow}
|
|
||||||
></InviteMembers>
|
|
||||||
</StyledMemberButtonContainer>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<StyledMemberWarp>
|
|
||||||
<>Collaborating with other members requires AFFiNE Cloud service.</>
|
|
||||||
<StyledPublishExplanation>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
shape="circle"
|
|
||||||
onClick={() => {
|
|
||||||
confirm({
|
|
||||||
title: 'Enable AFFiNE Cloud?',
|
|
||||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
|
||||||
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
|
||||||
cancelText: 'Skip',
|
|
||||||
}).then(confirm => {
|
|
||||||
if (confirm) {
|
|
||||||
if (user) {
|
|
||||||
updateWorkspaceMeta(workspace.id, { isPublish: true });
|
|
||||||
} else {
|
|
||||||
login();
|
|
||||||
updateWorkspaceMeta(workspace.id, { isPublish: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Enable AFFiNE Cloud
|
|
||||||
</Button>
|
|
||||||
</StyledPublishExplanation>
|
|
||||||
</StyledMemberWarp>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -9,20 +9,16 @@ import {
|
|||||||
import { Button } from '@/ui/button';
|
import { 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 { useConfirm } from '@/providers/ConfirmProvider';
|
|
||||||
// import { useAppState } from '@/providers/app-state-provider3';
|
// import { useAppState } from '@/providers/app-state-provider3';
|
||||||
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||||
import { WorkspaceInfo } from '@affine/datacenter';
|
|
||||||
|
|
||||||
export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
|
export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||||
const shareUrl =
|
const shareUrl = window.location.host + '/public-workspace/' + workspace.id;
|
||||||
window.location.host + '/workspace/' + workspace.id + '?share=true';
|
const { publishWorkspace, enableWorkspace } = useWorkspaceHelper();
|
||||||
const { publishWorkspace } = useWorkspaceHelper();
|
|
||||||
|
|
||||||
const { confirm } = useConfirm();
|
const togglePublic = async (flag: boolean) => {
|
||||||
|
await publishWorkspace(workspace.id.toString(), flag);
|
||||||
const togglePublic = (flag: boolean) => {
|
|
||||||
workspace.id && publishWorkspace(workspace?.id, flag);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyUrl = () => {
|
const copyUrl = () => {
|
||||||
@@ -30,30 +26,15 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
|
|||||||
toast('Copied url to clipboard');
|
toast('Copied url to clipboard');
|
||||||
};
|
};
|
||||||
|
|
||||||
const enableAffineCloud = () => {
|
const enableAffineCloud = async () => {
|
||||||
confirm({
|
await enableWorkspace();
|
||||||
title: 'Enable AFFiNE Cloud?',
|
|
||||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
|
||||||
confirmText:
|
|
||||||
workspace.provider === 'local' ? 'Enable' : 'Sign in and Enable',
|
|
||||||
cancelText: 'Skip',
|
|
||||||
}).then(confirm => {
|
|
||||||
if (confirm) {
|
|
||||||
// if (user) {
|
|
||||||
// updateWorkspaceMeta(workspace.id, { type: 'cloud' });
|
|
||||||
// } else {
|
|
||||||
// login();
|
|
||||||
// updateWorkspaceMeta(workspace.id, { type: 'cloud' });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{workspace.provider === 'cloud' ? (
|
{workspace.provider === 'affine' ? (
|
||||||
<div>
|
<div>
|
||||||
<StyledPublishContent>
|
<StyledPublishContent>
|
||||||
{workspace?.isPublish ? (
|
{workspace?.published ? (
|
||||||
<>
|
<>
|
||||||
<StyledPublishExplanation>
|
<StyledPublishExplanation>
|
||||||
Publishing to web requires AFFiNE Cloud service .
|
Publishing to web requires AFFiNE Cloud service .
|
||||||
@@ -75,7 +56,7 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
|
|||||||
</StyledPublishExplanation>
|
</StyledPublishExplanation>
|
||||||
)}
|
)}
|
||||||
</StyledPublishContent>
|
</StyledPublishContent>
|
||||||
{workspace.isPublish ? (
|
{workspace.published ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
togglePublic(false);
|
togglePublic(false);
|
||||||
|
|||||||
@@ -6,20 +6,10 @@ 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 { WorkspaceInfo } from '@affine/datacenter';
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||||
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 { enableWorkspace } = useWorkspaceHelper();
|
||||||
const { currentWorkspace } = useAppState();
|
|
||||||
const { confirm } = useConfirm();
|
|
||||||
const { user, login } = useUserHelper();
|
|
||||||
const router = useRouter();
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<StyledPublishContent>
|
<StyledPublishContent>
|
||||||
@@ -33,22 +23,8 @@ export const SyncPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
|
|||||||
|
|
||||||
<StyledPublishCopyContainer>
|
<StyledPublishCopyContainer>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
confirm({
|
await enableWorkspace();
|
||||||
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"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
|
|||||||
@@ -14,42 +14,29 @@ import { WorkspaceDelete } from './delete';
|
|||||||
import { WorkspaceLeave } from './leave';
|
import { WorkspaceLeave } from './leave';
|
||||||
import { Upload } from '@/components/file-upload';
|
import { Upload } from '@/components/file-upload';
|
||||||
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||||
import { WorkspaceInfo } from '@affine/datacenter';
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||||
export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
|
export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||||
const [showDelete, setShowDelete] = useState<boolean>(false);
|
const [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>(workspace.name);
|
const [workspaceName, setWorkspaceName] = useState<string>(workspace.name);
|
||||||
const { currentWorkspace } = useAppState();
|
const { currentWorkspace, isOwner } = useAppState();
|
||||||
const { updateWorkspace } = useWorkspaceHelper();
|
const { updateWorkspace } = useWorkspaceHelper();
|
||||||
const isOwner = true;
|
|
||||||
const handleChangeWorkSpaceName = (newName: string) => {
|
const handleChangeWorkSpaceName = (newName: string) => {
|
||||||
setWorkspaceName(newName);
|
setWorkspaceName(newName);
|
||||||
};
|
};
|
||||||
const handleClickDelete = () => {
|
|
||||||
setShowDelete(true);
|
|
||||||
};
|
|
||||||
const handleCloseDelete = () => {
|
|
||||||
setShowDelete(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickLeave = () => {
|
|
||||||
setShowLeave(true);
|
|
||||||
};
|
|
||||||
const handleCloseLeave = () => {
|
|
||||||
setShowLeave(false);
|
|
||||||
};
|
|
||||||
const handleUpdateWorkspaceName = () => {
|
const handleUpdateWorkspaceName = () => {
|
||||||
console.log('currentWorkspace: ', currentWorkspace);
|
currentWorkspace &&
|
||||||
updateWorkspace({ name: workspaceName }, currentWorkspace);
|
updateWorkspace({ name: workspaceName }, currentWorkspace);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileChange = async (file: File) => {
|
const fileChange = async (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 });
|
||||||
updateWorkspace({ avatarBlob: blob }, currentWorkspace);
|
currentWorkspace &&
|
||||||
|
(await updateWorkspace({ avatarBlob: blob }, currentWorkspace));
|
||||||
|
setUploading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return workspace ? (
|
return workspace ? (
|
||||||
@@ -84,16 +71,19 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
|
|||||||
placeholder="Workspace Name"
|
placeholder="Workspace Name"
|
||||||
maxLength={14}
|
maxLength={14}
|
||||||
minLength={1}
|
minLength={1}
|
||||||
|
disabled={!isOwner}
|
||||||
onChange={handleChangeWorkSpaceName}
|
onChange={handleChangeWorkSpaceName}
|
||||||
></Input>
|
></Input>
|
||||||
<TextButton
|
{isOwner ? (
|
||||||
onClick={() => {
|
<TextButton
|
||||||
handleUpdateWorkspaceName();
|
onClick={() => {
|
||||||
}}
|
handleUpdateWorkspaceName();
|
||||||
style={{ marginLeft: '0px' }}
|
}}
|
||||||
>
|
style={{ marginLeft: '0px' }}
|
||||||
✔️
|
>
|
||||||
</TextButton>
|
✔️
|
||||||
|
</TextButton>
|
||||||
|
) : null}
|
||||||
</StyledSettingInputContainer>
|
</StyledSettingInputContainer>
|
||||||
<StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2>
|
<StyledSettingH2 marginTop={20}>Workspace Type</StyledSettingH2>
|
||||||
<StyledSettingInputContainer>
|
<StyledSettingInputContainer>
|
||||||
@@ -102,25 +92,39 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceInfo }) => {
|
|||||||
<StyledDeleteButtonContainer>
|
<StyledDeleteButtonContainer>
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
<>
|
<>
|
||||||
<Button type="danger" shape="circle" onClick={handleClickDelete}>
|
<Button
|
||||||
|
type="danger"
|
||||||
|
shape="circle"
|
||||||
|
onClick={() => {
|
||||||
|
setShowDelete(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
Delete Workspace
|
Delete Workspace
|
||||||
</Button>
|
</Button>
|
||||||
<WorkspaceDelete
|
<WorkspaceDelete
|
||||||
open={showDelete}
|
open={showDelete}
|
||||||
onClose={handleCloseDelete}
|
onClose={() => {
|
||||||
|
setShowDelete(false);
|
||||||
|
}}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button type="danger" shape="circle" onClick={handleClickLeave}>
|
<Button
|
||||||
|
type="danger"
|
||||||
|
shape="circle"
|
||||||
|
onClick={() => {
|
||||||
|
setShowLeave(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
Leave Workspace
|
Leave Workspace
|
||||||
</Button>
|
</Button>
|
||||||
<WorkspaceLeave
|
<WorkspaceLeave
|
||||||
open={showLeave}
|
open={showLeave}
|
||||||
onClose={handleCloseLeave}
|
onClose={() => {
|
||||||
workspaceName={workspaceName}
|
setShowLeave(false);
|
||||||
workspaceId={workspace.id}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -12,17 +12,14 @@ import { useState } from 'react';
|
|||||||
import { ModalCloseButton } from '@/ui/modal';
|
import { ModalCloseButton } from '@/ui/modal';
|
||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import {
|
|
||||||
deleteWorkspace,
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
getWorkspaces,
|
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||||
// Workspace,
|
|
||||||
} from '@/hooks/mock-data/mock';
|
|
||||||
import { WorkspaceInfo } from '@affine/datacenter';
|
|
||||||
|
|
||||||
interface WorkspaceDeleteProps {
|
interface WorkspaceDeleteProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
workspace: WorkspaceInfo;
|
workspace: WorkspaceUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceDelete = ({
|
export const WorkspaceDelete = ({
|
||||||
@@ -32,23 +29,15 @@ export const WorkspaceDelete = ({
|
|||||||
}: WorkspaceDeleteProps) => {
|
}: WorkspaceDeleteProps) => {
|
||||||
const [deleteStr, setDeleteStr] = useState<string>('');
|
const [deleteStr, setDeleteStr] = useState<string>('');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { deleteWorkSpace } = useWorkspaceHelper();
|
||||||
const handlerInputChange = (workspaceName: string) => {
|
const handlerInputChange = (workspaceName: string) => {
|
||||||
setDeleteStr(workspaceName);
|
setDeleteStr(workspaceName);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
// const dc = await getDataCenter();
|
await deleteWorkSpace();
|
||||||
// await dc.apis.deleteWorkspace({ id: workspaceId });
|
|
||||||
// router.push(`/workspace/${nextWorkSpaceId}`);
|
|
||||||
deleteWorkspace(workspace.id);
|
|
||||||
const workspaceList = getWorkspaces();
|
|
||||||
if (workspaceList.length) {
|
|
||||||
router.push(`/workspace/${workspaceList[0].id}`);
|
|
||||||
} else {
|
|
||||||
router.push(`/workspace`);
|
|
||||||
}
|
|
||||||
onClose();
|
onClose();
|
||||||
|
router.push(`/workspace`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,29 +7,19 @@ import {
|
|||||||
} from './style';
|
} from './style';
|
||||||
import { ModalCloseButton } from '@/ui/modal';
|
import { ModalCloseButton } from '@/ui/modal';
|
||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
|
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||||
// 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;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
workspaceName: string;
|
|
||||||
workspaceId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceLeave = ({
|
export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
|
||||||
open,
|
const { leaveWorkSpace } = useWorkspaceHelper();
|
||||||
onClose,
|
|
||||||
workspaceId,
|
|
||||||
}: WorkspaceDeleteProps) => {
|
|
||||||
console.log('workspaceId: ', workspaceId);
|
|
||||||
// const router = useRouter();
|
|
||||||
// const { refreshWorkspacesMeta } = useAppState();
|
|
||||||
const handleLeave = async () => {
|
const handleLeave = async () => {
|
||||||
// const dc = await getDataCenter();
|
await leaveWorkSpace();
|
||||||
// await dc.apis.leaveWorkspace({ id: workspaceId });
|
|
||||||
// // router.push(`/workspace/${nextWorkSpaceId}`);
|
|
||||||
// refreshWorkspacesMeta();
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,3 @@ export const StyledSettingAvatarContent = styled('div')(() => {
|
|||||||
export const StyledSettingAvatar = styled(MuiAvatar)(() => {
|
export const StyledSettingAvatar = styled(MuiAvatar)(() => {
|
||||||
return { height: '72px', width: '72px', marginRight: '24px' };
|
return { height: '72px', width: '72px', marginRight: '24px' };
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledMemberWarp = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
padding: '40px 0',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export * from './general';
|
export * from './general';
|
||||||
export * from './ExportPage';
|
export * from './ExportPage';
|
||||||
export * from './MembersPage';
|
export * from './member';
|
||||||
export * from './SyncPage';
|
export * from './SyncPage';
|
||||||
export * from './PublishPage';
|
export * from './PublishPage';
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
|||||||
import { Button } from '@/ui/button';
|
import { Button } from '@/ui/button';
|
||||||
import Input from '@/ui/input';
|
import Input from '@/ui/input';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
// import { getDataCenter } from '@affine/datacenter';
|
|
||||||
import { Avatar } from '@mui/material';
|
import { Avatar } from '@mui/material';
|
||||||
import { setMember } from '@/hooks/mock-data/mock';
|
import useMembers from '@/hooks/use-members';
|
||||||
|
import { User } from '@affine/datacenter';
|
||||||
interface LoginModalProps {
|
interface LoginModalProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -43,46 +43,27 @@ export const debounce = <T extends (...args: any) => any>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const gmailReg = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@gmail\.com$/;
|
const gmailReg = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@gmail\.com$/;
|
||||||
export const InviteMembers = ({
|
export const InviteMemberModal = ({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
workspaceId,
|
|
||||||
onInviteSuccess,
|
onInviteSuccess,
|
||||||
}: LoginModalProps) => {
|
}: LoginModalProps) => {
|
||||||
const [email, setEmail] = useState<string>('');
|
const [email, setEmail] = useState<string>('');
|
||||||
const [showMember, setShowMember] = useState<boolean>(false);
|
const [showMember, setShowMember] = useState<boolean>(false);
|
||||||
const [showTip, setShowTip] = useState<boolean>(false);
|
const [showTip, setShowTip] = useState<boolean>(false);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const [userData, setUserData] = useState<User | null>(null);
|
||||||
const [userData, setUserData] = useState<any>({});
|
const { inviteMember, getUserByEmail } = useMembers();
|
||||||
const inputChange = (value: string) => {
|
const inputChange = (value: string) => {
|
||||||
setShowMember(true);
|
setShowMember(true);
|
||||||
if (gmailReg.test(value)) {
|
if (gmailReg.test(value)) {
|
||||||
setEmail(value);
|
setEmail(value);
|
||||||
setShowTip(false);
|
setShowTip(false);
|
||||||
setUserData({
|
getUserByEmail(value).then(data => {
|
||||||
name: 'wxl',
|
if (data?.name) {
|
||||||
avatar: 'https://avatars.githubusercontent.com/u/20501502?v=4',
|
setUserData(data);
|
||||||
email: value,
|
setShowTip(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// debounce(
|
|
||||||
// () => {
|
|
||||||
// getDataCenter()
|
|
||||||
// .then(dc =>
|
|
||||||
// dc.apis.getUserByEmail({
|
|
||||||
// email: value,
|
|
||||||
// workspace_id: workspaceId,
|
|
||||||
// })
|
|
||||||
// )
|
|
||||||
// .then(data => {
|
|
||||||
// if (data?.name) {
|
|
||||||
// setUserData(data);
|
|
||||||
// setShowTip(false);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// 300,
|
|
||||||
// true
|
|
||||||
// )();
|
|
||||||
} else {
|
} else {
|
||||||
setShowTip(true);
|
setShowTip(true);
|
||||||
}
|
}
|
||||||
@@ -118,8 +99,8 @@ export const InviteMembers = ({
|
|||||||
<NoFind>Non-Gmail is not supported</NoFind>
|
<NoFind>Non-Gmail is not supported</NoFind>
|
||||||
) : (
|
) : (
|
||||||
<Member>
|
<Member>
|
||||||
{userData?.avatar_url ? (
|
{userData?.avatar ? (
|
||||||
<Avatar src={userData?.avatar_url}></Avatar>
|
<Avatar src={userData?.avatar}></Avatar>
|
||||||
) : (
|
) : (
|
||||||
<MemberIcon>
|
<MemberIcon>
|
||||||
<EmailIcon></EmailIcon>
|
<EmailIcon></EmailIcon>
|
||||||
@@ -139,19 +120,9 @@ export const InviteMembers = ({
|
|||||||
<Button
|
<Button
|
||||||
shape="circle"
|
shape="circle"
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
setMember(workspaceId, userData);
|
await inviteMember(email);
|
||||||
onInviteSuccess();
|
onInviteSuccess();
|
||||||
// getDataCenter()
|
|
||||||
// .then(dc => dc.apis.inviteMember({ id: workspaceId, email }))
|
|
||||||
// .then(() => {
|
|
||||||
// onClose();
|
|
||||||
// onInviteSuccess && onInviteSuccess();
|
|
||||||
// })
|
|
||||||
// .catch(err => {
|
|
||||||
// // toast('Invite failed');
|
|
||||||
// console.log(err);
|
|
||||||
// });
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Invite
|
Invite
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import {
|
||||||
|
StyledMemberAvatar,
|
||||||
|
StyledMemberButtonContainer,
|
||||||
|
StyledMemberEmail,
|
||||||
|
StyledMemberInfo,
|
||||||
|
StyledMemberListContainer,
|
||||||
|
StyledMemberListItem,
|
||||||
|
StyledMemberName,
|
||||||
|
StyledMemberNameContainer,
|
||||||
|
StyledMemberRoleContainer,
|
||||||
|
StyledMemberTitleContainer,
|
||||||
|
StyledMoreVerticalButton,
|
||||||
|
StyledPublishExplanation,
|
||||||
|
StyledMemberWarp,
|
||||||
|
} from './style';
|
||||||
|
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button, IconButton } from '@/ui/button';
|
||||||
|
import { InviteMemberModal } from './InviteMemberModal';
|
||||||
|
import { Menu, MenuItem } from '@/ui/menu';
|
||||||
|
import { Empty } from '@/ui/empty';
|
||||||
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
|
import { toast } from '@/ui/toast';
|
||||||
|
import useMembers from '@/hooks/use-members';
|
||||||
|
import Loading from '@/components/loading';
|
||||||
|
import { Wrapper } from '@/ui/layout';
|
||||||
|
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||||
|
|
||||||
|
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||||
|
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||||
|
const { members, removeMember, loaded } = useMembers();
|
||||||
|
const { enableWorkspace } = useWorkspaceHelper();
|
||||||
|
const { confirm } = useConfirm();
|
||||||
|
|
||||||
|
if (workspace.provider === 'affine') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StyledMemberListContainer>
|
||||||
|
{!loaded && (
|
||||||
|
<Wrapper justifyContent="center">
|
||||||
|
<Loading size={25} />
|
||||||
|
</Wrapper>
|
||||||
|
)}
|
||||||
|
{loaded && members.length === 0 && (
|
||||||
|
<Empty width={648} sx={{ marginTop: '60px' }} height={300} />
|
||||||
|
)}
|
||||||
|
{loaded && members.length && (
|
||||||
|
<>
|
||||||
|
<StyledMemberTitleContainer>
|
||||||
|
<StyledMemberNameContainer>
|
||||||
|
Users({members.length})
|
||||||
|
</StyledMemberNameContainer>
|
||||||
|
<StyledMemberRoleContainer>
|
||||||
|
Access level
|
||||||
|
</StyledMemberRoleContainer>
|
||||||
|
</StyledMemberTitleContainer>
|
||||||
|
{members.map((member, index) => {
|
||||||
|
const user = Object.assign(
|
||||||
|
{
|
||||||
|
avatar_url: '',
|
||||||
|
email: '',
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
member.user
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<StyledMemberListItem key={index}>
|
||||||
|
<StyledMemberNameContainer>
|
||||||
|
<StyledMemberAvatar
|
||||||
|
alt="member avatar"
|
||||||
|
src={user.avatar_url}
|
||||||
|
>
|
||||||
|
<EmailIcon />
|
||||||
|
</StyledMemberAvatar>
|
||||||
|
|
||||||
|
<StyledMemberInfo>
|
||||||
|
<StyledMemberName>{user.name}</StyledMemberName>
|
||||||
|
<StyledMemberEmail>
|
||||||
|
{member.user.email}
|
||||||
|
</StyledMemberEmail>
|
||||||
|
</StyledMemberInfo>
|
||||||
|
</StyledMemberNameContainer>
|
||||||
|
<StyledMemberRoleContainer>
|
||||||
|
{member.accepted
|
||||||
|
? member.type !== 99
|
||||||
|
? 'Member'
|
||||||
|
: 'Workspace Owner'
|
||||||
|
: 'Pending'}
|
||||||
|
</StyledMemberRoleContainer>
|
||||||
|
<StyledMoreVerticalButton>
|
||||||
|
<Menu
|
||||||
|
content={
|
||||||
|
<>
|
||||||
|
<MenuItem
|
||||||
|
onClick={async () => {
|
||||||
|
const confirmRemove = await confirm({
|
||||||
|
title: 'Delete Member?',
|
||||||
|
content: `will delete member`,
|
||||||
|
confirmText: 'Delete',
|
||||||
|
confirmType: 'danger',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmRemove) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await removeMember(member.id);
|
||||||
|
toast(`${user.name} has been removed`);
|
||||||
|
}}
|
||||||
|
icon={<TrashIcon />}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
placement="bottom-end"
|
||||||
|
disablePortal={true}
|
||||||
|
>
|
||||||
|
<IconButton>
|
||||||
|
<MoreVerticalIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Menu>
|
||||||
|
</StyledMoreVerticalButton>
|
||||||
|
</StyledMemberListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</StyledMemberListContainer>
|
||||||
|
<StyledMemberButtonContainer>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsInviteModalShow(true);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
>
|
||||||
|
Invite Members
|
||||||
|
</Button>
|
||||||
|
<InviteMemberModal
|
||||||
|
onClose={() => {
|
||||||
|
setIsInviteModalShow(false);
|
||||||
|
}}
|
||||||
|
onInviteSuccess={() => {
|
||||||
|
setIsInviteModalShow(false);
|
||||||
|
// refreshMembers();
|
||||||
|
}}
|
||||||
|
workspaceId={workspace.id}
|
||||||
|
open={isInviteModalShow}
|
||||||
|
></InviteMemberModal>
|
||||||
|
</StyledMemberButtonContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledMemberWarp>
|
||||||
|
<>Collaborating with other members requires AFFiNE Cloud service.</>
|
||||||
|
<StyledPublishExplanation>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
shape="circle"
|
||||||
|
onClick={async () => {
|
||||||
|
await enableWorkspace();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Enable AFFiNE Cloud
|
||||||
|
</Button>
|
||||||
|
</StyledPublishExplanation>
|
||||||
|
</StyledMemberWarp>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './MembersPage';
|
||||||
106
packages/app/src/components/workspace-setting/member/style.ts
Normal file
106
packages/app/src/components/workspace-setting/member/style.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { styled } from '@/styles';
|
||||||
|
import MuiAvatar from '@mui/material/Avatar';
|
||||||
|
|
||||||
|
export const StyledMemberTitleContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
marginTop: '60px',
|
||||||
|
fontWeight: '500',
|
||||||
|
flex: 1,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberAvatar = styled(MuiAvatar)(() => {
|
||||||
|
return { height: '40px', width: '40px' };
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberNameContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '402px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberRoleContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '222px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberListContainer = styled('ul')(() => {
|
||||||
|
return {
|
||||||
|
marginTop: '15px',
|
||||||
|
overflowY: 'scroll',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberListItem = styled('li')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '72px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberInfo = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
paddingLeft: '12px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberName = styled('div')(({ theme }) => {
|
||||||
|
return {
|
||||||
|
fontWeight: '400',
|
||||||
|
fontSize: '18px',
|
||||||
|
lineHeight: '16px',
|
||||||
|
color: theme.colors.textColor,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberEmail = styled('div')(({ theme }) => {
|
||||||
|
return {
|
||||||
|
fontWeight: '400',
|
||||||
|
fontSize: '16px',
|
||||||
|
lineHeight: '22px',
|
||||||
|
color: theme.colors.iconColor,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberButtonContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
marginTop: '14px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMoreVerticalButton = styled('button')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '24px',
|
||||||
|
height: '24px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledPublishExplanation = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
paddingRight: '48px',
|
||||||
|
fontWeight: '500',
|
||||||
|
fontSize: '18px',
|
||||||
|
lineHeight: '26px',
|
||||||
|
flex: 1,
|
||||||
|
marginTop: '60px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StyledMemberWarp = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: '40px 0',
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import { styled } from '@/styles';
|
import { styled } from '@/styles';
|
||||||
import { Button } from '@/ui/button';
|
|
||||||
import MuiAvatar from '@mui/material/Avatar';
|
|
||||||
|
|
||||||
export const StyledSettingContainer = styled('div')(({ theme }) => {
|
export const StyledSettingContainer = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
padding: '50px',
|
padding: '50px',
|
||||||
@@ -12,7 +10,7 @@ export const StyledSettingContainer = styled('div')(({ theme }) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledSettingSidebar = styled('div')(({ theme }) => {
|
export const StyledSettingSidebar = styled('div')(() => {
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
@@ -96,99 +94,6 @@ export const StyledSettingH2 = styled('h2')<{ marginTop?: number }>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const StyledAvatarUploadBtn = styled(Button)(({ theme }) => {
|
|
||||||
return {
|
|
||||||
backgroundColor: theme.colors.hoverBackground,
|
|
||||||
color: theme.colors.primaryColor,
|
|
||||||
margin: '0 12px 0 24px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberTitleContainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
marginTop: '60px',
|
|
||||||
fontWeight: '500',
|
|
||||||
flex: 1,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberAvatar = styled(MuiAvatar)(() => {
|
|
||||||
return { height: '40px', width: '40px' };
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberNameContainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '402px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberRoleContainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '222px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberListContainer = styled('ul')(() => {
|
|
||||||
return {
|
|
||||||
marginTop: '15px',
|
|
||||||
overflowY: 'scroll',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberListItem = styled('li')(() => {
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '72px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberInfo = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
paddingLeft: '12px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberName = styled('div')(({ theme }) => {
|
|
||||||
return {
|
|
||||||
fontWeight: '400',
|
|
||||||
fontSize: '18px',
|
|
||||||
lineHeight: '16px',
|
|
||||||
color: theme.colors.textColor,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberEmail = styled('div')(({ theme }) => {
|
|
||||||
return {
|
|
||||||
fontWeight: '400',
|
|
||||||
fontSize: '16px',
|
|
||||||
lineHeight: '22px',
|
|
||||||
color: theme.colors.iconColor,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMemberButtonContainer = styled('div')(() => {
|
|
||||||
return {
|
|
||||||
marginTop: '14px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledMoreVerticalButton = styled('button')(() => {
|
|
||||||
return {
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '24px',
|
|
||||||
height: '24px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const StyledPublishExplanation = styled('div')(() => {
|
export const StyledPublishExplanation = styled('div')(() => {
|
||||||
return {
|
return {
|
||||||
paddingRight: '48px',
|
paddingRight: '48px',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
|||||||
import { useAppState } from '@/providers/app-state-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 { currentMetaWorkSpace, workspaceList } = useAppState();
|
const { currentWorkspace, workspaceList } = useAppState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspaceList.length === 0) {
|
if (workspaceList.length === 0) {
|
||||||
@@ -23,7 +23,7 @@ export const WorkspaceSelector = () => {
|
|||||||
<Avatar
|
<Avatar
|
||||||
alt="Affine"
|
alt="Affine"
|
||||||
data-testid="workspace-avatar"
|
data-testid="workspace-avatar"
|
||||||
src={currentMetaWorkSpace?.avatar}
|
src={currentWorkspace?.avatar}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -32,13 +32,13 @@ export const WorkspaceSelector = () => {
|
|||||||
>
|
>
|
||||||
<WorkspaceAvatar
|
<WorkspaceAvatar
|
||||||
size={28}
|
size={28}
|
||||||
name={currentMetaWorkSpace?.name ?? 'AFFiNE'}
|
name={currentWorkspace?.name ?? 'AFFiNE'}
|
||||||
avatar={currentMetaWorkSpace?.avatar ?? ''}
|
avatar={currentWorkspace?.avatar ?? ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<WorkspaceName data-testid="workspace-name">
|
<WorkspaceName data-testid="workspace-name">
|
||||||
{currentMetaWorkSpace?.name ?? 'AFFiNE'}
|
{currentWorkspace?.name ?? 'AFFiNE'}
|
||||||
</WorkspaceName>
|
</WorkspaceName>
|
||||||
</SelectorWrapper>
|
</SelectorWrapper>
|
||||||
<WorkspaceModal
|
<WorkspaceModal
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import {
|
|||||||
StyledLink,
|
StyledLink,
|
||||||
StyledListItem,
|
StyledListItem,
|
||||||
StyledListItemForWorkspace,
|
StyledListItemForWorkspace,
|
||||||
// StyledListItemForWorkspace,
|
|
||||||
StyledNewPageButton,
|
StyledNewPageButton,
|
||||||
StyledSliderBar,
|
StyledSliderBar,
|
||||||
StyledSliderBarWrapper,
|
StyledSliderBarWrapper,
|
||||||
StyledSubListItem,
|
StyledSubListItem,
|
||||||
} from './style';
|
} from './style';
|
||||||
import { Arrow } from './icons';
|
import { Arrow } from './icons';
|
||||||
// import { WorkspaceSelector } from './WorkspaceSelector';
|
|
||||||
import Collapse from '@mui/material/Collapse';
|
import Collapse from '@mui/material/Collapse';
|
||||||
import {
|
import {
|
||||||
ArrowDownIcon,
|
ArrowDownIcon,
|
||||||
@@ -30,15 +28,13 @@ import { useModal } from '@/providers/GlobalModalProvider';
|
|||||||
import { useAppState } from '@/providers/app-state-provider';
|
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 { usePageHelper } from '@/hooks/use-page-helper';
|
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||||
// import { WorkspaceSetting } from '@/components/workspace-setting';
|
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
import { WorkspaceSelector } from './WorkspaceSelector/WorkspaceSelector';
|
import { WorkspaceSelector } from './WorkspaceSelector/WorkspaceSelector';
|
||||||
|
|
||||||
const FavoriteList = ({ showList }: { showList: boolean }) => {
|
const FavoriteList = ({ showList }: { showList: boolean }) => {
|
||||||
const { openPage } = usePageHelper();
|
const { openPage } = usePageHelper();
|
||||||
const pageList = usePageMetaList();
|
const { pageList } = useAppState();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const favoriteList = pageList.filter(p => p.favorite && !p.trash);
|
const favoriteList = pageList.filter(p => p.favorite && !p.trash);
|
||||||
@@ -71,13 +67,13 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
|
|||||||
export const WorkSpaceSliderBar = () => {
|
export const WorkSpaceSliderBar = () => {
|
||||||
const { triggerQuickSearchModal, triggerImportModal } = useModal();
|
const { triggerQuickSearchModal, triggerImportModal } = useModal();
|
||||||
const [showSubFavorite, setShowSubFavorite] = useState(true);
|
const [showSubFavorite, setShowSubFavorite] = useState(true);
|
||||||
const { currentWorkspaceId } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const { openPage, createPage } = usePageHelper();
|
const { openPage, createPage } = usePageHelper();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
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 currentWorkspaceId = currentWorkspace?.id;
|
||||||
const paths = {
|
const paths = {
|
||||||
all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
|
all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '',
|
||||||
favorite: currentWorkspaceId
|
favorite: currentWorkspaceId
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
export interface Workspace {
|
|
||||||
name: string; // 名称
|
|
||||||
id: string; //唯一标识
|
|
||||||
isPublish?: boolean; // 是否公开
|
|
||||||
isLocal?: boolean; // 是否全部数据都在本地
|
|
||||||
avatar?: string; // 封面
|
|
||||||
type: 'local' | 'cloud' | 'join'; // cloud: 云端(本次暂不支持),local: 本地,join : 加入别人的
|
|
||||||
workspaceOwner?: User; // 本地工作空间的拥有者
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface User {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
email: string;
|
|
||||||
avatar: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateWorkspaceMeta(
|
|
||||||
workspaceId: string,
|
|
||||||
workspaceData: {
|
|
||||||
name?: string;
|
|
||||||
avatar?: string;
|
|
||||||
type?: 'local' | 'cloud' | 'join';
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
|
|
||||||
if (workspace.id === workspaceId) {
|
|
||||||
workspaceData.name && (workspace.name = workspaceData.name);
|
|
||||||
workspaceData.avatar && (workspace.avatar = workspaceData.avatar);
|
|
||||||
workspaceData.type && (workspace.type = workspaceData.type);
|
|
||||||
return workspaceData;
|
|
||||||
}
|
|
||||||
return workspace;
|
|
||||||
});
|
|
||||||
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
|
||||||
const activeWorkspace = getActiveWorkspace();
|
|
||||||
workspaceData.name && (activeWorkspace.name = workspaceData.name);
|
|
||||||
workspaceData.avatar && (activeWorkspace.avatar = workspaceData.avatar);
|
|
||||||
|
|
||||||
workspaceData.type && (activeWorkspace.type = workspaceData.type);
|
|
||||||
console.log(workspaceData);
|
|
||||||
setActiveWorkspace(activeWorkspace);
|
|
||||||
}
|
|
||||||
export function createWorkspace(workspaceName: string) {
|
|
||||||
const workspaceId = 'workspace-' + Date.now();
|
|
||||||
const workspaceData = {
|
|
||||||
name: workspaceName,
|
|
||||||
id: workspaceId,
|
|
||||||
isPublish: false,
|
|
||||||
isLocal: true,
|
|
||||||
avatar: '',
|
|
||||||
type: 'local',
|
|
||||||
} as Workspace;
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
workspacesMeta.push(workspaceData);
|
|
||||||
localStorage.setItem('affine-workspace', JSON.stringify(workspacesMeta));
|
|
||||||
setActiveWorkspace(workspaceData);
|
|
||||||
return { workspaceId };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getWorkspaces(): Workspace[] {
|
|
||||||
const workspacesMeta = JSON.parse(
|
|
||||||
localStorage.getItem('affine-workspace') ?? '[]'
|
|
||||||
);
|
|
||||||
return workspacesMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteWorkspace(workspaceId: string) {
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
const newWorkspacesMeta = workspacesMeta.filter(() => {
|
|
||||||
return workspaceId !== workspaceId;
|
|
||||||
});
|
|
||||||
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMembers(id: string): User[] {
|
|
||||||
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
|
||||||
return memberMap[id] || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setMember(workspaceId: string, member: User) {
|
|
||||||
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
|
||||||
memberMap[workspaceId] = memberMap[workspaceId] || [];
|
|
||||||
memberMap[workspaceId].push(member);
|
|
||||||
localStorage.setItem('affine-member', JSON.stringify(memberMap));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteMember(workspaceId: string, index: number) {
|
|
||||||
const memberMap = JSON.parse(localStorage.getItem('affine-member') ?? '{}');
|
|
||||||
const memberList = memberMap[workspaceId];
|
|
||||||
memberList.splice(index, 1);
|
|
||||||
memberMap[workspaceId] = memberList;
|
|
||||||
localStorage.setItem('affine-member', JSON.stringify(memberMap));
|
|
||||||
}
|
|
||||||
export function leaveWorkspace() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setWorkspacePublish(id: string, isPublish: boolean): boolean {
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
const newWorkspacesMeta = workspacesMeta.map((workspace: Workspace) => {
|
|
||||||
if (workspace.id === id) {
|
|
||||||
workspace.isPublish = isPublish;
|
|
||||||
}
|
|
||||||
return workspace;
|
|
||||||
});
|
|
||||||
localStorage.setItem('affine-workspace', JSON.stringify(newWorkspacesMeta));
|
|
||||||
return isPublish;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getWorkspaceById(id: string) {
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
return workspacesMeta.find((workspace: Workspace) => {
|
|
||||||
return workspace.id === id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPagesByWorkspaceId(workspaceId: string) {
|
|
||||||
if (!workspaceId) return [];
|
|
||||||
const workspacesMeta = [];
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
workspacesMeta.push({
|
|
||||||
id: 'page-' + i,
|
|
||||||
name: 'page ' + i,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getActiveWorkspace(): Workspace {
|
|
||||||
return JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setActiveWorkspace(workspaceData: Workspace) {
|
|
||||||
console.log('workspaceData: ', workspaceData);
|
|
||||||
localStorage.setItem(
|
|
||||||
'affine-active-workspace',
|
|
||||||
JSON.stringify(workspaceData)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUserInfo(): User {
|
|
||||||
return JSON.parse(localStorage.getItem('affine-user') ?? 'null');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Login(): void {
|
|
||||||
localStorage.setItem(
|
|
||||||
'affine-user',
|
|
||||||
JSON.stringify({
|
|
||||||
name: 'Diamond',
|
|
||||||
id: 'ttt',
|
|
||||||
email: 'diamond.shx@gmail.com',
|
|
||||||
avatar: 'string',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SignOut(): void {
|
|
||||||
localStorage.removeItem('affine-user');
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ export const useChangePageMeta = () => {
|
|||||||
|
|
||||||
return useCallback<ChangePageMeta>(
|
return useCallback<ChangePageMeta>(
|
||||||
(pageId, pageMeta) => {
|
(pageId, pageMeta) => {
|
||||||
currentWorkspace?.setPageMeta(pageId, {
|
currentWorkspace?.blocksuiteWorkspace?.setPageMeta(pageId, {
|
||||||
...pageMeta,
|
...pageMeta,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const useCurrentPageMeta = (): PageMeta | null => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
(currentWorkspace.meta.pageMetas.find(
|
(currentWorkspace.blocksuiteWorkspace?.meta.pageMetas.find(
|
||||||
p => p.id === currentPage.id
|
p => p.id === currentPage.id
|
||||||
) as PageMeta) ?? null
|
) as PageMeta) ?? null
|
||||||
);
|
);
|
||||||
@@ -22,9 +22,11 @@ export const useCurrentPageMeta = (): PageMeta | null => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentPageMeta(pageMetaHandler);
|
setCurrentPageMeta(pageMetaHandler);
|
||||||
|
|
||||||
const dispose = currentWorkspace?.meta.pagesUpdated.on(() => {
|
const dispose = currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on(
|
||||||
setCurrentPageMeta(pageMetaHandler);
|
() => {
|
||||||
}).dispose;
|
setCurrentPageMeta(pageMetaHandler);
|
||||||
|
}
|
||||||
|
).dispose;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
dispose?.();
|
dispose?.();
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
const defaultOutLineWorkspaceId = 'affine';
|
|
||||||
// 'local-first-' + '85b4ca0b9081421d903bbc2501ea280f';
|
|
||||||
// It is a fully effective hook
|
// It is a fully effective hook
|
||||||
// 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 { workspaceList, loadWorkspace, user } = useAppState();
|
const { workspaceList, loadWorkspace, user } = useAppState();
|
||||||
console.log('workspaceList: ', workspaceList);
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [activeWorkspaceId, setActiveWorkspaceId] = useState(
|
||||||
|
router.query.workspaceId as string
|
||||||
|
);
|
||||||
|
|
||||||
// const defaultOutLineWorkspaceId = '99ce7eb7';
|
// const defaultOutLineWorkspaceId = '99ce7eb7';
|
||||||
// console.log(defaultOutLineWorkspaceId);
|
// console.log(defaultOutLineWorkspaceId);
|
||||||
@@ -18,7 +18,8 @@ export const useEnsureWorkspace = () => {
|
|||||||
// If workspaceList is empty, we need to create a default workspace but not jump to 404
|
// If workspaceList is empty, we need to create a default workspace but not jump to 404
|
||||||
if (
|
if (
|
||||||
workspaceList.length &&
|
workspaceList.length &&
|
||||||
router.query.workspaceId &&
|
// FIXME: router is not ready when this hook is called
|
||||||
|
location.pathname.startsWith(`/workspace/${router.query.workspaceId}`) &&
|
||||||
workspaceList.findIndex(
|
workspaceList.findIndex(
|
||||||
meta => meta.id.toString() === router.query.workspaceId
|
meta => meta.id.toString() === router.query.workspaceId
|
||||||
) === -1
|
) === -1
|
||||||
@@ -35,18 +36,17 @@ export const useEnsureWorkspace = () => {
|
|||||||
// router.push('/404');
|
// router.push('/404');
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
|
const workspaceId =
|
||||||
const workspaceId = user
|
(router.query.workspaceId as string) || workspaceList[0]?.id;
|
||||||
? (router.query.workspaceId as string) || workspaceList[0]?.id
|
|
||||||
: (router.query.workspaceId as string) || defaultOutLineWorkspaceId;
|
|
||||||
|
|
||||||
loadWorkspace(workspaceId).finally(() => {
|
loadWorkspace(workspaceId).finally(() => {
|
||||||
setWorkspaceLoaded(true);
|
setWorkspaceLoaded(true);
|
||||||
|
setActiveWorkspaceId(activeWorkspaceId);
|
||||||
});
|
});
|
||||||
}, [loadWorkspace, router, user, workspaceList]);
|
}, [loadWorkspace, router, user, workspaceList, activeWorkspaceId]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
workspaceLoaded,
|
workspaceLoaded,
|
||||||
|
activeWorkspaceId,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
export const useInitWorkspace = (disabled?: boolean) => {
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
// Do not set as a constant, or it will trigger a hell of re-rendering
|
|
||||||
const defaultOutLineWorkspaceId = useRef(new Date().getTime().toString());
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { workspaceList, loadWorkspace, currentWorkspace, currentWorkspaceId } =
|
|
||||||
useAppState();
|
|
||||||
const workspaceId =
|
|
||||||
(router.query.workspaceId as string) ||
|
|
||||||
workspaceList?.[0]?.id ||
|
|
||||||
defaultOutLineWorkspaceId.current;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (disabled) {
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
loadWorkspace(workspaceId).finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}, [workspaceId, disabled, loadWorkspace]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
workspaceId,
|
|
||||||
workspace: workspaceId === currentWorkspaceId ? currentWorkspace : null,
|
|
||||||
loading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
51
packages/app/src/hooks/use-members.ts
Normal file
51
packages/app/src/hooks/use-members.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { Member } from '@affine/datacenter';
|
||||||
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
|
export const useMembers = () => {
|
||||||
|
const { dataCenter, currentWorkspace } = useAppState();
|
||||||
|
const [members, setMembers] = useState<Member[]>([]);
|
||||||
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
const refreshMembers = useCallback(async () => {
|
||||||
|
if (!currentWorkspace || !dataCenter) return;
|
||||||
|
const members = await dataCenter.getMembers(currentWorkspace.id);
|
||||||
|
setMembers(members);
|
||||||
|
}, [dataCenter, currentWorkspace]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const init = async () => {
|
||||||
|
await refreshMembers();
|
||||||
|
setLoaded(true);
|
||||||
|
};
|
||||||
|
init();
|
||||||
|
}, [refreshMembers]);
|
||||||
|
|
||||||
|
const inviteMember = async (email: string) => {
|
||||||
|
currentWorkspace &&
|
||||||
|
dataCenter &&
|
||||||
|
(await dataCenter.inviteMember(currentWorkspace?.id, email));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeMember = async (permissionId: number) => {
|
||||||
|
if (!currentWorkspace || !dataCenter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoaded(false);
|
||||||
|
await dataCenter.removeMember(currentWorkspace.id, permissionId);
|
||||||
|
await refreshMembers();
|
||||||
|
setLoaded(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserByEmail = async (email: string) => {
|
||||||
|
if (!currentWorkspace) return null;
|
||||||
|
return dataCenter?.getUserByEmail(currentWorkspace.id, email);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
members,
|
||||||
|
removeMember,
|
||||||
|
inviteMember,
|
||||||
|
getUserByEmail,
|
||||||
|
loaded,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMembers;
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Workspace, uuidv4 } from '@blocksuite/store';
|
import { uuidv4 } from '@blocksuite/store';
|
||||||
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
||||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||||
import { EditorContainer } from '@blocksuite/editor';
|
import { EditorContainer } from '@blocksuite/editor';
|
||||||
import { useChangePageMeta } from '@/hooks/use-change-page-meta';
|
import { useChangePageMeta } from '@/hooks/use-change-page-meta';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
|
|
||||||
export type EditorHandlers = {
|
export type EditorHandlers = {
|
||||||
createPage: (params?: {
|
createPage: (params?: {
|
||||||
@@ -27,13 +28,15 @@ export type EditorHandlers = {
|
|||||||
) => Promise<EditorContainer['mode']>;
|
) => Promise<EditorContainer['mode']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPageMeta = (workspace: Workspace | null, pageId: string) => {
|
const getPageMeta = (workspace: WorkspaceUnit | null, pageId: string) => {
|
||||||
return workspace?.meta.pageMetas.find(p => p.id === pageId);
|
return workspace?.blocksuiteWorkspace?.meta.pageMetas.find(
|
||||||
|
p => p.id === pageId
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const usePageHelper = (): EditorHandlers => {
|
export const usePageHelper = (): EditorHandlers => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const changePageMeta = useChangePageMeta();
|
const changePageMeta = useChangePageMeta();
|
||||||
const { currentWorkspace, editor, currentWorkspaceId } = useAppState();
|
const { currentWorkspace, editor } = useAppState();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createPage: ({
|
createPage: ({
|
||||||
@@ -44,11 +47,15 @@ export const usePageHelper = (): EditorHandlers => {
|
|||||||
if (!currentWorkspace) {
|
if (!currentWorkspace) {
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
}
|
}
|
||||||
currentWorkspace.createPage(pageId);
|
currentWorkspace.blocksuiteWorkspace?.createPage(pageId);
|
||||||
currentWorkspace.signals.pageAdded.once(addedPageId => {
|
currentWorkspace.blocksuiteWorkspace?.signals.pageAdded.once(
|
||||||
currentWorkspace.setPageMeta(addedPageId, { title });
|
addedPageId => {
|
||||||
resolve(addedPageId);
|
currentWorkspace.blocksuiteWorkspace?.setPageMeta(addedPageId, {
|
||||||
});
|
title,
|
||||||
|
});
|
||||||
|
resolve(addedPageId);
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toggleFavoritePage: async pageId => {
|
toggleFavoritePage: async pageId => {
|
||||||
@@ -78,7 +85,7 @@ export const usePageHelper = (): EditorHandlers => {
|
|||||||
},
|
},
|
||||||
search: (query: QueryContent) => {
|
search: (query: QueryContent) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
return currentWorkspace!.search(query);
|
return currentWorkspace!.blocksuiteWorkspace!.search(query);
|
||||||
},
|
},
|
||||||
changePageMode: async (pageId, mode) => {
|
changePageMode: async (pageId, mode) => {
|
||||||
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
||||||
@@ -96,17 +103,17 @@ export const usePageHelper = (): EditorHandlers => {
|
|||||||
permanentlyDeletePage: pageId => {
|
permanentlyDeletePage: pageId => {
|
||||||
// TODO: workspace.meta.removePage or workspace.removePage?
|
// TODO: workspace.meta.removePage or workspace.removePage?
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
currentWorkspace!.meta.removePage(pageId);
|
currentWorkspace!.blocksuiteWorkspace?.meta.removePage(pageId);
|
||||||
},
|
},
|
||||||
openPage: (pageId, query = {}, newTab = false) => {
|
openPage: (pageId, query = {}, newTab = false) => {
|
||||||
pageId = pageId.replace('space:', '');
|
pageId = pageId.replace('space:', '');
|
||||||
|
|
||||||
if (newTab) {
|
if (newTab) {
|
||||||
window.open(`/workspace/${currentWorkspaceId}/${pageId}`, '_blank');
|
window.open(`/workspace/${currentWorkspace?.id}/${pageId}`, '_blank');
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
return router.push({
|
return router.push({
|
||||||
pathname: `/workspace/${currentWorkspaceId}/${pageId}`,
|
pathname: `/workspace/${currentWorkspace?.id}/${pageId}`,
|
||||||
query,
|
query,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -116,7 +123,7 @@ export const usePageHelper = (): EditorHandlers => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(currentWorkspace.meta.pageMetas.find(
|
(currentWorkspace.blocksuiteWorkspace?.meta.pageMetas.find(
|
||||||
page => page.id === pageId
|
page => page.id === pageId
|
||||||
) as PageMeta) || null
|
) as PageMeta) || null
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { PageMeta } from '@/providers/app-state-provider';
|
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
|
||||||
|
|
||||||
export const usePageMetaList = () => {
|
|
||||||
const { currentWorkspace } = useAppState();
|
|
||||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!currentWorkspace) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPageList(currentWorkspace.meta.pageMetas as PageMeta[]);
|
|
||||||
const dispose = currentWorkspace.meta.pagesUpdated.on(() => {
|
|
||||||
setPageList(currentWorkspace.meta.pageMetas as PageMeta[]);
|
|
||||||
}).dispose;
|
|
||||||
return () => {
|
|
||||||
dispose();
|
|
||||||
};
|
|
||||||
}, [currentWorkspace]);
|
|
||||||
|
|
||||||
return pageList;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default usePageMetaList;
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { useAppState } from '@/providers/app-state-provider';
|
|
||||||
import { User } from '@affine/datacenter';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export const useUserHelper = () => {
|
|
||||||
const { dataCenter } = useAppState();
|
|
||||||
const [user, setUser] = useState<User | undefined>(undefined);
|
|
||||||
useEffect(() => {
|
|
||||||
dataCenter
|
|
||||||
.getUserInfo()
|
|
||||||
.then(user => {
|
|
||||||
setUser(user);
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
setUser(undefined);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const login = async () => {
|
|
||||||
await dataCenter.login();
|
|
||||||
const user = await dataCenter.getUserInfo();
|
|
||||||
setUser(user);
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
login,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import { Workspace } from '@blocksuite/store';
|
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||||
|
import { toast } from '@/ui/toast';
|
||||||
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
|
import router from 'next/router';
|
||||||
|
|
||||||
export const useWorkspaceHelper = () => {
|
export const useWorkspaceHelper = () => {
|
||||||
const { dataCenter } = useAppState();
|
const { confirm } = useConfirm();
|
||||||
|
const { dataCenter, currentWorkspace, user, login } = useAppState();
|
||||||
const createWorkspace = async (name: string) => {
|
const createWorkspace = async (name: string) => {
|
||||||
const workspaceInfo = await dataCenter.createWorkspace({
|
const workspaceInfo = await dataCenter.createWorkspace({
|
||||||
name: name,
|
name: name,
|
||||||
});
|
});
|
||||||
if (workspaceInfo && workspaceInfo.room) {
|
if (workspaceInfo && workspaceInfo.id) {
|
||||||
const workspace = await dataCenter.loadWorkspace(workspaceInfo.room);
|
return await dataCenter.loadWorkspace(workspaceInfo.id);
|
||||||
return workspace;
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
@@ -16,31 +20,56 @@ export const useWorkspaceHelper = () => {
|
|||||||
// const updateWorkspace = async (workspace: Workspace) => {};
|
// const updateWorkspace = async (workspace: Workspace) => {};
|
||||||
|
|
||||||
const publishWorkspace = async (workspaceId: string, publish: boolean) => {
|
const publishWorkspace = async (workspaceId: string, publish: boolean) => {
|
||||||
dataCenter.setWorkspacePublish(workspaceId, publish);
|
await dataCenter.setWorkspacePublish(workspaceId, publish);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateWorkspace = async (
|
const updateWorkspace = async (
|
||||||
{ name, avatarBlob }: { name?: string; avatarBlob?: Blob },
|
{ name, avatarBlob }: { name?: string; avatarBlob?: Blob },
|
||||||
workspace: Workspace
|
workspace: WorkspaceUnit
|
||||||
) => {
|
) => {
|
||||||
if (name) {
|
if (name) {
|
||||||
await dataCenter.updateWorkspaceMeta({ name }, workspace);
|
await dataCenter.updateWorkspaceMeta({ name }, workspace);
|
||||||
}
|
}
|
||||||
if (avatarBlob) {
|
if (avatarBlob) {
|
||||||
const blobId = await dataCenter.setBlob(workspace, avatarBlob);
|
// const blobId = await dataCenter.setBlob(workspace, avatarBlob);
|
||||||
await dataCenter.updateWorkspaceMeta({ avatar: blobId }, workspace);
|
// await dataCenter.updateWorkspaceMeta({ avatar: blobId }, workspace);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const enableWorkspace = async (workspace: Workspace) => {
|
const enableWorkspace = async () => {
|
||||||
const newWorkspaceId = await dataCenter.enableWorkspaceCloud(workspace);
|
confirm({
|
||||||
// console.log('newWorkspace: ', newWorkspace);
|
title: 'Enable AFFiNE Cloud?',
|
||||||
return newWorkspaceId;
|
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||||
|
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||||
|
cancelText: 'Skip',
|
||||||
|
}).then(async confirm => {
|
||||||
|
if (confirm && currentWorkspace) {
|
||||||
|
if (!user) {
|
||||||
|
await login();
|
||||||
|
}
|
||||||
|
const workspace = await dataCenter.enableWorkspaceCloud(
|
||||||
|
currentWorkspace
|
||||||
|
);
|
||||||
|
workspace && router.push(`/workspace/${workspace.id}/setting`);
|
||||||
|
toast('Enabled success');
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteWorkSpace = async () => {
|
||||||
|
currentWorkspace &&
|
||||||
|
(await dataCenter.deleteWorkspace(currentWorkspace?.id));
|
||||||
|
};
|
||||||
|
const leaveWorkSpace = async () => {
|
||||||
|
currentWorkspace && (await dataCenter.leaveWorkspace(currentWorkspace?.id));
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
publishWorkspace,
|
publishWorkspace,
|
||||||
updateWorkspace,
|
updateWorkspace,
|
||||||
enableWorkspace,
|
enableWorkspace,
|
||||||
|
deleteWorkSpace,
|
||||||
|
leaveWorkSpace,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ 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 '@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,
|
||||||
@@ -55,10 +54,8 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
|||||||
<Logger />
|
<Logger />
|
||||||
<ProviderComposer
|
<ProviderComposer
|
||||||
contexts={[
|
contexts={[
|
||||||
<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" />,
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import { WorkspaceModal } from '@/components/workspace-modal';
|
|
||||||
import { getWorkspaces } from '@/hooks/mock-data/mock';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { styled } from '@/styles';
|
|
||||||
import Button from '@/ui/button/Button';
|
|
||||||
|
|
||||||
const Page = () => {
|
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const data = getWorkspaces();
|
|
||||||
if (!data.length) {
|
|
||||||
setOpen(true);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
return (
|
|
||||||
<Workspace>
|
|
||||||
<h1>workspace</h1>
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
View Workspace List
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<WorkspaceModal
|
|
||||||
open={open}
|
|
||||||
onClose={() => {
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
></WorkspaceModal>
|
|
||||||
</Workspace>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default Page;
|
|
||||||
|
|
||||||
const Workspace = styled.div(({ theme }) => {
|
|
||||||
return {
|
|
||||||
height: '100vh',
|
|
||||||
background: theme.colors.pageBackground,
|
|
||||||
color: '#FFFFFF',
|
|
||||||
fontSize: '18px',
|
|
||||||
fontWeight: 500,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -37,11 +37,11 @@ const All = () => {
|
|||||||
const { openPage, createPage } = usePageHelper();
|
const { openPage, createPage } = usePageHelper();
|
||||||
const { currentWorkspace } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const _applyTemplate = function (pageId: string, template: Template) {
|
const _applyTemplate = function (pageId: string, template: Template) {
|
||||||
const page = currentWorkspace?.getPage(pageId);
|
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId);
|
||||||
|
|
||||||
const title = template.name;
|
const title = template.name;
|
||||||
if (page) {
|
if (page) {
|
||||||
currentWorkspace?.setPageMeta(page.id, { title });
|
currentWorkspace?.blocksuiteWorkspace?.setPageMeta(page.id, { title });
|
||||||
if (page && page.root === null) {
|
if (page && page.root === null) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const editor = document.querySelector('editor-container');
|
const editor = document.querySelector('editor-container');
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { ReactElement, useEffect, useState } from 'react';
|
||||||
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
|
import type { NextPageWithLayout } from '../..//_app';
|
||||||
|
import { styled } from '@/styles';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { Page as PageStore, Workspace } from '@blocksuite/store';
|
||||||
|
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
const Page: NextPageWithLayout = () => {
|
||||||
|
const [workspace, setWorkspace] = useState<Workspace>();
|
||||||
|
const [page, setPage] = useState<PageStore>();
|
||||||
|
const { dataCenter } = useAppState();
|
||||||
|
const router = useRouter();
|
||||||
|
useEffect(() => {
|
||||||
|
dataCenter
|
||||||
|
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||||
|
.then(data => {
|
||||||
|
if (data && data.blocksuiteWorkspace) {
|
||||||
|
setWorkspace(data.blocksuiteWorkspace);
|
||||||
|
if (
|
||||||
|
router.query.pageId &&
|
||||||
|
data.blocksuiteWorkspace.meta.pageMetas.find(
|
||||||
|
p => p.id === router.query.pageId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const page = data.blocksuiteWorkspace?.getPage(
|
||||||
|
router.query.pageId as string
|
||||||
|
);
|
||||||
|
page && setPage(page);
|
||||||
|
} else {
|
||||||
|
router.push('/404');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
router.push('/404');
|
||||||
|
});
|
||||||
|
}, [router, dataCenter]);
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
{workspace && page && (
|
||||||
|
<DynamicBlocksuite
|
||||||
|
page={page}
|
||||||
|
workspace={workspace}
|
||||||
|
setEditor={editor => {
|
||||||
|
editor.readonly = true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Page.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <div>{page}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
|
|
||||||
|
export const PageContainer = styled.div(({ theme }) => {
|
||||||
|
return {
|
||||||
|
height: 'calc(100vh)',
|
||||||
|
padding: '78px 72px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
backgroundColor: theme.colors.pageBackground,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { PageList } from '@/components/page-list';
|
||||||
|
import { ReactElement, useEffect, useState } from 'react';
|
||||||
|
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { PageContainer } from './[pageId]';
|
||||||
|
const All = () => {
|
||||||
|
const { dataCenter } = useAppState();
|
||||||
|
const router = useRouter();
|
||||||
|
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
dataCenter
|
||||||
|
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||||
|
.then(data => {
|
||||||
|
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
router.push('/404');
|
||||||
|
});
|
||||||
|
}, [router, dataCenter]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<PageList
|
||||||
|
pageList={pageList.filter(p => !p.trash)}
|
||||||
|
showFavoriteTag={false}
|
||||||
|
isPublic={true}
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
All.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return <div>{page}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default All;
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
import {
|
import {
|
||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
ReactElement,
|
ReactElement,
|
||||||
@@ -31,10 +30,10 @@ const Page: NextPageWithLayout = () => {
|
|||||||
<EditorHeader />
|
<EditorHeader />
|
||||||
<MobileModal />
|
<MobileModal />
|
||||||
|
|
||||||
{currentPage && (
|
{currentPage && currentWorkspace?.blocksuiteWorkspace && (
|
||||||
<DynamicBlocksuite
|
<DynamicBlocksuite
|
||||||
page={currentPage}
|
page={currentPage}
|
||||||
workspace={currentWorkspace}
|
workspace={currentWorkspace.blocksuiteWorkspace}
|
||||||
setEditor={setEditorHandler}
|
setEditor={setEditorHandler}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -52,9 +51,10 @@ const PageDefender = ({ children }: PropsWithChildren) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initPage = async () => {
|
const initPage = async () => {
|
||||||
const pageId = router.query.pageId as string;
|
const pageId = router.query.pageId as string;
|
||||||
const isPageExist = !!currentWorkspace!.meta.pageMetas.find(
|
const isPageExist =
|
||||||
p => p.id === pageId
|
currentWorkspace?.blocksuiteWorkspace?.meta?.pageMetas.find(
|
||||||
);
|
p => p.id === pageId
|
||||||
|
);
|
||||||
if (!isPageExist) {
|
if (!isPageExist) {
|
||||||
await createPage({ pageId });
|
await createPage({ pageId });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { PageList } from '@/components/page-list';
|
import { PageList } from '@/components/page-list';
|
||||||
import { AllPagesIcon } from '@blocksuite/icons';
|
import { AllPagesIcon } from '@blocksuite/icons';
|
||||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
|
||||||
import { PageListHeader } from '@/components/header';
|
import { PageListHeader } from '@/components/header';
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import WorkspaceLayout from '@/components/workspace-layout';
|
import WorkspaceLayout from '@/components/workspace-layout';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
const All = () => {
|
const All = () => {
|
||||||
const pageMetaList = usePageMetaList();
|
const { pageList } = useAppState();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageListHeader icon={<AllPagesIcon />}>{t('All pages')}</PageListHeader>
|
<PageListHeader icon={<AllPagesIcon />}>{t('All pages')}</PageListHeader>
|
||||||
<PageList
|
<PageList
|
||||||
pageList={pageMetaList.filter(p => !p.trash)}
|
pageList={pageList.filter(p => !p.trash)}
|
||||||
showFavoriteTag={true}
|
showFavoriteTag={true}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { PageListHeader } from '@/components/header';
|
import { PageListHeader } from '@/components/header';
|
||||||
import { PageList } from '@/components/page-list';
|
import { PageList } from '@/components/page-list';
|
||||||
import { FavouritesIcon } from '@blocksuite/icons';
|
import { FavouritesIcon } from '@blocksuite/icons';
|
||||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import WorkspaceLayout from '@/components/workspace-layout';
|
import WorkspaceLayout from '@/components/workspace-layout';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
export const Favorite = () => {
|
export const Favorite = () => {
|
||||||
const pageMetaList = usePageMetaList();
|
const { pageList } = useAppState();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageListHeader icon={<FavouritesIcon />}>
|
<PageListHeader icon={<FavouritesIcon />}>
|
||||||
{t('Favourites')}
|
{t('Favourites')}
|
||||||
</PageListHeader>
|
</PageListHeader>
|
||||||
<PageList pageList={pageMetaList.filter(p => p.favorite && !p.trash)} />
|
<PageList pageList={pageList.filter(p => p.favorite && !p.trash)} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,31 +7,32 @@ import usePageHelper from '@/hooks/use-page-helper';
|
|||||||
|
|
||||||
const WorkspaceIndex = () => {
|
const WorkspaceIndex = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { currentWorkspaceId, currentWorkspace } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const { createPage } = usePageHelper();
|
const { createPage } = usePageHelper();
|
||||||
const { workspaceLoaded } = useEnsureWorkspace();
|
const { workspaceLoaded, activeWorkspaceId } = useEnsureWorkspace();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initPage = async () => {
|
const initPage = async () => {
|
||||||
if (!workspaceLoaded) {
|
if (!workspaceLoaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const savedPageId = currentWorkspace?.meta.pageMetas[0]?.id;
|
const savedPageId =
|
||||||
|
currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas[0]?.id;
|
||||||
if (savedPageId) {
|
if (savedPageId) {
|
||||||
router.replace(`/workspace/${currentWorkspaceId}/${savedPageId}`);
|
router.replace(`/workspace/${activeWorkspaceId}/${savedPageId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageId = await createPage();
|
const pageId = await createPage();
|
||||||
router.replace(`/workspace/${currentWorkspaceId}/${pageId}`);
|
router.replace(`/workspace/${activeWorkspaceId}/${pageId}`);
|
||||||
};
|
};
|
||||||
initPage();
|
initPage();
|
||||||
}, [
|
}, [
|
||||||
currentWorkspace,
|
currentWorkspace,
|
||||||
currentWorkspaceId,
|
|
||||||
createPage,
|
createPage,
|
||||||
router,
|
router,
|
||||||
workspaceLoaded,
|
workspaceLoaded,
|
||||||
|
activeWorkspaceId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ import {
|
|||||||
} from '@/components/workspace-setting';
|
} from '@/components/workspace-setting';
|
||||||
import { useAppState } from '@/providers/app-state-provider';
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
import WorkspaceLayout from '@/components/workspace-layout';
|
import WorkspaceLayout from '@/components/workspace-layout';
|
||||||
import { WorkspaceInfo } from '@affine/datacenter';
|
import { WorkspaceUnit } from '@affine/datacenter';
|
||||||
|
|
||||||
type TabNames = 'general' | 'members' | 'publish' | 'sync' | 'export';
|
type TabNames = 'general' | 'members' | 'publish' | 'sync' | 'export';
|
||||||
|
|
||||||
const tabMap: {
|
const tabMap: {
|
||||||
name: TabNames;
|
name: TabNames;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
panelRender: (workspace: WorkspaceInfo) => ReactNode;
|
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
name: 'general',
|
name: 'general',
|
||||||
@@ -60,7 +60,7 @@ const tabMap: {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const WorkspaceSetting = () => {
|
const WorkspaceSetting = () => {
|
||||||
const { currentMetaWorkSpace } = useAppState();
|
const { currentWorkspace, isOwner } = useAppState();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
|
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
|
||||||
const handleTabChange = (tab: TabNames) => {
|
const handleTabChange = (tab: TabNames) => {
|
||||||
@@ -70,7 +70,20 @@ const WorkspaceSetting = () => {
|
|||||||
const activeTabPanelRender = tabMap.find(
|
const activeTabPanelRender = tabMap.find(
|
||||||
tab => tab.name === activeTab
|
tab => tab.name === activeTab
|
||||||
)?.panelRender;
|
)?.panelRender;
|
||||||
|
let tableArr: {
|
||||||
|
name: TabNames;
|
||||||
|
icon: ReactNode;
|
||||||
|
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||||
|
}[] = tabMap;
|
||||||
|
if (!isOwner) {
|
||||||
|
tableArr = [
|
||||||
|
{
|
||||||
|
name: 'general',
|
||||||
|
icon: <EditIcon />,
|
||||||
|
panelRender: workspace => <GeneralPage workspace={workspace} />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<StyledSettingContainer>
|
<StyledSettingContainer>
|
||||||
<StyledSettingSidebar>
|
<StyledSettingSidebar>
|
||||||
@@ -78,7 +91,7 @@ const WorkspaceSetting = () => {
|
|||||||
Workspace Settings
|
Workspace Settings
|
||||||
</StyledSettingSidebarHeader>
|
</StyledSettingSidebarHeader>
|
||||||
<StyledSettingTabContainer>
|
<StyledSettingTabContainer>
|
||||||
{tabMap.map(({ icon, name }) => {
|
{tableArr.map(({ icon, name }) => {
|
||||||
return (
|
return (
|
||||||
<WorkspaceSettingTagItem
|
<WorkspaceSettingTagItem
|
||||||
key={name}
|
key={name}
|
||||||
@@ -98,7 +111,7 @@ const WorkspaceSetting = () => {
|
|||||||
</StyledSettingSidebar>
|
</StyledSettingSidebar>
|
||||||
|
|
||||||
<StyledSettingContent>
|
<StyledSettingContent>
|
||||||
{currentMetaWorkSpace && activeTabPanelRender?.(currentMetaWorkSpace)}
|
{currentWorkspace && activeTabPanelRender?.(currentWorkspace)}
|
||||||
</StyledSettingContent>
|
</StyledSettingContent>
|
||||||
</StyledSettingContainer>
|
</StyledSettingContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { PageListHeader } from '@/components/header';
|
import { PageListHeader } from '@/components/header';
|
||||||
import { PageList } from '@/components/page-list';
|
import { PageList } from '@/components/page-list';
|
||||||
import { TrashIcon } from '@blocksuite/icons';
|
import { TrashIcon } from '@blocksuite/icons';
|
||||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import WorkspaceLayout from '@/components/workspace-layout';
|
import WorkspaceLayout from '@/components/workspace-layout';
|
||||||
import { useTranslation } from '@affine/i18n';
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
import { useAppState } from '@/providers/app-state-provider';
|
||||||
export const Trash = () => {
|
export const Trash = () => {
|
||||||
const pageMetaList = usePageMetaList();
|
const { pageList } = useAppState();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageListHeader icon={<TrashIcon />}>{t('Trash')}</PageListHeader>
|
<PageListHeader icon={<TrashIcon />}>{t('Trash')}</PageListHeader>
|
||||||
<PageList pageList={pageMetaList.filter(p => p.trash)} isTrash={true} />
|
<PageList pageList={pageList.filter(p => p.trash)} isTrash={true} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import { PageLoading } from '@/components/loading';
|
|||||||
|
|
||||||
export const WorkspaceIndex = () => {
|
export const WorkspaceIndex = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { currentWorkspaceId } = useAppState();
|
const { currentWorkspace } = useAppState();
|
||||||
const { workspaceLoaded } = useEnsureWorkspace();
|
const { workspaceLoaded } = useEnsureWorkspace();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspaceLoaded) {
|
if (workspaceLoaded) {
|
||||||
router.push(`/workspace/${currentWorkspaceId}`);
|
router.push(`/workspace/${currentWorkspace?.id}`);
|
||||||
}
|
}
|
||||||
}, [currentWorkspaceId, router, workspaceLoaded]);
|
}, [currentWorkspace, router, workspaceLoaded]);
|
||||||
|
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
PageMeta,
|
PageMeta,
|
||||||
} from './interface';
|
} from './interface';
|
||||||
import { createDefaultWorkspace } from './utils';
|
import { createDefaultWorkspace } from './utils';
|
||||||
import { WorkspaceInfo } from '@affine/datacenter';
|
import { User } from '@affine/datacenter';
|
||||||
|
|
||||||
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
|
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
|
||||||
|
|
||||||
@@ -20,63 +20,61 @@ export const AppStateProvider = ({
|
|||||||
children,
|
children,
|
||||||
}: PropsWithChildren<AppStateContextProps>) => {
|
}: PropsWithChildren<AppStateContextProps>) => {
|
||||||
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
|
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const initState = async () => {
|
||||||
const dataCenter = await getDataCenter();
|
const dataCenter = await getDataCenter();
|
||||||
|
// Ensure datacenter has at least one workspace
|
||||||
if (dataCenter.workspaces.length === 0) {
|
if (dataCenter.workspaces.length === 0) {
|
||||||
await createDefaultWorkspace(dataCenter);
|
await createDefaultWorkspace(dataCenter);
|
||||||
}
|
}
|
||||||
const currentWorkspace = await dataCenter.loadWorkspace(
|
|
||||||
dataCenter.workspaces[0].id
|
|
||||||
);
|
|
||||||
const currentMetaWorkSpace = dataCenter.workspaces.find(item => {
|
|
||||||
return item.id === currentWorkspace.room;
|
|
||||||
});
|
|
||||||
|
|
||||||
setAppState({
|
setAppState({
|
||||||
dataCenter,
|
dataCenter,
|
||||||
user: (await dataCenter.getUserInfo()) || null,
|
user: (await dataCenter.getUserInfo()) || null,
|
||||||
workspaceList: dataCenter.workspaces,
|
workspaceList: dataCenter.workspaces,
|
||||||
currentWorkspaceId: dataCenter.workspaces[0].id,
|
currentWorkspace: null,
|
||||||
currentWorkspace,
|
pageList: [],
|
||||||
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
|
|
||||||
currentPage: null,
|
currentPage: null,
|
||||||
editor: null,
|
editor: null,
|
||||||
synced: true,
|
synced: true,
|
||||||
currentMetaWorkSpace: currentMetaWorkSpace ?? null,
|
isOwner: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
init();
|
initState();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!appState?.currentWorkspace) {
|
if (!appState?.currentWorkspace?.blocksuiteWorkspace) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentWorkspace = appState.currentWorkspace;
|
const currentWorkspace = appState.currentWorkspace;
|
||||||
const dispose = currentWorkspace.meta.pagesUpdated.on(() => {
|
const dispose = currentWorkspace?.blocksuiteWorkspace?.meta.pagesUpdated.on(
|
||||||
setAppState({
|
() => {
|
||||||
...appState,
|
setAppState({
|
||||||
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
|
...appState,
|
||||||
});
|
pageList: currentWorkspace.blocksuiteWorkspace?.meta
|
||||||
}).dispose;
|
.pageMetas as PageMeta[],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
).dispose;
|
||||||
return () => {
|
return () => {
|
||||||
dispose();
|
dispose && dispose();
|
||||||
};
|
};
|
||||||
}, [appState]);
|
}, [appState]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { dataCenter } = appState;
|
const { dataCenter } = appState;
|
||||||
// FIXME: onWorkspacesChange should have dispose function
|
// FIXME: onWorkspacesChange should have dispose function
|
||||||
dataCenter?.onWorkspacesChange(() => {
|
dataCenter?.onWorkspacesChange(
|
||||||
setAppState({
|
() => {
|
||||||
...appState,
|
setAppState({
|
||||||
workspaceList: dataCenter.workspaces,
|
...appState,
|
||||||
});
|
workspaceList: dataCenter.workspaces,
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
{ immediate: false }
|
||||||
|
);
|
||||||
}, [appState]);
|
}, [appState]);
|
||||||
|
|
||||||
const loadPage = useRef<AppStateFunction['loadPage']>();
|
const loadPage = useRef<AppStateFunction['loadPage']>();
|
||||||
@@ -85,7 +83,7 @@ export const AppStateProvider = ({
|
|||||||
if (pageId === currentPage?.id) {
|
if (pageId === currentPage?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const page = currentWorkspace?.getPage(pageId) || null;
|
const page = currentWorkspace?.blocksuiteWorkspace?.getPage(pageId) || null;
|
||||||
setAppState({
|
setAppState({
|
||||||
...appState,
|
...appState,
|
||||||
currentPage: page,
|
currentPage: page,
|
||||||
@@ -94,28 +92,30 @@ export const AppStateProvider = ({
|
|||||||
|
|
||||||
const loadWorkspace = useRef<AppStateFunction['loadWorkspace']>();
|
const loadWorkspace = useRef<AppStateFunction['loadWorkspace']>();
|
||||||
loadWorkspace.current = async (workspaceId: string) => {
|
loadWorkspace.current = async (workspaceId: string) => {
|
||||||
const { dataCenter, workspaceList, currentWorkspaceId, currentWorkspace } =
|
const { dataCenter, workspaceList, currentWorkspace, user } = appState;
|
||||||
appState;
|
if (!workspaceList.find(v => v.id.toString() === workspaceId)) {
|
||||||
if (!workspaceList.find(v => v.id === workspaceId)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (workspaceId === currentWorkspaceId) {
|
if (workspaceId === currentWorkspace?.id) {
|
||||||
return currentWorkspace;
|
return currentWorkspace;
|
||||||
}
|
}
|
||||||
const workspace = await dataCenter.loadWorkspace(workspaceId);
|
const workspace = (await dataCenter.loadWorkspace(workspaceId)) ?? null;
|
||||||
const currentMetaWorkSpace = dataCenter.workspaces.find(
|
let isOwner;
|
||||||
(item: WorkspaceInfo) => {
|
if (workspace.provider === 'local') {
|
||||||
return item.id === workspace.room;
|
// isOwner is useful only in the cloud
|
||||||
}
|
isOwner = true;
|
||||||
);
|
} else {
|
||||||
|
isOwner = workspace?.owner && user?.id === workspace?.owner?.id;
|
||||||
|
}
|
||||||
|
const pageList =
|
||||||
|
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
|
||||||
setAppState({
|
setAppState({
|
||||||
...appState,
|
...appState,
|
||||||
currentWorkspace: workspace,
|
currentWorkspace: workspace,
|
||||||
currentWorkspaceId: workspaceId,
|
pageList: pageList,
|
||||||
currentMetaWorkSpace: currentMetaWorkSpace ?? null,
|
|
||||||
pageList: currentWorkspace.meta.pageMetas as PageMeta[],
|
|
||||||
currentPage: null,
|
currentPage: null,
|
||||||
editor: null,
|
editor: null,
|
||||||
|
isOwner,
|
||||||
});
|
});
|
||||||
|
|
||||||
return workspace;
|
return workspace;
|
||||||
@@ -130,6 +130,26 @@ export const AppStateProvider = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const login = async () => {
|
||||||
|
const { dataCenter } = appState;
|
||||||
|
await dataCenter.login();
|
||||||
|
const user = (await dataCenter.getUserInfo()) as User;
|
||||||
|
setAppState({
|
||||||
|
...appState,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
const { dataCenter } = appState;
|
||||||
|
await dataCenter.logout();
|
||||||
|
setAppState({
|
||||||
|
...appState,
|
||||||
|
user: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppState.Provider
|
<AppState.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -137,6 +157,8 @@ export const AppStateProvider = ({
|
|||||||
setEditor,
|
setEditor,
|
||||||
loadPage: loadPage.current,
|
loadPage: loadPage.current,
|
||||||
loadWorkspace: loadWorkspace.current,
|
loadWorkspace: loadWorkspace.current,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { DataCenter, User, WorkspaceInfo } from '@affine/datacenter';
|
import { DataCenter, User, WorkspaceUnit } from '@affine/datacenter';
|
||||||
import type { EditorContainer } from '@blocksuite/editor';
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Page as StorePage,
|
Page as StorePage,
|
||||||
Workspace as StoreWorkspace,
|
|
||||||
PageMeta as StorePageMeta,
|
PageMeta as StorePageMeta,
|
||||||
} from '@blocksuite/store';
|
} from '@blocksuite/store';
|
||||||
import { MutableRefObject } from 'react';
|
import { MutableRefObject } from 'react';
|
||||||
@@ -18,21 +17,23 @@ export interface PageMeta extends StorePageMeta {
|
|||||||
export type AppStateValue = {
|
export type AppStateValue = {
|
||||||
dataCenter: DataCenter;
|
dataCenter: DataCenter;
|
||||||
user: User | null;
|
user: User | null;
|
||||||
workspaceList: WorkspaceInfo[];
|
workspaceList: WorkspaceUnit[];
|
||||||
currentWorkspace: StoreWorkspace;
|
currentWorkspace: WorkspaceUnit | null;
|
||||||
currentMetaWorkSpace: WorkspaceInfo | null;
|
|
||||||
currentWorkspaceId: string;
|
|
||||||
pageList: PageMeta[];
|
pageList: PageMeta[];
|
||||||
currentPage: StorePage | null;
|
currentPage: StorePage | null;
|
||||||
editor?: EditorContainer | null;
|
editor?: EditorContainer | null;
|
||||||
synced: boolean;
|
synced: boolean;
|
||||||
|
isOwner?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppStateFunction = {
|
export type AppStateFunction = {
|
||||||
setEditor: MutableRefObject<(page: EditorContainer) => void>;
|
setEditor: MutableRefObject<(page: EditorContainer) => void>;
|
||||||
|
|
||||||
loadWorkspace: (workspaceId: string) => Promise<StoreWorkspace | null>;
|
loadWorkspace: (workspaceId: string) => Promise<WorkspaceUnit | null>;
|
||||||
loadPage: (pageId: string) => void;
|
loadPage: (pageId: string) => void;
|
||||||
|
|
||||||
|
login: () => Promise<User>;
|
||||||
|
logout: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppStateContext = AppStateValue & AppStateFunction;
|
export type AppStateContext = AppStateValue & AppStateFunction;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const DEFAULT_WORKSPACE_NAME = 'affine';
|
|||||||
|
|
||||||
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
||||||
return dataCenter.createWorkspace({
|
return dataCenter.createWorkspace({
|
||||||
avatar: 'test',
|
|
||||||
name: DEFAULT_WORKSPACE_NAME,
|
name: DEFAULT_WORKSPACE_NAME,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
import { createContext, useContext, useEffect, useState } from 'react';
|
|
||||||
import type { PropsWithChildren } from 'react';
|
|
||||||
import { getWorkspaces, User, Workspace } from '@/hooks/mock-data/mock';
|
|
||||||
|
|
||||||
type TemporaryContextValue = {
|
|
||||||
workspaceMetaList: Workspace[];
|
|
||||||
currentWorkspace: Workspace | null;
|
|
||||||
user: User | null;
|
|
||||||
|
|
||||||
updateWorkspaceMeta: (
|
|
||||||
workspaceId: string,
|
|
||||||
workspaceData: {
|
|
||||||
name?: string;
|
|
||||||
avatar?: string;
|
|
||||||
isPublish?: boolean;
|
|
||||||
type?: 'local' | 'cloud' | 'join';
|
|
||||||
}
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
createWorkspace: (workspaceName: string) => Workspace;
|
|
||||||
|
|
||||||
deleteWorkspace: (workspaceId: string) => void;
|
|
||||||
|
|
||||||
setWorkspacePublish: (id: string, isPublish: boolean) => void;
|
|
||||||
|
|
||||||
setActiveWorkspace: (workspaceData: Workspace) => void;
|
|
||||||
|
|
||||||
login: () => void;
|
|
||||||
|
|
||||||
signOut: () => void;
|
|
||||||
};
|
|
||||||
type TemporaryContextProps = PropsWithChildren<Record<string, unknown>>;
|
|
||||||
|
|
||||||
export const TemporaryContext = createContext<TemporaryContextValue>({
|
|
||||||
workspaceMetaList: [],
|
|
||||||
currentWorkspace: null,
|
|
||||||
user: null,
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
updateWorkspaceMeta: () => {},
|
|
||||||
|
|
||||||
createWorkspace: () => {
|
|
||||||
return {} as Workspace;
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
deleteWorkspace: () => {},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
setWorkspacePublish: () => {},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
setActiveWorkspace: () => {},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
login: () => {},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
||||||
signOut: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useTemporaryHelper = () => useContext(TemporaryContext);
|
|
||||||
|
|
||||||
export const TemporaryHelperProvider = ({
|
|
||||||
children,
|
|
||||||
}: PropsWithChildren<TemporaryContextProps>) => {
|
|
||||||
const [workspaceMetaList, setWorkspaceMetaList] = useState<Workspace[]>([]);
|
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useState<Workspace | null>(
|
|
||||||
null
|
|
||||||
);
|
|
||||||
const [user, setUser] = useState<User | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setWorkspaceMetaList(getWorkspaces());
|
|
||||||
setCurrentWorkspace(
|
|
||||||
JSON.parse(localStorage.getItem('affine-active-workspace') ?? '{}')
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TemporaryContext.Provider
|
|
||||||
value={{
|
|
||||||
workspaceMetaList,
|
|
||||||
currentWorkspace,
|
|
||||||
user,
|
|
||||||
|
|
||||||
updateWorkspaceMeta: (workspaceId, workspaceData) => {
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
const newWorkspacesMeta = workspacesMeta.map(
|
|
||||||
(workspace: Workspace) => {
|
|
||||||
if (workspace.id === workspaceId) {
|
|
||||||
const workspaceObj = Object.assign(workspace, workspaceData);
|
|
||||||
return workspaceObj;
|
|
||||||
}
|
|
||||||
return workspace;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
localStorage.setItem(
|
|
||||||
'affine-workspace',
|
|
||||||
JSON.stringify(newWorkspacesMeta)
|
|
||||||
);
|
|
||||||
setWorkspaceMetaList(newWorkspacesMeta);
|
|
||||||
if (workspaceId === currentWorkspace?.id) {
|
|
||||||
setCurrentWorkspace(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
newWorkspacesMeta.find(v => v.id === currentWorkspace.id)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createWorkspace: workspaceName => {
|
|
||||||
const workspaceData = {
|
|
||||||
name: workspaceName,
|
|
||||||
id: 'workspace-' + Date.now(),
|
|
||||||
isPublish: false,
|
|
||||||
isLocal: true,
|
|
||||||
avatar: '',
|
|
||||||
type: 'local',
|
|
||||||
} as Workspace;
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
workspacesMeta.push(workspaceData);
|
|
||||||
localStorage.setItem(
|
|
||||||
'affine-workspace',
|
|
||||||
JSON.stringify(workspacesMeta)
|
|
||||||
);
|
|
||||||
|
|
||||||
setWorkspaceMetaList([...workspacesMeta]);
|
|
||||||
return workspaceData;
|
|
||||||
},
|
|
||||||
deleteWorkspace: workspaceId => {
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
const newWorkspacesMeta = workspacesMeta.filter(() => {
|
|
||||||
return workspaceId !== workspaceId;
|
|
||||||
});
|
|
||||||
localStorage.setItem(
|
|
||||||
'affine-workspace',
|
|
||||||
JSON.stringify(newWorkspacesMeta)
|
|
||||||
);
|
|
||||||
setWorkspaceMetaList(workspacesMeta);
|
|
||||||
},
|
|
||||||
|
|
||||||
setWorkspacePublish: (id, isPublish) => {
|
|
||||||
const workspacesMeta = getWorkspaces();
|
|
||||||
const newWorkspacesMeta = workspacesMeta.map(
|
|
||||||
(workspace: Workspace) => {
|
|
||||||
if (workspace.id === id) {
|
|
||||||
workspace.isPublish = isPublish;
|
|
||||||
}
|
|
||||||
return workspace;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
localStorage.setItem(
|
|
||||||
'affine-workspace',
|
|
||||||
JSON.stringify(newWorkspacesMeta)
|
|
||||||
);
|
|
||||||
setWorkspaceMetaList(workspacesMeta);
|
|
||||||
},
|
|
||||||
setActiveWorkspace(workspaceData) {
|
|
||||||
localStorage.setItem(
|
|
||||||
'affine-active-workspace',
|
|
||||||
JSON.stringify(workspaceData)
|
|
||||||
);
|
|
||||||
setCurrentWorkspace(workspaceData);
|
|
||||||
},
|
|
||||||
|
|
||||||
login: () => {
|
|
||||||
const userInfo = {
|
|
||||||
name: 'Diamond',
|
|
||||||
id: 'ttt',
|
|
||||||
email: 'diamond.shx@gmail.com',
|
|
||||||
avatar: 'string',
|
|
||||||
};
|
|
||||||
localStorage.setItem('affine-user', JSON.stringify(userInfo));
|
|
||||||
setUser(userInfo);
|
|
||||||
},
|
|
||||||
|
|
||||||
signOut(): void {
|
|
||||||
localStorage.removeItem('affine-user');
|
|
||||||
setUser(null);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</TemporaryContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TemporaryHelperProvider;
|
|
||||||
@@ -9,7 +9,7 @@ export const StyledTable = styled.table<{ tableLayout: 'auto' | 'fixed' }>(
|
|||||||
tableLayout,
|
tableLayout,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
borderCollapse: 'separate',
|
borderCollapse: 'separate',
|
||||||
borderSpacing: '0 25px',
|
borderSpacing: '0',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -52,7 +52,6 @@ export const StyledTableHead = styled.thead(() => {
|
|||||||
|
|
||||||
export const StyledTableRow = styled.tr(({ theme }) => {
|
export const StyledTableRow = styled.tr(({ theme }) => {
|
||||||
return {
|
return {
|
||||||
marginBottom: '25px',
|
|
||||||
td: {
|
td: {
|
||||||
transition: 'background .15s',
|
transition: 'background .15s',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,9 +26,8 @@
|
|||||||
"typescript": "^4.8.4"
|
"typescript": "^4.8.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blocksuite/blocks": "0.4.0-20230110112105-ef07332",
|
"@blocksuite/blocks": "0.3.1-20230109032243-37ad3ba",
|
||||||
"@blocksuite/store": "0.4.0-20230110112105-ef07332",
|
"@blocksuite/store": "0.3.1-20230109032243-37ad3ba",
|
||||||
"@tauri-apps/api": "^1.2.0",
|
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"firebase": "^9.15.0",
|
"firebase": "^9.15.0",
|
||||||
@@ -37,8 +36,7 @@
|
|||||||
"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",
|
||||||
"y-protocols": "^1.0.5",
|
"y-protocols": "^1.0.5"
|
||||||
"yjs": "^13.5.44"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@blocksuite/blocks": "0.3.1-*",
|
"@blocksuite/blocks": "0.3.1-*",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import assert from 'assert';
|
|||||||
import { getLogger } from './logger';
|
import { getLogger } from './logger';
|
||||||
import { createBlocksuiteWorkspace } from './utils/index.js';
|
import { createBlocksuiteWorkspace } from './utils/index.js';
|
||||||
import { MessageCenter } from './message';
|
import { MessageCenter } from './message';
|
||||||
import type { WorkspaceUnit } from './workspace-unit';
|
import { WorkspaceUnit } from './workspace-unit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class DataCenter
|
* @class DataCenter
|
||||||
@@ -105,12 +105,7 @@ export class DataCenter {
|
|||||||
'There is no provider. You should add provider first.'
|
'There is no provider. You should add provider first.'
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceMeta = await this._mainProvider.createWorkspaceInfo(params);
|
const workspaceUnit = await this._mainProvider.createWorkspace(params);
|
||||||
|
|
||||||
const workspace = createBlocksuiteWorkspace(workspaceMeta.id);
|
|
||||||
|
|
||||||
await this._mainProvider.createWorkspace(workspace, workspaceMeta);
|
|
||||||
const workspaceUnit = this._workspaceUnitCollection.find(workspaceMeta.id);
|
|
||||||
return workspaceUnit;
|
return workspaceUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,8 +126,8 @@ export class DataCenter {
|
|||||||
* @param {string} workspaceId workspace id
|
* @param {string} workspaceId workspace id
|
||||||
*/
|
*/
|
||||||
private _getBlocksuiteWorkspace(workspaceId: string) {
|
private _getBlocksuiteWorkspace(workspaceId: string) {
|
||||||
const workspaceInfo = this._workspaceUnitCollection.find(workspaceId);
|
// const workspaceInfo = this._workspaceUnitCollection.find(workspaceId);
|
||||||
assert(workspaceInfo, 'Workspace not found');
|
// assert(workspaceInfo, 'Workspace not found');
|
||||||
return (
|
return (
|
||||||
// this._workspaceInstances.get(workspaceId) ||
|
// this._workspaceInstances.get(workspaceId) ||
|
||||||
createBlocksuiteWorkspace(workspaceId)
|
createBlocksuiteWorkspace(workspaceId)
|
||||||
@@ -178,12 +173,36 @@ export class DataCenter {
|
|||||||
this._workspaceInstances.set(workspaceId, workspace);
|
this._workspaceInstances.set(workspaceId, workspace);
|
||||||
await provider.warpWorkspace(workspace);
|
await provider.warpWorkspace(workspace);
|
||||||
this._workspaceUnitCollection.workspaces.forEach(workspaceUnit => {
|
this._workspaceUnitCollection.workspaces.forEach(workspaceUnit => {
|
||||||
workspaceUnit.setBlocksuiteWorkspace(null);
|
const provider = this.providerMap.get(workspaceUnit.provider);
|
||||||
|
assert(provider);
|
||||||
|
provider.closeWorkspace(workspaceUnit.id);
|
||||||
});
|
});
|
||||||
workspaceUnit.setBlocksuiteWorkspace(workspace);
|
workspaceUnit.setBlocksuiteWorkspace(workspace);
|
||||||
return workspaceUnit;
|
return workspaceUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async loadPublicWorkspace(workspaceId: string) {
|
||||||
|
// FIXME: hard code for public workspace
|
||||||
|
const provider = this.providerMap.get('affine');
|
||||||
|
assert(provider);
|
||||||
|
const blocksuiteWorkspace = this._getBlocksuiteWorkspace(workspaceId);
|
||||||
|
await provider.loadPublicWorkspace(blocksuiteWorkspace);
|
||||||
|
|
||||||
|
const workspaceUnitForPublic = new WorkspaceUnit({
|
||||||
|
id: workspaceId,
|
||||||
|
name: blocksuiteWorkspace.meta.name,
|
||||||
|
avatar: blocksuiteWorkspace.meta.avatar,
|
||||||
|
owner: undefined,
|
||||||
|
published: true,
|
||||||
|
provider: 'affine',
|
||||||
|
memberCount: 1,
|
||||||
|
syncMode: 'core',
|
||||||
|
});
|
||||||
|
|
||||||
|
workspaceUnitForPublic.setBlocksuiteWorkspace(blocksuiteWorkspace);
|
||||||
|
return workspaceUnitForPublic;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get user info by provider id
|
* get user info by provider id
|
||||||
* @param {string} providerId the provider name of workspace
|
* @param {string} providerId the provider name of workspace
|
||||||
@@ -309,55 +328,33 @@ export class DataCenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _transWorkspaceProvider(
|
public async enableProvider(
|
||||||
workspace: BlocksuiteWorkspace,
|
workspaceUnit: WorkspaceUnit,
|
||||||
providerId: string
|
providerId = 'affine'
|
||||||
) {
|
) {
|
||||||
assert(workspace.room, 'No workspace id');
|
if (workspaceUnit.provider === providerId) {
|
||||||
const workspaceInfo = this._workspaceUnitCollection.find(workspace.room);
|
|
||||||
assert(workspaceInfo, 'Workspace not found');
|
|
||||||
if (workspaceInfo.provider === providerId) {
|
|
||||||
this._logger('Workspace provider is same');
|
this._logger('Workspace provider is same');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentProvider = this.providerMap.get(workspaceInfo.provider);
|
const provider = this.providerMap.get(providerId);
|
||||||
assert(currentProvider, 'Provider not found');
|
assert(provider);
|
||||||
const newProvider = this.providerMap.get(providerId);
|
const newWorkspaceUnit = await provider.extendWorkspace(workspaceUnit);
|
||||||
assert(newProvider, `provide '${providerId}' is not registered`);
|
|
||||||
this._logger(`create ${providerId} workspace: `, workspaceInfo.name);
|
|
||||||
const newWorkspaceInfo = await newProvider.createWorkspaceInfo({
|
|
||||||
name: workspaceInfo.name,
|
|
||||||
// avatar: workspaceInfo.avatar,
|
|
||||||
});
|
|
||||||
const newWorkspace = createBlocksuiteWorkspace(newWorkspaceInfo.id);
|
|
||||||
// TODO optimize this function
|
|
||||||
await newProvider.createWorkspace(newWorkspace, {
|
|
||||||
...newWorkspaceInfo,
|
|
||||||
name: workspaceInfo.name,
|
|
||||||
avatar: workspaceInfo.avatar,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert(newWorkspace, 'Create workspace failed');
|
// Currently we only allow enable one provider, so after enable new provider,
|
||||||
this._logger(
|
// delete the old workspace from its provider.
|
||||||
`update workspace data from ${workspaceInfo.provider} to ${providerId}`
|
const oldProvider = this.providerMap.get(workspaceUnit.provider);
|
||||||
);
|
assert(oldProvider);
|
||||||
await newProvider.assign(newWorkspace, workspace);
|
await oldProvider.deleteWorkspace(workspaceUnit.id);
|
||||||
assert(newWorkspace, 'Create workspace failed');
|
|
||||||
await currentProvider.deleteWorkspace(workspace.room);
|
return newWorkspaceUnit;
|
||||||
return newWorkspace.room;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable workspace cloud
|
* Enable workspace cloud
|
||||||
* @param {string} id ID of workspace.
|
* @param {string} id ID of workspace.
|
||||||
*/
|
*/
|
||||||
public async enableWorkspaceCloud(workspace: WorkspaceUnit) {
|
public async enableWorkspaceCloud(workspaceUnit: WorkspaceUnit) {
|
||||||
assert(workspace?.id, 'No workspace to enable cloud');
|
return this.enableProvider(workspaceUnit);
|
||||||
assert(workspace.blocksuiteWorkspace);
|
|
||||||
return await this._transWorkspaceProvider(
|
|
||||||
workspace.blocksuiteWorkspace,
|
|
||||||
'affine'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -425,6 +422,18 @@ export class DataCenter {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* accept invitation
|
||||||
|
* @param {string} inviteCode
|
||||||
|
*/
|
||||||
|
async acceptInvitation(inviteCode: string, providerStr = 'affine') {
|
||||||
|
const provider = this.providerMap.get(providerStr);
|
||||||
|
if (provider) {
|
||||||
|
return await provider.acceptInvitation(inviteCode);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
onMessage(cb: (message: Message) => void) {
|
onMessage(cb: (message: Message) => void) {
|
||||||
return this._messageCenter.onMessage(cb);
|
return this._messageCenter.onMessage(cb);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ const _initializeDataCenter = () => {
|
|||||||
export const getDataCenter = _initializeDataCenter();
|
export const getDataCenter = _initializeDataCenter();
|
||||||
|
|
||||||
export type { DataCenter };
|
export type { DataCenter };
|
||||||
export type { AccessTokenMessage } from './provider/affine/apis';
|
export * from './provider/affine/apis';
|
||||||
export { WorkspaceUnit } from './workspace-unit';
|
export { WorkspaceUnit } from './workspace-unit';
|
||||||
export { getLogger } from './logger';
|
export { getLogger } from './logger';
|
||||||
export * from './message';
|
export * from './message';
|
||||||
|
export * from './types';
|
||||||
|
|||||||
@@ -6,17 +6,30 @@ import type {
|
|||||||
} from '../base';
|
} from '../base';
|
||||||
import type { User } from '../../types';
|
import type { User } from '../../types';
|
||||||
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
|
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
|
||||||
import { BlockSchema } from '@blocksuite/blocks/models';
|
|
||||||
import { storage } from './storage.js';
|
import { storage } from './storage.js';
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { WebsocketProvider } from './sync.js';
|
import { WebsocketProvider } from './sync.js';
|
||||||
// import { IndexedDBProvider } from '../local/indexeddb';
|
// import { IndexedDBProvider } from '../local/indexeddb';
|
||||||
import { getApis } from './apis/index.js';
|
import { getApis, Workspace } 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 '../../message';
|
import { MessageCode } from '../../message/index.js';
|
||||||
import { token } from './apis/token.js';
|
import { token } from './apis/token.js';
|
||||||
import { WebsocketClient } from './channel';
|
import { WebsocketClient } from './channel';
|
||||||
|
import {
|
||||||
|
loadWorkspaceUnit,
|
||||||
|
createWorkspaceUnit,
|
||||||
|
syncToCloud,
|
||||||
|
} from './utils.js';
|
||||||
|
import { WorkspaceUnit } from '../../workspace-unit.js';
|
||||||
|
import { createBlocksuiteWorkspace, applyUpdate } from '../../utils/index.js';
|
||||||
|
import type { SyncMode } from '../../workspace-unit';
|
||||||
|
|
||||||
|
type ChannelMessage = {
|
||||||
|
ws_list: Workspace[];
|
||||||
|
ws_details: Record<string, WorkspaceDetail>;
|
||||||
|
metadata: Record<string, { avatar: string; name: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
export interface AffineProviderConstructorParams
|
export interface AffineProviderConstructorParams
|
||||||
extends ProviderConstructorParams {
|
extends ProviderConstructorParams {
|
||||||
@@ -24,7 +37,7 @@ export interface AffineProviderConstructorParams
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Y: { applyUpdate, encodeStateAsUpdate },
|
Y: { encodeStateAsUpdate },
|
||||||
} = BlocksuiteWorkspace;
|
} = BlocksuiteWorkspace;
|
||||||
|
|
||||||
export class AffineProvider extends BaseProvider {
|
export class AffineProvider extends BaseProvider {
|
||||||
@@ -33,21 +46,12 @@ export class AffineProvider extends BaseProvider {
|
|||||||
private _onTokenRefresh?: Callback = undefined;
|
private _onTokenRefresh?: Callback = undefined;
|
||||||
private _wsMap: Map<string, WebsocketProvider> = new Map();
|
private _wsMap: Map<string, WebsocketProvider> = new Map();
|
||||||
private _apis: Apis;
|
private _apis: Apis;
|
||||||
private _channel: WebsocketClient;
|
private _channel?: WebsocketClient;
|
||||||
// private _idbMap: Map<string, IndexedDBProvider> = new Map();
|
// private _idbMap: Map<string, IndexedDBProvider> = new Map();
|
||||||
|
|
||||||
constructor({ apis, ...params }: AffineProviderConstructorParams) {
|
constructor({ apis, ...params }: AffineProviderConstructorParams) {
|
||||||
super(params);
|
super(params);
|
||||||
this._apis = apis || getApis();
|
this._apis = apis || getApis();
|
||||||
this._channel = new WebsocketClient(
|
|
||||||
`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${
|
|
||||||
window.location.host
|
|
||||||
}/global/sync/`,
|
|
||||||
this._logger
|
|
||||||
);
|
|
||||||
if (token.isLogin) {
|
|
||||||
this._connectChannel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async init() {
|
override async init() {
|
||||||
@@ -76,14 +80,59 @@ export class AffineProvider extends BaseProvider {
|
|||||||
} else {
|
} else {
|
||||||
storage.setItem('token', this._apis.token.refresh);
|
storage.setItem('token', this._apis.token.refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token.isLogin) {
|
||||||
|
this._connectChannel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _connectChannel() {
|
private _connectChannel() {
|
||||||
this._channel.connect();
|
if (!this._channel) {
|
||||||
|
this._channel = new WebsocketClient(
|
||||||
|
`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${
|
||||||
|
window.location.host
|
||||||
|
}/api/global/sync/`,
|
||||||
|
this._logger,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
token: this._apis.token.refresh,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._channel.on('message', (msg: ChannelMessage) => {
|
||||||
|
this._handlerAffineListMessage(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
private _handlerAffineListMessage({ ws_details, metadata }: ChannelMessage) {
|
||||||
this._channel.on('message', (message: any) => {
|
this._logger('receive server message');
|
||||||
console.log('message', message);
|
Object.entries(ws_details).forEach(async ([id, detail]) => {
|
||||||
|
const { name, avatar } = metadata[id];
|
||||||
|
assert(name);
|
||||||
|
const workspace = {
|
||||||
|
name: name,
|
||||||
|
avatar,
|
||||||
|
owner: {
|
||||||
|
name: detail.owner.name,
|
||||||
|
id: detail.owner.id,
|
||||||
|
email: detail.owner.email,
|
||||||
|
avatar: detail.owner.avatar_url,
|
||||||
|
},
|
||||||
|
published: detail.public,
|
||||||
|
memberCount: detail.member_count,
|
||||||
|
provider: 'affine',
|
||||||
|
syncMode: 'core' as SyncMode,
|
||||||
|
};
|
||||||
|
if (this._workspaces.get(id)) {
|
||||||
|
this._workspaces.update(id, workspace);
|
||||||
|
} else {
|
||||||
|
const workspaceUnit = await loadWorkspaceUnit(
|
||||||
|
{ id, ...workspace },
|
||||||
|
this._apis
|
||||||
|
);
|
||||||
|
this._workspaces.add(workspaceUnit);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,16 +153,19 @@ export class AffineProvider extends BaseProvider {
|
|||||||
return ws;
|
return ws;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _applyCloudUpdates(blocksuiteWorkspace: BlocksuiteWorkspace) {
|
private async _applyCloudUpdates(
|
||||||
const { doc, room: workspaceId } = blocksuiteWorkspace;
|
blocksuiteWorkspace: BlocksuiteWorkspace,
|
||||||
|
published = false
|
||||||
|
) {
|
||||||
|
const { room: workspaceId } = blocksuiteWorkspace;
|
||||||
assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
|
assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
|
||||||
const updates = await this._apis.downloadWorkspace(workspaceId);
|
const updates = await this._apis.downloadWorkspace(workspaceId, published);
|
||||||
if (updates && updates.byteLength) {
|
await applyUpdate(blocksuiteWorkspace, new Uint8Array(updates));
|
||||||
await new Promise(resolve => {
|
}
|
||||||
doc.once('update', resolve);
|
|
||||||
BlocksuiteWorkspace.Y.applyUpdate(doc, new Uint8Array(updates));
|
override async loadPublicWorkspace(blocksuiteWorkspace: BlocksuiteWorkspace) {
|
||||||
});
|
await this._applyCloudUpdates(blocksuiteWorkspace, true);
|
||||||
}
|
return blocksuiteWorkspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async warpWorkspace(workspace: BlocksuiteWorkspace) {
|
override async warpWorkspace(workspace: BlocksuiteWorkspace) {
|
||||||
@@ -145,78 +197,25 @@ export class AffineProvider extends BaseProvider {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const workspacesList = await this._apis.getWorkspaces();
|
const workspacesList = await this._apis.getWorkspaces();
|
||||||
const workspaces: WorkspaceMeta0[] = workspacesList.map(w => {
|
const workspaceUnits = await Promise.all(
|
||||||
return {
|
workspacesList.map(w => {
|
||||||
...w,
|
return loadWorkspaceUnit(
|
||||||
memberCount: 0,
|
{
|
||||||
name: '',
|
id: w.id,
|
||||||
provider: 'affine',
|
name: '',
|
||||||
syncMode: 'core',
|
avatar: undefined,
|
||||||
};
|
owner: undefined,
|
||||||
});
|
published: w.public,
|
||||||
const workspaceInstances = workspaces.map(({ id }) => {
|
memberCount: 1,
|
||||||
const workspace =
|
provider: 'affine',
|
||||||
this._workspacesCache.get(id) ||
|
syncMode: 'core',
|
||||||
new BlocksuiteWorkspace({
|
},
|
||||||
room: id,
|
this._apis
|
||||||
}).register(BlockSchema);
|
);
|
||||||
this._workspacesCache.set(id, workspace);
|
})
|
||||||
if (workspace) {
|
);
|
||||||
return new Promise<BlocksuiteWorkspace>(resolve => {
|
this._workspaces.add(workspaceUnits);
|
||||||
this._apis.downloadWorkspace(id).then(data => {
|
return workspaceUnits;
|
||||||
applyUpdate(workspace.doc, new Uint8Array(data));
|
|
||||||
resolve(workspace);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
(await Promise.all(workspaceInstances)).forEach((workspace, i) => {
|
|
||||||
if (workspace) {
|
|
||||||
workspaces[i] = {
|
|
||||||
...workspaces[i],
|
|
||||||
name: workspace.meta.name,
|
|
||||||
avatar: workspace.meta.avatar,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const getDetailList = workspacesList.map(w => {
|
|
||||||
const { id } = w;
|
|
||||||
return new Promise<{ id: string; detail: WorkspaceDetail | null }>(
|
|
||||||
resolve => {
|
|
||||||
this._apis.getWorkspaceDetail({ id }).then(data => {
|
|
||||||
resolve({ id, detail: data || null });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const ownerList = await Promise.all(getDetailList);
|
|
||||||
(await Promise.all(ownerList)).forEach(detail => {
|
|
||||||
if (detail) {
|
|
||||||
const { id, detail: workspaceDetail } = detail;
|
|
||||||
if (workspaceDetail) {
|
|
||||||
const { owner, member_count } = workspaceDetail;
|
|
||||||
const currentWorkspace = workspaces.find(w => w.id === id);
|
|
||||||
if (currentWorkspace) {
|
|
||||||
currentWorkspace.owner = {
|
|
||||||
id: owner.id,
|
|
||||||
name: owner.name,
|
|
||||||
avatar: owner.avatar_url,
|
|
||||||
email: owner.email,
|
|
||||||
};
|
|
||||||
currentWorkspace.memberCount = member_count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
workspaces.forEach(workspace => {
|
|
||||||
this._workspaces.add(workspace);
|
|
||||||
});
|
|
||||||
|
|
||||||
return workspaces;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async auth() {
|
override async auth() {
|
||||||
@@ -229,7 +228,7 @@ export class AffineProvider extends BaseProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const user = await this._apis.signInWithGoogle?.();
|
const user = await this._apis.signInWithGoogle?.();
|
||||||
if (!this._channel.connected) {
|
if (!this._channel?.connected) {
|
||||||
this._connectChannel();
|
this._connectChannel();
|
||||||
}
|
}
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -238,6 +237,7 @@ export class AffineProvider extends BaseProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override async getUserInfo(): Promise<User | undefined> {
|
public override async getUserInfo(): Promise<User | undefined> {
|
||||||
|
await this.init();
|
||||||
const user = this._apis.token.user;
|
const user = this._apis.token.user;
|
||||||
await this.init;
|
await this.init;
|
||||||
return user
|
return user
|
||||||
@@ -302,52 +302,29 @@ export class AffineProvider extends BaseProvider {
|
|||||||
// return workspace;
|
// return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async createWorkspaceInfo(
|
public override async createWorkspace(
|
||||||
meta: CreateWorkspaceInfoParams
|
meta: CreateWorkspaceInfoParams
|
||||||
): Promise<WorkspaceMeta0> {
|
): Promise<WorkspaceUnit | undefined> {
|
||||||
const { id } = await this._apis.createWorkspace(meta);
|
const { id } = await this._apis.createWorkspace(meta);
|
||||||
|
|
||||||
const workspaceInfo: WorkspaceMeta0 = {
|
const workspaceUnit = await createWorkspaceUnit({
|
||||||
|
id,
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
id: id,
|
avatar: undefined,
|
||||||
published: false,
|
|
||||||
avatar: '',
|
|
||||||
owner: await this.getUserInfo(),
|
owner: await this.getUserInfo(),
|
||||||
syncMode: 'core',
|
|
||||||
memberCount: 1,
|
|
||||||
provider: 'affine',
|
|
||||||
};
|
|
||||||
return workspaceInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async createWorkspace(
|
|
||||||
blocksuiteWorkspace: BlocksuiteWorkspace,
|
|
||||||
meta: WorkspaceMeta0
|
|
||||||
): Promise<BlocksuiteWorkspace | undefined> {
|
|
||||||
const workspaceId = blocksuiteWorkspace.room;
|
|
||||||
assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
|
|
||||||
this._logger('Creating affine workspace');
|
|
||||||
|
|
||||||
this._applyCloudUpdates(blocksuiteWorkspace);
|
|
||||||
this.linkLocal(blocksuiteWorkspace);
|
|
||||||
|
|
||||||
const workspaceInfo: WorkspaceMeta0 = {
|
|
||||||
name: meta.name,
|
|
||||||
id: workspaceId,
|
|
||||||
published: false,
|
published: false,
|
||||||
avatar: '',
|
|
||||||
owner: undefined,
|
|
||||||
syncMode: 'core',
|
|
||||||
memberCount: 1,
|
memberCount: 1,
|
||||||
provider: 'affine',
|
provider: 'affine',
|
||||||
};
|
syncMode: 'core',
|
||||||
|
});
|
||||||
|
|
||||||
if (!blocksuiteWorkspace.meta.avatar) {
|
await syncToCloud(
|
||||||
await setDefaultAvatar(blocksuiteWorkspace);
|
workspaceUnit.blocksuiteWorkspace!,
|
||||||
workspaceInfo.avatar = blocksuiteWorkspace.meta.avatar;
|
this._apis.token.refresh
|
||||||
}
|
);
|
||||||
this._workspaces.add(workspaceInfo);
|
this._workspaces.add(workspaceUnit);
|
||||||
return blocksuiteWorkspace;
|
|
||||||
|
return workspaceUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async publish(id: string, isPublish: boolean): Promise<void> {
|
public override async publish(id: string, isPublish: boolean): Promise<void> {
|
||||||
@@ -358,38 +335,52 @@ export class AffineProvider extends BaseProvider {
|
|||||||
workspace_id: string,
|
workspace_id: string,
|
||||||
email: string
|
email: string
|
||||||
): Promise<User | null> {
|
): Promise<User | null> {
|
||||||
const user = await this._apis.getUserByEmail({ workspace_id, email });
|
const users = await this._apis.getUserByEmail({ workspace_id, email });
|
||||||
return user
|
return users?.length
|
||||||
? {
|
? {
|
||||||
id: user.id,
|
id: users[0].id,
|
||||||
name: user.name,
|
name: users[0].name,
|
||||||
avatar: user.avatar_url,
|
avatar: users[0].avatar_url,
|
||||||
email: user.email,
|
email: users[0].email,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async assign(
|
public override async extendWorkspace(
|
||||||
to: BlocksuiteWorkspace,
|
workspaceUnit: WorkspaceUnit
|
||||||
from: BlocksuiteWorkspace
|
): Promise<WorkspaceUnit> {
|
||||||
): Promise<BlocksuiteWorkspace> {
|
const { id } = await this._apis.createWorkspace({
|
||||||
assert(to.room, 'Blocksuite Workspace without room(workspaceId).');
|
name: workspaceUnit.name,
|
||||||
const ws = this._getWebsocketProvider(to);
|
|
||||||
applyUpdate(to.doc, encodeStateAsUpdate(from.doc));
|
|
||||||
// TODO: upload blobs and make sure doc is synced
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
ws.once('synced', () => {
|
|
||||||
setTimeout(() => resolve(), 1000);
|
|
||||||
});
|
|
||||||
ws.once('lost-connection', () => reject());
|
|
||||||
ws.once('connection-error', () => reject());
|
|
||||||
});
|
});
|
||||||
return to;
|
const newWorkspaceUnit = new WorkspaceUnit({
|
||||||
|
id,
|
||||||
|
name: workspaceUnit.name,
|
||||||
|
avatar: undefined,
|
||||||
|
owner: await this.getUserInfo(),
|
||||||
|
published: false,
|
||||||
|
memberCount: 1,
|
||||||
|
provider: 'affine',
|
||||||
|
syncMode: 'core',
|
||||||
|
});
|
||||||
|
|
||||||
|
const blocksuiteWorkspace = createBlocksuiteWorkspace(id);
|
||||||
|
assert(workspaceUnit.blocksuiteWorkspace);
|
||||||
|
await applyUpdate(
|
||||||
|
blocksuiteWorkspace,
|
||||||
|
encodeStateAsUpdate(workspaceUnit.blocksuiteWorkspace.doc)
|
||||||
|
);
|
||||||
|
|
||||||
|
await syncToCloud(blocksuiteWorkspace, this._apis.token.refresh);
|
||||||
|
|
||||||
|
newWorkspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
|
||||||
|
|
||||||
|
this._workspaces.add(newWorkspaceUnit);
|
||||||
|
return newWorkspaceUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async logout(): Promise<void> {
|
public override async logout(): Promise<void> {
|
||||||
token.clear();
|
token.clear();
|
||||||
this._channel.disconnect();
|
this._channel?.disconnect();
|
||||||
this._wsMap.forEach(ws => ws.disconnect());
|
this._wsMap.forEach(ws => ws.disconnect());
|
||||||
storage.removeItem('token');
|
storage.removeItem('token');
|
||||||
}
|
}
|
||||||
@@ -397,4 +388,8 @@ export class AffineProvider extends BaseProvider {
|
|||||||
public override async getWorkspaceMembers(id: string) {
|
public override async getWorkspaceMembers(id: string) {
|
||||||
return this._apis.getWorkspaceMembers({ id });
|
return this._apis.getWorkspaceMembers({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async acceptInvitation(invitingCode: string): Promise<void> {
|
||||||
|
await this._apis.acceptInviting({ invitingCode });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export interface User {
|
|||||||
|
|
||||||
export async function getUserByEmail(
|
export async function getUserByEmail(
|
||||||
params: GetUserByEmailParams
|
params: GetUserByEmailParams
|
||||||
): Promise<User | null> {
|
): Promise<User[] | null> {
|
||||||
const searchParams = new URLSearchParams({ ...params });
|
const searchParams = new URLSearchParams({ ...params });
|
||||||
return client.get('api/user', { searchParams }).json<User | null>();
|
return client.get('api/user', { searchParams }).json<User[] | null>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,10 @@ export interface CreateWorkspaceParams {
|
|||||||
export async function createWorkspace(
|
export async function createWorkspace(
|
||||||
params: CreateWorkspaceParams
|
params: CreateWorkspaceParams
|
||||||
): Promise<{ id: string }> {
|
): Promise<{ id: string }> {
|
||||||
return client.post('api/workspace', { json: params }).json();
|
// FIXME: avatar should be optional
|
||||||
|
return client
|
||||||
|
.post('api/workspace', { json: { ...params, avatar: '123' } })
|
||||||
|
.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateWorkspaceParams {
|
export interface UpdateWorkspaceParams {
|
||||||
@@ -169,7 +172,11 @@ export async function leaveWorkspace({ id }: LeaveWorkspaceParams) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadWorkspace(
|
export async function downloadWorkspace(
|
||||||
workspaceId: string
|
workspaceId: string,
|
||||||
|
published = false
|
||||||
): Promise<ArrayBuffer> {
|
): Promise<ArrayBuffer> {
|
||||||
|
if (published) {
|
||||||
|
return bareClient.get(`api/public/doc/${workspaceId}`).arrayBuffer();
|
||||||
|
}
|
||||||
return client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer();
|
return client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as websocket from 'lib0/websocket';
|
import * as websocket from 'lib0/websocket';
|
||||||
import { Logger } from 'src/types';
|
import { Logger } from 'src/types';
|
||||||
import { token } from './apis/token';
|
import { token } from './apis/token';
|
||||||
|
import * as url from 'lib0/url';
|
||||||
|
|
||||||
const RECONNECT_INTERVAL_TIME = 5000;
|
const RECONNECT_INTERVAL_TIME = 5000;
|
||||||
const MAX_RECONNECT_TIMES = 50;
|
const MAX_RECONNECT_TIMES = 50;
|
||||||
@@ -10,11 +11,21 @@ export class WebsocketClient extends websocket.WebsocketClient {
|
|||||||
private _reconnectInterval: number | null = null;
|
private _reconnectInterval: number | null = null;
|
||||||
private _logger: Logger;
|
private _logger: Logger;
|
||||||
constructor(
|
constructor(
|
||||||
url: string,
|
serverUrl: string,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
options?: { binaryType: 'arraybuffer' | 'blob' | null }
|
options?: ConstructorParameters<typeof websocket.WebsocketClient>[1] & {
|
||||||
|
params: Record<string, string>;
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
super(url, options);
|
const params = options?.params || {};
|
||||||
|
// ensure that url is always ends with /
|
||||||
|
while (serverUrl[serverUrl.length - 1] === '/') {
|
||||||
|
serverUrl = serverUrl.slice(0, serverUrl.length - 1);
|
||||||
|
}
|
||||||
|
const encodedParams = url.encodeQueryParams(params);
|
||||||
|
const newUrl =
|
||||||
|
serverUrl + '/' + (encodedParams.length === 0 ? '' : '?' + encodedParams);
|
||||||
|
super(newUrl, options);
|
||||||
this._logger = logger;
|
this._logger = logger;
|
||||||
this._setupChannel();
|
this._setupChannel();
|
||||||
}
|
}
|
||||||
|
|||||||
90
packages/data-center/src/provider/affine/utils.ts
Normal file
90
packages/data-center/src/provider/affine/utils.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
|
||||||
|
import { WorkspaceUnit } from '../../workspace-unit.js';
|
||||||
|
import type { WorkspaceUnitCtorParams } from '../../workspace-unit';
|
||||||
|
import { createBlocksuiteWorkspace } from '../../utils/index.js';
|
||||||
|
import type { Apis } from './apis';
|
||||||
|
import { WebsocketProvider } from './sync.js';
|
||||||
|
import { setDefaultAvatar } from '../utils.js';
|
||||||
|
import { applyUpdate } from '../../utils/index.js';
|
||||||
|
|
||||||
|
export const loadWorkspaceUnit = async (
|
||||||
|
params: WorkspaceUnitCtorParams,
|
||||||
|
apis: Apis
|
||||||
|
) => {
|
||||||
|
const workspaceUnit = new WorkspaceUnit(params);
|
||||||
|
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceUnit.id);
|
||||||
|
|
||||||
|
const updates = await apis.downloadWorkspace(
|
||||||
|
workspaceUnit.id,
|
||||||
|
params.published
|
||||||
|
);
|
||||||
|
applyUpdate(blocksuiteWorkspace, new Uint8Array(updates));
|
||||||
|
|
||||||
|
const details = await apis.getWorkspaceDetail({ id: workspaceUnit.id });
|
||||||
|
const owner = details?.owner;
|
||||||
|
|
||||||
|
workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
|
||||||
|
workspaceUnit.update({
|
||||||
|
name: blocksuiteWorkspace.meta.name,
|
||||||
|
avatar: blocksuiteWorkspace.meta.avatar,
|
||||||
|
memberCount: details?.member_count || 1,
|
||||||
|
owner: owner
|
||||||
|
? {
|
||||||
|
id: owner.id,
|
||||||
|
name: owner.name,
|
||||||
|
avatar: owner.avatar_url,
|
||||||
|
email: owner.email,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
return workspaceUnit;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const syncToCloud = async (
|
||||||
|
blocksuiteWorkspace: BlocksuiteWorkspace,
|
||||||
|
refreshToken: string
|
||||||
|
) => {
|
||||||
|
const workspaceId = blocksuiteWorkspace.room;
|
||||||
|
assert(workspaceId, 'Blocksuite workspace without room(workspaceId).');
|
||||||
|
|
||||||
|
const wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${
|
||||||
|
window.location.host
|
||||||
|
}/api/sync/`;
|
||||||
|
|
||||||
|
const ws = new WebsocketProvider(
|
||||||
|
wsUrl,
|
||||||
|
workspaceId,
|
||||||
|
blocksuiteWorkspace.doc,
|
||||||
|
{
|
||||||
|
params: { token: refreshToken },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
ws.once('synced', () => {
|
||||||
|
// FIXME: we don't when send local data to cloud successfully, so hack to wait 1s.
|
||||||
|
// Server will support this by add a new api.
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
});
|
||||||
|
ws.once('lost-connection', () => reject());
|
||||||
|
ws.once('connection-error', () => reject());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => {
|
||||||
|
const workspaceUnit = new WorkspaceUnit(params);
|
||||||
|
|
||||||
|
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceUnit.id);
|
||||||
|
|
||||||
|
blocksuiteWorkspace.meta.setName(workspaceUnit.name);
|
||||||
|
if (!workspaceUnit.avatar) {
|
||||||
|
await setDefaultAvatar(blocksuiteWorkspace);
|
||||||
|
workspaceUnit.update({ avatar: blocksuiteWorkspace.meta.avatar });
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
|
||||||
|
|
||||||
|
return workspaceUnit;
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store';
|
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
|
||||||
import { MessageCenter } from '../message';
|
import { MessageCenter } from '../message';
|
||||||
import { Logger, User } from '../types';
|
import { Logger, User } from '../types';
|
||||||
import type { WorkspaceUnitCollectionScope } from '../workspace-unit-collection';
|
import type { WorkspaceUnitCollectionScope } from '../workspace-unit-collection';
|
||||||
import type { WorkspaceUnitCtorParams } from '../workspace-unit';
|
import type { WorkspaceUnitCtorParams, WorkspaceUnit } from '../workspace-unit';
|
||||||
import { Member } from './affine/apis';
|
import { Member } from './affine/apis';
|
||||||
|
|
||||||
const defaultLogger = () => {
|
const defaultLogger = () => {
|
||||||
@@ -44,12 +44,6 @@ export class BaseProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createWorkspaceInfo(
|
|
||||||
params: CreateWorkspaceInfoParams
|
|
||||||
): Promise<WorkspaceMeta0> {
|
|
||||||
throw new Error(`provider: ${this.id} createWorkspaceInfo Not implemented`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* auth provider
|
* auth provider
|
||||||
*/
|
*/
|
||||||
@@ -75,10 +69,19 @@ export class BaseProvider {
|
|||||||
return workspace;
|
return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Temporary for public workspace
|
||||||
|
* @param blocksuiteWorkspace
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async loadPublicWorkspace(blocksuiteWorkspace: BlocksuiteWorkspace) {
|
||||||
|
return blocksuiteWorkspace;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* load workspaces
|
* load workspaces
|
||||||
**/
|
**/
|
||||||
public async loadWorkspaces(): Promise<WorkspaceMeta0[]> {
|
public async loadWorkspaces(): Promise<WorkspaceUnit[]> {
|
||||||
throw new Error(`provider: ${this.id} loadWorkSpace Not implemented`);
|
throw new Error(`provider: ${this.id} loadWorkSpace Not implemented`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,13 +177,18 @@ export class BaseProvider {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* create workspace by workspace meta
|
* create workspace by workspace meta
|
||||||
* @param {WorkspaceMeta} meta
|
* @param {CreateWorkspaceInfoParams} meta
|
||||||
*/
|
*/
|
||||||
public async createWorkspace(
|
public async createWorkspace(
|
||||||
blocksuiteWorkspace: BlocksuiteWorkspace,
|
meta: CreateWorkspaceInfoParams
|
||||||
meta: WorkspaceMeta0
|
): Promise<WorkspaceUnit | undefined> {
|
||||||
): Promise<BlocksuiteWorkspace | undefined> {
|
throw new Error(`provider: ${this.id} createWorkspace not implemented`);
|
||||||
return blocksuiteWorkspace;
|
}
|
||||||
|
|
||||||
|
public async extendWorkspace(
|
||||||
|
workspaceUnit: WorkspaceUnit
|
||||||
|
): Promise<WorkspaceUnit | undefined> {
|
||||||
|
throw new Error(`provider: ${this.id} extendWorkspace not implemented`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,16 +213,6 @@ export class BaseProvider {
|
|||||||
return workspace;
|
return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* merge one workspaces to another
|
|
||||||
* @param workspace
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async assign(to: BlocksuiteWorkspace, from: BlocksuiteWorkspace) {
|
|
||||||
from;
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get workspace members
|
* get workspace members
|
||||||
* @param {string} workspaceId
|
* @param {string} workspaceId
|
||||||
@@ -224,4 +222,14 @@ export class BaseProvider {
|
|||||||
workspaceId;
|
workspaceId;
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* accept invitation
|
||||||
|
* @param {string} inviteCode
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async acceptInvitation(inviteCode: string): Promise<void> {
|
||||||
|
inviteCode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import * as idb from 'lib0/indexeddb.js';
|
import * as idb from 'lib0/indexeddb.js';
|
||||||
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
|
import { Workspace as BlocksuiteWorkspace } from '@blocksuite/store';
|
||||||
|
import { applyUpdate } from '../../../utils/index.js';
|
||||||
|
|
||||||
const { encodeStateAsUpdate } = BlocksuiteWorkspace.Y;
|
const { encodeStateAsUpdate, mergeUpdates } = BlocksuiteWorkspace.Y;
|
||||||
|
|
||||||
export const initStore = async (blocksuiteWorkspace: BlocksuiteWorkspace) => {
|
export const writeUpdatesToLocal = async (
|
||||||
|
blocksuiteWorkspace: BlocksuiteWorkspace
|
||||||
|
) => {
|
||||||
const workspaceId = blocksuiteWorkspace.room;
|
const workspaceId = blocksuiteWorkspace.room;
|
||||||
assert(workspaceId);
|
assert(workspaceId);
|
||||||
await idb.deleteDB(workspaceId);
|
await idb.deleteDB(workspaceId);
|
||||||
@@ -18,3 +21,21 @@ export const initStore = async (blocksuiteWorkspace: BlocksuiteWorkspace) => {
|
|||||||
await idb.addAutoKey(updatesStore, currState);
|
await idb.addAutoKey(updatesStore, currState);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const applyLocalUpdates = async (
|
||||||
|
blocksuiteWorkspace: BlocksuiteWorkspace
|
||||||
|
) => {
|
||||||
|
const workspaceId = blocksuiteWorkspace.room;
|
||||||
|
assert(workspaceId, 'Blocksuite workspace without room(workspaceId).');
|
||||||
|
const db = await idb.openDB(workspaceId, db =>
|
||||||
|
idb.createStores(db, [['updates', { autoIncrement: true }], ['custom']])
|
||||||
|
);
|
||||||
|
|
||||||
|
const [updatesStore] = idb.transact(db, ['updates']); // , 'readonly')
|
||||||
|
if (updatesStore) {
|
||||||
|
const updates = await idb.getAll(updatesStore);
|
||||||
|
const mergedUpdates = mergeUpdates(updates);
|
||||||
|
await applyUpdate(blocksuiteWorkspace, mergedUpdates);
|
||||||
|
}
|
||||||
|
return blocksuiteWorkspace;
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,12 +16,10 @@ test.describe.serial('local provider', () => {
|
|||||||
let workspaceId: string | undefined;
|
let workspaceId: string | undefined;
|
||||||
|
|
||||||
test('create workspace', async () => {
|
test('create workspace', async () => {
|
||||||
const workspaceInfo = await provider.createWorkspaceInfo({
|
const workspaceUnit = await provider.createWorkspace({
|
||||||
name: workspaceName,
|
name: workspaceName,
|
||||||
});
|
});
|
||||||
workspaceId = workspaceInfo.id;
|
workspaceId = workspaceUnit?.id;
|
||||||
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceId);
|
|
||||||
await provider.createWorkspace(blocksuiteWorkspace, workspaceInfo);
|
|
||||||
|
|
||||||
expect(workspaceMetaCollection.workspaces.length).toEqual(1);
|
expect(workspaceMetaCollection.workspaces.length).toEqual(1);
|
||||||
expect(workspaceMetaCollection.workspaces[0].name).toEqual(workspaceName);
|
expect(workspaceMetaCollection.workspaces[0].name).toEqual(workspaceName);
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import type {
|
|||||||
import { varStorage as storage } from 'lib0/storage';
|
import { varStorage as storage } from 'lib0/storage';
|
||||||
import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store';
|
import { Workspace as BlocksuiteWorkspace, uuidv4 } from '@blocksuite/store';
|
||||||
import { IndexedDBProvider } from './indexeddb/indexeddb.js';
|
import { IndexedDBProvider } from './indexeddb/indexeddb.js';
|
||||||
import { initStore } from './indexeddb/utils.js';
|
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { setDefaultAvatar } from '../utils.js';
|
import { loadWorkspaceUnit, createWorkspaceUnit } from './utils.js';
|
||||||
|
import type { WorkspaceUnit } from '../../workspace-unit';
|
||||||
|
|
||||||
const WORKSPACE_KEY = 'workspaces';
|
const WORKSPACE_KEY = 'workspaces';
|
||||||
|
|
||||||
@@ -22,21 +22,12 @@ export class LocalProvider extends BaseProvider {
|
|||||||
super(params);
|
super(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _storeWorkspaces(workspaces: WorkspaceMeta0[]) {
|
private _storeWorkspaces(workspaceUnits: WorkspaceUnit[]) {
|
||||||
storage.setItem(
|
storage.setItem(
|
||||||
WORKSPACE_KEY,
|
WORKSPACE_KEY,
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
workspaces.map(w => {
|
workspaceUnits.map(w => {
|
||||||
return {
|
return w.toJSON();
|
||||||
id: w.id,
|
|
||||||
name: w.name,
|
|
||||||
avatar: w.avatar,
|
|
||||||
owner: w.owner,
|
|
||||||
published: w.published,
|
|
||||||
memberCount: w.memberCount,
|
|
||||||
provider: w.provider,
|
|
||||||
syncMode: w.syncMode,
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -61,20 +52,23 @@ export class LocalProvider extends BaseProvider {
|
|||||||
return workspace;
|
return workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
override loadWorkspaces(): Promise<WorkspaceMeta0[]> {
|
override async loadWorkspaces(): Promise<WorkspaceUnit[]> {
|
||||||
const workspaceStr = storage.getItem(WORKSPACE_KEY);
|
const workspaceStr = storage.getItem(WORKSPACE_KEY);
|
||||||
let workspaces: WorkspaceMeta0[] = [];
|
|
||||||
if (workspaceStr) {
|
if (workspaceStr) {
|
||||||
try {
|
try {
|
||||||
workspaces = JSON.parse(workspaceStr) as WorkspaceMeta0[];
|
const workspaceMetas = JSON.parse(workspaceStr) as WorkspaceMeta0[];
|
||||||
workspaces.forEach(workspace => {
|
const workspaceUnits = await Promise.all(
|
||||||
this._workspaces.add(workspace);
|
workspaceMetas.map(meta => {
|
||||||
});
|
return loadWorkspaceUnit(meta);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this._workspaces.add(workspaceUnits);
|
||||||
|
return workspaceUnits;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._logger(`Failed to parse workspaces from storage`);
|
this._logger(`Failed to parse workspaces from storage`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve(workspaces);
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async deleteWorkspace(id: string): Promise<void> {
|
public override async deleteWorkspace(id: string): Promise<void> {
|
||||||
@@ -96,10 +90,10 @@ export class LocalProvider extends BaseProvider {
|
|||||||
this._storeWorkspaces(this._workspaces.list());
|
this._storeWorkspaces(this._workspaces.list());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async createWorkspaceInfo(
|
public override async createWorkspace(
|
||||||
meta: CreateWorkspaceInfoParams
|
meta: CreateWorkspaceInfoParams
|
||||||
): Promise<WorkspaceMeta0> {
|
): Promise<WorkspaceUnit | undefined> {
|
||||||
const workspaceInfo: WorkspaceMeta0 = {
|
const workspaceUnit = await createWorkspaceUnit({
|
||||||
name: meta.name,
|
name: meta.name,
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
published: false,
|
published: false,
|
||||||
@@ -108,35 +102,10 @@ export class LocalProvider extends BaseProvider {
|
|||||||
syncMode: 'core',
|
syncMode: 'core',
|
||||||
memberCount: 1,
|
memberCount: 1,
|
||||||
provider: 'local',
|
provider: 'local',
|
||||||
};
|
});
|
||||||
return Promise.resolve(workspaceInfo);
|
this._workspaces.add(workspaceUnit);
|
||||||
}
|
|
||||||
|
|
||||||
public override async createWorkspace(
|
|
||||||
blocksuiteWorkspace: BlocksuiteWorkspace,
|
|
||||||
meta: WorkspaceMeta0
|
|
||||||
): Promise<BlocksuiteWorkspace | undefined> {
|
|
||||||
const workspaceId = blocksuiteWorkspace.room;
|
|
||||||
assert(workspaceId, 'Blocksuite Workspace without room(workspaceId).');
|
|
||||||
this._logger('Creating affine workspace');
|
|
||||||
|
|
||||||
const workspaceInfo: WorkspaceMeta0 = {
|
|
||||||
...meta,
|
|
||||||
};
|
|
||||||
|
|
||||||
blocksuiteWorkspace.meta.setName(meta.name);
|
|
||||||
|
|
||||||
if (!meta.avatar) {
|
|
||||||
await setDefaultAvatar(blocksuiteWorkspace);
|
|
||||||
workspaceInfo.avatar = blocksuiteWorkspace.meta.avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
await initStore(blocksuiteWorkspace);
|
|
||||||
|
|
||||||
this._workspaces.add(workspaceInfo);
|
|
||||||
this._storeWorkspaces(this._workspaces.list());
|
this._storeWorkspaces(this._workspaces.list());
|
||||||
|
return workspaceUnit;
|
||||||
return blocksuiteWorkspace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async clear(): Promise<void> {
|
public override async clear(): Promise<void> {
|
||||||
|
|||||||
34
packages/data-center/src/provider/local/utils.ts
Normal file
34
packages/data-center/src/provider/local/utils.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { WorkspaceUnit } from '../../workspace-unit.js';
|
||||||
|
import type { WorkspaceUnitCtorParams } from '../../workspace-unit';
|
||||||
|
import { createBlocksuiteWorkspace } from '../../utils/index.js';
|
||||||
|
import { applyLocalUpdates, writeUpdatesToLocal } from './indexeddb/utils.js';
|
||||||
|
import { setDefaultAvatar } from '../utils.js';
|
||||||
|
|
||||||
|
export const loadWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => {
|
||||||
|
const workspaceUnit = new WorkspaceUnit(params);
|
||||||
|
|
||||||
|
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceUnit.id);
|
||||||
|
|
||||||
|
await applyLocalUpdates(blocksuiteWorkspace);
|
||||||
|
|
||||||
|
workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
|
||||||
|
|
||||||
|
return workspaceUnit;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createWorkspaceUnit = async (params: WorkspaceUnitCtorParams) => {
|
||||||
|
const workspaceUnit = new WorkspaceUnit(params);
|
||||||
|
|
||||||
|
const blocksuiteWorkspace = createBlocksuiteWorkspace(workspaceUnit.id);
|
||||||
|
blocksuiteWorkspace.meta.setName(workspaceUnit.name);
|
||||||
|
if (!workspaceUnit.avatar) {
|
||||||
|
await setDefaultAvatar(blocksuiteWorkspace);
|
||||||
|
workspaceUnit.update({ avatar: blocksuiteWorkspace.meta.avatar });
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeUpdatesToLocal(blocksuiteWorkspace);
|
||||||
|
|
||||||
|
workspaceUnit.setBlocksuiteWorkspace(blocksuiteWorkspace);
|
||||||
|
|
||||||
|
return workspaceUnit;
|
||||||
|
};
|
||||||
@@ -45,3 +45,25 @@ export async function getDefaultHeadImgBlob(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const applyUpdate = async (
|
||||||
|
blocksuiteWorkspace: BlocksuiteWorkspace,
|
||||||
|
updates: Uint8Array
|
||||||
|
) => {
|
||||||
|
if (updates && updates.byteLength) {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
// FIXME: if we merge two empty doc, there will no update event.
|
||||||
|
// So we set a timer to cancel update listener.
|
||||||
|
const doc = blocksuiteWorkspace.doc;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
doc.off('update', resolve);
|
||||||
|
resolve(undefined);
|
||||||
|
}, 1000);
|
||||||
|
doc.once('update', () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
setTimeout(resolve, 100);
|
||||||
|
});
|
||||||
|
BlocksuiteWorkspace.Y.applyUpdate(doc, new Uint8Array(updates));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
import { WorkspaceUnitCollection } from './workspace-unit-collection.js';
|
import { WorkspaceUnitCollection } from './workspace-unit-collection.js';
|
||||||
import type { WorkspaceUnitCollectionChangeEvent } from './workspace-unit-collection';
|
import type { WorkspaceUnitCollectionChangeEvent } from './workspace-unit-collection';
|
||||||
|
import { WorkspaceUnit } from './workspace-unit.js';
|
||||||
|
|
||||||
test.describe.serial('workspace meta collection observable', () => {
|
test.describe.serial('workspace meta collection observable', () => {
|
||||||
const workspaceUnitCollection = new WorkspaceUnitCollection();
|
const workspaceUnitCollection = new WorkspaceUnitCollection();
|
||||||
@@ -14,13 +15,15 @@ test.describe.serial('workspace meta collection observable', () => {
|
|||||||
expect(event.added?.[0]?.id).toEqual('123');
|
expect(event.added?.[0]?.id).toEqual('123');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
scope.add({
|
const workspaceUnit = new WorkspaceUnit({
|
||||||
id: '123',
|
id: '123',
|
||||||
name: 'test',
|
name: 'test',
|
||||||
|
avatar: undefined,
|
||||||
memberCount: 1,
|
memberCount: 1,
|
||||||
provider: '',
|
provider: '',
|
||||||
syncMode: 'core',
|
syncMode: 'core',
|
||||||
});
|
});
|
||||||
|
scope.add(workspaceUnit);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('list workspace', () => {
|
test('list workspace', () => {
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
import { Observable } from 'lib0/observable';
|
import { Observable } from 'lib0/observable';
|
||||||
import { WorkspaceUnit } from './workspace-unit.js';
|
|
||||||
import type {
|
import type {
|
||||||
WorkspaceUnitCtorParams,
|
WorkspaceUnit,
|
||||||
UpdateWorkspaceUnitParams,
|
UpdateWorkspaceUnitParams,
|
||||||
} from './workspace-unit';
|
} from './workspace-unit';
|
||||||
|
|
||||||
export interface WorkspaceUnitCollectionScope {
|
export interface WorkspaceUnitCollectionScope {
|
||||||
get: (workspaceId: string) => WorkspaceUnit | undefined;
|
get: (workspaceId: string) => WorkspaceUnit | undefined;
|
||||||
list: () => WorkspaceUnit[];
|
list: () => WorkspaceUnit[];
|
||||||
add: (workspace: WorkspaceUnitCtorParams) => void;
|
add: (workspace: WorkspaceUnit | WorkspaceUnit[]) => void;
|
||||||
remove: (workspaceId: string) => boolean;
|
remove: (workspaceId: string) => boolean;
|
||||||
clear: () => void;
|
clear: () => void;
|
||||||
update: (
|
update: (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
workspaceMeta: UpdateWorkspaceUnitParams
|
workspaceUnit: UpdateWorkspaceUnitParams
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,19 +58,23 @@ export class WorkspaceUnitCollection {
|
|||||||
return this._workspaceUnitMap.get(workspaceId);
|
return this._workspaceUnitMap.get(workspaceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const add = (workspace: WorkspaceUnitCtorParams) => {
|
const add = (workspaceUnit: WorkspaceUnit | WorkspaceUnit[]) => {
|
||||||
if (this._workspaceUnitMap.has(workspace.id)) {
|
const workspaceUnits = Array.isArray(workspaceUnit)
|
||||||
throw new Error(`Duplicate workspace id.`);
|
? workspaceUnit
|
||||||
}
|
: [workspaceUnit];
|
||||||
|
|
||||||
const workspaceUnit = new WorkspaceUnit(workspace);
|
workspaceUnits.forEach(workspaceUnit => {
|
||||||
this._workspaceUnitMap.set(workspace.id, workspaceUnit);
|
if (this._workspaceUnitMap.has(workspaceUnit.id)) {
|
||||||
|
// FIXME: multiple add same workspace
|
||||||
scopedWorkspaceIds.add(workspace.id);
|
return;
|
||||||
|
}
|
||||||
|
this._workspaceUnitMap.set(workspaceUnit.id, workspaceUnit);
|
||||||
|
scopedWorkspaceIds.add(workspaceUnit.id);
|
||||||
|
});
|
||||||
|
|
||||||
this._events.emit('change', [
|
this._events.emit('change', [
|
||||||
{
|
{
|
||||||
added: [workspaceUnit],
|
added: workspaceUnits,
|
||||||
} as WorkspaceUnitCollectionChangeEvent,
|
} as WorkspaceUnitCollectionChangeEvent,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
@@ -106,10 +109,7 @@ export class WorkspaceUnitCollection {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = (
|
const update = (workspaceId: string, meta: UpdateWorkspaceUnitParams) => {
|
||||||
workspaceId: string,
|
|
||||||
workspaceMeta: UpdateWorkspaceUnitParams
|
|
||||||
) => {
|
|
||||||
if (!scopedWorkspaceIds.has(workspaceId)) {
|
if (!scopedWorkspaceIds.has(workspaceId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ export class WorkspaceUnitCollection {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceUnit.update(workspaceMeta);
|
workspaceUnit.update(meta);
|
||||||
|
|
||||||
this._events.emit('change', [
|
this._events.emit('change', [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,16 @@ export class WorkspaceUnit {
|
|||||||
this.update(params);
|
this.update(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isPublish() {
|
||||||
|
console.error('Suggest changing to published');
|
||||||
|
return this.published;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isLocal() {
|
||||||
|
console.error('Suggest changing to syncMode');
|
||||||
|
return this.syncMode === 'all';
|
||||||
|
}
|
||||||
|
|
||||||
get blocksuiteWorkspace() {
|
get blocksuiteWorkspace() {
|
||||||
return this._blocksuiteWorkspace;
|
return this._blocksuiteWorkspace;
|
||||||
}
|
}
|
||||||
@@ -52,13 +62,16 @@ export class WorkspaceUnit {
|
|||||||
Object.assign(this, params);
|
Object.assign(this, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isPublish() {
|
toJSON(): Omit<WorkspaceUnitCtorParams, 'blocksuiteWorkspace'> {
|
||||||
console.error('Suggest changing to published');
|
return {
|
||||||
return this.published;
|
id: this.id,
|
||||||
}
|
name: this.name,
|
||||||
|
avatar: this.avatar,
|
||||||
get isLocal() {
|
owner: this.owner,
|
||||||
console.error('Suggest changing to syncMode');
|
published: this.published,
|
||||||
return this.syncMode === 'all';
|
memberCount: this.memberCount,
|
||||||
|
provider: this.provider,
|
||||||
|
syncMode: this.syncMode,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2414
pnpm-lock.yaml
generated
2414
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user