diff --git a/packages/app/src/components/help-island/style.ts b/packages/app/src/components/help-island/style.ts
index c31179005e..9373a53da6 100644
--- a/packages/app/src/components/help-island/style.ts
+++ b/packages/app/src/components/help-island/style.ts
@@ -12,24 +12,24 @@ export const StyledIsland = styled('div')(({ theme }) => {
zIndex: theme.zIndex.popover,
};
});
-export const StyledTransformIcon = styled.div<{ in: boolean }>(
- ({ in: isIn, theme }) => ({
- height: '32px',
- width: '32px',
- borderRadius: '50%',
- position: 'absolute',
- left: '0',
- right: '0',
- bottom: '0',
- top: '0',
- margin: 'auto',
- ...displayFlex('center', 'center'),
- opacity: isIn ? 1 : 0,
- backgroundColor: isIn
- ? theme.colors.hoverBackground
- : theme.colors.pageBackground,
- })
-);
+export const StyledTransformIcon = styled('div', {
+ shouldForwardProp: prop => prop !== 'in',
+})<{ in: boolean }>(({ in: isIn, theme }) => ({
+ height: '32px',
+ width: '32px',
+ borderRadius: '50%',
+ position: 'absolute',
+ left: '0',
+ right: '0',
+ bottom: '0',
+ top: '0',
+ margin: 'auto',
+ ...displayFlex('center', 'center'),
+ opacity: isIn ? 1 : 0,
+ backgroundColor: isIn
+ ? theme.colors.hoverBackground
+ : theme.colors.pageBackground,
+}));
export const StyledIconWrapper = styled('div')<{ isEdgelessDark: boolean }>(
({ theme, isEdgelessDark }) => {
return {
diff --git a/packages/app/src/components/workspace-setting/workspace-setting.tsx b/packages/app/src/components/workspace-setting/workspace-setting.tsx
index 1efadaf131..0c4a19f396 100644
--- a/packages/app/src/components/workspace-setting/workspace-setting.tsx
+++ b/packages/app/src/components/workspace-setting/workspace-setting.tsx
@@ -194,8 +194,9 @@ const MembersPage = ({ workspace }: { workspace: Workspace }) => {
.catch(err => {
console.log(err);
});
- }, []);
+ }, [workspace.id]);
const { confirm } = useConfirm();
+
return (
@@ -206,7 +207,7 @@ const MembersPage = ({ workspace }: { workspace: Workspace }) => {
{members.length ? (
members.map(member => {
return (
-
+
{member.user.type === 'Registered' ? (
diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx
index 9ed64239e6..656ecb6cfa 100644
--- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx
+++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx
@@ -11,20 +11,72 @@ import {
LoginItem,
} from './WorkspaceItem';
import { WorkspaceSetting } from '@/components/workspace-setting';
-import { useState } from 'react';
-import { WorkspaceType } from '@pathfinder/data-services';
+import { useCallback, useEffect, useState } from 'react';
+import { getWorkspaceDetail, WorkspaceType } from '@pathfinder/data-services';
-export const SelectorPopperContent = () => {
- const { user, workspacesMeta } = useAppState();
+type WorkspaceDetails = Record<
+ string,
+ { memberCount: number; owner: { id: string; name: string } }
+>;
+
+type SelectorPopperContentProps = {
+ isShow: boolean;
+};
+
+export const SelectorPopperContent = ({
+ isShow,
+}: SelectorPopperContentProps) => {
+ const { user, workspacesMeta, workspaces } = useAppState();
const [settingWorkspaceId, setSettingWorkspaceId] = useState(
null
);
+ const [workSpaceDetails, setWorkSpaceDetails] = useState(
+ {}
+ );
const handleClickSettingWorkspace = (workspaceId: string) => {
setSettingWorkspaceId(workspaceId);
};
const handleCloseWorkSpace = () => {
setSettingWorkspaceId(null);
};
+
+ const refreshDetails = useCallback(async () => {
+ const workspaceDetailList = await Promise.all(
+ workspacesMeta?.map(async ({ id, type }) => {
+ if (user) {
+ if (type === WorkspaceType.Private) {
+ return { id, member_count: 1, owner: user };
+ } else {
+ const data = await getWorkspaceDetail({ id });
+ return { id, ...data } || { id, member_count: 0, owner: user };
+ }
+ }
+ })
+ );
+ let workSpaceDetails: WorkspaceDetails = {};
+ workspaceDetailList.forEach(details => {
+ if (details) {
+ const { id, member_count, owner } = details;
+ if (!owner) return;
+ workSpaceDetails[id] = {
+ memberCount: member_count || 1,
+ owner: {
+ id: owner.id,
+ name: owner.name,
+ },
+ };
+ }
+ });
+ setWorkSpaceDetails(workSpaceDetails);
+ }, [user, workspacesMeta]);
+
+ useEffect(() => {
+ if (isShow) {
+ setSettingWorkspaceId(null);
+ refreshDetails();
+ }
+ }, [isShow, refreshDetails]);
+
return !user ? (
@@ -47,9 +99,13 @@ export const SelectorPopperContent = () => {
type={workspace.type}
key={workspace.id}
id={workspace.id}
- name={`workspace-${workspace.id}`}
- icon={''}
+ icon={workspaces[workspace.id]?.meta.avatar || ''}
onClick={handleClickSettingWorkspace}
+ name={
+ workspaces[workspace.id]?.meta.name ||
+ `workspace-${workspace.id}`
+ }
+ memberCount={workSpaceDetails[workspace.id]?.memberCount || 1}
/>
);
})}
diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx
index cdd86df00b..533288e21a 100644
--- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx
+++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx
@@ -1,4 +1,8 @@
-import { createWorkspace } from '@pathfinder/data-services';
+import {
+ createWorkspace,
+ getBlob,
+ uploadBlob,
+} from '@pathfinder/data-services';
import Modal from '@/ui/modal';
import Input from '@/ui/input';
import { Button } from '@/ui/button';
@@ -19,6 +23,14 @@ interface WorkspaceCreateProps {
onClose: () => void;
}
+const DefaultHeadImgColors = [
+ ['#C6F2F3', '#0C6066'],
+ ['#FFF5AB', '#896406'],
+ ['#FFCCA7', '#8F4500'],
+ ['#FFCECE', '#AF1212'],
+ ['#E3DEFF', '#511AAB'],
+];
+
export const WorkspaceCreate = ({ open, onClose }: WorkspaceCreateProps) => {
const [workspaceName, setWorkspaceId] = useState('');
const [canCreate, setCanCreate] = useState(false);
@@ -30,22 +42,34 @@ export const WorkspaceCreate = ({ open, onClose }: WorkspaceCreateProps) => {
canvas.height = 100;
canvas.width = 100;
const ctx = canvas.getContext('2d');
- if (ctx) {
- // TODO: add a image list
- ctx.fillStyle = '#FFDE03';
- ctx.fillRect(0, 0, 100, 100);
- ctx.font = "600 50px 'PingFang SC', 'Microsoft Yahei'";
- ctx.fillStyle = '#FFF';
- ctx.textAlign = 'center';
- ctx.textBaseline = 'middle';
- ctx.fillText(workspaceName[0], 50, 50);
- const blob = canvas.toBlob(blob => {}, 'image/png');
- }
+ return new Promise((resolve, reject) => {
+ if (ctx) {
+ const randomNumber = Math.floor(Math.random() * 5);
+ const randomColor = DefaultHeadImgColors[randomNumber];
+ ctx.fillStyle = randomColor[0];
+ ctx.fillRect(0, 0, 100, 100);
+ ctx.font = "600 50px 'PingFang SC', 'Microsoft Yahei'";
+ ctx.fillStyle = randomColor[1];
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(workspaceName[0], 50, 50);
+ canvas.toBlob(blob => {
+ if (blob) {
+ const blobId = uploadBlob({ blob });
+ resolve(blobId);
+ } else {
+ reject();
+ }
+ }, 'image/png');
+ } else {
+ reject();
+ }
+ });
};
- const handleCreateWorkspace = () => {
+ const handleCreateWorkspace = async () => {
setCanCreate(true);
- createDefaultHeadImg(workspaceName);
- createWorkspace({ name: workspaceName, avatar: '' })
+ const blobId = await createDefaultHeadImg(workspaceName);
+ createWorkspace({ name: workspaceName, avatar: `/api/blob/${blobId}` })
.then(data => {
// @ts-ignore
router.push(`/workspace/${data.created_at}`);
diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterSetting.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterSetting.tsx
index 218407df22..b82fc7d781 100644
--- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterSetting.tsx
+++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterSetting.tsx
@@ -1,13 +1,15 @@
import { SettingsIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { IconButton } from '@/ui/button';
+import { MouseEventHandler } from 'react';
type SettingProps = {
onClick?: () => void;
};
export const FooterSetting = ({ onClick }: SettingProps) => {
- const handleClick = () => {
+ const handleClick: MouseEventHandler = e => {
+ e.stopPropagation();
onClick && onClick();
};
return (
@@ -15,7 +17,7 @@ export const FooterSetting = ({ onClick }: SettingProps) => {
className="footer-setting"
onClick={e => {
e.stopPropagation();
- handleClick();
+ handleClick(e);
}}
>
diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterUsers.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterUsers.tsx
index 1a067faa9b..c241555877 100644
--- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterUsers.tsx
+++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterUsers.tsx
@@ -2,12 +2,16 @@ import { UsersIcon } from '@blocksuite/icons';
import { styled } from '@/styles';
import { IconButton } from '@/ui/button';
-export const FooterUsers = () => {
+type FooterUsersProps = {
+ memberCount: number;
+};
+
+export const FooterUsers = ({ memberCount = 1 }: FooterUsersProps) => {
return (
<>
- 99+
+ {memberCount > 99 ? '99+' : memberCount}
>
);
diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/WorkspaceItem.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/WorkspaceItem.tsx
index a9c0ae6de0..debe408651 100644
--- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/WorkspaceItem.tsx
+++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/WorkspaceItem.tsx
@@ -14,6 +14,7 @@ interface WorkspaceItemProps {
name: string;
icon: string;
type: WorkspaceType;
+ memberCount: number;
onClick?: (workspaceId: string) => void;
}
@@ -23,6 +24,7 @@ export const WorkspaceItem = ({
icon,
type,
onClick,
+ memberCount,
}: WorkspaceItemProps) => {
const router = useRouter();
@@ -44,7 +46,7 @@ export const WorkspaceItem = ({
{name}
diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx
index d06ed53f75..c7be2949ae 100644
--- a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx
+++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx
@@ -1,14 +1,18 @@
import { Popper } from '@/ui/popper';
import { Avatar, WorkspaceName, SelectorWrapper } from './styles';
import { SelectorPopperContent } from './SelectorPopperContent';
+import { useState } from 'react';
export const WorkspaceSelector = () => {
+ const [isShow, setIsShow] = useState(false);
+
return (
}
+ content={}
zIndex={1000}
placement="bottom-start"
trigger="click"
+ onVisibleChange={setIsShow}
>
diff --git a/packages/app/src/pages/invite/[invite_code].tsx b/packages/app/src/pages/invite/[invite_code].tsx
index eb83157b01..f7042c0e95 100644
--- a/packages/app/src/pages/invite/[invite_code].tsx
+++ b/packages/app/src/pages/invite/[invite_code].tsx
@@ -29,7 +29,7 @@ export default function DevPage() {
})
.catch(err => {});
};
- }, []);
+ }, [router.query.invite_code]);
return (
diff --git a/packages/app/src/pages/workspace/[workspaceId]/index.tsx b/packages/app/src/pages/workspace/[workspaceId]/index.tsx
index 8d476bc36a..88479715ea 100644
--- a/packages/app/src/pages/workspace/[workspaceId]/index.tsx
+++ b/packages/app/src/pages/workspace/[workspaceId]/index.tsx
@@ -1,33 +1,13 @@
-import { useEffect } from 'react';
-import { useRouter } from 'next/router';
-import { useLoadWorkspace } from '@/providers/app-state-provider/hooks';
-import { useAppState } from '@/providers/app-state-provider/context';
+import {
+ useLoadWorkspace,
+ useLoadPage,
+} from '@/providers/app-state-provider/hooks';
const Page = () => {
- const router = useRouter();
- const workspace = useLoadWorkspace();
- const { createPage, currentWorkspaceId } = useAppState();
+ useLoadWorkspace();
+ useLoadPage();
- useEffect(() => {
- if (!workspace) {
- return;
- }
-
- const savedPageId = workspace.meta.pageMetas[0]?.id;
- if (savedPageId) {
- router.push(`/workspace/${currentWorkspaceId}/${savedPageId}`);
- return;
- }
-
- createPage?.current?.()?.then(async pageId => {
- if (!pageId) {
- return;
- }
- router.push(`/workspace/${currentWorkspaceId}/${pageId}`);
- });
- }, [workspace, currentWorkspaceId, createPage, router]);
-
- return ;
+ return <>>;
};
export default Page;
diff --git a/packages/app/src/providers/app-state-provider/context.ts b/packages/app/src/providers/app-state-provider/context.ts
index e254ccd868..e5b1c57d4b 100644
--- a/packages/app/src/providers/app-state-provider/context.ts
+++ b/packages/app/src/providers/app-state-provider/context.ts
@@ -10,12 +10,12 @@ export type LoadWorkspaceHandler = (
workspaceId: string,
websocket?: boolean
) => Promise | null;
-
export type CreateEditorHandler = (page: StorePage) => EditorContainer | null;
export interface AppStateValue {
user: AccessTokenMessage | null;
workspacesMeta: Workspace[];
+ workspaces: Record;
currentWorkspaceId: string;
currentWorkspace: StoreWorkspace | null;
@@ -59,6 +59,7 @@ export const AppState = createContext({
loadWorkspace: undefined,
loadPage: undefined,
createPage: undefined,
+ workspaces: {},
});
export const useAppState = () => {
diff --git a/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx b/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx
index c2a803e529..04611c2d70 100644
--- a/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx
+++ b/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx
@@ -101,6 +101,11 @@ const DynamicBlocksuite = ({
resolve(workspace);
});
} else {
+ const updates = await downloadWorkspace({ workspaceId });
+ updates &&
+ Workspace.Y.applyUpdate(workspace.doc, new Uint8Array(updates));
+ // if after update, the space:meta is empty, then we need to get map with doc
+ workspace.doc.getMap('space:meta');
resolve(workspace);
}
});
diff --git a/packages/app/src/providers/app-state-provider/hooks.ts b/packages/app/src/providers/app-state-provider/hooks.ts
index c57dd89ab9..6abce3eaf1 100644
--- a/packages/app/src/providers/app-state-provider/hooks.ts
+++ b/packages/app/src/providers/app-state-provider/hooks.ts
@@ -17,14 +17,33 @@ export const useLoadWorkspace = () => {
export const useLoadPage = () => {
const router = useRouter();
- const { loadPage, currentPage } = useAppState();
+ const { loadPage, currentPage, createPage, currentWorkspaceId } =
+ useAppState();
const workspace = useLoadWorkspace();
const pageId = router.query.pageId as string;
useEffect(() => {
+ if (!workspace) {
+ return;
+ }
+ const page = pageId ? workspace?.getPage(pageId) : null;
+ if (!page) {
+ const savedPageId = workspace.meta.pageMetas[0]?.id;
+ if (savedPageId) {
+ router.push(`/workspace/${currentWorkspaceId}/${savedPageId}`);
+ return;
+ }
+
+ createPage?.current?.()?.then(async pageId => {
+ if (!pageId) {
+ return;
+ }
+ router.push(`/workspace/${currentWorkspaceId}/${pageId}`);
+ });
+ }
loadPage?.current?.(pageId);
- }, [workspace, pageId, loadPage]);
+ }, [workspace, pageId, loadPage, createPage, router, currentWorkspaceId]);
return currentPage?.pageId === pageId ? currentPage : null;
};
diff --git a/packages/app/src/providers/app-state-provider/provider.tsx b/packages/app/src/providers/app-state-provider/provider.tsx
index 78fab25175..a22d23476e 100644
--- a/packages/app/src/providers/app-state-provider/provider.tsx
+++ b/packages/app/src/providers/app-state-provider/provider.tsx
@@ -5,6 +5,8 @@ import {
authorizationEvent,
AccessTokenMessage,
getWorkspaces,
+ WorkspaceType,
+ getWorkspaceDetail,
} from '@pathfinder/data-services';
import { AppState } from './context';
import type {
@@ -26,6 +28,7 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
currentWorkspace: null,
currentPage: null,
editor: null,
+ workspaces: {},
});
const [loadWorkspaceHandler, _setLoadWorkspaceHandler] =
@@ -129,14 +132,21 @@ export const AppStateProvider = ({ children }: { children?: ReactNode }) => {
useEffect(() => {
const callback = async (user: AccessTokenMessage | null) => {
const workspacesMeta = user ? await getWorkspaces() : [];
- const workspaces = await Promise.all(
+ const workspacesList = await Promise.all(
workspacesMeta?.map(async ({ id }) => {
const workspace = (await loadWorkspaceHandler?.(id)) || null;
- return workspace;
+ return { id, workspace };
})
);
+
+ let workspaces: Record = {};
+
+ workspacesList.forEach(({ id, workspace }) => {
+ workspaces[id] = workspace;
+ });
+
// TODO: add meta info to workspace meta
- setState(state => ({ ...state, user: user, workspacesMeta }));
+ setState(state => ({ ...state, user: user, workspacesMeta, workspaces }));
};
authorizationEvent.onChange(callback);
diff --git a/packages/app/src/utils/print-build-info.ts b/packages/app/src/utils/print-build-info.ts
index 4b286a69ec..2bb17042b6 100644
--- a/packages/app/src/utils/print-build-info.ts
+++ b/packages/app/src/utils/print-build-info.ts
@@ -29,7 +29,7 @@ const printBuildInfo = () => {
'Environment:',
`${publicRuntimeConfig.NODE_ENV}${publicRuntimeConfig.CI ? '(ci)' : ''}`
);
- console.log('Editor Version:', publicRuntimeConfig.EDITOR_VERSION)
+ console.log('Editor Version:', publicRuntimeConfig.EDITOR_VERSION);
console.log('Version:', publicRuntimeConfig.VERSION);
console.log(
'AFFiNE is an open source project, you can view its source code on GitHub!'
diff --git a/packages/data-services/src/request/request/authorization/authorization.ts b/packages/data-services/src/request/request/authorization/authorization.ts
index ce023ac697..04d135f224 100644
--- a/packages/data-services/src/request/request/authorization/authorization.ts
+++ b/packages/data-services/src/request/request/authorization/authorization.ts
@@ -57,6 +57,7 @@ function parseAccessToken(token: string): AccessTokenMessage | null {
const message: AccessTokenMessage = JSON.parse(
b64DecodeUnicode(token.split('.')[1])
);
+ message.id = message.id.toString();
return message;
} catch (error) {
return null;
diff --git a/packages/data-services/src/request/request/authorization/types.ts b/packages/data-services/src/request/request/authorization/types.ts
index a8e70044d5..fa7069395b 100644
--- a/packages/data-services/src/request/request/authorization/types.ts
+++ b/packages/data-services/src/request/request/authorization/types.ts
@@ -2,7 +2,7 @@ export interface AccessTokenMessage {
create_at: number;
exp: number;
email: string;
- id: number;
+ id: string;
name: string;
avatar_url: string;
}
diff --git a/packages/data-services/src/sdks/user.ts b/packages/data-services/src/sdks/user.ts
index ecc9a4e5f4..8f3c699c3a 100644
--- a/packages/data-services/src/sdks/user.ts
+++ b/packages/data-services/src/sdks/user.ts
@@ -42,7 +42,7 @@ export interface GetUserByEmailParams {
}
export interface User {
- id: number;
+ id: string;
name: string;
email: string;
avatar_url: string;
diff --git a/packages/data-services/src/sdks/workspace.ts b/packages/data-services/src/sdks/workspace.ts
index 149eebb02d..23e843ea26 100644
--- a/packages/data-services/src/sdks/workspace.ts
+++ b/packages/data-services/src/sdks/workspace.ts
@@ -46,7 +46,9 @@ export async function getWorkspaceDetail(
url: `/api/workspace/${params.id}`,
method: 'GET',
});
-
+ if (data.data?.owner.id) {
+ data.data.owner.id = String(data.data?.owner.id);
+ }
return data.data;
}
@@ -199,3 +201,20 @@ export async function downloadWorkspace(
return data.data;
}
+
+export async function uploadBlob(params: { blob: Blob }): Promise {
+ const data = await request({
+ url: '/api/blob',
+ method: 'PUT',
+ data: params.blob,
+ });
+ return data.data;
+}
+
+export async function getBlob(params: { blobId: string }): Promise {
+ const data = await request({
+ url: `/api/blob/${params.blobId}`,
+ method: 'GET',
+ });
+ return data.data;
+}