@dxos/plugin-masonry 0.8.4-main.c351d160a8 → 0.8.4-main.d9fc60f731

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/LICENSE +102 -5
  2. package/PLUGIN.mdl +328 -0
  3. package/README.md +1 -1
  4. package/dist/lib/neutral/MasonryContainer-XXJXCOOA.mjs +96 -0
  5. package/dist/lib/neutral/MasonryContainer-XXJXCOOA.mjs.map +7 -0
  6. package/dist/lib/neutral/MasonryPlugin.mjs +38 -0
  7. package/dist/lib/neutral/MasonryPlugin.mjs.map +7 -0
  8. package/dist/lib/neutral/capabilities/index.mjs +11 -0
  9. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  10. package/dist/lib/neutral/chunk-IJ2FIXSI.mjs +32 -0
  11. package/dist/lib/neutral/chunk-IJ2FIXSI.mjs.map +7 -0
  12. package/dist/lib/{browser/chunk-ANLADCDB.mjs → neutral/chunk-SS54S63V.mjs} +10 -11
  13. package/dist/lib/neutral/chunk-SS54S63V.mjs.map +7 -0
  14. package/dist/lib/neutral/components/index.mjs +1 -0
  15. package/dist/lib/neutral/containers/index.mjs +9 -0
  16. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  17. package/dist/lib/neutral/create-object-WRCYV4HT.mjs +39 -0
  18. package/dist/lib/neutral/create-object-WRCYV4HT.mjs.map +7 -0
  19. package/dist/lib/neutral/index.mjs +14 -0
  20. package/dist/lib/neutral/meta.json +1 -0
  21. package/dist/lib/neutral/meta.mjs +8 -0
  22. package/dist/lib/neutral/plugin.mjs +12 -0
  23. package/dist/lib/neutral/plugin.mjs.map +7 -0
  24. package/dist/lib/{browser/react-surface-E3X4DF5N.mjs → neutral/react-surface-7CZHE3XA.mjs} +15 -22
  25. package/dist/lib/neutral/react-surface-7CZHE3XA.mjs.map +7 -0
  26. package/dist/lib/neutral/translations.mjs +30 -0
  27. package/dist/lib/neutral/translations.mjs.map +7 -0
  28. package/dist/lib/{browser → neutral}/types/index.mjs +1 -1
  29. package/dist/lib/neutral/types/index.mjs.map +7 -0
  30. package/dist/types/src/MasonryPlugin.d.ts +1 -0
  31. package/dist/types/src/MasonryPlugin.d.ts.map +1 -1
  32. package/dist/types/src/MasonryPlugin.test.d.ts +2 -0
  33. package/dist/types/src/MasonryPlugin.test.d.ts.map +1 -0
  34. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  35. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/index.d.ts +8 -1
  37. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  38. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  39. package/dist/types/src/containers/MasonryContainer/MasonryContainer.d.ts +1 -2
  40. package/dist/types/src/containers/MasonryContainer/MasonryContainer.d.ts.map +1 -1
  41. package/dist/types/src/containers/MasonryContainer/MasonryContainer.stories.d.ts.map +1 -1
  42. package/dist/types/src/containers/MasonryContainer/index.d.ts +1 -2
  43. package/dist/types/src/containers/MasonryContainer/index.d.ts.map +1 -1
  44. package/dist/types/src/index.d.ts +1 -1
  45. package/dist/types/src/index.d.ts.map +1 -1
  46. package/dist/types/src/meta.d.ts +1 -1
  47. package/dist/types/src/meta.d.ts.map +1 -1
  48. package/dist/types/src/plugin.d.ts +3 -0
  49. package/dist/types/src/plugin.d.ts.map +1 -0
  50. package/dist/types/src/translations.d.ts +20 -20
  51. package/dist/types/src/translations.d.ts.map +1 -1
  52. package/dist/types/src/types/Masonry.d.ts +6 -6
  53. package/dist/types/src/types/Masonry.d.ts.map +1 -1
  54. package/dist/types/tsconfig.tsbuildinfo +1 -1
  55. package/package.json +79 -46
  56. package/src/MasonryPlugin.test.ts +26 -0
  57. package/src/MasonryPlugin.tsx +13 -26
  58. package/src/capabilities/create-object.ts +36 -0
  59. package/src/capabilities/index.ts +4 -1
  60. package/src/capabilities/{react-surface/react-surface.tsx → react-surface.tsx} +10 -9
  61. package/src/containers/MasonryContainer/MasonryContainer.stories.tsx +11 -13
  62. package/src/containers/MasonryContainer/MasonryContainer.tsx +63 -41
  63. package/src/containers/MasonryContainer/index.ts +1 -3
  64. package/src/index.ts +1 -2
  65. package/src/meta.ts +19 -7
  66. package/src/plugin.ts +9 -0
  67. package/src/translations.ts +12 -12
  68. package/src/types/Masonry.ts +12 -13
  69. package/src/types/MasonryAction.ts +1 -1
  70. package/src/vite-env.d.ts +10 -0
  71. package/dist/lib/browser/MasonryContainer-Q7VPG6YS.mjs +0 -84
  72. package/dist/lib/browser/MasonryContainer-Q7VPG6YS.mjs.map +0 -7
  73. package/dist/lib/browser/chunk-7W5GV4MG.mjs +0 -19
  74. package/dist/lib/browser/chunk-7W5GV4MG.mjs.map +0 -7
  75. package/dist/lib/browser/chunk-ANLADCDB.mjs.map +0 -7
  76. package/dist/lib/browser/index.mjs +0 -78
  77. package/dist/lib/browser/index.mjs.map +0 -7
  78. package/dist/lib/browser/meta.json +0 -1
  79. package/dist/lib/browser/react-surface-E3X4DF5N.mjs.map +0 -7
  80. package/dist/lib/node-esm/MasonryContainer-NQGW3OZD.mjs +0 -85
  81. package/dist/lib/node-esm/MasonryContainer-NQGW3OZD.mjs.map +0 -7
  82. package/dist/lib/node-esm/chunk-CCWUD5KF.mjs +0 -21
  83. package/dist/lib/node-esm/chunk-CCWUD5KF.mjs.map +0 -7
  84. package/dist/lib/node-esm/chunk-GHHWMU6E.mjs +0 -68
  85. package/dist/lib/node-esm/chunk-GHHWMU6E.mjs.map +0 -7
  86. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
  87. package/dist/lib/node-esm/index.mjs +0 -79
  88. package/dist/lib/node-esm/index.mjs.map +0 -7
  89. package/dist/lib/node-esm/meta.json +0 -1
  90. package/dist/lib/node-esm/react-surface-4C6BZVEJ.mjs +0 -43
  91. package/dist/lib/node-esm/react-surface-4C6BZVEJ.mjs.map +0 -7
  92. package/dist/lib/node-esm/types/index.mjs +0 -11
  93. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  94. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  95. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  96. package/src/capabilities/react-surface/index.ts +0 -7
  97. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
  98. /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
  99. /package/dist/lib/{browser/types → neutral/components}/index.mjs.map +0 -0
  100. /package/dist/lib/{node-esm/types → neutral}/index.mjs.map +0 -0
  101. /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/meta.mjs.map} +0 -0
  102. /package/dist/types/src/capabilities/{react-surface/react-surface.d.ts → react-surface.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-masonry",
3
- "version": "0.8.4-main.c351d160a8",
3
+ "version": "0.8.4-main.d9fc60f731",
4
4
  "description": "Masonry DXOS Surface plugin",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -8,76 +8,109 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "DXOS.org",
13
13
  "sideEffects": true,
14
14
  "type": "module",
15
+ "imports": {
16
+ "#capabilities": {
17
+ "source": "./src/capabilities/index.ts",
18
+ "types": "./dist/types/src/capabilities/index.d.ts",
19
+ "default": "./dist/lib/neutral/capabilities/index.mjs"
20
+ },
21
+ "#components": {
22
+ "source": "./src/components/index.ts",
23
+ "types": "./dist/types/src/components/index.d.ts",
24
+ "default": "./dist/lib/neutral/components/index.mjs"
25
+ },
26
+ "#containers": {
27
+ "source": "./src/containers/index.ts",
28
+ "types": "./dist/types/src/containers/index.d.ts",
29
+ "default": "./dist/lib/neutral/containers/index.mjs"
30
+ },
31
+ "#meta": {
32
+ "source": "./src/meta.ts",
33
+ "types": "./dist/types/src/meta.d.ts",
34
+ "default": "./dist/lib/neutral/meta.mjs"
35
+ },
36
+ "#plugin": {
37
+ "source": "./src/MasonryPlugin.tsx",
38
+ "types": "./dist/types/src/MasonryPlugin.d.ts",
39
+ "default": "./dist/lib/neutral/MasonryPlugin.mjs"
40
+ },
41
+ "#translations": {
42
+ "source": "./src/translations.ts",
43
+ "types": "./dist/types/src/translations.d.ts",
44
+ "default": "./dist/lib/neutral/translations.mjs"
45
+ },
46
+ "#types": {
47
+ "source": "./src/types/index.ts",
48
+ "types": "./dist/types/src/types/index.d.ts",
49
+ "default": "./dist/lib/neutral/types/index.mjs"
50
+ }
51
+ },
15
52
  "exports": {
16
53
  ".": {
17
54
  "source": "./src/index.ts",
18
55
  "types": "./dist/types/src/index.d.ts",
19
- "browser": "./dist/lib/browser/index.mjs",
20
- "node": "./dist/lib/node-esm/index.mjs"
56
+ "default": "./dist/lib/neutral/index.mjs"
21
57
  },
22
- "./types": {
23
- "source": "./src/types/index.ts",
24
- "types": "./dist/types/src/types/index.d.ts",
25
- "browser": "./dist/lib/browser/types/index.mjs",
26
- "node": "./dist/lib/node-esm/types/index.mjs"
58
+ "./assets/PLUGIN.mdl": "./PLUGIN.mdl",
59
+ "./plugin": {
60
+ "source": "./src/plugin.ts",
61
+ "types": "./dist/types/src/plugin.d.ts",
62
+ "default": "./dist/lib/neutral/plugin.mjs"
63
+ },
64
+ "./translations": {
65
+ "source": "./src/translations.ts",
66
+ "types": "./dist/types/src/translations.d.ts",
67
+ "default": "./dist/lib/neutral/translations.mjs"
27
68
  }
28
69
  },
29
70
  "types": "dist/types/src/index.d.ts",
30
- "typesVersions": {
31
- "*": {
32
- "types": [
33
- "dist/types/src/types/index.d.ts"
34
- ]
35
- }
36
- },
37
71
  "files": [
38
72
  "dist",
39
- "src"
73
+ "src",
74
+ "PLUGIN.mdl"
40
75
  ],
41
76
  "dependencies": {
42
- "@dxos/app-toolkit": "0.8.4-main.c351d160a8",
43
- "@dxos/operation": "0.8.4-main.c351d160a8",
44
- "@dxos/effect": "0.8.4-main.c351d160a8",
45
- "@dxos/plugin-client": "0.8.4-main.c351d160a8",
46
- "@dxos/app-framework": "0.8.4-main.c351d160a8",
47
- "@dxos/plugin-search": "0.8.4-main.c351d160a8",
48
- "@dxos/plugin-space": "0.8.4-main.c351d160a8",
49
- "@dxos/react-client": "0.8.4-main.c351d160a8",
50
- "@dxos/react-ui": "0.8.4-main.c351d160a8",
51
- "@dxos/react-ui-form": "0.8.4-main.c351d160a8",
52
- "@dxos/react-ui-masonry": "0.8.4-main.c351d160a8",
53
- "@dxos/react-ui-menu": "0.8.4-main.c351d160a8",
54
- "@dxos/react-ui-mosaic": "0.8.4-main.c351d160a8",
55
- "@dxos/types": "0.8.4-main.c351d160a8",
56
- "@dxos/react-ui-stack": "0.8.4-main.c351d160a8",
57
- "@dxos/schema": "0.8.4-main.c351d160a8",
58
- "@dxos/util": "0.8.4-main.c351d160a8",
59
- "@dxos/echo": "0.8.4-main.c351d160a8"
77
+ "@dxos/app-framework": "0.8.4-main.d9fc60f731",
78
+ "@dxos/compute": "0.8.4-main.d9fc60f731",
79
+ "@dxos/app-toolkit": "0.8.4-main.d9fc60f731",
80
+ "@dxos/echo": "0.8.4-main.d9fc60f731",
81
+ "@dxos/effect": "0.8.4-main.d9fc60f731",
82
+ "@dxos/plugin-client": "0.8.4-main.d9fc60f731",
83
+ "@dxos/keys": "0.8.4-main.d9fc60f731",
84
+ "@dxos/react-client": "0.8.4-main.d9fc60f731",
85
+ "@dxos/plugin-space": "0.8.4-main.d9fc60f731",
86
+ "@dxos/react-ui": "0.8.4-main.d9fc60f731",
87
+ "@dxos/react-ui-masonry": "0.8.4-main.d9fc60f731",
88
+ "@dxos/react-ui-menu": "0.8.4-main.d9fc60f731",
89
+ "@dxos/react-ui-search": "0.8.4-main.d9fc60f731",
90
+ "@dxos/schema": "0.8.4-main.d9fc60f731",
91
+ "@dxos/types": "0.8.4-main.d9fc60f731",
92
+ "@dxos/util": "0.8.4-main.d9fc60f731"
60
93
  },
61
94
  "devDependencies": {
62
95
  "@types/react": "~19.2.7",
63
96
  "@types/react-dom": "~19.2.3",
64
- "effect": "3.19.16",
97
+ "effect": "3.21.2",
65
98
  "react": "~19.2.3",
66
99
  "react-dom": "~19.2.3",
67
- "vite": "^7.1.11",
68
- "@dxos/plugin-preview": "0.8.4-main.c351d160a8",
69
- "@dxos/plugin-theme": "0.8.4-main.c351d160a8",
70
- "@dxos/random": "0.8.4-main.c351d160a8",
71
- "@dxos/storybook-utils": "0.8.4-main.c351d160a8",
72
- "@dxos/plugin-testing": "0.8.4-main.c351d160a8",
73
- "@dxos/ui-theme": "0.8.4-main.c351d160a8"
100
+ "vite": "^8.0.14",
101
+ "@dxos/plugin-testing": "0.8.4-main.d9fc60f731",
102
+ "@dxos/plugin-preview": "0.8.4-main.d9fc60f731",
103
+ "@dxos/random": "0.8.4-main.d9fc60f731",
104
+ "@dxos/storybook-utils": "0.8.4-main.d9fc60f731",
105
+ "@dxos/ui-theme": "0.8.4-main.d9fc60f731",
106
+ "@dxos/plugin-theme": "0.8.4-main.d9fc60f731"
74
107
  },
75
108
  "peerDependencies": {
76
- "effect": "3.19.16",
109
+ "effect": "3.21.2",
77
110
  "react": "~19.2.3",
78
111
  "react-dom": "~19.2.3",
79
- "@dxos/react-ui": "0.8.4-main.c351d160a8",
80
- "@dxos/ui-theme": "0.8.4-main.c351d160a8"
112
+ "@dxos/react-ui": "0.8.4-main.d9fc60f731",
113
+ "@dxos/ui-theme": "0.8.4-main.d9fc60f731"
81
114
  },
82
115
  "publishConfig": {
83
116
  "access": "public"
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, test } from 'vitest';
6
+
7
+ import { ClientPlugin } from '@dxos/plugin-client/plugin';
8
+ import { createComposerTestApp } from '@dxos/plugin-testing/harness';
9
+
10
+ import { MasonryPlugin } from '#plugin';
11
+
12
+ import { meta } from './meta';
13
+
14
+ const moduleId = (name: string) => `${meta.id}.module.${name}`;
15
+
16
+ describe('MasonryPlugin', () => {
17
+ test('modules activate on the expected events', async ({ expect }) => {
18
+ await using harness = await createComposerTestApp({
19
+ plugins: [ClientPlugin({}), MasonryPlugin()],
20
+ });
21
+
22
+ expect(harness.manager.getActive()).toEqual(
23
+ expect.arrayContaining([moduleId('CreateObject'), moduleId('schema'), moduleId('ReactSurface')]),
24
+ );
25
+ });
26
+ });
@@ -2,39 +2,26 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import * as Effect from 'effect/Effect';
6
-
7
- import * as Option from 'effect/Option';
8
-
9
5
  import { Plugin } from '@dxos/app-framework';
10
6
  import { AppPlugin } from '@dxos/app-toolkit';
11
- import { Annotation, Type } from '@dxos/echo';
12
- import { type CreateObject } from '@dxos/plugin-space/types';
13
- import { ViewModel } from '@dxos/schema';
14
7
 
15
- import { ReactSurface } from './capabilities';
16
- import { meta } from './meta';
17
- import { translations } from './translations';
18
- import { Masonry, MasonryAction } from './types';
8
+ import { CreateObject, ReactSurface } from '#capabilities';
9
+ import { meta } from '#meta';
10
+ import { translations } from '#translations';
11
+ import { Masonry } from '#types';
12
+
13
+ // eslint-disable-next-line import/no-relative-packages
14
+ import pluginSpec from '../PLUGIN.mdl?raw';
19
15
 
20
16
  export const MasonryPlugin = Plugin.define(meta).pipe(
21
- AppPlugin.addMetadataModule({
22
- metadata: {
23
- id: Type.getTypename(Masonry.Masonry),
24
- metadata: {
25
- icon: Annotation.IconAnnotation.get(Masonry.Masonry).pipe(Option.getOrThrow).icon,
26
- iconHue: Annotation.IconAnnotation.get(Masonry.Masonry).pipe(Option.getOrThrow).hue ?? 'white',
27
- inputSchema: MasonryAction.MasonryProps,
28
- createObject: ((props, { db }) =>
29
- Effect.promise(async () => {
30
- const { view } = await ViewModel.makeFromDatabase({ db, typename: props.typename });
31
- return Masonry.make({ name: props.name, view });
32
- })) satisfies CreateObject,
33
- },
34
- },
35
- }),
17
+ AppPlugin.addCreateObjectModule({ activate: CreateObject }),
36
18
  AppPlugin.addSchemaModule({ schema: [Masonry.Masonry] }),
37
19
  AppPlugin.addSurfaceModule({ activate: ReactSurface }),
38
20
  AppPlugin.addTranslationsModule({ translations }),
21
+ AppPlugin.addPluginAssetModule({
22
+ asset: { pluginId: meta.id, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
23
+ }),
39
24
  Plugin.make,
40
25
  );
26
+
27
+ export default MasonryPlugin;
@@ -0,0 +1,36 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capability } from '@dxos/app-framework';
8
+ import { Operation } from '@dxos/compute';
9
+ import { Type } from '@dxos/echo';
10
+ import { SpaceOperation } from '@dxos/plugin-space';
11
+ import { SpaceCapabilities } from '@dxos/plugin-space';
12
+ import { ViewModel } from '@dxos/schema';
13
+
14
+ import { Masonry, MasonryAction } from '#types';
15
+
16
+ export default Capability.makeModule(
17
+ Effect.fnUntraced(function* () {
18
+ return Capability.contributes(SpaceCapabilities.CreateObjectEntry, {
19
+ id: Type.getTypename(Masonry.Masonry),
20
+ inputSchema: MasonryAction.MasonryProps,
21
+ createObject: (props, options) =>
22
+ Effect.gen(function* () {
23
+ const object = yield* Effect.promise(async () => {
24
+ const { view } = await ViewModel.makeFromDatabase({ db: options.db, typename: props.typename });
25
+ return Masonry.make({ name: props.name, view });
26
+ });
27
+ return yield* Operation.invoke(SpaceOperation.AddObject, {
28
+ object,
29
+ target: options.target,
30
+ hidden: true,
31
+ targetNodeId: options.targetNodeId,
32
+ });
33
+ }),
34
+ });
35
+ }),
36
+ );
@@ -2,4 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './react-surface';
5
+ import { Capability } from '@dxos/app-framework';
6
+
7
+ export const CreateObject = Capability.lazy('CreateObject', () => import('./create-object'));
8
+ export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
@@ -7,21 +7,22 @@ import React from 'react';
7
7
 
8
8
  import { Capabilities, Capability } from '@dxos/app-framework';
9
9
  import { Surface } from '@dxos/app-framework/ui';
10
- import { Obj } from '@dxos/echo';
11
- import { View } from '@dxos/echo';
10
+ import { AppSurface } from '@dxos/app-toolkit/ui';
11
+ import { Obj, View } from '@dxos/echo';
12
12
 
13
- import { MasonryContainer } from '../../containers';
14
- import { meta } from '../../meta';
15
- import { Masonry } from '../../types';
13
+ import { MasonryContainer } from '#containers';
14
+ import { Masonry } from '#types';
16
15
 
17
16
  export default Capability.makeModule(() =>
18
17
  Effect.succeed(
19
18
  Capability.contributes(Capabilities.ReactSurface, [
20
19
  Surface.create({
21
- id: meta.id,
22
- role: ['article', 'section'],
23
- filter: (data): data is { subject: Masonry.Masonry | View.View } =>
24
- Obj.instanceOf(Masonry.Masonry, data.subject) || Obj.instanceOf(View.View, data.subject),
20
+ id: 'root',
21
+ // TODO(wittjosiah): Split into multiple surfaces if this filter proves too strict for non-article roles.
22
+ filter: AppSurface.oneOf(
23
+ AppSurface.object(AppSurface.Article, [Masonry.Masonry, View.View]),
24
+ AppSurface.object(AppSurface.Section, [Masonry.Masonry, View.View]),
25
+ ),
25
26
  component: ({ data, role }) => {
26
27
  const view = Obj.instanceOf(View.View, data.subject) ? data.subject : data.subject.view;
27
28
  return <MasonryContainer view={view} role={role} />;
@@ -7,22 +7,22 @@ import * as Effect from 'effect/Effect';
7
7
  import React from 'react';
8
8
 
9
9
  import { withPluginManager } from '@dxos/app-framework/testing';
10
- import { View } from '@dxos/echo';
11
- import { ClientPlugin } from '@dxos/plugin-client';
12
- import { PreviewPlugin } from '@dxos/plugin-preview';
10
+ import { Filter, Type, View } from '@dxos/echo';
11
+ import { ClientPlugin } from '@dxos/plugin-client/testing';
12
+ import { initializeIdentity } from '@dxos/plugin-client/testing';
13
+ import { PreviewPlugin } from '@dxos/plugin-preview/testing';
13
14
  import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
14
- import { faker } from '@dxos/random';
15
- import { Filter, useObject, useQuery, useSpaces } from '@dxos/react-client/echo';
16
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
+ import { random } from '@dxos/random';
16
+ import { useObject, useQuery, useSpaces } from '@dxos/react-client/echo';
17
17
  import { ViewModel } from '@dxos/schema';
18
18
  import { createObjectFactory } from '@dxos/schema/testing';
19
19
  import { Organization } from '@dxos/types';
20
20
 
21
- import { Masonry } from '../../types';
21
+ import { Masonry } from '#types';
22
22
 
23
23
  import { MasonryContainer } from './MasonryContainer';
24
24
 
25
- faker.seed(0);
25
+ random.seed(0);
26
26
 
27
27
  const StorybookMasonry = () => {
28
28
  const spaces = useSpaces();
@@ -38,8 +38,6 @@ const meta = {
38
38
  component: StorybookMasonry,
39
39
  render: () => <StorybookMasonry />,
40
40
  decorators: [
41
- withTheme(),
42
- withLayout({ layout: 'fullscreen' }),
43
41
  withPluginManager({
44
42
  plugins: [
45
43
  ...corePlugins(),
@@ -48,18 +46,18 @@ const meta = {
48
46
  types: [Organization.Organization, View.View, Masonry.Masonry],
49
47
  onClientInitialized: ({ client }) =>
50
48
  Effect.gen(function* () {
51
- yield* Effect.promise(() => client.halo.createIdentity());
49
+ yield* initializeIdentity(client);
52
50
  const space = yield* Effect.promise(() => client.spaces.create());
53
51
  yield* Effect.promise(() => space.waitUntilReady());
54
52
  const { view } = yield* Effect.promise(() =>
55
53
  ViewModel.makeFromDatabase({
56
54
  db: space.db,
57
- typename: Organization.Organization.typename,
55
+ typename: Type.getTypename(Organization.Organization),
58
56
  }),
59
57
  );
60
58
  const masonry = Masonry.make({ view });
61
59
  space.db.add(masonry);
62
- const factory = createObjectFactory(space.db, faker as any);
60
+ const factory = createObjectFactory(space.db, random as any);
63
61
  yield* Effect.promise(() => factory([{ type: Organization.Organization, count: 64 }]));
64
62
  }),
65
63
  }),
@@ -2,22 +2,19 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import * as Function from 'effect/Function';
6
- import * as Option from 'effect/Option';
7
- import type * as Schema from 'effect/Schema';
8
- import React, { useEffect, useState } from 'react';
5
+ import React, { useEffect, useMemo, useState } from 'react';
9
6
 
10
7
  import { Surface, useCapabilities } from '@dxos/app-framework/ui';
11
8
  import { AppCapabilities } from '@dxos/app-toolkit';
12
- import { useObjectMenuItems } from '@dxos/app-toolkit/ui';
13
- import { Annotation, Filter, Obj, type Ref, Type } from '@dxos/echo';
14
- import { type View } from '@dxos/echo';
15
- import { useGlobalFilteredObjects } from '@dxos/plugin-search';
9
+ import { AppSurface, useObjectMenuItems, useSchemaFilter } from '@dxos/app-toolkit/ui';
10
+ import { Filter, Obj, Query, type Ref, Type, type View } from '@dxos/echo';
16
11
  import { useObject, useQuery } from '@dxos/react-client/echo';
17
- import { Card, Toolbar } from '@dxos/react-ui';
12
+ import { Card, Panel, Toolbar } from '@dxos/react-ui';
18
13
  import { Masonry as MasonryComponent } from '@dxos/react-ui-masonry';
19
14
  import { Menu } from '@dxos/react-ui-menu';
20
- import { getTypenameFromQuery } from '@dxos/schema';
15
+ import { SearchList, useSearchListResults } from '@dxos/react-ui-search';
16
+ import { getTagFromQuery, getTypenameFromQuery } from '@dxos/schema';
17
+ import { isNonNullable } from '@dxos/util';
21
18
 
22
19
  export type MasonryContainerProps = {
23
20
  view: View.View;
@@ -35,64 +32,89 @@ export const MasonryContainer = ({
35
32
  const schemas = useCapabilities(AppCapabilities.Schema);
36
33
  const db = view && Obj.getDatabase(view);
37
34
  const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
35
+ const tag = view?.query ? getTagFromQuery(view.query.ast) : undefined;
38
36
 
39
- const [cardSchema, setCardSchema] = useState<Schema.Schema.AnyNoContext>();
37
+ const [cardSchema, setCardSchema] = useState<Type.AnyEntity>();
40
38
 
41
39
  useEffect(() => {
42
40
  const staticSchema = schemas.flat().find((schema) => Type.getTypename(schema) === typename);
43
41
  if (staticSchema) {
44
42
  setCardSchema(() => staticSchema);
43
+ return;
45
44
  }
46
- if (!staticSchema && typename && db) {
47
- const query = db.schemaRegistry.query({ typename });
48
- const unsubscribe = query.subscribe(
49
- () => {
50
- const [schema] = query.results;
51
- if (schema) {
52
- setCardSchema(schema);
53
- }
54
- },
55
- { fire: true },
56
- );
57
- return unsubscribe;
45
+ if (typename && db) {
46
+ const findInRegistry = () =>
47
+ db.graph.registry
48
+ .list()
49
+ .filter(Type.isType)
50
+ .find((t) => Type.getTypename(t) === typename);
51
+ setCardSchema(() => findInRegistry());
52
+ return db.graph.registry.changed.on(() => {
53
+ setCardSchema(() => findInRegistry());
54
+ });
58
55
  }
56
+ setCardSchema(undefined);
59
57
  }, [schemas, typename, db]);
60
58
 
61
- const objects = useQuery(db, cardSchema ? Filter.type(cardSchema) : Filter.nothing());
62
- const filteredObjects = useGlobalFilteredObjects(objects);
59
+ const baseFilter = useSchemaFilter(cardSchema);
60
+ const query = useMemo(
61
+ () => (tag ? Query.select(baseFilter).select(Filter.tag(tag)) : Query.select(baseFilter)),
62
+ [baseFilter, tag],
63
+ );
64
+ const objects = useQuery(db, query);
65
+
66
+ const sortedObjects = useMemo(
67
+ () =>
68
+ objects.filter(isNonNullable).toSorted((a, b) => (Obj.getLabel(a) ?? '').localeCompare(Obj.getLabel(b) ?? '')),
69
+ [objects],
70
+ );
71
+
72
+ const { results, handleSearch } = useSearchListResults({
73
+ items: sortedObjects,
74
+ extract: (obj) => Obj.getLabel(obj) ?? '',
75
+ });
63
76
 
64
77
  return (
65
- <MasonryComponent.Root
66
- items={filteredObjects}
67
- render={Item as any}
68
- classNames='w-full max-w-full h-full max-h-full overflow-y-auto p-4'
69
- />
78
+ <MasonryComponent.Root Tile={Item}>
79
+ <SearchList.Root onSearch={handleSearch}>
80
+ <Panel.Root>
81
+ <Panel.Toolbar asChild>
82
+ <Toolbar.Root>
83
+ <SearchList.Input placeholder='Search...' />
84
+ </Toolbar.Root>
85
+ </Panel.Toolbar>
86
+ <Panel.Content>
87
+ <MasonryComponent.Content>
88
+ <MasonryComponent.Viewport items={results} getId={(data: any) => data?.id} />
89
+ </MasonryComponent.Content>
90
+ </Panel.Content>
91
+ </Panel.Root>
92
+ </SearchList.Root>
93
+ </MasonryComponent.Root>
70
94
  );
71
95
  };
72
96
 
73
97
  const Item = ({ data }: { data: any }) => {
74
98
  const objectMenuItems = useObjectMenuItems(data);
75
- const icon = Function.pipe(
76
- Obj.getSchema(data),
77
- Option.fromNullable,
78
- Option.flatMap(Annotation.IconAnnotation.get),
79
- Option.map(({ icon }) => icon),
80
- Option.getOrElse(() => 'ph--placeholder--regular'),
81
- );
99
+ const icon = Obj.getIcon(data)?.icon ?? 'ph--circle-dashed--regular';
82
100
 
83
101
  return (
84
102
  <Menu.Root>
85
103
  <Card.Root>
86
- <Card.Toolbar>
104
+ <Card.Header>
87
105
  <Card.Icon icon={icon} />
88
- <Card.Title>{Obj.getLabel(data)}</Card.Title>
106
+ <Card.Title>{Obj.getLabel(data, { fallback: 'typename' })}</Card.Title>
89
107
  {/* TODO(wittjosiah): Reconcile with Card.Menu. */}
90
108
  <Menu.Trigger asChild disabled={!objectMenuItems?.length}>
91
109
  <Toolbar.IconButton iconOnly variant='ghost' icon='ph--dots-three-vertical--regular' label='Actions' />
92
110
  </Menu.Trigger>
93
111
  <Menu.Content items={objectMenuItems} />
94
- </Card.Toolbar>
95
- <Surface.Surface role='card--content' limit={1} data={{ subject: data }} />
112
+ </Card.Header>
113
+ <Surface.Surface
114
+ type={AppSurface.Card}
115
+ limit={1}
116
+ data={{ subject: data } satisfies AppSurface.ObjectCardData}
117
+ />
96
118
  </Card.Root>
97
119
  </Menu.Root>
98
120
  );
@@ -2,6 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { MasonryContainer } from './MasonryContainer';
6
-
7
- export default MasonryContainer;
5
+ export { MasonryContainer as default } from './MasonryContainer';
package/src/index.ts CHANGED
@@ -3,5 +3,4 @@
3
3
  //
4
4
 
5
5
  export * from './meta';
6
-
7
- export * from './MasonryPlugin';
6
+ export * from './types';
package/src/meta.ts CHANGED
@@ -2,18 +2,30 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type Plugin } from '@dxos/app-framework';
5
+ import { Plugin } from '@dxos/app-framework';
6
+ import { DXN } from '@dxos/keys';
6
7
  import { trim } from '@dxos/util';
7
8
 
8
- export const meta: Plugin.Meta = {
9
- id: 'org.dxos.plugin.masonry',
9
+ export const meta = Plugin.makeMeta({
10
+ key: DXN.make('org.dxos.plugin.masonry'),
10
11
  name: 'Masonry',
12
+ author: 'DXOS',
11
13
  description: trim`
12
- Responsive grid layout that displays query results in an adaptive masonry pattern.
13
- Visualize collections of cards, images, or mixed content that automatically adjusts to available screen space.
14
+ Masonry renders a live, query-driven collection as a responsive column-balanced card grid.
15
+
16
+ A Masonry object wraps an ECHO View that defines which objects to show and in what order.
17
+ As objects are added or removed — by any peer — the grid reflows automatically, keeping cards
18
+ balanced across columns without manual arrangement.
19
+
20
+ Each card delegates its body to a Surface slot, so other plugins can supply rich, type-specific
21
+ content while Masonry handles layout, search, and context menus.
22
+
23
+ A built-in search bar filters cards client-side by label without modifying the underlying query,
24
+ making it easy to explore large collections without leaving the view.
14
25
  `,
15
26
  icon: 'ph--wall--regular',
16
- iconHue: 'green',
27
+ iconHue: 'teal',
17
28
  source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-masonry',
29
+ spec: 'PLUGIN.mdl',
18
30
  screenshots: [],
19
- };
31
+ });
package/src/plugin.ts ADDED
@@ -0,0 +1,9 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Plugin } from '@dxos/app-framework';
6
+
7
+ import { meta } from './meta';
8
+
9
+ export const MasonryPlugin = Plugin.lazy(meta, () => import('#plugin'));
@@ -5,25 +5,25 @@
5
5
  import { Type } from '@dxos/echo';
6
6
  import { type Resource } from '@dxos/react-ui';
7
7
 
8
- import { meta } from './meta';
9
- import { Masonry } from './types';
8
+ import { meta } from '#meta';
9
+ import { Masonry } from '#types';
10
10
 
11
11
  export const translations = [
12
12
  {
13
13
  'en-US': {
14
14
  [Type.getTypename(Masonry.Masonry)]: {
15
- 'typename label': 'Masonry',
16
- 'typename label_zero': 'Masonries',
17
- 'typename label_one': 'Masonry',
18
- 'typename label_other': 'Masonries',
19
- 'object name placeholder': 'New masonry',
20
- 'add object label': 'Add masonry',
21
- 'rename object label': 'Rename masonry',
22
- 'delete object label': 'Delete masonry',
23
- 'object deleted label': 'Masonry deleted',
15
+ 'typename.label': 'Masonry',
16
+ 'typename.label_zero': 'Masonries',
17
+ 'typename.label_one': 'Masonry',
18
+ 'typename.label_other': 'Masonries',
19
+ 'object-name.placeholder': 'New masonry',
20
+ 'add-object.label': 'Add masonry',
21
+ 'rename-object.label': 'Rename masonry',
22
+ 'delete-object.label': 'Delete masonry',
23
+ 'object-deleted.label': 'Masonry deleted',
24
24
  },
25
25
  [meta.id]: {
26
- 'plugin name': 'Masonry',
26
+ 'plugin.name': 'Masonry',
27
27
  },
28
28
  },
29
29
  },