@dxos/plugin-pipeline 0.8.4-main.3eb6e50203 → 0.8.4-main.c85a9c8dae

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 (68) hide show
  1. package/dist/lib/browser/PipelineContainer-2ZYSZATA.mjs +320 -0
  2. package/dist/lib/browser/PipelineContainer-2ZYSZATA.mjs.map +7 -0
  3. package/dist/lib/browser/{PipelineSettings-YSGSJHBT.mjs → PipelineObjectSettings-D36OYVHW.mjs} +66 -54
  4. package/dist/lib/browser/PipelineObjectSettings-D36OYVHW.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-7GVESVYR.mjs → chunk-N6FZHYWT.mjs} +2 -2
  6. package/dist/lib/browser/chunk-N6FZHYWT.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +4 -4
  8. package/dist/lib/browser/index.mjs.map +3 -3
  9. package/dist/lib/browser/meta.json +1 -1
  10. package/dist/lib/browser/{react-surface-JMMCTIL3.mjs → react-surface-ZGQVH4VX.mjs} +11 -8
  11. package/dist/lib/browser/react-surface-ZGQVH4VX.mjs.map +7 -0
  12. package/dist/lib/browser/types/index.mjs +1 -1
  13. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +1 -1
  14. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +1 -1
  15. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +1 -1
  16. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -1
  17. package/dist/types/src/components/PipelineColumn.d.ts +3 -4
  18. package/dist/types/src/components/PipelineColumn.d.ts.map +1 -1
  19. package/dist/types/src/components/PipelineComponent.d.ts +20 -11
  20. package/dist/types/src/components/PipelineComponent.d.ts.map +1 -1
  21. package/dist/types/src/components/PipelineComponent.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/index.d.ts +2 -4
  23. package/dist/types/src/components/index.d.ts.map +1 -1
  24. package/dist/types/src/{components → containers/PipelineContainer}/PipelineContainer.d.ts +2 -3
  25. package/dist/types/src/containers/PipelineContainer/PipelineContainer.d.ts.map +1 -0
  26. package/dist/types/src/containers/PipelineContainer/PipelineContainer.stories.d.ts.map +1 -0
  27. package/dist/types/src/containers/PipelineContainer/index.d.ts +3 -0
  28. package/dist/types/src/containers/PipelineContainer/index.d.ts.map +1 -0
  29. package/dist/types/src/{components/PipelineSettings.d.ts → containers/PipelineObjectSettings/PipelineObjectSettings.d.ts} +1 -2
  30. package/dist/types/src/containers/PipelineObjectSettings/PipelineObjectSettings.d.ts.map +1 -0
  31. package/dist/types/src/containers/PipelineObjectSettings/index.d.ts +3 -0
  32. package/dist/types/src/containers/PipelineObjectSettings/index.d.ts.map +1 -0
  33. package/dist/types/src/containers/index.d.ts +4 -0
  34. package/dist/types/src/containers/index.d.ts.map +1 -0
  35. package/dist/types/src/hooks/index.d.ts +2 -0
  36. package/dist/types/src/hooks/index.d.ts.map +1 -0
  37. package/dist/types/src/hooks/usePipelineBoardModel.d.ts +6 -0
  38. package/dist/types/src/hooks/usePipelineBoardModel.d.ts.map +1 -0
  39. package/dist/types/src/templates/research.d.ts.map +1 -1
  40. package/dist/types/src/types/PipelineAction.d.ts +1 -1
  41. package/dist/types/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +42 -39
  43. package/src/capabilities/operation-resolver/operation-resolver.ts +2 -2
  44. package/src/capabilities/react-surface/react-surface.tsx +7 -5
  45. package/src/components/PipelineColumn.tsx +24 -36
  46. package/src/components/PipelineComponent.stories.tsx +27 -14
  47. package/src/components/PipelineComponent.tsx +55 -27
  48. package/src/components/index.ts +2 -7
  49. package/src/{components → containers/PipelineContainer}/PipelineContainer.stories.tsx +91 -88
  50. package/src/containers/PipelineContainer/PipelineContainer.tsx +87 -0
  51. package/src/containers/PipelineContainer/index.ts +7 -0
  52. package/src/{components/PipelineSettings.tsx → containers/PipelineObjectSettings/PipelineObjectSettings.tsx} +65 -53
  53. package/src/containers/PipelineObjectSettings/index.ts +7 -0
  54. package/src/containers/index.ts +8 -0
  55. package/src/hooks/index.ts +5 -0
  56. package/src/hooks/usePipelineBoardModel.ts +79 -0
  57. package/src/templates/research.ts +14 -16
  58. package/src/types/PipelineAction.ts +1 -1
  59. package/dist/lib/browser/PipelineContainer-I3TPA7ZN.mjs +0 -217
  60. package/dist/lib/browser/PipelineContainer-I3TPA7ZN.mjs.map +0 -7
  61. package/dist/lib/browser/PipelineSettings-YSGSJHBT.mjs.map +0 -7
  62. package/dist/lib/browser/chunk-7GVESVYR.mjs.map +0 -7
  63. package/dist/lib/browser/react-surface-JMMCTIL3.mjs.map +0 -7
  64. package/dist/types/src/components/PipelineContainer.d.ts.map +0 -1
  65. package/dist/types/src/components/PipelineContainer.stories.d.ts.map +0 -1
  66. package/dist/types/src/components/PipelineSettings.d.ts.map +0 -1
  67. package/src/components/PipelineContainer.tsx +0 -57
  68. /package/dist/types/src/{components → containers/PipelineContainer}/PipelineContainer.stories.d.ts +0 -0
