mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 18:16:15 +08:00
feat: add show workspace names and members
This commit is contained in:
+62
-6
@@ -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<string | null>(
|
||||
null
|
||||
);
|
||||
const [workSpaceDetails, setWorkSpaceDetails] = useState<WorkspaceDetails>(
|
||||
{}
|
||||
);
|
||||
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 ? (
|
||||
<SelectorPopperContainer placement="bottom-start">
|
||||
<LoginItem />
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
+39
-15
@@ -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<string>('');
|
||||
const [canCreate, setCanCreate] = useState<boolean>(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<string>((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}`);
|
||||
|
||||
+4
-2
@@ -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<HTMLButtonElement> = 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);
|
||||
}}
|
||||
>
|
||||
<SettingsIcon />
|
||||
|
||||
+6
-2
@@ -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 (
|
||||
<Wrapper className="footer-users">
|
||||
<>
|
||||
<UsersIcon />
|
||||
<Tip>99+</Tip>
|
||||
<Tip>{memberCount > 99 ? '99+' : memberCount}</Tip>
|
||||
</>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
+3
-1
@@ -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 title={name}>{name}</Name>
|
||||
</WorkspaceItemContent>
|
||||
<Footer>
|
||||
<FooterUsers />
|
||||
<FooterUsers memberCount={memberCount} />
|
||||
<FooterSetting onClick={handleClickSetting} />
|
||||
</Footer>
|
||||
</StyledWrapper>
|
||||
|
||||
+5
-1
@@ -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 (
|
||||
<Popper
|
||||
content={<SelectorPopperContent />}
|
||||
content={<SelectorPopperContent isShow={isShow} />}
|
||||
zIndex={1000}
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
onVisibleChange={setIsShow}
|
||||
>
|
||||
<SelectorWrapper>
|
||||
<Avatar alt="Affine" />
|
||||
|
||||
Reference in New Issue
Block a user