@dxos/plugin-board 0.8.4-main.28f8d3d → 0.8.4-main.2c6827d

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 (50) hide show
  1. package/dist/lib/browser/{chunk-V67SBM4U.mjs → chunk-6IYOP7JD.mjs} +12 -7
  2. package/dist/lib/browser/chunk-6IYOP7JD.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-UDI6JPP5.mjs +143 -0
  4. package/dist/lib/browser/chunk-UDI6JPP5.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +46 -47
  6. package/dist/lib/browser/index.mjs.map +3 -3
  7. package/dist/lib/browser/{intent-resolver-6NM32ZSV.mjs → intent-resolver-MGJCQT3N.mjs} +4 -4
  8. package/dist/lib/browser/{intent-resolver-6NM32ZSV.mjs.map → intent-resolver-MGJCQT3N.mjs.map} +1 -1
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/{react-surface-S7H45RGL.mjs → react-surface-PBKZGBFJ.mjs} +5 -5
  11. package/dist/lib/browser/{react-surface-S7H45RGL.mjs.map → react-surface-PBKZGBFJ.mjs.map} +1 -1
  12. package/dist/lib/browser/types/index.mjs +1 -1
  13. package/dist/lib/node-esm/chunk-LJPQ2ELJ.mjs +145 -0
  14. package/dist/lib/node-esm/chunk-LJPQ2ELJ.mjs.map +7 -0
  15. package/dist/lib/node-esm/{chunk-XCGDC6OV.mjs → chunk-WFLKZNL3.mjs} +12 -7
  16. package/dist/lib/node-esm/chunk-WFLKZNL3.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +46 -47
  18. package/dist/lib/node-esm/index.mjs.map +3 -3
  19. package/dist/lib/node-esm/{intent-resolver-5DQ5PPTT.mjs → intent-resolver-TV2AARM7.mjs} +4 -4
  20. package/dist/lib/node-esm/{intent-resolver-5DQ5PPTT.mjs.map → intent-resolver-TV2AARM7.mjs.map} +1 -1
  21. package/dist/lib/node-esm/meta.json +1 -1
  22. package/dist/lib/node-esm/{react-surface-UGBLNFXL.mjs → react-surface-NELFNJ6M.mjs} +5 -5
  23. package/dist/lib/node-esm/{react-surface-UGBLNFXL.mjs.map → react-surface-NELFNJ6M.mjs.map} +1 -1
  24. package/dist/lib/node-esm/types/index.mjs +1 -1
  25. package/dist/types/src/BoardPlugin.d.ts +1 -1
  26. package/dist/types/src/BoardPlugin.d.ts.map +1 -1
  27. package/dist/types/src/capabilities/index.d.ts +1 -1
  28. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  29. package/dist/types/src/capabilities/intent-resolver.d.ts +1 -1
  30. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  31. package/dist/types/src/components/BoardContainer.d.ts +1 -1
  32. package/dist/types/src/components/BoardContainer.d.ts.map +1 -1
  33. package/dist/types/src/components/BoardContainer.stories.d.ts +42 -5
  34. package/dist/types/src/components/BoardContainer.stories.d.ts.map +1 -1
  35. package/dist/types/src/meta.d.ts.map +1 -1
  36. package/dist/types/src/types/Board.d.ts +3 -3
  37. package/dist/types/src/types/Board.d.ts.map +1 -1
  38. package/dist/types/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +43 -42
  40. package/src/BoardPlugin.tsx +41 -42
  41. package/src/components/BoardContainer.stories.tsx +29 -47
  42. package/src/components/BoardContainer.tsx +94 -52
  43. package/src/meta.ts +6 -2
  44. package/src/types/Board.ts +5 -5
  45. package/dist/lib/browser/chunk-2AR36W5A.mjs +0 -119
  46. package/dist/lib/browser/chunk-2AR36W5A.mjs.map +0 -7
  47. package/dist/lib/browser/chunk-V67SBM4U.mjs.map +0 -7
  48. package/dist/lib/node-esm/chunk-AWGKINO6.mjs +0 -121
  49. package/dist/lib/node-esm/chunk-AWGKINO6.mjs.map +0 -7
  50. package/dist/lib/node-esm/chunk-XCGDC6OV.mjs.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-board",
