@dxos/plugin-pipeline 0.8.4-main.2244d791bb → 0.8.4-main.3fbcb4aa9b

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 (160) hide show
  1. package/dist/lib/neutral/PipelineContainer-7MK2SS76.mjs +69 -0
  2. package/dist/lib/neutral/PipelineContainer-7MK2SS76.mjs.map +7 -0
  3. package/dist/lib/neutral/PipelinePlugin.mjs +26 -0
  4. package/dist/lib/neutral/PipelinePlugin.mjs.map +7 -0
  5. package/dist/lib/neutral/PipelinePlugin.node.mjs +21 -0
  6. package/dist/lib/neutral/PipelinePlugin.node.mjs.map +7 -0
  7. package/dist/lib/{browser/PipelineSettings-R54RTVWZ.mjs → neutral/PipelineProperties-RRPQ3V6I.mjs} +45 -56
  8. package/dist/lib/neutral/PipelineProperties-RRPQ3V6I.mjs.map +7 -0
  9. package/dist/lib/neutral/app-graph-builder-ZLGRWRJU.mjs +44 -0
  10. package/dist/lib/neutral/app-graph-builder-ZLGRWRJU.mjs.map +7 -0
  11. package/dist/lib/neutral/capabilities/index.mjs +11 -0
  12. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  13. package/dist/lib/{browser/chunk-QWNKLHBY.mjs → neutral/chunk-ULB5DKWO.mjs} +2 -9
  14. package/dist/lib/{browser/chunk-QWNKLHBY.mjs.map → neutral/chunk-ULB5DKWO.mjs.map} +3 -3
  15. package/dist/lib/neutral/components/index.mjs +206 -0
  16. package/dist/lib/neutral/components/index.mjs.map +7 -0
  17. package/dist/lib/neutral/containers/index.mjs +9 -0
  18. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  19. package/dist/lib/neutral/create-object-ZJYFACOI.mjs +25 -0
  20. package/dist/lib/neutral/create-object-ZJYFACOI.mjs.map +7 -0
  21. package/dist/lib/neutral/hooks/index.mjs +70 -0
  22. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  23. package/dist/lib/neutral/index.mjs +7 -0
  24. package/dist/lib/neutral/meta.json +1 -0
  25. package/dist/lib/neutral/meta.mjs +7 -0
  26. package/dist/lib/neutral/meta.mjs.map +7 -0
  27. package/dist/lib/neutral/plugin.mjs +11 -0
  28. package/dist/lib/neutral/plugin.mjs.map +7 -0
  29. package/dist/lib/neutral/react-surface-TW6TRMO6.mjs +49 -0
  30. package/dist/lib/neutral/react-surface-TW6TRMO6.mjs.map +7 -0
  31. package/dist/lib/neutral/translations.mjs +38 -0
  32. package/dist/lib/neutral/translations.mjs.map +7 -0
  33. package/dist/lib/neutral/types/index.mjs +1 -0
  34. package/dist/lib/neutral/types/index.mjs.map +7 -0
  35. package/dist/types/src/PipelinePlugin.d.ts +1 -0
  36. package/dist/types/src/PipelinePlugin.d.ts.map +1 -1
  37. package/dist/types/src/PipelinePlugin.node.d.ts +4 -0
  38. package/dist/types/src/PipelinePlugin.node.d.ts.map +1 -0
  39. package/dist/types/src/PipelinePlugin.test.d.ts +2 -0
  40. package/dist/types/src/PipelinePlugin.test.d.ts.map +1 -0
  41. package/dist/types/src/capabilities/app-graph-builder.d.ts +6 -0
  42. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  43. package/dist/types/src/capabilities/create-object.d.ts +11 -0
  44. package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
  45. package/dist/types/src/capabilities/index.d.ts +9 -3
  46. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  47. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  48. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  49. package/dist/types/src/components/{PipelineColumn.d.ts → PipelineComponent/PipelineColumn.d.ts} +4 -4
  50. package/dist/types/src/components/PipelineComponent/PipelineColumn.d.ts.map +1 -0
  51. package/dist/types/src/components/PipelineComponent/PipelineComponent.d.ts +53 -0
  52. package/dist/types/src/components/PipelineComponent/PipelineComponent.d.ts.map +1 -0
  53. package/dist/types/src/components/PipelineComponent/PipelineComponent.stories.d.ts +62 -0
  54. package/dist/types/src/components/PipelineComponent/PipelineComponent.stories.d.ts.map +1 -0
  55. package/dist/types/src/components/PipelineComponent/index.d.ts +3 -0
  56. package/dist/types/src/components/PipelineComponent/index.d.ts.map +1 -0
  57. package/dist/types/src/components/index.d.ts +1 -4
  58. package/dist/types/src/components/index.d.ts.map +1 -1
  59. package/dist/types/src/containers/PipelineContainer/PipelineContainer.d.ts +6 -0
  60. package/dist/types/src/containers/PipelineContainer/PipelineContainer.d.ts.map +1 -0
  61. package/dist/types/src/containers/PipelineContainer/PipelineContainer.stories.d.ts +76 -0
  62. package/dist/types/src/containers/PipelineContainer/PipelineContainer.stories.d.ts.map +1 -0
  63. package/dist/types/src/containers/PipelineContainer/index.d.ts +2 -0
  64. package/dist/types/src/containers/PipelineContainer/index.d.ts.map +1 -0
  65. package/dist/types/src/containers/PipelineProperties/PipelineProperties.d.ts +11 -0
  66. package/dist/types/src/containers/PipelineProperties/PipelineProperties.d.ts.map +1 -0
  67. package/dist/types/src/containers/PipelineProperties/index.d.ts +2 -0
  68. package/dist/types/src/containers/PipelineProperties/index.d.ts.map +1 -0
  69. package/dist/types/src/containers/index.d.ts +4 -0
  70. package/dist/types/src/containers/index.d.ts.map +1 -0
  71. package/dist/types/src/hooks/index.d.ts +2 -0
  72. package/dist/types/src/hooks/index.d.ts.map +1 -0
  73. package/dist/types/src/hooks/usePipelineBoardModel.d.ts +6 -0
  74. package/dist/types/src/hooks/usePipelineBoardModel.d.ts.map +1 -0
  75. package/dist/types/src/index.d.ts +0 -1
  76. package/dist/types/src/index.d.ts.map +1 -1
  77. package/dist/types/src/plugin.d.ts +3 -0
  78. package/dist/types/src/plugin.d.ts.map +1 -0
  79. package/dist/types/src/templates/research.d.ts.map +1 -1
  80. package/dist/types/src/translations.d.ts +44 -25
  81. package/dist/types/src/translations.d.ts.map +1 -1
  82. package/dist/types/src/types/index.d.ts +0 -2
  83. package/dist/types/src/types/index.d.ts.map +1 -1
  84. package/dist/types/tsconfig.tsbuildinfo +1 -1
  85. package/package.json +100 -54
  86. package/src/PipelinePlugin.node.ts +19 -0
  87. package/src/PipelinePlugin.test.ts +27 -0
  88. package/src/PipelinePlugin.tsx +7 -31
  89. package/src/capabilities/app-graph-builder.ts +40 -0
  90. package/src/capabilities/create-object.ts +29 -0
  91. package/src/capabilities/index.ts +5 -3
  92. package/src/capabilities/react-surface.tsx +53 -0
  93. package/src/components/{PipelineColumn.tsx → PipelineComponent/PipelineColumn.tsx} +67 -48
  94. package/src/components/{PipelineComponent.stories.tsx → PipelineComponent/PipelineComponent.stories.tsx} +40 -32
  95. package/src/components/PipelineComponent/PipelineComponent.tsx +140 -0
  96. package/src/components/PipelineComponent/index.ts +6 -0
  97. package/src/components/index.ts +1 -7
  98. package/src/containers/PipelineContainer/PipelineContainer.stories.tsx +230 -0
  99. package/src/containers/PipelineContainer/PipelineContainer.tsx +84 -0
  100. package/src/containers/PipelineContainer/index.ts +5 -0
  101. package/src/{components/PipelineSettings.tsx → containers/PipelineProperties/PipelineProperties.tsx} +50 -43
  102. package/src/containers/PipelineProperties/index.ts +5 -0
  103. package/src/containers/index.ts +8 -0
  104. package/src/hooks/index.ts +5 -0
  105. package/src/hooks/usePipelineBoardModel.ts +79 -0
  106. package/src/index.ts +0 -1
  107. package/src/meta.ts +1 -1
  108. package/src/plugin.ts +9 -0
  109. package/src/templates/research.ts +16 -17
  110. package/src/translations.ts +22 -21
  111. package/src/types/index.ts +0 -3
  112. package/dist/lib/browser/PipelineContainer-2BRZPECS.mjs +0 -289
  113. package/dist/lib/browser/PipelineContainer-2BRZPECS.mjs.map +0 -7
  114. package/dist/lib/browser/PipelineSettings-R54RTVWZ.mjs.map +0 -7
  115. package/dist/lib/browser/app-graph-builder-NUJB33F7.mjs +0 -65
  116. package/dist/lib/browser/app-graph-builder-NUJB33F7.mjs.map +0 -7
  117. package/dist/lib/browser/chunk-7GVESVYR.mjs +0 -42
  118. package/dist/lib/browser/chunk-7GVESVYR.mjs.map +0 -7
  119. package/dist/lib/browser/index.mjs +0 -109
  120. package/dist/lib/browser/index.mjs.map +0 -7
  121. package/dist/lib/browser/meta.json +0 -1
  122. package/dist/lib/browser/react-surface-CMTEEE6F.mjs +0 -57
  123. package/dist/lib/browser/react-surface-CMTEEE6F.mjs.map +0 -7
  124. package/dist/lib/browser/types/index.mjs +0 -10
  125. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +0 -6
  126. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +0 -1
  127. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +0 -3
  128. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +0 -1
  129. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -2
  130. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  131. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  132. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  133. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  134. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  135. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +0 -5
  136. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  137. package/dist/types/src/components/PipelineColumn.d.ts.map +0 -1
  138. package/dist/types/src/components/PipelineComponent.d.ts +0 -44
  139. package/dist/types/src/components/PipelineComponent.d.ts.map +0 -1
  140. package/dist/types/src/components/PipelineComponent.stories.d.ts +0 -42
  141. package/dist/types/src/components/PipelineComponent.stories.d.ts.map +0 -1
  142. package/dist/types/src/components/PipelineContainer.d.ts +0 -7
  143. package/dist/types/src/components/PipelineContainer.d.ts.map +0 -1
  144. package/dist/types/src/components/PipelineContainer.stories.d.ts +0 -56
  145. package/dist/types/src/components/PipelineContainer.stories.d.ts.map +0 -1
  146. package/dist/types/src/components/PipelineSettings.d.ts +0 -12
  147. package/dist/types/src/components/PipelineSettings.d.ts.map +0 -1
  148. package/dist/types/src/types/PipelineAction.d.ts +0 -25
  149. package/dist/types/src/types/PipelineAction.d.ts.map +0 -1
  150. package/src/capabilities/app-graph-builder/app-graph-builder.ts +0 -51
  151. package/src/capabilities/app-graph-builder/index.ts +0 -7
  152. package/src/capabilities/operation-resolver/index.ts +0 -5
  153. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -29
  154. package/src/capabilities/react-surface/index.ts +0 -7
  155. package/src/capabilities/react-surface/react-surface.tsx +0 -50
  156. package/src/components/PipelineComponent.tsx +0 -186
  157. package/src/components/PipelineContainer.stories.tsx +0 -226
  158. package/src/components/PipelineContainer.tsx +0 -61
  159. package/src/types/PipelineAction.ts +0 -29
  160. /package/dist/lib/{browser/types → neutral}/index.mjs.map +0 -0
