From 0d23cf31e3f8ba04da2ce32ba8731e7eba648e7d Mon Sep 17 00:00:00 2001 From: MingLiang Wang Date: Thu, 22 Dec 2022 16:36:50 +0800 Subject: [PATCH] feat: add show workspace names and members --- .../SelectorPopperContent.tsx | 68 +++++++++++++++++-- .../workspace-create/workspace-create.tsx | 54 +++++++++++---- .../WorkspaceItem/FooterSetting.tsx | 6 +- .../WorkspaceItem/FooterUsers.tsx | 8 ++- .../WorkspaceItem/WorkspaceItem.tsx | 4 +- .../WorkspaceSelector/WorkspaceSelector.tsx | 6 +- .../providers/app-state-provider/context.ts | 3 +- .../app-state-provider/dynamic-blocksuite.tsx | 5 ++ .../providers/app-state-provider/provider.tsx | 16 ++++- .../request/authorization/authorization.ts | 1 + .../request/request/authorization/types.ts | 2 +- packages/data-services/src/sdks/user.ts | 2 +- packages/data-services/src/sdks/workspace.ts | 21 +++++- 13 files changed, 162 insertions(+), 34 deletions(-) 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/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/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/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; +}