3
- "version": "0.8.4-main.28f8d3d",
3
+ "version": "0.8.4-main.2c6827d",
4
4
  "description": "Surface plugin for card baords",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -36,52 +36,53 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "@preact-signals/safe-react": "^0.9.0",
39
- "@preact/signals-core": "^1.9.0",
40
- "@preact/signals-react": "^3.2.0",
41
- "effect": "3.17.7",
42
- "@dxos/ai": "0.8.4-main.28f8d3d",
43
- "@dxos/app-framework": "0.8.4-main.28f8d3d",
44
- "@dxos/assistant": "0.8.4-main.28f8d3d",
45
- "@dxos/blueprints": "0.8.4-main.28f8d3d",
46
- "@dxos/async": "0.8.4-main.28f8d3d",
47
- "@dxos/echo": "0.8.4-main.28f8d3d",
48
- "@dxos/echo-schema": "0.8.4-main.28f8d3d",
49
- "@dxos/client": "0.8.4-main.28f8d3d",
50
- "@dxos/effect": "0.8.4-main.28f8d3d",
51
- "@dxos/invariant": "0.8.4-main.28f8d3d",
52
- "@dxos/log": "0.8.4-main.28f8d3d",
53
- "@dxos/plugin-client": "0.8.4-main.28f8d3d",
54
- "@dxos/plugin-graph": "0.8.4-main.28f8d3d",
55
- "@dxos/plugin-search": "0.8.4-main.28f8d3d",
56
- "@dxos/random": "0.8.4-main.28f8d3d",
57
- "@dxos/plugin-space": "0.8.4-main.28f8d3d",
58
- "@dxos/react-ui": "0.8.4-main.28f8d3d",
59
- "@dxos/react-ui-form": "0.8.4-main.28f8d3d",
60
- "@dxos/react-client": "0.8.4-main.28f8d3d",
61
- "@dxos/react-ui-stack": "0.8.4-main.28f8d3d",
62
- "@dxos/react-ui-board": "0.8.4-main.28f8d3d",
63
- "@dxos/schema": "0.8.4-main.28f8d3d",
64
- "@dxos/util": "0.8.4-main.28f8d3d"
39
+ "@preact/signals-core": "^1.12.1",
40
+ "@preact/signals-react": "^3.3.0",
41
+ "effect": "3.18.3",
42
+ "@dxos/ai": "0.8.4-main.2c6827d",
43
+ "@dxos/assistant": "0.8.4-main.2c6827d",
44
+ "@dxos/blueprints": "0.8.4-main.2c6827d",
45
+ "@dxos/async": "0.8.4-main.2c6827d",
46
+ "@dxos/client": "0.8.4-main.2c6827d",
47
+ "@dxos/echo": "0.8.4-main.2c6827d",
48
+ "@dxos/effect": "0.8.4-main.2c6827d",
49
+ "@dxos/invariant": "0.8.4-main.2c6827d",
50
+ "@dxos/log": "0.8.4-main.2c6827d",
51
+ "@dxos/plugin-client": "0.8.4-main.2c6827d",
52
+ "@dxos/app-framework": "0.8.4-main.2c6827d",
53
+ "@dxos/plugin-space": "0.8.4-main.2c6827d",
54
+ "@dxos/random": "0.8.4-main.2c6827d",
55
+ "@dxos/react-ui": "0.8.4-main.2c6827d",
56
+ "@dxos/react-client": "0.8.4-main.2c6827d",
57
+ "@dxos/plugin-graph": "0.8.4-main.2c6827d",
58
+ "@dxos/react-ui-form": "0.8.4-main.2c6827d",
59
+ "@dxos/react-ui-board": "0.8.4-main.2c6827d",
60
+ "@dxos/react-ui-stack": "0.8.4-main.2c6827d",
61
+ "@dxos/plugin-search": "0.8.4-main.2c6827d",
62
+ "@dxos/schema": "0.8.4-main.2c6827d",
63
+ "@dxos/types": "0.8.4-main.2c6827d",
64
+ "@dxos/util": "0.8.4-main.2c6827d",
65
+ "@dxos/react-ui-attention": "0.8.4-main.2c6827d"
65
66
  },