@@ -4,28 +4,30 @@
4
4
 
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Effect from 'effect/Effect';
7
+ import * as Layer from 'effect/Layer';
7
8
  import React from 'react';
8
9
 
9
10
  import { withPluginManager } from '@dxos/app-framework/testing';
10
11
  import { Filter, Ref } from '@dxos/client/echo';
11
- import { Obj, Query, Tag, Type } from '@dxos/echo';
12
+ import { Database, Feed, JsonSchema, Obj, Query, Tag } from '@dxos/echo';
13
+ import { Collection, View } from '@dxos/echo';
14
+ import { createFeedServiceLayer } from '@dxos/echo-db';
12
15
  import { ClientPlugin } from '@dxos/plugin-client';
13
16
  import { InboxPlugin } from '@dxos/plugin-inbox';
14
17
  import { PreviewPlugin } from '@dxos/plugin-preview';
15
18
  import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
16
19
  import { faker } from '@dxos/random';
17
20
  import { useDatabase, useQuery } from '@dxos/react-client/echo';
18
- import { withTheme } from '@dxos/react-ui/testing';
21
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
19
22
  import { translations as stackTranslations } from '@dxos/react-ui-stack';
20
- import { Stack } from '@dxos/react-ui-stack';
21
- import { Collection, View } from '@dxos/schema';
23
+ import { ViewModel } from '@dxos/schema';
22
24
  import { createObjectFactory } from '@dxos/schema/testing';
23
25
  import { Message, Organization, Person, Pipeline, Task } from '@dxos/types';
24
26
 
25
- import { translations } from '../translations';
27
+ import { translations } from '../../translations';
28
+ import PipelineObjectSettings from '../PipelineObjectSettings';
26
29
 
27
30
  import { PipelineContainer } from './PipelineContainer';
28
- import { PipelineObjectSettings } from './PipelineSettings';
29
31
 
30
32
  faker.seed(0);
31
33
 
@@ -39,18 +41,19 @@ const DefaultStory = () => {
39
41
  }
40
42
 
41
43
  return (
42
- <Stack orientation='horizontal' size='split' rail={false} classNames='pli-0'>
44
+ <div className='grow grid grid-cols-[1fr_350px] overflow-hidden h-full w-full'>
43
45
  <PipelineContainer role='article' subject={pipeline} />
44
- <PipelineObjectSettings pipeline={pipeline} classNames='border-is border-separator' />
45
- </Stack>
46
+ <PipelineObjectSettings pipeline={pipeline} classNames='border-s border-separator' />
47
+ </div>
46
48
  );
47
49
  };
