feat: add editor record (#7938)

fix CLOUD-58, CLOUD-61, CLOUD-62, PD-1607, PD-1608
This commit is contained in:
darkskygit
2024-09-02 09:37:39 +00:00
parent d9cedf89e1
commit d93d39e29d
33 changed files with 622 additions and 55 deletions
@@ -89,6 +89,7 @@ export const iconNames = [
'edgeless',
'journal',
'payment',
'createdEdited',
] as const satisfies fromLibIconName<LibIconComponentName>[];
export type PagePropertyIcon = (typeof iconNames)[number];
@@ -109,6 +110,10 @@ export const getDefaultIconName = (
return 'checkBoxCheckLinear';
case 'number':
return 'number';
case 'createdBy':
return 'createdEdited';
case 'updatedBy':
return 'createdEdited';
default:
return 'text';
}
@@ -35,9 +35,16 @@ export const newPropertyTypes: PagePropertyType[] = [
PagePropertyType.Number,
PagePropertyType.Checkbox,
PagePropertyType.Date,
PagePropertyType.CreatedBy,
PagePropertyType.UpdatedBy,
// TODO(@Peng): add more
];
export const readonlyPropertyTypes: PagePropertyType[] = [
PagePropertyType.CreatedBy,
PagePropertyType.UpdatedBy,
];
export class PagePropertiesMetaManager {
constructor(private readonly adapter: WorkspacePropertiesAdapter) {}
@@ -95,6 +102,7 @@ export class PagePropertiesMetaManager {
type,
order: newOrder,
icon: icon ?? getDefaultIconName(type),
readonly: readonlyPropertyTypes.includes(type) || undefined,
} as const;
this.customPropertiesSchema[id] = property;
return property;
@@ -1,14 +1,28 @@
import { Checkbox, DatePicker, Menu } from '@affine/component';
import { Avatar, Checkbox, DatePicker, Menu } from '@affine/component';
import { CloudDocMetaService } from '@affine/core/modules/cloud/services/cloud-doc-meta';
import type {
PageInfoCustomProperty,
PageInfoCustomPropertyMeta,
PagePropertyType,
} from '@affine/core/modules/properties/services/schema';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { i18nTime, useI18n } from '@affine/i18n';
import { DocService, useService } from '@toeverything/infra';
import {
DocService,
useLiveData,
useService,
WorkspaceService,
} from '@toeverything/infra';
import { noop } from 'lodash-es';
import type { ChangeEventHandler } from 'react';
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { managerContext } from './common';
import * as styles from './styles.css';
@@ -190,6 +204,102 @@ export const TagsValue = () => {
);
};
const CloudUserAvatar = (props: { type: 'CreatedBy' | 'UpdatedBy' }) => {
const cloudDocMetaService = useService(CloudDocMetaService);
const cloudDocMeta = useLiveData(cloudDocMetaService.cloudDocMeta.meta$);
const isRevalidating = useLiveData(
cloudDocMetaService.cloudDocMeta.isRevalidating$
);
const error = useLiveData(cloudDocMetaService.cloudDocMeta.error$);
useEffect(() => {
cloudDocMetaService.cloudDocMeta.revalidate();
}, [cloudDocMetaService]);
const user = useMemo(() => {
if (!cloudDocMeta) return null;
if (props.type === 'CreatedBy' && cloudDocMeta.createdBy) {
return {
name: cloudDocMeta.createdBy.name,
avatarUrl: cloudDocMeta.createdBy.avatarUrl,
};
} else if (props.type === 'UpdatedBy' && cloudDocMeta.updatedBy) {
return {
name: cloudDocMeta.updatedBy.name,
avatarUrl: cloudDocMeta.updatedBy.avatarUrl,
};
}
return null;
}, [cloudDocMeta, props.type]);
const t = useI18n();
if (!cloudDocMeta) {
if (isRevalidating) {
// TODO: loading ui
return null;
}
if (error) {
// error ui
return;
}
return null;
}
if (user) {
return (
<>
<Avatar url={user.avatarUrl || ''} name={user.name} size={20} />
<span>{user.name}</span>
</>
);
}
return (
<>
<Avatar name="?" size={20} />
<span>
{t['com.affine.page-properties.property-user-avatar-no-record']()}
</span>
</>
);
};
export const LocalUserValue = () => {
const t = useI18n();
return <span>{t['com.affine.page-properties.local-user']()}</span>;
};
export const CreatedUserValue = () => {
const workspaceService = useService(WorkspaceService);
const isCloud =
workspaceService.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
if (!isCloud) {
return <LocalUserValue />;
}
return (
<div className={styles.propertyRowValueUserCell}>
<CloudUserAvatar type="CreatedBy" />
</div>
);
};
export const UpdatedUserValue = () => {
const workspaceService = useService(WorkspaceService);
const isCloud =
workspaceService.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
if (!isCloud) {
return <LocalUserValue />;
}
return (
<div className={styles.propertyRowValueUserCell}>
<CloudUserAvatar type="UpdatedBy" />
</div>
);
};
export const propertyValueRenderers: Record<
PagePropertyType,
typeof DateValue
@@ -198,6 +308,8 @@ export const propertyValueRenderers: Record<
checkbox: CheckboxValue,
text: TextValue,
number: NumberValue,
createdBy: CreatedUserValue,
updatedBy: UpdatedUserValue,
// TODO(@Peng): fix following
tags: TagsValue,
progress: TextValue,
@@ -361,6 +361,16 @@ export const propertyRowValueTextCell = style([
},
]);
export const propertyRowValueUserCell = style([
propertyRowValueCell,
{
border: 'none',
overflow: 'hidden',
columnGap: '0.5rem',
alignItems: 'center',
},
]);
export const propertyRowValueTextarea = style([
propertyRowValueCell,
{
@@ -279,29 +279,51 @@ const CustomPropertyRowsList = ({
return <CustomPropertyRows properties={filtered} statistics={statistics} />;
} else {
const required = properties.filter(property => property.required);
const optional = properties.filter(property => !property.required);
const partition = Object.groupBy(properties, p =>
p.required ? 'required' : p.readonly ? 'readonly' : 'optional'
);
return (
<>
{required.length > 0 ? (
{partition.required && partition.required.length > 0 ? (
<>
<div className={styles.subListHeader}>
{t[
'com.affine.settings.workspace.properties.required-properties'
]()}
</div>
<CustomPropertyRows properties={required} statistics={statistics} />
<CustomPropertyRows
properties={partition.required}
statistics={statistics}
/>
</>
) : null}
{optional.length > 0 ? (
{partition.optional && partition.optional.length > 0 ? (
<>
<div className={styles.subListHeader}>
{t[
'com.affine.settings.workspace.properties.general-properties'
]()}
</div>
<CustomPropertyRows properties={optional} statistics={statistics} />
<CustomPropertyRows
properties={partition.optional}
statistics={statistics}
/>
</>
) : null}
{partition.readonly && partition.readonly.length > 0 ? (
<>
<div className={styles.subListHeader}>
{t[
'com.affine.settings.workspace.properties.readonly-properties'
]()}
</div>
<CustomPropertyRows
properties={partition.readonly}
statistics={statistics}
/>
</>
) : null}
</>