66
67
  "devDependencies": {
67
- "@types/react": "~18.2.0",
68
- "@types/react-dom": "~18.2.0",
69
- "react": "~18.2.0",
70
- "react-dom": "~18.2.0",
71
- "vite": "5.4.7",
72
- "@dxos/plugin-preview": "0.8.4-main.28f8d3d",
73
- "@dxos/plugin-theme": "0.8.4-main.28f8d3d",
74
- "@dxos/react-ui-syntax-highlighter": "0.8.4-main.28f8d3d",
75
- "@dxos/test-utils": "0.8.4-main.28f8d3d",
76
- "@dxos/react-ui-theme": "0.8.4-main.28f8d3d",
77
- "@dxos/storybook-utils": "0.8.4-main.28f8d3d"
68
+ "@types/react": "~19.2.2",
69
+ "@types/react-dom": "~19.2.2",
70
+ "react": "~19.2.0",
71
+ "react-dom": "~19.2.0",
72
+ "vite": "7.1.9",
73
+ "@dxos/plugin-preview": "0.8.4-main.2c6827d",
74
+ "@dxos/plugin-theme": "0.8.4-main.2c6827d",
75
+ "@dxos/react-ui-theme": "0.8.4-main.2c6827d",
76
+ "@dxos/test-utils": "0.8.4-main.2c6827d",
77
+ "@dxos/storybook-utils": "0.8.4-main.2c6827d",
78
+ "@dxos/react-ui-syntax-highlighter": "0.8.4-main.2c6827d"
78
79
  },
79
80
  "peerDependencies": {
80
81
  "effect": "^3.13.3",
81
- "react": "~18.2.0",
82
- "react-dom": "~18.2.0",
83
- "@dxos/react-ui": "0.8.4-main.28f8d3d",
84
- "@dxos/react-ui-theme": "0.8.4-main.28f8d3d"
82
+ "react": "^19.0.0",
83
+ "react-dom": "^19.0.0",
84
+ "@dxos/react-ui-theme": "0.8.4-main.2c6827d",
85
+ "@dxos/react-ui": "0.8.4-main.2c6827d"
85
86
  },
