mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 01:56:27 +08:00
@@ -18,6 +18,7 @@ import {
|
||||
} from '../../../components/ui/popover';
|
||||
import { useDebouncedValue } from '../../../hooks/use-debounced-value';
|
||||
import { useServerConfig } from '../../common';
|
||||
import type { WorkspaceFlagFilter } from '../schema';
|
||||
|
||||
interface DataTableToolbarProps<TData> {
|
||||
table?: Table<TData>;
|
||||
@@ -25,19 +26,21 @@ interface DataTableToolbarProps<TData> {
|
||||
onKeywordChange: (keyword: string) => void;
|
||||
selectedFeatures: FeatureType[];
|
||||
onFeaturesChange: (features: FeatureType[]) => void;
|
||||
flags: WorkspaceFlagFilter;
|
||||
onFlagsChange: (flags: WorkspaceFlagFilter) => void;
|
||||
sort: AdminWorkspaceSort | undefined;
|
||||
onSortChange: (sort: AdminWorkspaceSort | undefined) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const sortOptions: { value: AdminWorkspaceSort; label: string }[] = [
|
||||
{ value: AdminWorkspaceSort.SnapshotSize, label: 'Snapshot size' },
|
||||
{ value: AdminWorkspaceSort.CreatedAt, label: 'Created time' },
|
||||
{ value: AdminWorkspaceSort.BlobCount, label: 'Blob count' },
|
||||
{ value: AdminWorkspaceSort.BlobSize, label: 'Blob size' },
|
||||
{ value: AdminWorkspaceSort.SnapshotCount, label: 'Snapshot count' },
|
||||
{ value: AdminWorkspaceSort.SnapshotSize, label: 'Snapshot size' },
|
||||
{ value: AdminWorkspaceSort.MemberCount, label: 'Member count' },
|
||||
{ value: AdminWorkspaceSort.PublicPageCount, label: 'Public pages' },
|
||||
{ value: AdminWorkspaceSort.CreatedAt, label: 'Created time' },
|
||||
];
|
||||
|
||||
export function DataTableToolbar<TData>({
|
||||
@@ -45,6 +48,8 @@ export function DataTableToolbar<TData>({
|
||||
onKeywordChange,
|
||||
selectedFeatures,
|
||||
onFeaturesChange,
|
||||
flags,
|
||||
onFlagsChange,
|
||||
sort,
|
||||
onSortChange,
|
||||
disabled = false,
|
||||
@@ -80,6 +85,35 @@ export function DataTableToolbar<TData>({
|
||||
[sort]
|
||||
);
|
||||
|
||||
const flagOptions: { key: keyof WorkspaceFlagFilter; label: string }[] = [
|
||||
{ key: 'public', label: 'Public' },
|
||||
{ key: 'enableSharing', label: 'Enable sharing' },
|
||||
{ key: 'enableAi', label: 'Enable AI' },
|
||||
{ key: 'enableUrlPreview', label: 'Enable URL preview' },
|
||||
{ key: 'enableDocEmbedding', label: 'Enable doc embedding' },
|
||||
];
|
||||
|
||||
const flagLabel = (value: boolean | undefined) => {
|
||||
if (value === true) return 'On';
|
||||
if (value === false) return 'Off';
|
||||
return 'Any';
|
||||
};
|
||||
|
||||
const handleFlagToggle = useCallback(
|
||||
(key: keyof WorkspaceFlagFilter) => {
|
||||
const current = flags[key];
|
||||
const next =
|
||||
current === undefined ? true : current === true ? false : undefined;
|
||||
onFlagsChange({ ...flags, [key]: next });
|
||||
},
|
||||
[flags, onFlagsChange]
|
||||
);
|
||||
|
||||
const hasFlagFilter = useMemo(
|
||||
() => Object.values(flags).some(v => v !== undefined),
|
||||
[flags]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-y-2 gap-x-4 flex-wrap">
|
||||
<FeatureFilterPopover
|
||||
@@ -119,6 +153,37 @@ export function DataTableToolbar<TData>({
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Popover open={disabled ? false : undefined}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={hasFlagFilter ? 'secondary' : 'outline'}
|
||||
size="sm"
|
||||
className="h-8 px-2 lg:px-3"
|
||||
disabled={disabled}
|
||||
>
|
||||
Flags
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[260px] p-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
{flagOptions.map(option => (
|
||||
<Button
|
||||
key={option.key}
|
||||
variant="ghost"
|
||||
className="justify-between"
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
onClick={() => handleFlagToggle(option.key)}
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{flagLabel(flags[option.key])}
|
||||
</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<div className="flex">
|
||||
<Input
|
||||
placeholder="Search Workspace / Owner"
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ColumnDef, PaginationState } from '@tanstack/react-table';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
import { SharedDataTable } from '../../../components/shared/data-table';
|
||||
import type { WorkspaceFlagFilter } from '../schema';
|
||||
import { DataTableToolbar } from './data-table-toolbar';
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
@@ -14,6 +15,8 @@ interface DataTableProps<TData, TValue> {
|
||||
onKeywordChange: (value: string) => void;
|
||||
selectedFeatures: FeatureType[];
|
||||
onFeaturesChange: (features: FeatureType[]) => void;
|
||||
flags: WorkspaceFlagFilter;
|
||||
onFlagsChange: Dispatch<SetStateAction<WorkspaceFlagFilter>>;
|
||||
sort: AdminWorkspaceSort | undefined;
|
||||
onSortChange: (sort: AdminWorkspaceSort | undefined) => void;
|
||||
loading?: boolean;
|
||||
@@ -34,6 +37,8 @@ export function DataTable<TData extends { id: string }, TValue>({
|
||||
onKeywordChange,
|
||||
selectedFeatures,
|
||||
onFeaturesChange,
|
||||
flags,
|
||||
onFlagsChange,
|
||||
sort,
|
||||
onSortChange,
|
||||
onPaginationChange,
|
||||
@@ -46,7 +51,7 @@ export function DataTable<TData extends { id: string }, TValue>({
|
||||
totalCount={workspacesCount}
|
||||
pagination={pagination}
|
||||
onPaginationChange={onPaginationChange}
|
||||
resetFiltersDeps={[keyword, selectedFeatures, sort]}
|
||||
resetFiltersDeps={[keyword, selectedFeatures, sort, flags]}
|
||||
renderToolbar={table => (
|
||||
<DataTableToolbar
|
||||
table={table}
|
||||
@@ -54,6 +59,8 @@ export function DataTable<TData extends { id: string }, TValue>({
|
||||
onKeywordChange={onKeywordChange}
|
||||
selectedFeatures={selectedFeatures}
|
||||
onFeaturesChange={onFeaturesChange}
|
||||
flags={flags}
|
||||
onFlagsChange={onFlagsChange}
|
||||
sort={sort}
|
||||
onSortChange={onSortChange}
|
||||
disabled={loading}
|
||||
|
||||
@@ -86,6 +86,7 @@ function WorkspacePanelContent({
|
||||
flags: {
|
||||
public: workspace.public,
|
||||
enableAi: workspace.enableAi,
|
||||
enableSharing: workspace.enableSharing,
|
||||
enableUrlPreview: workspace.enableUrlPreview,
|
||||
enableDocEmbedding: workspace.enableDocEmbedding,
|
||||
name: workspace.name ?? '',
|
||||
@@ -110,6 +111,7 @@ function WorkspacePanelContent({
|
||||
return (
|
||||
flags.public !== baseline.flags.public ||
|
||||
flags.enableAi !== baseline.flags.enableAi ||
|
||||
flags.enableSharing !== baseline.flags.enableSharing ||
|
||||
flags.enableUrlPreview !== baseline.flags.enableUrlPreview ||
|
||||
flags.enableDocEmbedding !== baseline.flags.enableDocEmbedding ||
|
||||
flags.name !== baseline.flags.name ||
|
||||
@@ -134,6 +136,7 @@ function WorkspacePanelContent({
|
||||
id: workspace.id,
|
||||
public: flags.public,
|
||||
enableAi: flags.enableAi,
|
||||
enableSharing: flags.enableSharing,
|
||||
enableUrlPreview: flags.enableUrlPreview,
|
||||
enableDocEmbedding: flags.enableDocEmbedding,
|
||||
name: flags.name || null,
|
||||
@@ -231,6 +234,15 @@ function WorkspacePanelContent({
|
||||
}
|
||||
/>
|
||||
<Separator />
|
||||
<FlagItem
|
||||
label="Allow Workspace Sharing"
|
||||
description="Allow pages in this workspace to be shared publicly"
|
||||
checked={flags.enableSharing}
|
||||
onCheckedChange={value =>
|
||||
setFlags(prev => ({ ...prev, enableSharing: value }))
|
||||
}
|
||||
/>
|
||||
<Separator />
|
||||
<FlagItem
|
||||
label="Enable Doc Embedding"
|
||||
description="Allow document embedding for search"
|
||||
|
||||
@@ -4,11 +4,13 @@ import { useState } from 'react';
|
||||
import { Header } from '../header';
|
||||
import { useColumns } from './components/columns';
|
||||
import { DataTable } from './components/data-table';
|
||||
import type { WorkspaceFlagFilter } from './schema';
|
||||
import { useWorkspaceList } from './use-workspace-list';
|
||||
|
||||
export function WorkspacePage() {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [featureFilters, setFeatureFilters] = useState<FeatureType[]>([]);
|
||||
const [flagFilters, setFlagFilters] = useState<WorkspaceFlagFilter>({});
|
||||
const [sort, setSort] = useState<AdminWorkspaceSort | undefined>(
|
||||
AdminWorkspaceSort.CreatedAt
|
||||
);
|
||||
@@ -18,6 +20,7 @@ export function WorkspacePage() {
|
||||
keyword,
|
||||
features: featureFilters,
|
||||
orderBy: sort,
|
||||
flags: flagFilters,
|
||||
});
|
||||
|
||||
const columns = useColumns();
|
||||
@@ -36,6 +39,8 @@ export function WorkspacePage() {
|
||||
onKeywordChange={setKeyword}
|
||||
selectedFeatures={featureFilters}
|
||||
onFeaturesChange={setFeatureFilters}
|
||||
flags={flagFilters}
|
||||
onFlagsChange={setFlagFilters}
|
||||
sort={sort}
|
||||
onSortChange={setSort}
|
||||
loading={loading}
|
||||
|
||||
@@ -16,3 +16,11 @@ export type WorkspaceUpdateInput =
|
||||
AdminUpdateWorkspaceMutation['adminUpdateWorkspace'];
|
||||
|
||||
export type WorkspaceFeatureFilter = FeatureType[];
|
||||
|
||||
export type WorkspaceFlagFilter = {
|
||||
public?: boolean;
|
||||
enableAi?: boolean;
|
||||
enableSharing?: boolean;
|
||||
enableUrlPreview?: boolean;
|
||||
enableDocEmbedding?: boolean;
|
||||
};
|
||||
|
||||
@@ -7,10 +7,13 @@ import {
|
||||
} from '@affine/graphql';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { WorkspaceFlagFilter } from './schema';
|
||||
|
||||
export const useWorkspaceList = (filter?: {
|
||||
keyword?: string;
|
||||
features?: FeatureType[];
|
||||
orderBy?: AdminWorkspaceSort;
|
||||
flags?: WorkspaceFlagFilter;
|
||||
}) => {
|
||||
const [pagination, setPagination] = useState({
|
||||
pageIndex: 0,
|
||||
@@ -21,8 +24,10 @@ export const useWorkspaceList = (filter?: {
|
||||
() =>
|
||||
`${filter?.keyword ?? ''}-${[...(filter?.features ?? [])]
|
||||
.sort()
|
||||
.join(',')}-${filter?.orderBy ?? ''}`,
|
||||
[filter?.features, filter?.keyword, filter?.orderBy]
|
||||
.join(',')}-${filter?.orderBy ?? ''}-${JSON.stringify(
|
||||
filter?.flags ?? {}
|
||||
)}`,
|
||||
[filter?.features, filter?.flags, filter?.keyword, filter?.orderBy]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -40,10 +45,20 @@ export const useWorkspaceList = (filter?: {
|
||||
? filter.features
|
||||
: undefined,
|
||||
orderBy: filter?.orderBy,
|
||||
public: filter?.flags?.public,
|
||||
enableAi: filter?.flags?.enableAi,
|
||||
enableSharing: filter?.flags?.enableSharing,
|
||||
enableUrlPreview: filter?.flags?.enableUrlPreview,
|
||||
enableDocEmbedding: filter?.flags?.enableDocEmbedding,
|
||||
},
|
||||
}),
|
||||
[
|
||||
filter?.features,
|
||||
filter?.flags?.enableAi,
|
||||
filter?.flags?.enableDocEmbedding,
|
||||
filter?.flags?.enableSharing,
|
||||
filter?.flags?.enableUrlPreview,
|
||||
filter?.flags?.public,
|
||||
filter?.keyword,
|
||||
filter?.orderBy,
|
||||
pagination.pageIndex,
|
||||
|
||||
Reference in New Issue
Block a user