48
50
 
49
51
  const meta = {
50
- title: 'plugins/plugin-pipeline/PipelineContainer',
52
+ title: 'plugins/plugin-pipeline/containers/PipelineContainer',
51
53
  render: DefaultStory,
52
54
  decorators: [
53
55
  withTheme(),
56
+ withLayout({ layout: 'fullscreen' }),
54
57
  withPluginManager({
55
58
  plugins: [
56
59
  ...corePlugins(),
@@ -58,6 +61,7 @@ const meta = {
58
61
  ClientPlugin({
59
62
  types: [
60
63
  Tag.Tag,
64
+ Feed.Feed,
61
65
  Pipeline.Pipeline,
62
66
  View.View,
63
67
  Collection.Collection,
@@ -66,50 +70,71 @@ const meta = {
66
70
  Person.Person,
67
71
  Message.Message,
68
72
  ],
69
- onClientInitialized: ({ client }) =>
70
- Effect.gen(function* () {
71
- yield* Effect.promise(() => client.halo.createIdentity());
72
- yield* Effect.promise(() => client.spaces.waitUntilReady());
73
- const space = client.spaces.default;
74
- yield* Effect.promise(() => space.waitUntilReady());
75
-
76
- const tag = space.db.add(Tag.make({ label: 'important', hue: 'green' }));
73
+ onClientInitialized: Effect.fnUntraced(function* ({ client }) {
74
+ yield* Effect.promise(() => client.halo.createIdentity());
75
+ yield* Effect.promise(() => client.spaces.waitUntilReady());
76
+ const space = client.spaces.default;
77
+ yield* Effect.promise(() => space.waitUntilReady());
78
+
79
+ yield* Effect.gen(function* () {
80
+ const tag = yield* Database.add(Tag.make({ label: 'important', hue: 'green' }));
77
81
  const tagDxn = Obj.getDXN(tag).toString();
78
82
 
79
83
  // Create a view for Contacts.
80
- const personView = View.make({
84
+ const personView = ViewModel.make({
81
85
  query: Query.select(Filter.type(Person.Person)),
82
- jsonSchema: Type.toJsonSchema(Person.Person),
86
+ jsonSchema: JsonSchema.toJsonSchema(Person.Person),
83
87
  });
84
88
 
85
89
  // Create a view for Organizations.
86
- const organizationView = View.make({
90
+ const organizationView = ViewModel.make({
87
91
  query: Query.select(Filter.type(Organization.Organization)).select(Filter.tag(tagDxn)),
88
- jsonSchema: Type.toJsonSchema(Organization.Organization),
92
+ jsonSchema: JsonSchema.toJsonSchema(Organization.Organization),
89
93
  });
90
94
 
91
95
  // Create a view for Tasks.
92
- const taskView = View.make({
96
+ const taskView = ViewModel.make({
93
97
  query: Query.select(Filter.type(Task.Task)).select(Filter.tag(tagDxn)),
94
- jsonSchema: Type.toJsonSchema(Task.Task),
98
+ jsonSchema: JsonSchema.toJsonSchema(Task.Task),
95
99
  });
96
100
 
97
101
  // Create a view for Project-Projects.
98
- const projectView = View.make({
102
+ const projectView = ViewModel.make({
99
103
  query: Query.select(Filter.type(Pipeline.Pipeline)),
100
- jsonSchema: Type.toJsonSchema(Pipeline.Pipeline),
104
+ jsonSchema: JsonSchema.toJsonSchema(Pipeline.Pipeline),
101
105
  });
102
106
 
103
- // Create a view for Messages.
104
- const messageQueue = space.queues.create();
105
- const messageView = View.make({
106
- query: Query.select(Filter.type(Message.Message)).options({
107
- queues: [messageQueue.dxn.toString()],
107
+ // Create a feed for Messages.
108
+ const messageFeed = yield* Database.add(Feed.make({ name: 'Messages' }));
109
+ const messages = Array.from({ length: 20 }).map(() =>
110
+ Obj.make(Message.Message, {
111
+ created: faker.date.recent().toISOString(),
112
+ sender: { role: 'user' },
113
+ blocks: [{ _tag: 'text' as const, text: faker.lorem.sentences(2) }],
114
+ }),
115
+ );
116
+ yield* Feed.append(messageFeed, messages);
117
+
118
+ const messageQueueDxn = Feed.getQueueDxn(messageFeed)!.toString();
119
+ const messageView = ViewModel.make({
120
+ query: Query.select(Filter.type(Message.Message)).from({
121
+ queues: [messageQueueDxn],
108
122
  }),
109
- jsonSchema: Type.toJsonSchema(Message.Message),
123
+ jsonSchema: JsonSchema.toJsonSchema(Message.Message),
110
124
  });
111
125
 
112
- // Create pipline with columns.
126
+ // Create a feed for Tasks (for testing feed switching in settings).
127
+ const taskFeed = yield* Database.add(Feed.make({ name: 'Tasks' }));
128
+ const feedTasks = Array.from({ length: 10 }).map(() =>
129
+ Obj.make(Task.Task, {
130
+ title: faker.lorem.sentence(),
131
+ status: faker.helpers.arrayElement(['todo', 'in-progress', 'done']) as any,
132
+ priority: faker.helpers.arrayElement(['low', 'medium', 'high']) as any,
133
+ }),
134
+ );
135
+ yield* Feed.append(taskFeed, feedTasks);
136
+
137
+ // Create pipeline with columns.
113
138
  const pipeline = Pipeline.make({
114
139
  columns: [
115
140
  {
@@ -139,75 +164,53 @@ const meta = {
139
164
  },
140
165
  ],
141
166
  });
142
-
143
- // Add pipeline to space.
144
- space.db.add(pipeline);
145
-
146
- // Generate sample Organizations
147
- Array.from({ length: 5 }).forEach(() => {
148
- const org = Obj.make(
149
- Organization.Organization,
150
- {
167
+ yield* Database.add(pipeline);
168
+
169
+ // Generate sample Organizations.
170
+ for (const _ of Array.from({ length: 20 })) {
171
+ yield* Database.add(
172
+ Obj.make(Organization.Organization, {
173
+ [Obj.Meta]: {
174
+ tags: faker.datatype.boolean() ? [tagDxn] : [],
175
+ },
151
176
  name: faker.company.name(),
152
177
  website: faker.internet.url(),
153
178
  description: faker.lorem.paragraph(),
154
179
  image: faker.image.url(),
155
- },
156
- {
157
- tags: faker.datatype.boolean() ? [Obj.getDXN(tag).toString()] : [],
158
- },
180
+ }),
159
181
  );
160
- space.db.add(org);
161
- });
162
-
163
- // Generate sample Tasks
164
- Array.from({ length: 8 }).forEach(() => {
165
- const task = Obj.make(
166
- Task.Task,
167
- {
182
+ }
183
+
184
+ // Generate sample Tasks.
185
+ for (const _ of Array.from({ length: 20 })) {
186
+ yield* Database.add(
187
+ Obj.make(Task.Task, {
188
+ [Obj.Meta]: {
189
+ tags: faker.datatype.boolean() ? [tagDxn] : [],
190
+ },
168
191
  title: faker.lorem.sentence(),
169
192
  status: faker.helpers.arrayElement(['todo', 'in-progress', 'done']) as any,
170
193
  priority: faker.helpers.arrayElement(['low', 'medium', 'high']) as any,
171
- },
172
- {
173
- tags: faker.datatype.boolean() ? [Obj.getDXN(tag).toString()] : [],
174
- },
194
+ }),
175
195
  );
176
- space.db.add(task);
177
- });
196
+ }
178
197
 
179
- // Generate sample Contacts
198
+ // Generate sample Contacts.
180
199
  const factory = createObjectFactory(space.db, faker as any);
181
200
  yield* Effect.promise(() => factory([{ type: Person.Person, count: 12 }]));
182
201
 
183
- // Generate sample Projects
184
- Array.from({ length: 3 }).forEach(() => {
185
- const nestedProject = Pipeline.make({
186
- name: faker.commerce.productName(),
187
- description: faker.lorem.sentence(),
188
- });
189
- space.db.add(nestedProject);
190
- });
191
-
192
- // Generate sample Messages
193
- const messages = Array.from({ length: 6 }).map(() => {
194
- const message = Obj.make(Message.Message, {
195
- created: faker.date.recent().toISOString(),
196
- sender: { role: 'user' },
197
- blocks: [
198
- {
199
- _tag: 'text' as const,
200
- text: faker.lorem.sentences(2),
201
- },
202
- ],
203
- });
204
- return message;
205
- });
206
-
207
- yield* Effect.promise(() => messageQueue.append(messages));
208
- }),
202
+ // Generate sample Projects.
203
+ for (const _ of Array.from({ length: 20 })) {
204
+ yield* Database.add(
205
+ Pipeline.make({
206
+ name: faker.commerce.productName(),
207
+ description: faker.lorem.sentence(),
208
+ }),
209
+ );
210
+ }
211
+ }).pipe(Effect.provide(Layer.merge(Database.layer(space.db), createFeedServiceLayer(space.queues))));
212
+ }),
209
213
  }),
210
-
211
214
  InboxPlugin(),
212
215
  PreviewPlugin(),
213
216
  ],
@@ -0,0 +1,87 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback, useEffect } from 'react';
6
+
7
+ import { Capabilities } from '@dxos/app-framework';
8
+ import { useCapability } from '@dxos/app-framework/ui';
9
+ import { useOperationInvoker } from '@dxos/app-framework/ui';
10
+ import { Surface } from '@dxos/app-framework/ui';
11
+ import {
12
+ OBJECT_ACTIONS_CONTRIBUTION_ID,
13
+ OBJECT_ACTIONS_CONTRIBUTION_PRIORITY,
14
+ type SurfaceComponentProps,
15
+ useObjectMenuItems,
16
+ } from '@dxos/app-toolkit/ui';
17
+ import { Obj } from '@dxos/echo';
18
+ import { ATTENDABLE_PATH_SEPARATOR, DeckOperation } from '@dxos/plugin-deck/types';
19
+ import { Panel } from '@dxos/react-ui';
20
+ import { useAttention } from '@dxos/react-ui-attention';
21
+ import { useMenu } from '@dxos/react-ui-menu';
22
+ import { type Pipeline } from '@dxos/types';
23
+
24
+ import { type ItemProps, PipelineComponent } from '../../components';
25
+ import { usePipelineBoardModel } from '../../hooks';
26
+
27
+ const PIPELINE_ITEM = 'PipelineItem';
28
+
29
+ export type PipelineContainerProps = SurfaceComponentProps<Pipeline.Pipeline>;
30
+
31
+ export const PipelineContainer = ({ role, subject: pipeline }: PipelineContainerProps) => {
32
+ const registry = useCapability(Capabilities.AtomRegistry);
33
+ const model = usePipelineBoardModel(pipeline, registry);
34
+ const { invokePromise } = useOperationInvoker();
35
+ const attendableId = Obj.getDXN(pipeline).toString();
36
+ const { hasAttention } = useAttention(attendableId);
37
+
38
+ const handleColumnAdd = useCallback(
39
+ () =>
40
+ invokePromise(DeckOperation.ChangeCompanion, {
41
+ companion: `${attendableId}${ATTENDABLE_PATH_SEPARATOR}settings`,
42
+ }),
43
+ [invokePromise, attendableId],
44
+ );
45
+
46
+ return (
47
+ <PipelineComponent.Root Item={PipelineItem} onAddColumn={handleColumnAdd}>
48
+ <Panel.Root role={role} className='dx-article'>
49
+ <Panel.Toolbar asChild>
50
+ <PipelineComponent.Toolbar disabled={!hasAttention} />
51
+ </Panel.Toolbar>
52
+ <Panel.Content asChild>
53
+ <PipelineComponent.Content model={model}>
54
+ <PipelineComponent.Columns pipeline={pipeline} />
55
+ </PipelineComponent.Content>
56
+ </Panel.Content>
57
+ </Panel.Root>
58
+ </PipelineComponent.Root>
59
+ );
60
+ };
61
+
62
+ const PipelineItem = ({ item, projectionModel }: ItemProps) => {
63
+ const menu = useMenu(PIPELINE_ITEM);
64
+ const items = useObjectMenuItems(item);
65
+
66
+ useEffect(() => {
67
+ menu.addMenuItems({
68
+ id: OBJECT_ACTIONS_CONTRIBUTION_ID,
69
+ mode: 'additive',
70
+ priority: OBJECT_ACTIONS_CONTRIBUTION_PRIORITY,
71
+ items,
72
+ });
73
+
74
+ return () => menu.removeMenuItems(OBJECT_ACTIONS_CONTRIBUTION_ID);
75
+ }, [menu, items]);
76
+
77
+ return (
78
+ <Surface.Surface
79
+ role='card--content'
80
+ data={{
81
+ subject: item,
82
+ projection: projectionModel,
83
+ }}
84
+ limit={1}
85
+ />
86
+ );
87
+ };
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { PipelineContainer } from './PipelineContainer';
6
+
7
+ export default PipelineContainer;
@@ -5,20 +5,20 @@
5
5
  import * as Schema from 'effect/Schema';
6
6
  import React, { useCallback, useMemo, useRef, useState } from 'react';
7
7
 
8
- import { DXN, Filter, Obj, Query, type QueryAST, Ref, Tag, Type } from '@dxos/echo';
9
- import { type Mutable } from '@dxos/echo/internal';
8
+ import { DXN, Filter, JsonSchema, Obj, Query, type QueryAST, Ref, Tag } from '@dxos/echo';
9
+ import { type JsonPath, type Mutable } from '@dxos/echo/internal';
10
10
  import { useTypeOptions } from '@dxos/plugin-space';
11
11
  import { resolveSchemaWithRegistry } from '@dxos/plugin-space';
12
- import { getSpace, useQuery } from '@dxos/react-client/echo';
12
+ import { getSpace, useObject, useQuery } from '@dxos/react-client/echo';
13
13
  import { IconButton, type ThemedClassName, useAsyncEffect, useTranslation } from '@dxos/react-ui';
14
14
  import { Form, ViewEditor } from '@dxos/react-ui-form';
15
15
  import { List } from '@dxos/react-ui-list';
16
- import { type ProjectionModel, View } from '@dxos/schema';
16
+ import { type ProjectionModel, ViewModel } from '@dxos/schema';
17
17
  import { Pipeline, Task } from '@dxos/types';
18
18
  import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
19
19
  import { arrayMove } from '@dxos/util';
20
20
 
21
- import { meta } from '../meta';
21
+ import { meta } from '../../meta';
22
22
 
23
23
  const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
24
24
  const listItemGrid = 'grid grid-cols-subgrid col-span-5';
@@ -37,11 +37,12 @@ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectS
37
37
  const { t } = useTranslation(meta.id);
38
38
  const space = getSpace(pipeline);
39
39
  const [expandedId, setExpandedId] = useState<string>();
40
+ const [columns, updateColumns] = useObject(pipeline, 'columns');
40
41
  const column = useMemo(
41
- () => pipeline.columns.find((column) => column.view.dxn.toString() === expandedId),
42
- [pipeline.columns, expandedId],
42
+ () => columns.find((column) => column.view.dxn.toString() === expandedId),
43
+ [columns, expandedId],
43
44
  );
44
- const view = column?.view.target;
45
+ const [view, updateView] = useObject(column?.view);
45
46
  const [schema, setSchema] = useState<Schema.Schema.AnyNoContext>(() => Schema.Struct({}));
46
47
  const projectionRef = useRef<ProjectionModel>(null);
47
48
  const tags = useQuery(space?.db, Filter.type(Tag.Tag));
@@ -67,10 +68,10 @@ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectS
67
68
 
68
69
  const handleMove = useCallback(
69
70
  (fromIndex: number, toIndex: number) =>
70
- Obj.change(pipeline, (p) => {
71
- arrayMove(p.columns, fromIndex, toIndex);
71
+ updateColumns((columns) => {
72
+ arrayMove(columns, fromIndex, toIndex);
72
73
  }),
73
- [pipeline],
74
+ [updateColumns],
74
75
  );
75
76
 
76
77
  const handleQueryChanged = useCallback(
@@ -80,26 +81,42 @@ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectS
80
81
  }
81
82
 
82
83
  const queue = target && DXN.tryParse(target) ? target : undefined;
83
- const query = queue ? Query.fromAst(newQuery).options({ queues: [queue] }) : Query.fromAst(newQuery);
84
- Obj.change(view, (v) => {
85
- v.query.ast = query.ast as Mutable<typeof query.ast>;
84
+ const query = queue ? Query.fromAst(newQuery).from({ queues: [queue] }) : Query.fromAst(newQuery);
85
+ updateView((view) => {
86
+ view.query.ast = query.ast as Mutable<typeof query.ast>;
86
87
  });
87
88
  const newSchema = await resolveSchemaWithRegistry(space.db.schemaRegistry, query.ast);
88
89
  if (!newSchema) {
89
90
  return;
90
91
  }
91
92
 
92
- const newView = View.make({
93
+ const newView = ViewModel.make({
93
94
  query,
94
- jsonSchema: Type.toJsonSchema(newSchema),
95
+ jsonSchema: JsonSchema.toJsonSchema(newSchema),
95
96
  });
96
- Obj.change(view, (v) => {
97
- v.projection = Obj.getSnapshot(newView).projection as Mutable<typeof v.projection>;
97
+ updateView((view) => {
98
+ view.projection = Obj.getSnapshot(newView).projection as Mutable<typeof view.projection>;
98
99
  });
99
100
 
100
101
  setSchema(() => newSchema);
101
102
  },
102
- [view, schema],
103
+ [view, updateView, schema],
104
+ );
105
+
106
+ const handleColumnValuesChanged = useCallback(
107
+ (column: Pipeline.Column) =>
108
+ (newValues: { name?: string }, { changed }: { changed: Record<JsonPath, boolean> }) => {
109
+ if (changed['name' as JsonPath]) {
110
+ const columnIndex = columns.findIndex((c) => c === column);
111
+ if (columnIndex === -1) {
112
+ return;
113
+ }
114
+ updateColumns((cols) => {
115
+ cols[columnIndex].name = newValues.name ?? '';
116
+ });
117
+ }
118
+ },
119
+ [columns, updateColumns],
103
120
  );
104
121
 
105
122
  const handleToggleField = useCallback((column: Pipeline.Column) => {
@@ -114,50 +131,42 @@ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectS
114
131
  setExpandedId(undefined);
115
132
  }
116
133
 
117
- const index = pipeline.columns.findIndex((l) => l === column);
134
+ const index = columns.findIndex((l) => l === column);
118
135
  const viewToRemove = await column.view.load();
119
- Obj.change(pipeline, (p) => {
120
- p.columns.splice(index, 1);
136
+ updateColumns((columns) => {
137
+ columns.splice(index, 1);
121
138
  });
122
139
  space?.db.remove(viewToRemove);
123
140
  },
124
- [expandedId, pipeline.columns, space],
141
+ [expandedId, columns, updateColumns, space],
125
142
  );
126
143
 
127
144
  const handleAdd = useCallback(() => {
128
- const newView = View.make({
145
+ if (!space) {
146
+ return;
147
+ }
148
+ const newView = ViewModel.make({
129
149
  query: Query.select(Filter.type(Task.Task)),
130
- jsonSchema: Type.toJsonSchema(Task.Task),
150
+ jsonSchema: JsonSchema.toJsonSchema(Task.Task),
131
151
  });
132
- Obj.change(pipeline, (p) => {
133
- p.columns.push({
152
+ space.db.add(newView);
153
+ updateColumns((columns) => {
154
+ columns.push({
134
155
  name: 'Tasks',
135
156
  // Type assertion needed due to QueryAST type variance.
136
- view: Ref.make(newView) as (typeof p.columns)[number]['view'],
157
+ view: Ref.make(newView) as (typeof columns)[number]['view'],
137
158
  order: [],
138
159
  });
139
160
  });
140
161
  setExpandedId(newView.id);
141
- }, [pipeline]);
142
-
143
- const handleColumnSave = useCallback(
144
- (values: Schema.Schema.Type<typeof ColumnFormSchema>) => {
145
- if (column) {
146
- const columnIndex = pipeline.columns.findIndex((c) => c === column);
147
- Obj.change(pipeline, (p) => {
148
- p.columns[columnIndex].name = values.name;
149
- });
150
- }
151
- },
152
- [column, pipeline],
153
- );
162
+ }, [space, updateColumns]);
154
163
 
155
164
  return (
156
- <div role='none' className={mx('plb-cardSpacingBlock overflow-y-auto', classNames)}>
157
- <h2 className='text-sm text-description plb-1'>{t('views label')}</h2>
165
+ <div role='none' className={mx('py-form-padding overflow-y-auto', classNames)}>
166
+ <h2 className='text-sm text-description py-1'>{t('views label')}</h2>
158
167
 
159
168
  <List.Root<Pipeline.Column>
160
- items={pipeline.columns}
169
+ items={columns}
161
170
  isItem={Schema.is(Pipeline.Column)}
162
171
  getId={(column) => column.view.dxn.toString()}
163
172
  onMove={handleMove}
@@ -172,7 +181,7 @@ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectS
172
181
  classNames={listItemGrid}
173
182
  aria-expanded={expandedId === column.view.dxn.toString()}
174
183
  >
175
- <div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
184
+ <div role='none' className={mx(subtleHover, listItemGrid, 'rounded-xs cursor-pointer min-h-10')}>
176
185
  <List.ItemDragHandle />
177
186
  <List.ItemTitle onClick={() => handleToggleField(column)}>{column.name}</List.ItemTitle>
178
187
  <List.ItemDeleteButton
@@ -193,9 +202,13 @@ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectS
193
202
  onClick={() => handleToggleField(column)}
194
203
  />
195
204
  </div>
196
- {expandedId === column.view.dxn.toString() && view && (
197
- <div role='none' className='col-span-5 mlb-2 border border-separator rounded-md'>
198
- <Form.Root schema={ColumnFormSchema} values={column} autoSave onSave={handleColumnSave}>
205
+ {expandedId === column.view.dxn.toString() && column?.view.target && (
206
+ <div role='none' className='col-span-5 my-2 border border-separator rounded-md'>
207
+ <Form.Root
208
+ schema={ColumnFormSchema}
209
+ values={column}
210
+ onValuesChanged={handleColumnValuesChanged(column)}
211
+ >
199
212
  <Form.Content>
200
213
  <Form.FieldSet />
201
214
  </Form.Content>
@@ -205,8 +218,9 @@ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectS
205
218
  mode='tag'
206
219
  readonly
207
220
  schema={schema}
208
- view={view}
221
+ view={column.view.target}
209
222
  registry={space?.db.schemaRegistry}
223
+ db={space?.db}
210
224
  tags={tags}
211
225
  types={types}
212
226
  onQueryChanged={handleQueryChanged}
@@ -220,11 +234,9 @@ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectS
220
234
  )}
221
235
  </List.Root>
222
236
 
223
- <div role='none' className='mlb-cardSpacingBlock'>
224
- <IconButton icon='ph--plus--regular' label={t('add view label')} onClick={handleAdd} classNames='is-full' />
237
+ <div role='none' className='my-form-padding'>
238
+ <IconButton icon='ph--plus--regular' label={t('add view label')} onClick={handleAdd} classNames='w-full' />
225
239
  </div>
226
240
  </div>
227
241
  );
228
242
  };
229
-
230
- export default PipelineObjectSettings;
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { PipelineObjectSettings } from './PipelineObjectSettings';
6
+
7
+ export default PipelineObjectSettings;
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type ComponentType, lazy } from 'react';
6
+
7
+ export const PipelineContainer: ComponentType<any> = lazy(() => import('./PipelineContainer'));
8
+ export const PipelineObjectSettings: ComponentType<any> = lazy(() => import('./PipelineObjectSettings'));
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './usePipelineBoardModel';