86
87
  "publishConfig": {
87
88
  "access": "public"
@@ -13,46 +13,45 @@ import { meta } from './meta';
13
13
  import { translations } from './translations';
14
14
  import { Board } from './types';
15
15
 
16
- export const BoardPlugin = () => {
17
- return definePlugin(meta, [
18
- defineModule({
19
- id: `${meta.id}/module/translations`,
20
- activatesOn: Events.SetupTranslations,
21
- activate: () => contributes(Capabilities.Translations, [...translations, ...boardTranslations]),
22
- }),
23
- defineModule({
24
- id: `${meta.id}/module/metadata`,
25
- activatesOn: Events.SetupMetadata,
26
- activate: () =>
27
- // TODO(burdon): "Metadata" here seems non-descriptive; is this specifically for the type? ObjectMetadata?
28
- contributes(Capabilities.Metadata, {
29
- id: Board.Board.typename,
30
- metadata: {
31
- icon: 'ph--squares-four--regular',
32
- },
16
+ export const BoardPlugin = definePlugin(meta, () => [
17
+ defineModule({
18
+ id: `${meta.id}/module/translations`,
19
+ activatesOn: Events.SetupTranslations,
20
+ activate: () => contributes(Capabilities.Translations, [...translations, ...boardTranslations]),
21
+ }),
22
+ defineModule({
23
+ id: `${meta.id}/module/metadata`,
24
+ activatesOn: Events.SetupMetadata,
25
+ activate: () =>
26
+ // TODO(burdon): "Metadata" here seems non-descriptive; is this specifically for the type? ObjectMetadata?
27
+ contributes(Capabilities.Metadata, {
28
+ id: Board.Board.typename,
29
+ metadata: {
30
+ icon: 'ph--squares-four--regular',
31
+ iconHue: 'green',
32
+ },
33
+ }),
34
+ }),
35
+ defineModule({
36
+ id: `${meta.id}/module/object-form`,
37
+ activatesOn: ClientEvents.SetupSchema,
38
+ activate: () =>
39
+ contributes(
40
+ SpaceCapabilities.ObjectForm,
41
+ defineObjectForm({
42
+ objectSchema: Board.Board,
43
+ getIntent: () => createIntent(Board.Create),
33
44
  }),
34
- }),
35
- defineModule({
36
- id: `${meta.id}/module/object-form`,
37
- activatesOn: ClientEvents.SetupSchema,
38
- activate: () =>
39
- contributes(
40
- SpaceCapabilities.ObjectForm,
41
- defineObjectForm({
42
- objectSchema: Board.Board,
43
- getIntent: () => createIntent(Board.Create),
44
- }),
45
- ),
46
- }),
47
- defineModule({
48
- id: `${meta.id}/module/react-surface`,
49
- activatesOn: Events.SetupReactSurface,
50
- activate: ReactSurface,
51
- }),
52
- defineModule({
53
- id: `${meta.id}/module/intent-resolver`,
54
- activatesOn: Events.SetupIntentResolver,
55
- activate: IntentResolver,
56
- }),
57
- ]);
58
- };
45
+ ),
46
+ }),
47
+ defineModule({
48
+ id: `${meta.id}/module/react-surface`,
49
+ activatesOn: Events.SetupReactSurface,
50
+ activate: ReactSurface,
51
+ }),
52
+ defineModule({
53
+ id: `${meta.id}/module/intent-resolver`,
54
+ activatesOn: Events.SetupIntentResolver,
55
+ activate: IntentResolver,
56
+ }),
57
+ ]);
@@ -2,9 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { type Meta, type StoryObj } from '@storybook/react-vite';
5
+ import { type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useEffect, useState } from 'react';
9
7
 
10
8
  import { IntentPlugin, SettingsPlugin } from '@dxos/app-framework';
@@ -16,12 +14,11 @@ import { SpacePlugin } from '@dxos/plugin-space';
16
14
  import { StorybookLayoutPlugin } from '@dxos/plugin-storybook-layout';
17
15
  import { ThemePlugin } from '@dxos/plugin-theme';
18
16
  import { faker } from '@dxos/random';
19
- import { type Client, useClient } from '@dxos/react-client';
20
- import { Filter, Ref, type Space, useQuery, useSpaces } from '@dxos/react-client/echo';
17
+ import { Filter, Ref, useQuery, useSpaces } from '@dxos/react-client/echo';
18
+ import { withTheme } from '@dxos/react-ui/testing';
21
19
  import { translations as stackTranslations } from '@dxos/react-ui-stack';
22
20
  import { defaultTx } from '@dxos/react-ui-theme';
23
- import { DataType } from '@dxos/schema';
24
- import { withLayout } from '@dxos/storybook-utils';
21
+ import { Organization, Person } from '@dxos/types';
25
22
 
26
23
  import { translations } from '../translations';
27
24
  import { Board } from '../types';
@@ -30,13 +27,8 @@ import { BoardContainer } from './BoardContainer';
30
27
 
31
28
  faker.seed(0);
32
29
 
33
- //
34
- // Initialization utilities
35
- //
36
-
37
- const initializeBoard = async ({ space, client }: { space: Space; client: Client }) => {
38
- // Create a new board
39
- const board = Obj.make(Board.Board, {
30
+ const createBoard = () =>
31
+ Obj.make(Board.Board, {
40
32
  name: 'Test Board',
41
33
  items: [],
42
34
  layout: {
@@ -45,25 +37,17 @@ const initializeBoard = async ({ space, client }: { space: Space; client: Client
45
37
  },
46
38
  });
47
39
 
48
- return { board };
49
- };
50
-
51
- //
52
- // Story components
53
- //
54
-
55
- const rollOrg = () =>
56
- ({
40
+ const createOrg = () =>
41
+ Obj.make(Organization.Organization, {
57
42
  name: faker.commerce.productName(),
58
43
  description: faker.lorem.paragraph(),
59
44
  image: faker.image.url(),
60
45
  website: faker.internet.url(),
61
- status: faker.helpers.arrayElement(DataType.OrganizationStatusOptions).id,
62
- // TODO(thure): Why is this so difficult to type?
63
- }) as unknown as DataType.Organization;
46
+ // TODO(burdon): Fix.
47
+ // status: faker.helpers.arrayElement(Organization.StatusOptions).id,
48
+ });
64
49
 
65
- const StorybookBoard = () => {
66
- const _client = useClient();
50
+ const DefaultStory = () => {
67
51
  const spaces = useSpaces();
68
52
  const space = spaces[spaces.length - 1];
69
53
  const boards = useQuery(space, Filter.type(Board.Board));
@@ -83,37 +67,28 @@ const StorybookBoard = () => {
83
67
  return <BoardContainer role='board' board={board} />;
84
68
  };
85
69
 
86
- type StoryProps = {};
87
-
88
70
  //
89
71
  // Story definitions
90
72
  //
91
73
 
92
- const meta: Meta<StoryProps> = {
74
+ const meta = {
93
75
  title: 'plugins/plugin-board/Board',
94
- component: StorybookBoard,
95
- render: () => <StorybookBoard />,
96
- parameters: { translations: [...translations, ...stackTranslations] },
76
+ render: DefaultStory,
97
77
  decorators: [
98
- withLayout({ fullscreen: true }),
78
+ withTheme,
99
79
  withPluginManager({
100
80
  plugins: [
101
- ThemePlugin({ tx: defaultTx }),
102
81
  ClientPlugin({
103
- types: [DataType.Organization, DataType.Person, Board.Board],
82
+ types: [Organization.Organization, Person.Person, Board.Board],
104
83
  onClientInitialized: async ({ client }) => {
105
84
  await client.halo.createIdentity();
106
85
  const space = await client.spaces.create();
107
86
  await space.waitUntilReady();
108
- const { board } = await initializeBoard({
109
- space,
110
- client,
111
- });
112
- space.db.add(board);
87
+ const board = space.db.add(createBoard());
113
88
 
114
89
  // Add some sample items
115
90
  Array.from({ length: 10 }).map(() => {
116
- const org = Obj.make(DataType.Organization, rollOrg());
91
+ const org = createOrg();
117
92
  space.db.add(org);
118
93
  board.items.push(Ref.make(org));
119
94
  board.layout.cells[org.id] = {
@@ -126,18 +101,25 @@ const meta: Meta<StoryProps> = {
126
101
  });
127
102
  },
128
103
  }),
129
- StorybookLayoutPlugin(),
130
- PreviewPlugin(),
131
- SpacePlugin(),
104
+ SpacePlugin({}),
132
105
  IntentPlugin(),
133
106
  SettingsPlugin(),
107
+
108
+ // UI
109
+ ThemePlugin({ tx: defaultTx }),
110
+ PreviewPlugin(),
111
+ StorybookLayoutPlugin({}),
134
112
  ],
135
113
  }),
136
114
  ],
115
+ parameters: {
116
+ layout: 'fullscreen',
117
+ translations: [...translations, ...stackTranslations],
118
+ },
137
119
  };
138
120
 
139
121
  export default meta;
140
122
 
141
- type Story = StoryObj<StoryProps>;
123
+ type Story = StoryObj<typeof meta>;
142
124
 
143
125
  export const Default: Story = {};
@@ -2,62 +2,72 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { effect } from '@preact/signals-react';
6
- import React, { useCallback, useEffect, useRef, useState } from 'react';
5
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
7
6
 
8
- import { Surface, createIntent, useIntentDispatcher } from '@dxos/app-framework';
7
+ import { Surface } from '@dxos/app-framework/react';
9
8
  import { getSpace } from '@dxos/client/echo';
10
- import { type Obj, Ref, type Type } from '@dxos/echo';
9
+ import { Filter, Obj, Ref } from '@dxos/echo';
11
10
  import { invariant } from '@dxos/invariant';
12
- import { SpaceAction } from '@dxos/plugin-space/types';
13
- import { Board, type BoardController, type BoardRootProps } from '@dxos/react-ui-board';
11
+ import { useQuery } from '@dxos/react-client/echo';
12
+ import { useSignalsMemo } from '@dxos/react-ui';
13
+ import { useAttention } from '@dxos/react-ui-attention';
14
+ import { Board, type BoardController, type BoardRootProps, type Position } from '@dxos/react-ui-board';
15
+ import { ObjectPicker, type ObjectPickerContentProps } from '@dxos/react-ui-form';
14
16
  import { StackItem } from '@dxos/react-ui-stack';
15
17
  import { isNonNullable } from '@dxos/util';
16
18
 
17
- // TODO(thure): There is debate about having to rename either the type or the React component. A best practice should be chosen and either Board or Kanban (or both) should be refactored to apply it.
18
19
  import { type Board as BoardType } from '../types';
19
20
 
21
+ const DEFAULT_POSITION = { x: 0, y: 0 } satisfies Position;
22
+
23
+ type PickerState = {
24
+ position: Position;
25
+ };
26
+
20
27
  export type BoardContainerProps = {
21
28
  role?: string;
22
29
  board: BoardType.Board;
23
30
  };
24
31
 
25
- export const BoardContainer = ({ role, board }: BoardContainerProps) => {
26
- const { dispatchPromise: dispatch } = useIntentDispatcher();
32
+ export const BoardContainer = ({ board }: BoardContainerProps) => {
27
33
  const controller = useRef<BoardController>(null);
34
+ const items = useSignalsMemo(() => board.items.map((ref) => ref.target).filter(isNonNullable), [board]);
35
+ const addTriggerRef = useRef<HTMLButtonElement | null>(null);
36
+ const [pickerState, setPickerState] = useState<PickerState | null>(null);
37
+ const attendableId = Obj.getDXN(board).toString();
38
+ const { hasAttention } = useAttention(attendableId);
28
39
 
29
- // TODO(burdon): Create effect utility for reactive arrays.
30
- const [items, setItems] = useState<Type.Expando[]>([]);
31
- useEffect(() => {
32
- let t: NodeJS.Timeout;
33
- effect(() => {
34
- const refs = [...board.items];
35
- t = setTimeout(async () => {
36
- const items = await Ref.Array.loadAll(refs);
37
- setItems(items.filter(isNonNullable));
38
- });
39
- });
40
-
41
- return () => clearTimeout(t);
42
- }, [board.items]);
40
+ // TODO(burdon): Use search.
41
+ const objects = useQuery(getSpace(board), Filter.everything());
42
+ const options = useMemo<ObjectPickerContentProps['options']>(
43
+ () =>
44
+ objects
45
+ .filter((obj) => obj.id !== board.id)
46
+ .map((obj) => {
47
+ const label = Obj.getLabel(obj);
48
+ if (label) {
49
+ return {
50
+ id: obj.id,
51
+ label,
52
+ hue: 'neutral' as const,
53
+ };
54
+ }
55
+ })
56
+ .filter(isNonNullable)
57
+ .sort(({ label: a }, { label: b }) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase())),
58
+ [objects],
59
+ );
43
60
 
44
61
  const handleAdd = useCallback<NonNullable<BoardRootProps['onAdd']>>(
45
- async (position = { x: 0, y: 0 }) => {
62
+ async (anchor, position = DEFAULT_POSITION) => {
46
63
  const space = getSpace(board);
47
64
  invariant(space);
48
- await dispatch(
49
- createIntent(SpaceAction.OpenCreateObject, {
50
- target: space,
51
- navigable: false,
52
- onCreateObject: (object: Obj.Any) => {
53
- board.items.push(Ref.make(object));
54
- board.layout.cells[object.id] = { ...position, width: 1, height: 1 };
55
- controller.current?.center(position);
56
- },
57
- }),
58
- );
65
+ addTriggerRef.current = anchor;
66
+ setPickerState({
67
+ position,
68
+ });
59
69
  },
60
- [board, controller, dispatch],
70
+ [board],
61
71
  );
62
72
 
63
73
  // TODO(burdon): Use intents so can be undone.
@@ -69,7 +79,6 @@ export const BoardContainer = ({ role, board }: BoardContainerProps) => {
69
79
  board.items.splice(idx, 1);
70
80
  }
71
81
  delete board.layout.cells[id];
72
- setItems((items) => items.filter((item) => item.id !== id));
73
82
  },
74
83
  [board],
75
84
  );
@@ -82,23 +91,56 @@ export const BoardContainer = ({ role, board }: BoardContainerProps) => {
82
91
  [board],
83
92
  );
84
93
 
94
+ const handleSelect = useCallback<NonNullable<ObjectPickerContentProps['onSelect']>>(
95
+ (id) => {
96
+ if (!pickerState) {
97
+ return;
98
+ }
99
+
100
+ // Find the selected object by id from the space.
101
+ const selectedObject = objects.find((obj) => obj.id === id);
102
+ if (!selectedObject) {
103
+ return;
104
+ }
105
+
106
+ // Create a reference to the selected object and add it to the board.
107
+ board.items.push(Ref.make(selectedObject));
108
+
109
+ // Set the layout position for the new item.
110
+ board.layout.cells[selectedObject.id.toString()] = pickerState.position;
111
+
112
+ // Close the picker.
113
+ setPickerState(null);
114
+ },
115
+ [pickerState, objects, board],
116
+ );
117
+
85
118
  return (
86
119
  <Board.Root ref={controller} layout={board.layout} onAdd={handleAdd} onDelete={handleDelete} onMove={handleMove}>
87
- <StackItem.Content toolbar classNames='overflow-hidden'>
88
- <Board.Toolbar />
89
- <Board.Container>
90
- <Board.Viewport classNames='border-none'>
91
- <Board.Backdrop />
92
- <Board.Content>
93
- {items?.map((item, index) => (
94
- <Board.Cell item={item} key={index} layout={board.layout?.cells[item.id] ?? { x: 0, y: 0 }}>
95
- <Surface role='card--extrinsic' data={{ subject: item }} limit={1} />
96
- </Board.Cell>
97
- ))}
98
- </Board.Content>
99
- </Board.Viewport>
100
- </Board.Container>
101
- </StackItem.Content>
120
+ <ObjectPicker.Root
121
+ open={!!pickerState}
122
+ onOpenChange={(nextOpen: boolean) => {
123
+ setPickerState(nextOpen ? { position: DEFAULT_POSITION } : null);
124
+ }}
125
+ >
126
+ <StackItem.Content toolbar>
127
+ <Board.Toolbar disabled={!hasAttention} />
128
+ <Board.Container>
129
+ <Board.Viewport classNames='border-none'>
130
+ <Board.Backdrop />
131
+ <Board.Content>
132
+ {items?.map((item, index) => (
133
+ <Board.Cell item={item} key={index} layout={board.layout?.cells[item.id] ?? { x: 0, y: 0 }}>
134
+ <Surface role='card--extrinsic' data={{ subject: item }} limit={1} />
135
+ </Board.Cell>
136
+ ))}
137
+ </Board.Content>
138
+ </Board.Viewport>
139
+ </Board.Container>
140
+ </StackItem.Content>
141
+ <ObjectPicker.Content options={options} onSelect={handleSelect} classNames='popover-card-width' />
142
+ <ObjectPicker.VirtualTrigger virtualRef={addTriggerRef} />
143
+ </ObjectPicker.Root>
102
144
  </Board.Root>
103
145
  );
104
146
  };
package/src/meta.ts CHANGED
@@ -3,13 +3,17 @@
3
3
  //
4
4
 
5
5
  import { type PluginMeta } from '@dxos/app-framework';
6
+ import { trim } from '@dxos/util';
6
7
 
7
8
  export const meta: PluginMeta = {
8
9
  id: 'dxos.org/plugin/board',
9
10
  name: 'Board',
10
- description:
11
- 'A spatial, infinite canvas app combining notes, media, and whiteboarding in a tactile, visual interface.',
11
+ description: trim`
12
+ Infinite canvas workspace that combines sticky notes, media, and whiteboarding tools.
13
+ Arrange and connect ideas freely in a visual space perfect for brainstorming and creative collaboration.
14
+ `,
12
15
  icon: 'ph--squares-four--regular',
16
+ iconHue: 'green',
13
17
  source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-board',
14
18
  screenshots: [],
15
19
  };
@@ -2,10 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
 
7
7
  import { Obj, Type } from '@dxos/echo';
8
- import { LabelAnnotation } from '@dxos/echo-schema';
8
+ import { FormAnnotation, LabelAnnotation } from '@dxos/echo/internal';
9
9
  import { BoardLayout, defaultLayout } from '@dxos/react-ui-board';
10
10
 
11
11
  import { meta } from '../meta';
@@ -14,9 +14,9 @@ import { meta } from '../meta';
14
14
  * Board and layout.
15
15
  */
16
16
  export const Board = Schema.Struct({
17
- name: Schema.optional(Schema.String),
18
- items: Schema.mutable(Schema.Array(Type.Ref(Type.Expando))),
19
- layout: Schema.mutable(BoardLayout),
17
+ name: Schema.String.pipe(Schema.optional),
18
+ items: Type.Ref(Type.Expando).pipe(Schema.Array, Schema.mutable, FormAnnotation.set(false)),
19
+ layout: BoardLayout.pipe(Schema.mutable, FormAnnotation.set(false)),
20
20
  }).pipe(
21
21
  Type.Obj({
22
22
  typename: 'dxos.org/type/Board',