@@ -4,31 +4,35 @@
4
4
 
5
5
  import { RegistryContext } from '@effect-atom/atom-react';
6
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
7
- import React, { useCallback, useContext, useEffect } from 'react';
7
+ import React, { useCallback, useContext, useEffect, useMemo } from 'react';
8
8
 
9
9
  import { Filter, Ref } from '@dxos/client/echo';
10
- import { Obj, Query, Type } from '@dxos/echo';
11
- import { faker } from '@dxos/random';
10
+ import { JsonSchema, Obj, Query } from '@dxos/echo';
11
+ import { Collection, View } from '@dxos/echo';
12
+ import { random } from '@dxos/random';
12
13
  import { useQuery } from '@dxos/react-client/echo';
13
14
  import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
14
- import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
15
  import { Form, omitId } from '@dxos/react-ui-form';
16
16
  import { withMosaic } from '@dxos/react-ui-mosaic/testing';
17
- import { Collection, View } from '@dxos/schema';
17
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
18
+ import { ViewModel } from '@dxos/schema';
18
19
  import { createObjectFactory } from '@dxos/schema/testing';
19
20
  import { withRegistry } from '@dxos/storybook-utils';
20
21
  import { Person, Pipeline } from '@dxos/types';
21
22
 
22
- import { translations } from '../translations';
23
+ import { usePipelineBoardModel } from '#hooks';
24
+ import { translations } from '#translations';
23
25
 
