Skip to content

Commit

Permalink
refactor(core): doc property (#8465)
Browse files Browse the repository at this point in the history
doc property upgraded to use orm.

The visibility of the property are simplified to three types: `always show`, `always hide`, `hide when empty`, and the default is `always show`.

![CleanShot 2024-10-14 at 15 34 52](https://github.com/user-attachments/assets/748b8b80-061f-4d6a-8579-52e59df717c2)

Added a sidebar view to manage properties
![CleanShot 2024-10-14 at 15 35 58](https://github.com/user-attachments/assets/bffa9b1a-a1a5-4708-b2e8-4963120f3af9)

new property ui in workspace settings
![CleanShot 2024-10-14 at 15 36 44](https://github.com/user-attachments/assets/572d8dcc-9b3d-462a-9bcc-5f5fa8e622da)

Property lists can be collapsed
![CleanShot 2024-10-14 at 15 37 59](https://github.com/user-attachments/assets/2b20be1a-8141-478a-8fe7-405aff6d04fd)
  • Loading branch information
EYHN committed Oct 15, 2024
1 parent 13b24eb commit 24e0c57
Show file tree
Hide file tree
Showing 88 changed files with 3,151 additions and 3,617 deletions.
1 change: 1 addition & 0 deletions packages/common/infra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@blocksuite/affine": "0.17.18",
"@datastructures-js/binary-search-tree": "^5.3.2",
"foxact": "^0.2.33",
"fractional-indexing": "^3.2.0",
"fuse.js": "^7.0.0",
"graphemer": "^1.4.0",
"idb": "^8.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/common/infra/src/modules/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { WorkspaceDB } from './entities/db';
import { WorkspaceDBTable } from './entities/table';
import { WorkspaceDBService } from './services/db';

export type { DocProperties } from './schema';
export type { DocCustomPropertyInfo, DocProperties } from './schema';
export { WorkspaceDBService } from './services/db';
export { transformWorkspaceDBLocalToCloud } from './services/db';

Expand Down
2 changes: 1 addition & 1 deletion packages/common/infra/src/modules/db/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { DocProperties } from './schema';
export type { DocCustomPropertyInfo, DocProperties } from './schema';
export {
AFFiNE_WORKSPACE_DB_SCHEMA,
AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA,
Expand Down
1 change: 1 addition & 0 deletions packages/common/infra/src/modules/db/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const AFFiNE_WORKSPACE_DB_SCHEMA = {
type: f.string(),
show: f.string().optional(),
index: f.string().optional(),
icon: f.string().optional(),
additionalData: f.json().optional(),
isDeleted: f.boolean().optional(),
// we will keep deleted properties in the database, for override legacy data
Expand Down
11 changes: 11 additions & 0 deletions packages/common/infra/src/modules/doc/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { DocCustomPropertyInfo } from '../db';

/**
* default built-in custom property, user can update and delete them
*/
export const BUILT_IN_CUSTOM_PROPERTY_TYPE = [
{
id: 'tags',
type: 'tags',
},
] as DocCustomPropertyInfo[];
8 changes: 8 additions & 0 deletions packages/common/infra/src/modules/doc/entities/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ export class Doc extends Entity {
readonly title$ = this.record.title$;
readonly trash$ = this.record.trash$;

customProperty$(propertyId: string) {
return this.record.customProperty$(propertyId);
}

setCustomProperty(propertyId: string, value: string) {
return this.record.setCustomProperty(propertyId, value);
}

setPrimaryMode(mode: DocMode) {
return this.record.setPrimaryMode(mode);
}
Expand Down
49 changes: 48 additions & 1 deletion packages/common/infra/src/modules/doc/entities/property-list.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Entity } from '../../../framework';
import { LiveData } from '../../../livedata';
import { generateFractionalIndexingKeyBetween } from '../../../utils';
import type { DocCustomPropertyInfo } from '../../db/schema/schema';
import type { DocPropertiesStore } from '../stores/doc-properties';

Expand All @@ -13,15 +14,61 @@ export class DocPropertyList extends Entity {
[]
);

sortedProperties$ = this.properties$.map(list =>
// default index key is '', so always before any others
list.toSorted((a, b) => ((a.index ?? '') > (b.index ?? '') ? 1 : -1))
);

propertyInfo$(id: string) {
return this.properties$.map(list => list.find(info => info.id === id));
}

updatePropertyInfo(id: string, properties: Partial<DocCustomPropertyInfo>) {
this.docPropertiesStore.updateDocPropertyInfo(id, properties);
}

createProperty(properties: DocCustomPropertyInfo) {
createProperty(
properties: Omit<DocCustomPropertyInfo, 'id'> & { id?: string }
) {
return this.docPropertiesStore.createDocPropertyInfo(properties);
}

removeProperty(id: string) {
this.docPropertiesStore.removeDocPropertyInfo(id);
}

indexAt(at: 'before' | 'after', targetId?: string) {
const sortedChildren = this.sortedProperties$.value.filter(
node => node.index
) as (DocCustomPropertyInfo & { index: string })[];
const targetIndex = targetId
? sortedChildren.findIndex(node => node.id === targetId)
: -1;
if (targetIndex === -1) {
if (at === 'before') {
const first = sortedChildren.at(0);
return generateFractionalIndexingKeyBetween(null, first?.index ?? null);
} else {
const last = sortedChildren.at(-1);
return generateFractionalIndexingKeyBetween(last?.index ?? null, null);
}
} else {
const target = sortedChildren[targetIndex];
const before: DocCustomPropertyInfo | null =
sortedChildren[targetIndex - 1] || null;
const after: DocCustomPropertyInfo | null =
sortedChildren[targetIndex + 1] || null;
if (at === 'before') {
return generateFractionalIndexingKeyBetween(
before?.index ?? null,
target.index
);
} else {
return generateFractionalIndexingKeyBetween(
target.index,
after?.index ?? null
);
}
}
}
}
12 changes: 10 additions & 2 deletions packages/common/infra/src/modules/doc/entities/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,16 @@ export class DocRecord extends Entity<{ id: string }> {
{ id: this.id }
);

setProperties(properties: Partial<DocProperties>): void {
this.docPropertiesStore.updateDocProperties(this.id, properties);
customProperty$(propertyId: string) {
return this.properties$.selector(
p => p['custom:' + propertyId]
) as LiveData<string | undefined | null>;
}

setCustomProperty(propertyId: string, value: string) {
this.docPropertiesStore.updateDocProperties(this.id, {
['custom:' + propertyId]: value,
});
}

setMeta(meta: Partial<DocMeta>): void {
Expand Down
54 changes: 42 additions & 12 deletions packages/common/infra/src/modules/doc/stores/doc-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
DocProperties,
} from '../../db/schema/schema';
import type { WorkspaceService } from '../../workspace';
import { BUILT_IN_CUSTOM_PROPERTY_TYPE } from '../constants';

interface LegacyDocProperties {
custom?: Record<string, { value: unknown } | undefined>;
Expand All @@ -23,6 +24,7 @@ type LegacyDocPropertyInfo = {
id?: string;
name?: string;
type?: string;
icon?: string;
};

type LegacyDocPropertyInfoList = Record<
Expand Down Expand Up @@ -50,11 +52,18 @@ export class DocPropertiesStore extends Store {
const legacy = this.upgradeLegacyDocPropertyInfoList(
this.getLegacyDocPropertyInfoList()
);
const notOverridden = differenceBy(legacy, db, i => i.id);
return [...db, ...notOverridden].filter(i => !i.isDeleted);
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE;
const withLegacy = [...db, ...differenceBy(legacy, db, i => i.id)];
const all = [
...withLegacy,
...differenceBy(builtIn, withLegacy, i => i.id),
];
return all.filter(i => !i.isDeleted);
}

createDocPropertyInfo(config: DocCustomPropertyInfo) {
createDocPropertyInfo(
config: Omit<DocCustomPropertyInfo, 'id'> & { id?: string }
) {
return this.dbService.db.docCustomPropertyInfo.create(config).id;
}

Expand All @@ -67,7 +76,11 @@ export class DocPropertiesStore extends Store {

updateDocPropertyInfo(id: string, config: Partial<DocCustomPropertyInfo>) {
const needMigration = !this.dbService.db.docCustomPropertyInfo.get(id);
if (needMigration) {
const isBuiltIn =
needMigration && BUILT_IN_CUSTOM_PROPERTY_TYPE.some(i => i.id === id);
if (isBuiltIn) {
this.createPropertyFromBuiltIn(id, config);
} else if (needMigration) {
// if this property is not in db, we need to migration it from legacy to db, only type and name is needed
this.migrateLegacyDocPropertyInfo(id, config);
} else {
Expand All @@ -90,16 +103,32 @@ export class DocPropertiesStore extends Store {
});
}

createPropertyFromBuiltIn(
id: string,
override: Partial<DocCustomPropertyInfo>
) {
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE.find(i => i.id === id);
if (!builtIn) {
return;
}
this.createDocPropertyInfo({ ...builtIn, ...override });
}

watchDocPropertyInfoList() {
return combineLatest([
this.watchLegacyDocPropertyInfoList().pipe(
map(this.upgradeLegacyDocPropertyInfoList)
),
this.dbService.db.docCustomPropertyInfo.find$({}),
this.dbService.db.docCustomPropertyInfo.find$(),
]).pipe(
map(([legacy, db]) => {
const notOverridden = differenceBy(legacy, db, i => i.id);
return [...db, ...notOverridden].filter(i => !i.isDeleted);
const builtIn = BUILT_IN_CUSTOM_PROPERTY_TYPE;
const withLegacy = [...db, ...differenceBy(legacy, db, i => i.id)];
const all = [
...withLegacy,
...differenceBy(builtIn, withLegacy, i => i.id),
];
return all.filter(i => !i.isDeleted);
})
);
}
Expand Down Expand Up @@ -133,15 +162,15 @@ export class DocPropertiesStore extends Store {
if (!properties) {
return {};
}
const newProperties: Record<string, unknown> = {};
const newProperties: Record<string, string> = {};
for (const [key, info] of Object.entries(properties.system ?? {})) {
if (info?.value !== undefined) {
newProperties[key] = info.value;
if (info?.value !== undefined && info.value !== null) {
newProperties[key] = info.value.toString();
}
}
for (const [key, info] of Object.entries(properties.custom ?? {})) {
if (info?.value !== undefined) {
newProperties['custom:' + key] = info.value;
if (info?.value !== undefined && info.value !== null) {
newProperties['custom:' + key] = info.value.toString();
}
}
return newProperties;
Expand All @@ -162,6 +191,7 @@ export class DocPropertiesStore extends Store {
id,
name: info.name,
type: info.type,
icon: info.icon,
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/common/infra/src/orm/core/validators/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { DataValidator } from './types';
function inputType(val: any) {
return val === null ||
Array.isArray(val) ||
val.constructor === 'Object' ||
val.constructor === Object ||
!val.constructor /* Object.create(null) */
? 'json'
: typeof val;
Expand Down
1 change: 1 addition & 0 deletions packages/common/infra/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './async-lock';
export * from './async-queue';
export * from './exhaustmap-with-trailing';
export * from './fractional-indexing';
export * from './merge-updates';
export * from './object-pool';
export * from './stable-hash';
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/component/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * from './ui/menu';
export * from './ui/modal';
export * from './ui/notification';
export * from './ui/popover';
export * from './ui/property';
export * from './ui/radio';
export * from './ui/safe-area';
export * from './ui/scrollbar';
Expand Down
5 changes: 5 additions & 0 deletions packages/frontend/component/src/ui/dnd/draggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ export const useDraggable = <D extends DNDData = DNDData>(
let previewPosition: DraggableDragPreviewPosition =
options.dragPreviewPosition ?? 'native';

source.element.dataset['dragPreview'] = 'true';
requestAnimationFrame(() => {
delete source.element.dataset['dragPreview'];
});

if (enableCustomDragPreview.current) {
setCustomNativeDragPreview({
getOffset: (...args) => {
Expand Down
18 changes: 15 additions & 3 deletions packages/frontend/component/src/ui/dnd/drop-indicator.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export const treeLine = style({
height: 2,
right: 0,
},

selectors: {
['&[data-no-terminal="true"]::before']: {
display: 'none',
},
},
});

export const lineAboveStyles = style({
Expand Down Expand Up @@ -143,7 +149,7 @@ export const left = style({

export const edgeLine = style({
vars: {
[terminalSize]: '8px',
[terminalSize]: '6px',
},
display: 'block',
position: 'absolute',
Expand All @@ -156,11 +162,17 @@ export const edgeLine = style({
// Terminal
'::before': {
content: '""',
width: terminalSize,
height: terminalSize,
width: 0,
height: 0,
boxSizing: 'border-box',
position: 'absolute',
border: `${terminalSize} solid ${cssVar('--affine-primary-color')}`,
borderRadius: '50%',
},

selectors: {
['&[data-no-terminal="true"]::before']: {
display: 'none',
},
},
});
Loading

0 comments on commit 24e0c57

Please sign in to comment.