From e4bc43df67daef46c29515484487967fe5e47f90 Mon Sep 17 00:00:00 2001 From: CatsJuice Date: Thu, 20 Mar 2025 23:20:56 +0000 Subject: [PATCH] feat(core): integration property ui (#10844) close AF-2258, AF-2256 --- .../src/components/doc-properties/table.tsx | 2 + .../core/src/modules/integration/constant.ts | 26 +++++++- .../core/src/modules/integration/index.ts | 1 + .../services/integration-property.ts | 8 +++ .../core/src/modules/integration/type.ts | 5 +- .../integration/views/properties-table.tsx | 66 +++++++++++++++++++ .../views/property-values/date-value.tsx | 24 +++++++ .../views/property-values/index.tsx | 51 ++++++++++++++ .../views/property-values/link-value.tsx | 17 +++++ .../views/property-values/source-value.tsx | 25 +++++++ .../views/property-values/styles.css.ts | 36 ++++++++++ .../views/property-values/text-value.tsx | 10 +++ .../integration/views/property-values/type.ts | 6 ++ .../i18n/src/i18n-completenesses.json | 6 +- packages/frontend/i18n/src/i18n.gen.ts | 12 ++++ packages/frontend/i18n/src/resources/en.json | 3 + 16 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 packages/frontend/core/src/modules/integration/views/properties-table.tsx create mode 100644 packages/frontend/core/src/modules/integration/views/property-values/date-value.tsx create mode 100644 packages/frontend/core/src/modules/integration/views/property-values/index.tsx create mode 100644 packages/frontend/core/src/modules/integration/views/property-values/link-value.tsx create mode 100644 packages/frontend/core/src/modules/integration/views/property-values/source-value.tsx create mode 100644 packages/frontend/core/src/modules/integration/views/property-values/styles.css.ts create mode 100644 packages/frontend/core/src/modules/integration/views/property-values/text-value.tsx create mode 100644 packages/frontend/core/src/modules/integration/views/property-values/type.ts diff --git a/packages/frontend/core/src/components/doc-properties/table.tsx b/packages/frontend/core/src/components/doc-properties/table.tsx index 971c2edecb..57c12f785e 100644 --- a/packages/frontend/core/src/components/doc-properties/table.tsx +++ b/packages/frontend/core/src/components/doc-properties/table.tsx @@ -15,6 +15,7 @@ import type { DatabaseRow, DatabaseValueCell, } from '@affine/core/modules/doc-info/types'; +import { DocIntegrationPropertiesTable } from '@affine/core/modules/integration'; import { GuardService } from '@affine/core/modules/permissions'; import { ViewService, WorkbenchService } from '@affine/core/modules/workbench'; import type { AffineDNDData } from '@affine/core/types/dnd'; @@ -448,6 +449,7 @@ const DocPropertiesTableInner = ({ onOpenChange={setExpanded} /> + p.integrationType + ); + schema$ = this.docService.doc.properties$ .selector(p => p.integrationType) .map(type => (type ? INTEGRATION_PROPERTY_SCHEMA[type] : null)); + integrationProperty$( + type: IntegrationType, + key: string + ): LiveData | undefined | null>; integrationProperty$< T extends IntegrationType, Key extends keyof IntegrationDocPropertiesMap[T], diff --git a/packages/frontend/core/src/modules/integration/type.ts b/packages/frontend/core/src/modules/integration/type.ts index 558d2d3c40..795e961056 100644 --- a/packages/frontend/core/src/modules/integration/type.ts +++ b/packages/frontend/core/src/modules/integration/type.ts @@ -1,4 +1,5 @@ import type { I18nString } from '@affine/i18n'; +import type { ComponentType, SVGProps } from 'react'; import type { DocIntegrationRef } from '../db/schema/schema'; @@ -11,8 +12,10 @@ export type IntegrationDocPropertiesMap = { export type IntegrationProperty = { key: keyof IntegrationDocPropertiesMap[T]; - label?: I18nString; + label: I18nString; type: 'link' | 'text' | 'date' | 'source'; + icon?: ComponentType>; + order?: string; }; // =============================== diff --git a/packages/frontend/core/src/modules/integration/views/properties-table.tsx b/packages/frontend/core/src/modules/integration/views/properties-table.tsx new file mode 100644 index 0000000000..0fafa7831e --- /dev/null +++ b/packages/frontend/core/src/modules/integration/views/properties-table.tsx @@ -0,0 +1,66 @@ +import { + PropertyCollapsibleContent, + PropertyCollapsibleSection, + PropertyName, + PropertyRoot, +} from '@affine/component'; +import { useI18n } from '@affine/i18n'; +import { useLiveData, useService } from '@toeverything/infra'; +import { useMemo } from 'react'; + +import { IntegrationPropertyService } from '../services/integration-property'; +import type { IntegrationProperty } from '../type'; +import { ValueRenderer } from './property-values'; + +export const DocIntegrationPropertiesTable = () => { + const t = useI18n(); + const integrationPropertyService = useService(IntegrationPropertyService); + + const integrationType = useLiveData( + integrationPropertyService.integrationType$ + ); + const schema = useLiveData(integrationPropertyService.schema$); + + const properties = useMemo( + () => + (Object.values(schema || {}) as IntegrationProperty[]).sort( + (a, b) => { + const aOrder = a.order ?? '9999'; + const bOrder = b.order ?? '9999'; + return aOrder.localeCompare(bOrder); + } + ), + [schema] + ); + + if (!schema || !integrationType) return null; + + return ( + + + {properties.map(property => { + const Icon = property.icon; + const key = property.key as string; + const label = property.label; + const displayName = + typeof label === 'string' + ? t[label]() + : t.t(label?.i18nKey, label?.options); + + return ( + + : null} /> + + + ); + })} + + + ); +}; diff --git a/packages/frontend/core/src/modules/integration/views/property-values/date-value.tsx b/packages/frontend/core/src/modules/integration/views/property-values/date-value.tsx new file mode 100644 index 0000000000..8eb781250d --- /dev/null +++ b/packages/frontend/core/src/modules/integration/views/property-values/date-value.tsx @@ -0,0 +1,24 @@ +import { PropertyValue, Tooltip } from '@affine/component'; +import { i18nTime } from '@affine/i18n'; +import { useMemo } from 'react'; + +import * as styles from './styles.css'; +import type { PropertyValueProps } from './type'; + +export const DateValue = ({ value }: PropertyValueProps) => { + const accuracySecond = useMemo(() => { + return i18nTime(value, { absolute: { accuracy: 'second' } }); + }, [value]); + + const accuracyDay = useMemo(() => { + return i18nTime(value, { absolute: { accuracy: 'day' } }); + }, [value]); + + return ( + + + {accuracyDay} + + + ); +}; diff --git a/packages/frontend/core/src/modules/integration/views/property-values/index.tsx b/packages/frontend/core/src/modules/integration/views/property-values/index.tsx new file mode 100644 index 0000000000..aed22cecd7 --- /dev/null +++ b/packages/frontend/core/src/modules/integration/views/property-values/index.tsx @@ -0,0 +1,51 @@ +import { useLiveData, useService } from '@toeverything/infra'; +import { type ComponentType, useMemo } from 'react'; + +import { IntegrationPropertyService } from '../../services/integration-property'; +import type { IntegrationProperty } from '../../type'; +import { DateValue } from './date-value'; +import { LinkValue } from './link-value'; +import { SourceValue } from './source-value'; +import { TextValue } from './text-value'; +import type { PropertyValueProps } from './type'; + +type IntegrationPropertyType = IntegrationProperty['type']; + +const valueRenderers: Record< + IntegrationPropertyType, + ComponentType +> = { + link: LinkValue, + source: SourceValue, + text: TextValue, + date: DateValue, +}; + +type ValueRendererProps = { + type: IntegrationPropertyType; + propertyKey: string; +} & Pick; + +export const ValueRenderer = ({ + integration, + type, + propertyKey, +}: ValueRendererProps) => { + const Renderer = valueRenderers[type]; + + const integrationPropertyService = useService(IntegrationPropertyService); + + const propertyValue = useLiveData( + useMemo(() => { + return integrationPropertyService.integrationProperty$( + integration, + propertyKey + ); + }, [integration, integrationPropertyService, propertyKey]) + ); + + if (!Renderer) { + return null; + } + return ; +}; diff --git a/packages/frontend/core/src/modules/integration/views/property-values/link-value.tsx b/packages/frontend/core/src/modules/integration/views/property-values/link-value.tsx new file mode 100644 index 0000000000..751a8bb778 --- /dev/null +++ b/packages/frontend/core/src/modules/integration/views/property-values/link-value.tsx @@ -0,0 +1,17 @@ +import { PropertyValue } from '@affine/component'; + +import * as styles from './styles.css'; +import type { PropertyValueProps } from './type'; + +export const LinkValue = ({ value }: PropertyValueProps) => { + return ( + + {value} + + ); +}; diff --git a/packages/frontend/core/src/modules/integration/views/property-values/source-value.tsx b/packages/frontend/core/src/modules/integration/views/property-values/source-value.tsx new file mode 100644 index 0000000000..6736c0f159 --- /dev/null +++ b/packages/frontend/core/src/modules/integration/views/property-values/source-value.tsx @@ -0,0 +1,25 @@ +import { PropertyValue } from '@affine/component'; +import { DualLinkIcon } from '@blocksuite/icons/rc'; + +import { IntegrationTypeIcon } from '../icon'; +import * as styles from './styles.css'; +import type { PropertyValueProps } from './type'; + +export const SourceValue = ({ value, integration }: PropertyValueProps) => { + return ( + + +
+ +
+ {value} + +
+
+ ); +}; diff --git a/packages/frontend/core/src/modules/integration/views/property-values/styles.css.ts b/packages/frontend/core/src/modules/integration/views/property-values/styles.css.ts new file mode 100644 index 0000000000..2d737c80d6 --- /dev/null +++ b/packages/frontend/core/src/modules/integration/views/property-values/styles.css.ts @@ -0,0 +1,36 @@ +import { cssVarV2 } from '@toeverything/theme/v2'; +import { style } from '@vanilla-extract/css'; + +export const value = style({ + fontSize: 14, + fontWeight: 400, + lineHeight: '22px', + color: cssVarV2.text.primary, +}); + +export const linkWrapper = style({ + flex: 1, +}); +export const sourceValue = style([ + value, + { + display: 'flex', + alignItems: 'center', + gap: 6, + }, +]); +export const sourceValueIcon = style({ + fontSize: 18, + width: 22, + height: 22, + borderRadius: 3, + boxShadow: `0px 0px 0px 1px ${cssVarV2.layer.insideBorder.border}`, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: cssVarV2.integrations.background.iconSolid, +}); +export const sourceValueLinkIcon = style({ + fontSize: 18, + color: cssVarV2.icon.primary, +}); diff --git a/packages/frontend/core/src/modules/integration/views/property-values/text-value.tsx b/packages/frontend/core/src/modules/integration/views/property-values/text-value.tsx new file mode 100644 index 0000000000..e1e906346f --- /dev/null +++ b/packages/frontend/core/src/modules/integration/views/property-values/text-value.tsx @@ -0,0 +1,10 @@ +import { PropertyValue } from '@affine/component'; + +import * as styles from './styles.css'; +export const TextValue = ({ value }: { value: string }) => { + return ( + + {value} + + ); +}; diff --git a/packages/frontend/core/src/modules/integration/views/property-values/type.ts b/packages/frontend/core/src/modules/integration/views/property-values/type.ts new file mode 100644 index 0000000000..4abf388ffe --- /dev/null +++ b/packages/frontend/core/src/modules/integration/views/property-values/type.ts @@ -0,0 +1,6 @@ +import type { IntegrationType } from '../../type'; + +export interface PropertyValueProps { + value: any; + integration: IntegrationType; +} diff --git a/packages/frontend/i18n/src/i18n-completenesses.json b/packages/frontend/i18n/src/i18n-completenesses.json index 73827fa8d4..06b4cc9bc6 100644 --- a/packages/frontend/i18n/src/i18n-completenesses.json +++ b/packages/frontend/i18n/src/i18n-completenesses.json @@ -2,16 +2,16 @@ "ar": 95, "ca": 4, "da": 5, - "de": 96, + "de": 95, "el-GR": 95, "en": 100, - "es-AR": 96, + "es-AR": 95, "es-CL": 97, "es": 95, "fa": 95, "fr": 95, "hi": 2, - "it-IT": 96, + "it-IT": 95, "it": 1, "ja": 95, "ko": 60, diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts index 92636b2eb4..566472649d 100644 --- a/packages/frontend/i18n/src/i18n.gen.ts +++ b/packages/frontend/i18n/src/i18n.gen.ts @@ -7321,6 +7321,18 @@ export function useAFFiNEI18N(): { * `Source` */ ["com.affine.integration.readwise-prop.source"](): string; + /** + * `Created` + */ + ["com.affine.integration.readwise-prop.created"](): string; + /** + * `Updated` + */ + ["com.affine.integration.readwise-prop.updated"](): string; + /** + * `Integration properties` + */ + ["com.affine.integration.properties"](): string; /** * `Notes` */ diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 5df3dcc94b..11124017ea 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1823,6 +1823,9 @@ "com.affine.integration.readwise.import.abort-notify-desc": "Import aborted, with {{finished}} highlights processed", "com.affine.integration.readwise-prop.author": "Author", "com.affine.integration.readwise-prop.source": "Source", + "com.affine.integration.readwise-prop.created": "Created", + "com.affine.integration.readwise-prop.updated": "Updated", + "com.affine.integration.properties": "Integration properties", "com.affine.attachmentViewer.audio.notes": "Notes", "com.affine.attachmentViewer.audio.transcribing": "Transcribing", "error.INTERNAL_SERVER_ERROR": "An internal error occurred.",