24
- import { type ItemProps, PipelineComponent, usePipelineBoardModel } from './PipelineComponent';
26
+ import { type ItemProps, PipelineComponent } from './PipelineComponent';
25
27
 
26
28
  const StorybookProjectItem = ({ item, projectionModel }: ItemProps) => {
29
+ const personSchema = useMemo(() => omitId(Person.Person), []);
30
+
27
31
  if (Obj.instanceOf(Person.Person, item)) {
28
- const contact = item as Obj.Obj<Person.Person>;
32
+ const contact = item as Obj.OfShape<Person.Person>;
29
33
 
30
34
  return (
31
- <Form.Root schema={omitId(Person.Person)} projection={projectionModel} values={contact} autoSave>
35
+ <Form.Root schema={personSchema} projection={projectionModel} values={contact} autoSave>
32
36
  <Form.Viewport>
33
37
  <Form.Content>
34
38
  <Form.FieldSet />
@@ -54,16 +58,16 @@ const DefaultStory = () => {
54
58
  }
55
59
 
56
60
  // Create a new view for contacts similar to the initialization.
57
- const view = View.make({
61
+ const view = ViewModel.make({
58
62
  query: Query.select(Filter.type(Person.Person)),
59
- jsonSchema: Type.toJsonSchema(Person.Person),
63
+ jsonSchema: JsonSchema.toJsonSchema(Person.Person),
60
64
  fields: ['fullName'],
61
65
  });
62
66
 
63
- Obj.change(pipeline, (p) => {
64
- p.columns.push({
67
+ Obj.update(pipeline, (pipeline) => {
68
+ pipeline.columns.push({
65
69
  name: 'New Contacts',
66
- view: Ref.make(view) as (typeof p.columns)[number]['view'],
70
+ view: Ref.make(view) as (typeof pipeline.columns)[number]['view'],
67
71
  order: [],
68
72
  });
69
73
  });
@@ -76,8 +80,10 @@ const DefaultStory = () => {
76
80
  }
77
81
 
78
82
  return (
79
- <PipelineComponent.Root Item={StorybookProjectItem} model={model} onAddColumn={handleAddColumn}>
80
- <PipelineComponent.Content pipeline={pipeline} />
83
+ <PipelineComponent.Root Item={StorybookProjectItem} onAddColumn={handleAddColumn}>
84
+ <PipelineComponent.Content model={model}>
85
+ <PipelineComponent.Columns pipeline={pipeline} />
86
+ </PipelineComponent.Content>
81
87
  </PipelineComponent.Root>
82
88
  );
83
89
  };
@@ -96,16 +102,16 @@ const MutationsStory = () => {
96
102
  }
97
103
 
98
104
  // Create a new view for contacts similar to the initialization.
99
- const view = View.make({
105
+ const view = ViewModel.make({
100
106
  query: Query.select(Filter.type(Person.Person)),
101
- jsonSchema: Type.toJsonSchema(Person.Person),
107
+ jsonSchema: JsonSchema.toJsonSchema(Person.Person),
102
108
  fields: ['fullName'],
103
109
  });
104
110
 
105
- Obj.change(pipeline, (p) => {
106
- p.columns.push({
111
+ Obj.update(pipeline, (pipeline) => {
112
+ pipeline.columns.push({
107
113
  name: 'New Contacts',
108
- view: Ref.make(view) as (typeof p.columns)[number]['view'],
114
+ view: Ref.make(view) as (typeof pipeline.columns)[number]['view'],
109
115
  order: [],
110
116
  });
111
117
  });
@@ -119,19 +125,19 @@ const MutationsStory = () => {
119
125
  }
120
126
 
121
127
  const interval = setInterval(() => {
122
- const factory = createObjectFactory(space.db, faker as any);
128
+ const factory = createObjectFactory(space.db, random as any);
123
129
  const p = Math.random();
124
130
 
125
131
  if (p < 0.4) {
126
132
  // Append to the name
127
- const contactToAdjust = faker.helpers.arrayElement(contacts);
128
- Obj.change(contactToAdjust, (c) => {
129
- c.fullName = (c.fullName ?? '') + ' X';
133
+ const contactToAdjust = random.helpers.arrayElement(contacts);
134
+ Obj.update(contactToAdjust, (contactToAdjust) => {
135
+ contactToAdjust.fullName = (contactToAdjust.fullName ?? '') + ' X';
130
136
  });
131
137
  return;
132
138
  } else if (p < 0.7 && contacts.length > 1) {
133
139
  // Remove a contact (30% chance, but only if we have more than 1)
134
- const contactToRemove = faker.helpers.arrayElement(contacts);
140
+ const contactToRemove = random.helpers.arrayElement(contacts);
135
141
  space.db.remove(contactToRemove);
136
142
  } else {
137
143
  // Add a new contact (30% chance)
@@ -147,14 +153,16 @@ const MutationsStory = () => {
147
153
  }
148
154
 
149
155
  return (
150
- <PipelineComponent.Root Item={StorybookProjectItem} model={model} onAddColumn={handleAddColumn}>
151
- <PipelineComponent.Content pipeline={pipeline} />
156
+ <PipelineComponent.Root Item={StorybookProjectItem} onAddColumn={handleAddColumn}>
157
+ <PipelineComponent.Content model={model}>
158
+ <PipelineComponent.Columns pipeline={pipeline} />
159
+ </PipelineComponent.Content>
152
160
  </PipelineComponent.Root>
153
161
  );
154
162
  };
155
163
 
156
164
  const meta = {
157
- title: 'plugins/plugin-pipeline/PipelineComponent',
165
+ title: 'plugins/plugin-pipeline/components/PipelineComponent',
158
166
  decorators: [
159
167
  withTheme(),
160
168
  withLayout({ layout: 'column' }),
@@ -166,10 +174,10 @@ const meta = {
166
174
  createSpace: true,
167
175
  onCreateSpace: async ({ space }) => {
168
176
  // Create a view for contacts.
169
- const view = View.make({
177
+ const view = ViewModel.make({
170
178
  name: 'Contacts',
171
179
  query: Query.select(Filter.type(Person.Person)),
172
- jsonSchema: Type.toJsonSchema(Person.Person),
180
+ jsonSchema: JsonSchema.toJsonSchema(Person.Person),
173
181
  fields: ['fullName'],
174
182
  });
175
183
 
@@ -187,7 +195,7 @@ const meta = {
187
195
  );
188
196
 
189
197
  // Generate random contacts.
190
- const factory = createObjectFactory(space.db, faker as any);
198
+ const factory = createObjectFactory(space.db, random as any);
191
199
  await factory([{ type: Person.Person, count: 12 }]);
192
200
  },
193
201
  }),
@@ -0,0 +1,140 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useAtomValue } from '@effect-atom/atom-react';
6
+ import { createContext } from '@radix-ui/react-context';
7
+ import { Slot } from '@radix-ui/react-slot';
8
+ import React, { type FC, type PropsWithChildren } from 'react';
9
+
10
+ import { Obj } from '@dxos/echo';
11
+ import { Toolbar, type ToolbarRootProps, useTranslation } from '@dxos/react-ui';
12
+ import { Board, type BoardModel, useBoard, useEventHandlerAdapter } from '@dxos/react-ui-mosaic';
13
+ import { type ProjectionModel } from '@dxos/schema';
14
+ import { type Pipeline } from '@dxos/types';
15
+ import { composable, composableProps, slottable } from '@dxos/ui-theme';
16
+
17
+ import { meta } from '#meta';
18
+
19
+ import { PipelineColumn } from './PipelineColumn';
20
+
21
+ type ItemProps = {
22
+ item: Obj.Unknown;
23
+ projectionModel?: ProjectionModel;
24
+ };
25
+
26
+ const itemNoOp = ({ item }: ItemProps) => <span>{item.id}</span>;
27
+
28
+ //
29
+ // Root
30
+ //
31
+
32
+ type PipelineContextValue = {
33
+ Item: FC<ItemProps>;
34
+ // TODO(wittjosiah): Support adding items.
35
+ // If the created item doesn't match the current query, it will not be visible.
36
+ // TODO(wittjosiah): onAddItem?: (schema: Schema.Schema.AnyNoContext) => void;
37
+ onAddColumn?: () => void;
38
+ };
39
+
40
+ type PipelineRootProps = PropsWithChildren<PipelineContextValue>;
41
+
42
+ const PIPELINE_ROOT = 'Pipeline.Root';
43
+
44
+ const [PipelineRootContext, usePipeline] = createContext<PipelineContextValue>(PIPELINE_ROOT, {
45
+ Item: itemNoOp,
46
+ });
47
+
48
+ const PipelineRoot = ({ children, ...contextValue }: PipelineRootProps) => (
49
+ <PipelineRootContext {...contextValue}>{children}</PipelineRootContext>
50
+ );
51
+
52
+ PipelineRoot.displayName = PIPELINE_ROOT;
53
+
54
+ //
55
+ // Content
56
+ //
57
+
58
+ const PIPELINE_CONTENT_NAME = 'Pipeline.Content';
59
+
60
+ type PipelineContentProps = PropsWithChildren<{
61
+ model: BoardModel<Pipeline.Column, Obj.Unknown>;
62
+ }>;
63
+
64
+ const PipelineContent = slottable<HTMLDivElement, PipelineContentProps>(
65
+ ({ asChild, model, children, ...props }, forwardedRef) => {
66
+ const Comp = asChild ? Slot : 'div';
67
+ return (
68
+ <Board.Root model={model}>
69
+ <Comp {...composableProps(props)} ref={forwardedRef}>
70
+ {children}
71
+ </Comp>
72
+ </Board.Root>
73
+ );
74
+ },
75
+ );
76
+
77
+ PipelineContent.displayName = PIPELINE_CONTENT_NAME;
78
+
79
+ //
80
+ // Columns
81
+ //
82
+
83
+ const PIPELINE_COLUMNS_NAME = 'Pipeline.Columns';
84
+
85
+ type PipelineColumnsProps = {
86
+ pipeline: Pipeline.Pipeline;
87
+ };
88
+
89
+ const PipelineColumns = composable<HTMLDivElement, PipelineColumnsProps>(({ pipeline, ...props }) => {
90
+ const { model } = useBoard(PIPELINE_COLUMNS_NAME);
91
+ const columns = useAtomValue(model.columns);
92
+ const eventHandler = useEventHandlerAdapter<Pipeline.Column, Obj.Unknown>({
93
+ id: pipeline.id,
94
+ // TODO(wittjosiah): Cast because columns array is readonly.
95
+ items: columns as any,
96
+ getId: model.getColumnId,
97
+ get: (data) => data as unknown as Obj.Unknown,
98
+ make: (object) => object as unknown as Pipeline.Column,
99
+ canDrop: ({ source }) => model.isColumn(source.data),
100
+ onChange: (mutate) => Obj.update(pipeline, (pipeline) => mutate(pipeline.columns)),
101
+ });
102
+
103
+ return <Board.Content {...props} id='pipeline' eventHandler={eventHandler} Tile={PipelineColumn} />;
104
+ });
105
+
106
+ PipelineColumns.displayName = PIPELINE_COLUMNS_NAME;
107
+
108
+ //
109
+ // Toolbar
110
+ //
111
+
112
+ const PIPELINE_TOOLBAR_NAME = 'Pipeline.Toolbar';
113
+
114
+ export const PipelineToolbar = composable<HTMLDivElement, ToolbarRootProps>(({ children, ...props }, forwardedRef) => {
115
+ const { t } = useTranslation(meta.id);
116
+ const { onAddColumn } = usePipeline(PIPELINE_TOOLBAR_NAME);
117
+
118
+ return (
119
+ <Toolbar.Root {...composableProps(props)} ref={forwardedRef}>
120
+ <Toolbar.IconButton icon='ph--plus--regular' iconOnly label={t('add-column.label')} onClick={onAddColumn} />
121
+ </Toolbar.Root>
122
+ );
123
+ });
124
+
125
+ PipelineToolbar.displayName = PIPELINE_TOOLBAR_NAME;
126
+
127
+ //
128
+ // Project
129
+ //
130
+
131
+ export const PipelineComponent = {
132
+ Root: PipelineRoot,
133
+ Content: PipelineContent,
134
+ Columns: PipelineColumns,
135
+ Toolbar: PipelineToolbar,
136
+ };
137
+
138
+ export { usePipeline };
139
+
140
+ export type { ItemProps, PipelineContextValue, PipelineRootProps, PipelineContentProps, PipelineColumnsProps };
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './PipelineColumn';
6
+ export * from './PipelineComponent';
@@ -2,10 +2,4 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { lazy } from 'react';
6
-
7
- export type { PipelineContainerProps } from './PipelineContainer';
8
- export type { PipelineObjectSettingsProps } from './PipelineSettings';
9
-
10
- export const PipelineContainer = lazy(() => import('./PipelineContainer'));
11
- export const PipelineObjectSettings = lazy(() => import('./PipelineSettings'));
5
+ export * from './PipelineComponent';
@@ -0,0 +1,230 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Layer from 'effect/Layer';
8
+ import React from 'react';
9
+
10
+ import { withPluginManager } from '@dxos/app-framework/testing';
11
+ import { Filter, Ref } from '@dxos/client/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';
15
+ import { ClientPlugin } from '@dxos/plugin-client/plugin';
16
+ import { initializeIdentity } from '@dxos/plugin-client/testing';
17
+ import { InboxPlugin } from '@dxos/plugin-inbox/plugin';
18
+ import { PreviewPlugin } from '@dxos/plugin-preview/testing';
19
+ import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
20
+ import { random } from '@dxos/random';
21
+ import { useDatabase, useQuery } from '@dxos/react-client/echo';
22
+ import { translations as stackTranslations } from '@dxos/react-ui-stack/translations';
23
+ import { withLayout } from '@dxos/react-ui/testing';
24
+ import { ViewModel } from '@dxos/schema';
25
+ import { createObjectFactory } from '@dxos/schema/testing';
26
+ import { Message, Organization, Person, Pipeline, Task } from '@dxos/types';
27
+
28
+ import { translations } from '#translations';
29
+
30
+ import PipelineProperties from '../PipelineProperties';
31
+ import { PipelineContainer } from './PipelineContainer';
32
+
33
+ random.seed(0);
34
+
35
+ const DefaultStory = () => {
36
+ const db = useDatabase();
37
+ const pipelines = useQuery(db, Filter.type(Pipeline.Pipeline));
38
+ const pipeline = pipelines[0];
39
+
40
+ if (!pipeline) {
41
+ return <p>Loading…</p>;
42
+ }
43
+
44
+ return (
45
+ <div className='grow grid grid-cols-[1fr_350px] overflow-hidden h-full w-full'>
46
+ <PipelineContainer role='article' subject={pipeline} attendableId='test' />
47
+ <PipelineProperties pipeline={pipeline} classNames='border-s border-separator' />
48
+ </div>
49
+ );
50
+ };
51
+
52
+ const meta = {
53
+ title: 'plugins/plugin-pipeline/containers/PipelineContainer',
54
+ render: DefaultStory,
55
+ decorators: [
56
+ withLayout({ layout: 'fullscreen' }),
57
+ withPluginManager({
58
+ plugins: [
59
+ ...corePlugins(),
60
+ StorybookPlugin({}),
61
+ ClientPlugin({
62
+ types: [
63
+ Tag.Tag,
64
+ Feed.Feed,
65
+ Pipeline.Pipeline,
66
+ View.View,
67
+ Collection.Collection,
68
+ Organization.Organization,
69
+ Task.Task,
70
+ Person.Person,
71
+ Message.Message,
72
+ ],
73
+ onClientInitialized: Effect.fnUntraced(function* ({ client }) {
74
+ const { personalSpace } = yield* initializeIdentity(client);
75
+
76
+ yield* Effect.gen(function* () {
77
+ const tag = yield* Database.add(Tag.make({ label: 'important', hue: 'green' }));
78
+ const tagDxn = Obj.getDXN(tag).toString();
79
+
80
+ // Create a view for Contacts.
81
+ const personView = ViewModel.make({
82
+ query: Query.select(Filter.type(Person.Person)),
83
+ jsonSchema: JsonSchema.toJsonSchema(Person.Person),
84
+ });
85
+
86
+ // Create a view for Organizations.
87
+ const organizationView = ViewModel.make({
88
+ query: Query.select(Filter.type(Organization.Organization)).select(Filter.tag(tagDxn)),
89
+ jsonSchema: JsonSchema.toJsonSchema(Organization.Organization),
90
+ });
91
+
92
+ // Create a view for Tasks.
93
+ const taskView = ViewModel.make({
94
+ query: Query.select(Filter.type(Task.Task)).select(Filter.tag(tagDxn)),
95
+ jsonSchema: JsonSchema.toJsonSchema(Task.Task),
96
+ });
97
+
98
+ // Create a view for Project-Projects.
99
+ const projectView = ViewModel.make({
100
+ query: Query.select(Filter.type(Pipeline.Pipeline)),
101
+ jsonSchema: JsonSchema.toJsonSchema(Pipeline.Pipeline),
102
+ });
103
+
104
+ // Create a feed for Messages.
105
+ const messageFeed = yield* Database.add(Feed.make({ name: 'Messages' }));
106
+ const messages = Array.from({ length: 20 }).map(() =>
107
+ Obj.make(Message.Message, {
108
+ created: random.date.recent().toISOString(),
109
+ sender: { role: 'user' },
110
+ blocks: [{ _tag: 'text' as const, text: random.lorem.sentences(2) }],
111
+ }),
112
+ );
113
+ yield* Feed.append(messageFeed, messages);
114
+
115
+ const messageQueueDxn = Feed.getQueueDxn(messageFeed)!.toString();
116
+ const messageView = ViewModel.make({
117
+ query: Query.select(Filter.type(Message.Message)).from({
118
+ queues: [messageQueueDxn],
119
+ }),
120
+ jsonSchema: JsonSchema.toJsonSchema(Message.Message),
121
+ });
122
+
123
+ // Create a feed for Tasks (for testing feed switching in settings).
124
+ const taskFeed = yield* Database.add(Feed.make({ name: 'Tasks' }));
125
+ const feedTasks = Array.from({ length: 10 }).map(() =>
126
+ Obj.make(Task.Task, {
127
+ title: random.lorem.sentence(),
128
+ status: random.helpers.arrayElement(['todo', 'in-progress', 'done']) as any,
129
+ priority: random.helpers.arrayElement(['low', 'medium', 'high']) as any,
130
+ }),
131
+ );
132
+ yield* Feed.append(taskFeed, feedTasks);
133
+
134
+ // Create pipeline with columns.
135
+ const pipeline = Pipeline.make({
136
+ columns: [
137
+ {
138
+ name: 'Contacts',
139
+ view: Ref.make(personView),
140
+ order: [],
141
+ },
142
+ {
143
+ name: 'Organizations',
144
+ view: Ref.make(organizationView),
145
+ order: [],
146
+ },
147
+ {
148
+ name: 'Tasks',
149
+ view: Ref.make(taskView),
150
+ order: [],
151
+ },
152
+ {
153
+ name: 'Projects',
154
+ view: Ref.make(projectView),
155
+ order: [],
156
+ },
157
+ {
158
+ name: 'Messages',
159
+ view: Ref.make(messageView),
160
+ order: [],
161
+ },
162
+ ],
163
+ });
164
+ yield* Database.add(pipeline);
165
+
166
+ // Generate sample Organizations.
167
+ for (const _ of Array.from({ length: 20 })) {
168
+ yield* Database.add(
169
+ Obj.make(Organization.Organization, {
170
+ [Obj.Meta]: {
171
+ tags: random.datatype.boolean() ? [tagDxn] : [],
172
+ },
173
+ name: random.company.name(),
174
+ website: random.internet.url(),
175
+ description: random.lorem.paragraph(),
176
+ image: random.image.url(),
177
+ }),
178
+ );
179
+ }
180
+
181
+ // Generate sample Tasks.
182
+ for (const _ of Array.from({ length: 20 })) {
183
+ yield* Database.add(
184
+ Obj.make(Task.Task, {
185
+ [Obj.Meta]: {
186
+ tags: random.datatype.boolean() ? [tagDxn] : [],
187
+ },
188
+ title: random.lorem.sentence(),
189
+ status: random.helpers.arrayElement(['todo', 'in-progress', 'done']) as any,
190
+ priority: random.helpers.arrayElement(['low', 'medium', 'high']) as any,
191
+ }),
192
+ );
193
+ }
194
+
195
+ // Generate sample Contacts.
196
+ const factory = createObjectFactory(personalSpace.db, random as any);
197
+ yield* Effect.promise(() => factory([{ type: Person.Person, count: 12 }]));
198
+
199
+ // Generate sample Projects.
200
+ for (const _ of Array.from({ length: 20 })) {
201
+ yield* Database.add(
202
+ Pipeline.make({
203
+ name: random.commerce.productName(),
204
+ description: random.lorem.sentence(),
205
+ }),
206
+ );
207
+ }
208
+ }).pipe(
209
+ Effect.provide(
210
+ Layer.merge(Database.layer(personalSpace.db), createFeedServiceLayer(personalSpace.queues)),
211
+ ),
212
+ );
213
+ }),
214
+ }),
215
+ InboxPlugin(),
216
+ PreviewPlugin(),
217
+ ],
218
+ }),
219
+ ],
220
+ parameters: {
221
+ layout: 'fullscreen',
222
+ translations: [...translations, ...stackTranslations],
223
+ },
224
+ } satisfies Meta<typeof PipelineContainer>;
225
+
226
+ export default meta;
227
+
228
+ type Story = StoryObj<typeof meta>;
229
+
230
+ export const Default: Story = {};
@@ -0,0 +1,84 @@
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 { Surface, useCapability, useOperationInvoker } from '@dxos/app-framework/ui';
9
+ import { LayoutOperation } from '@dxos/app-toolkit';
10
+ import {
11
+ AppSurface,
12
+ OBJECT_ACTIONS_CONTRIBUTION_ID,
13
+ OBJECT_ACTIONS_CONTRIBUTION_PRIORITY,
14
+ useObjectMenuItems,
15
+ } from '@dxos/app-toolkit/ui';
16
+ import { Panel } from '@dxos/react-ui';
17
+ import { linkedSegment } from '@dxos/react-ui-attention';
18
+ import { useAttention } from '@dxos/react-ui-attention';
19
+ import { useMenu } from '@dxos/react-ui-menu';
20
+ import { type Pipeline } from '@dxos/types';
21
+
22
+ import { type ItemProps, PipelineComponent } from '#components';
23
+ import { usePipelineBoardModel } from '#hooks';
24
+
25
+ const PIPELINE_ITEM = 'PipelineItem';
26
+
27
+ export type PipelineContainerProps = AppSurface.ObjectArticleProps<Pipeline.Pipeline>;
28
+
29
+ export const PipelineContainer = ({ role, subject: pipeline, attendableId }: PipelineContainerProps) => {
30
+ const registry = useCapability(Capabilities.AtomRegistry);
31
+ const model = usePipelineBoardModel(pipeline, registry);
32
+ const { invokePromise } = useOperationInvoker();
33
+ const { hasAttention } = useAttention(attendableId);
34
+
35
+ const handleColumnAdd = useCallback(
36
+ () =>
37
+ invokePromise(LayoutOperation.UpdateCompanion, {
38
+ subject: linkedSegment('settings'),
39
+ }),
40
+ [invokePromise],
41
+ );
42
+
43
+ return (
44
+ <PipelineComponent.Root Item={PipelineItem} onAddColumn={handleColumnAdd}>
45
+ <Panel.Root role={role}>
46
+ <Panel.Toolbar asChild>
47
+ <PipelineComponent.Toolbar disabled={!hasAttention} />
48
+ </Panel.Toolbar>
49
+ <Panel.Content asChild>
50
+ <PipelineComponent.Content asChild model={model}>
51
+ <PipelineComponent.Columns pipeline={pipeline} />
52
+ </PipelineComponent.Content>
53
+ </Panel.Content>
54
+ </Panel.Root>
55
+ </PipelineComponent.Root>
56
+ );
57
+ };
58
+
59
+ const PipelineItem = ({ item, projectionModel }: ItemProps) => {
60
+ const menu = useMenu(PIPELINE_ITEM);
61
+ const items = useObjectMenuItems(item);
62
+
63
+ useEffect(() => {
64
+ menu.addMenuItems({
65
+ id: OBJECT_ACTIONS_CONTRIBUTION_ID,
66
+ mode: 'additive',
67
+ priority: OBJECT_ACTIONS_CONTRIBUTION_PRIORITY,
68
+ items,
69
+ });
70
+
71
+ return () => menu.removeMenuItems(OBJECT_ACTIONS_CONTRIBUTION_ID);
72
+ }, [menu, items]);
73
+
74
+ return (
75
+ <Surface.Surface
76
+ type={AppSurface.Card}
77
+ data={{
78
+ subject: item,
79
+ projection: projectionModel,
80
+ }}
81
+ limit={1}
82
+ />
83
+ );
84
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export { PipelineContainer as default } from './PipelineContainer';