mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-19 02:51:47 +08:00
feat(core): remove empty workspace (#13317)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added the ability to remove an empty workspace directly from the workspace card when you are the owner. * Workspace cards now display a "Remove" button for eligible workspaces. * **Improvements** * Workspace information now indicates if a workspace is empty, improving clarity for users. * **Bug Fixes** * Enhanced accuracy in displaying workspace status by updating how workspace profile data is handled. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
import { Button, Skeleton, Tooltip } from '@affine/component';
|
import { Button, notify, Skeleton, Tooltip } from '@affine/component';
|
||||||
import { Loading } from '@affine/component/ui/loading';
|
import { Loading } from '@affine/component/ui/loading';
|
||||||
import { useSystemOnline } from '@affine/core/components/hooks/use-system-online';
|
import { useSystemOnline } from '@affine/core/components/hooks/use-system-online';
|
||||||
import { useWorkspace } from '@affine/core/components/hooks/use-workspace';
|
import { useWorkspace } from '@affine/core/components/hooks/use-workspace';
|
||||||
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
|
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
|
||||||
import type {
|
import {
|
||||||
WorkspaceMetadata,
|
type WorkspaceMetadata,
|
||||||
WorkspaceProfileInfo,
|
type WorkspaceProfileInfo,
|
||||||
|
WorkspacesService,
|
||||||
} from '@affine/core/modules/workspace';
|
} from '@affine/core/modules/workspace';
|
||||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
@@ -21,13 +22,15 @@ import {
|
|||||||
TeamWorkspaceIcon,
|
TeamWorkspaceIcon,
|
||||||
UnsyncIcon,
|
UnsyncIcon,
|
||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import { LiveData, useLiveData } from '@toeverything/infra';
|
import { LiveData, useLiveData, useService } from '@toeverything/infra';
|
||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { useAsyncCallback } from '../../hooks/affine-async-hooks';
|
||||||
import { useCatchEventCallback } from '../../hooks/use-catch-event-hook';
|
import { useCatchEventCallback } from '../../hooks/use-catch-event-hook';
|
||||||
|
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
|
||||||
import { WorkspaceAvatar } from '../../workspace-avatar';
|
import { WorkspaceAvatar } from '../../workspace-avatar';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
export { PureWorkspaceCard } from './pure-workspace-card';
|
export { PureWorkspaceCard } from './pure-workspace-card';
|
||||||
@@ -284,6 +287,8 @@ export const WorkspaceCard = forwardRef<
|
|||||||
) => {
|
) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const information = useWorkspaceInfo(workspaceMetadata);
|
const information = useWorkspaceInfo(workspaceMetadata);
|
||||||
|
const workspacesService = useService(WorkspacesService);
|
||||||
|
const navigate = useNavigateHelper();
|
||||||
|
|
||||||
const name = information?.name ?? UNTITLED_WORKSPACE_NAME;
|
const name = information?.name ?? UNTITLED_WORKSPACE_NAME;
|
||||||
|
|
||||||
@@ -291,10 +296,24 @@ export const WorkspaceCard = forwardRef<
|
|||||||
onClickEnableCloud?.(workspaceMetadata);
|
onClickEnableCloud?.(workspaceMetadata);
|
||||||
}, [onClickEnableCloud, workspaceMetadata]);
|
}, [onClickEnableCloud, workspaceMetadata]);
|
||||||
|
|
||||||
|
const onRemoveWorkspace = useAsyncCallback(async () => {
|
||||||
|
await workspacesService
|
||||||
|
.deleteWorkspace(workspaceMetadata)
|
||||||
|
.then(() => {
|
||||||
|
notify.success({ title: t['Successfully removed workspace']() });
|
||||||
|
navigate.jumpToIndex();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notify.error({ title: t['Failed to remove workspace']() });
|
||||||
|
});
|
||||||
|
}, [workspacesService, workspaceMetadata, t, navigate]);
|
||||||
|
|
||||||
const onOpenSettings = useCatchEventCallback(() => {
|
const onOpenSettings = useCatchEventCallback(() => {
|
||||||
onClickOpenSettings?.(workspaceMetadata);
|
onClickOpenSettings?.(workspaceMetadata);
|
||||||
}, [onClickOpenSettings, workspaceMetadata]);
|
}, [onClickOpenSettings, workspaceMetadata]);
|
||||||
|
|
||||||
|
console.log(information);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@@ -337,6 +356,9 @@ export const WorkspaceCard = forwardRef<
|
|||||||
<Skeleton width={100} />
|
<Skeleton width={100} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{information?.isEmpty && information.isOwner ? (
|
||||||
|
<Button onClick={onRemoveWorkspace}>Remove</Button>
|
||||||
|
) : null}
|
||||||
<div className={styles.showOnCardHover}>
|
<div className={styles.showOnCardHover}>
|
||||||
{onClickEnableCloud && workspaceMetadata.flavour === 'local' ? (
|
{onClickEnableCloud && workspaceMetadata.flavour === 'local' ? (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -335,6 +335,10 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
|
|||||||
const localData = (await docStorage.getDoc(id))?.bin;
|
const localData = (await docStorage.getDoc(id))?.bin;
|
||||||
const cloudData = (await cloudStorage.getDoc(id))?.bin;
|
const cloudData = (await cloudStorage.getDoc(id))?.bin;
|
||||||
|
|
||||||
|
const isEmpty = isEmptyUpdate(localData) && isEmptyUpdate(cloudData);
|
||||||
|
|
||||||
|
console.log('isEmpty', isEmpty, localData, cloudData);
|
||||||
|
|
||||||
docStorage.connection.disconnect();
|
docStorage.connection.disconnect();
|
||||||
|
|
||||||
const info = await this.getWorkspaceInfo(id, signal);
|
const info = await this.getWorkspaceInfo(id, signal);
|
||||||
@@ -344,6 +348,7 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
|
|||||||
isOwner: info.workspace.role === Permission.Owner,
|
isOwner: info.workspace.role === Permission.Owner,
|
||||||
isAdmin: info.workspace.role === Permission.Admin,
|
isAdmin: info.workspace.role === Permission.Admin,
|
||||||
isTeam: info.workspace.team,
|
isTeam: info.workspace.team,
|
||||||
|
isEmpty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,8 +365,10 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
|
|||||||
isOwner: info.workspace.role === Permission.Owner,
|
isOwner: info.workspace.role === Permission.Owner,
|
||||||
isAdmin: info.workspace.role === Permission.Admin,
|
isAdmin: info.workspace.role === Permission.Admin,
|
||||||
isTeam: info.workspace.team,
|
isTeam: info.workspace.team,
|
||||||
|
isEmpty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspaceBlob(id: string, blob: string): Promise<Blob | null> {
|
async getWorkspaceBlob(id: string, blob: string): Promise<Blob | null> {
|
||||||
const storage = new this.BlobStorageType({
|
const storage = new this.BlobStorageType({
|
||||||
id: id,
|
id: id,
|
||||||
@@ -659,3 +666,13 @@ export class CloudWorkspaceFlavoursProvider
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isEmptyUpdate(binary: Uint8Array | undefined) {
|
||||||
|
if (!binary) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
binary.byteLength === 0 ||
|
||||||
|
(binary.byteLength === 2 && binary[0] === 0 && binary[1] === 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface WorkspaceProfileInfo {
|
|||||||
isOwner?: boolean;
|
isOwner?: boolean;
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean;
|
||||||
isTeam?: boolean;
|
isTeam?: boolean;
|
||||||
|
isEmpty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,6 +62,7 @@ export class WorkspaceProfile extends Entity<{ metadata: WorkspaceMetadata }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setProfile(info: WorkspaceProfileInfo) {
|
private setProfile(info: WorkspaceProfileInfo) {
|
||||||
|
console.log('setProfile', info, isEqual(this.profile$.value, info));
|
||||||
if (isEqual(this.profile$.value, info)) {
|
if (isEqual(this.profile$.value, info)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,7 @@ export class WorkspaceProfileCacheStore extends Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const info = data as WorkspaceProfileInfo;
|
const info = data as WorkspaceProfileInfo;
|
||||||
return {
|
return info;
|
||||||
avatar: info.avatar,
|
|
||||||
name: info.name,
|
|
||||||
isOwner: info.isOwner,
|
|
||||||
isAdmin: info.isAdmin,
|
|
||||||
isTeam: info.isTeam,
|
|
||||||
};
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user