@dxos/plugin-pipeline 0.8.3-main.ef1bc66f44

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 (90) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +13 -0
  3. package/dist/lib/browser/PipelineContainer-TRO66W6L.mjs +216 -0
  4. package/dist/lib/browser/PipelineContainer-TRO66W6L.mjs.map +7 -0
  5. package/dist/lib/browser/PipelineSettings-EBR3UU5C.mjs +212 -0
  6. package/dist/lib/browser/PipelineSettings-EBR3UU5C.mjs.map +7 -0
  7. package/dist/lib/browser/app-graph-builder-NKUZQ6A7.mjs +64 -0
  8. package/dist/lib/browser/app-graph-builder-NKUZQ6A7.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-J5YNPKHT.mjs +28 -0
  10. package/dist/lib/browser/chunk-J5YNPKHT.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-XDHJKK7D.mjs +42 -0
  12. package/dist/lib/browser/chunk-XDHJKK7D.mjs.map +7 -0
  13. package/dist/lib/browser/index.mjs +108 -0
  14. package/dist/lib/browser/index.mjs.map +7 -0
  15. package/dist/lib/browser/meta.json +1 -0
  16. package/dist/lib/browser/react-surface-CH2SUJTQ.mjs +56 -0
  17. package/dist/lib/browser/react-surface-CH2SUJTQ.mjs.map +7 -0
  18. package/dist/lib/browser/types/index.mjs +10 -0
  19. package/dist/lib/browser/types/index.mjs.map +7 -0
  20. package/dist/types/src/PipelinePlugin.d.ts +3 -0
  21. package/dist/types/src/PipelinePlugin.d.ts.map +1 -0
  22. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +6 -0
  23. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
  24. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
  25. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
  26. package/dist/types/src/capabilities/index.d.ts +4 -0
  27. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  28. package/dist/types/src/capabilities/operation-resolver/index.d.ts +2 -0
  29. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
  30. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
  31. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
  32. package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
  33. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
  34. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
  35. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
  36. package/dist/types/src/components/PipelineColumn.d.ts +10 -0
  37. package/dist/types/src/components/PipelineColumn.d.ts.map +1 -0
  38. package/dist/types/src/components/PipelineComponent.d.ts +38 -0
  39. package/dist/types/src/components/PipelineComponent.d.ts.map +1 -0
  40. package/dist/types/src/components/PipelineComponent.stories.d.ts +42 -0
  41. package/dist/types/src/components/PipelineComponent.stories.d.ts.map +1 -0
  42. package/dist/types/src/components/PipelineContainer.d.ts +7 -0
  43. package/dist/types/src/components/PipelineContainer.d.ts.map +1 -0
  44. package/dist/types/src/components/PipelineContainer.stories.d.ts +56 -0
  45. package/dist/types/src/components/PipelineContainer.stories.d.ts.map +1 -0
  46. package/dist/types/src/components/PipelineSettings.d.ts +12 -0
  47. package/dist/types/src/components/PipelineSettings.d.ts.map +1 -0
  48. package/dist/types/src/components/index.d.ts +5 -0
  49. package/dist/types/src/components/index.d.ts.map +1 -0
  50. package/dist/types/src/index.d.ts +3 -0
  51. package/dist/types/src/index.d.ts.map +1 -0
  52. package/dist/types/src/meta.d.ts +3 -0
  53. package/dist/types/src/meta.d.ts.map +1 -0
  54. package/dist/types/src/templates/index.d.ts +2 -0
  55. package/dist/types/src/templates/index.d.ts.map +1 -0
  56. package/dist/types/src/templates/research.d.ts +4 -0
  57. package/dist/types/src/templates/research.d.ts.map +1 -0
  58. package/dist/types/src/templates/templates.d.ts +4 -0
  59. package/dist/types/src/templates/templates.d.ts.map +1 -0
  60. package/dist/types/src/translations.d.ts +31 -0
  61. package/dist/types/src/translations.d.ts.map +1 -0
  62. package/dist/types/src/types/PipelineAction.d.ts +25 -0
  63. package/dist/types/src/types/PipelineAction.d.ts.map +1 -0
  64. package/dist/types/src/types/index.d.ts +3 -0
  65. package/dist/types/src/types/index.d.ts.map +1 -0
  66. package/dist/types/tsconfig.tsbuildinfo +1 -0
  67. package/package.json +98 -0
  68. package/src/PipelinePlugin.tsx +46 -0
  69. package/src/capabilities/app-graph-builder/app-graph-builder.ts +51 -0
  70. package/src/capabilities/app-graph-builder/index.ts +7 -0
  71. package/src/capabilities/index.ts +7 -0
  72. package/src/capabilities/operation-resolver/index.ts +5 -0
  73. package/src/capabilities/operation-resolver/operation-resolver.ts +29 -0
  74. package/src/capabilities/react-surface/index.ts +7 -0
  75. package/src/capabilities/react-surface/react-surface.tsx +50 -0
  76. package/src/components/PipelineColumn.tsx +145 -0
  77. package/src/components/PipelineComponent.stories.tsx +204 -0
  78. package/src/components/PipelineComponent.tsx +103 -0
  79. package/src/components/PipelineContainer.stories.tsx +226 -0
  80. package/src/components/PipelineContainer.tsx +57 -0
  81. package/src/components/PipelineSettings.tsx +230 -0
  82. package/src/components/index.ts +11 -0
  83. package/src/index.ts +6 -0
  84. package/src/meta.ts +19 -0
  85. package/src/templates/index.ts +5 -0
  86. package/src/templates/research.ts +71 -0
  87. package/src/templates/templates.ts +11 -0
  88. package/src/translations.ts +38 -0
  89. package/src/types/PipelineAction.ts +29 -0
  90. package/src/types/index.ts +6 -0
@@ -0,0 +1,103 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { createContext } from '@radix-ui/react-context';
6
+ import React, { type FC } from 'react';
7
+
8
+ import { type Obj } from '@dxos/echo';
9
+ import { useObject } from '@dxos/react-client/echo';
10
+ import { Toolbar, type ToolbarRootProps, useTranslation } from '@dxos/react-ui';
11
+ import { Stack } from '@dxos/react-ui-stack';
12
+ import { type ProjectionModel } from '@dxos/schema';
13
+ import { type Pipeline as PipelineType } from '@dxos/types';
14
+
15
+ import { meta } from '../meta';
16
+
17
+ import { PipelineColumn } from './PipelineColumn';
18
+
19
+ type ItemProps = {
20
+ item: Obj.Unknown;
21
+ projectionModel?: ProjectionModel;
22
+ };
23
+
24
+ const itemNoOp = ({ item }: ItemProps) => <span>{item.id}</span>;
25
+
26
+ type PipelineContextValue = {
27
+ Item: FC<ItemProps>;
28
+ // TODO(wittjosiah): Support adding items.
29
+ // If the created item doesn't match the current query, it will not be visible.
30
+ // onAddItem?: (schema: Schema.Schema.AnyNoContext) => void;
31
+ onAddColumn?: () => void;
32
+ };
33
+
34
+ //
35
+ // Root
36
+ //
37
+
38
+ type PipelineRootProps = PipelineContextValue;
39
+
40
+ const PIPELINE_ROOT = 'Pipeline.Root';
41
+
42
+ const [PipelineRoot, usePipeline] = createContext<PipelineContextValue>(PIPELINE_ROOT, {
43
+ Item: itemNoOp,
44
+ });
45
+
46
+ PipelineRoot.displayName = PIPELINE_ROOT;
47
+
48
+ //
49
+ // Content
50
+ //
51
+
52
+ const PIPELINE_CONTENT_NAME = 'Pipeline.Content';
53
+
54
+ type PipelineContentProps = {
55
+ pipeline: PipelineType.Pipeline;
56
+ };
57
+
58
+ const PipelineContent = ({ pipeline }: PipelineContentProps) => {
59
+ const [columns] = useObject(pipeline, 'columns');
60
+
61
+ return (
62
+ <Stack orientation='horizontal' size='contain' rail={false}>
63
+ {columns.map((column) => {
64
+ return <PipelineColumn key={column.view.dxn.toString()} column={column} />;
65
+ })}
66
+ </Stack>
67
+ );
68
+ };
69
+
70
+ PipelineContent.displayName = PIPELINE_CONTENT_NAME;
71
+
72
+ //
73
+ // Toolbar
74
+ //
75
+
76
+ const PIPELINE_TOOLBAR_NAME = 'Pipeline.Toolbar';
77
+
78
+ export const PipelineToolbar = (props: ToolbarRootProps) => {
79
+ const { t } = useTranslation(meta.id);
80
+ const { onAddColumn } = usePipeline(PIPELINE_TOOLBAR_NAME);
81
+
82
+ return (
83
+ <Toolbar.Root {...props}>
84
+ <Toolbar.IconButton icon='ph--plus--regular' iconOnly label={t('add column label')} onClick={onAddColumn} />
85
+ </Toolbar.Root>
86
+ );
87
+ };
88
+
89
+ PipelineToolbar.displayName = PIPELINE_TOOLBAR_NAME;
90
+
91
+ //
92
+ // Project
93
+ //
94
+
95
+ export const PipelineComponent = {
96
+ Root: PipelineRoot,
97
+ Content: PipelineContent,
98
+ Toolbar: PipelineToolbar,
99
+ };
100
+
101
+ export { usePipeline };
102
+
103
+ export type { ItemProps, PipelineContextValue, PipelineRootProps };
@@ -0,0 +1,226 @@
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 React from 'react';
8
+
9
+ import { withPluginManager } from '@dxos/app-framework/testing';
10
+ import { Filter, Ref } from '@dxos/client/echo';
11
+ import { Obj, Query, Tag, Type } from '@dxos/echo';
12
+ import { ClientPlugin } from '@dxos/plugin-client';
13
+ import { InboxPlugin } from '@dxos/plugin-inbox';
14
+ import { PreviewPlugin } from '@dxos/plugin-preview';
15
+ import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
16
+ import { faker } from '@dxos/random';
17
+ import { useDatabase, useQuery } from '@dxos/react-client/echo';
18
+ import { withTheme } from '@dxos/react-ui/testing';
19
+ import { translations as stackTranslations } from '@dxos/react-ui-stack';
20
+ import { Stack } from '@dxos/react-ui-stack';
21
+ import { Collection, View } from '@dxos/schema';
22
+ import { createObjectFactory } from '@dxos/schema/testing';
23
+ import { Message, Organization, Person, Pipeline, Task } from '@dxos/types';
24
+
25
+ import { translations } from '../translations';
26
+
27
+ import { PipelineContainer } from './PipelineContainer';
28
+ import { PipelineObjectSettings } from './PipelineSettings';
29
+
30
+ faker.seed(0);
31
+
32
+ const DefaultStory = () => {
33
+ const db = useDatabase();
34
+ const pipelines = useQuery(db, Filter.type(Pipeline.Pipeline));
35
+ const pipeline = pipelines[0];
36
+
37
+ if (!pipeline) {
38
+ return <p>Loading…</p>;
39
+ }
40
+
41
+ return (
42
+ <Stack orientation='horizontal' size='split' rail={false} classNames='pli-0'>
43
+ <PipelineContainer role='article' subject={pipeline} />
44
+ <PipelineObjectSettings pipeline={pipeline} classNames='border-is border-separator' />
45
+ </Stack>
46
+ );
47
+ };
48
+
49
+ const meta = {
50
+ title: 'plugins/plugin-pipeline/PipelineContainer',
51
+ render: DefaultStory,
52
+ decorators: [
53
+ withTheme(),
54
+ withPluginManager({
55
+ plugins: [
56
+ ...corePlugins(),
57
+ StorybookPlugin({}),
58
+ ClientPlugin({
59
+ types: [
60
+ Tag.Tag,
61
+ Pipeline.Pipeline,
62
+ View.View,
63
+ Collection.Collection,
64
+ Organization.Organization,
65
+ Task.Task,
66
+ Person.Person,
67
+ Message.Message,
68
+ ],
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' }));
77
+ const tagDxn = Obj.getDXN(tag).toString();
78
+
79
+ // Create a view for Contacts.
80
+ const personView = View.make({
81
+ query: Query.select(Filter.type(Person.Person)),
82
+ jsonSchema: Type.toJsonSchema(Person.Person),
83
+ });
84
+
85
+ // Create a view for Organizations.
86
+ const organizationView = View.make({
87
+ query: Query.select(Filter.type(Organization.Organization)).select(Filter.tag(tagDxn)),
88
+ jsonSchema: Type.toJsonSchema(Organization.Organization),
89
+ });
90
+
91
+ // Create a view for Tasks.
92
+ const taskView = View.make({
93
+ query: Query.select(Filter.type(Task.Task)).select(Filter.tag(tagDxn)),
94
+ jsonSchema: Type.toJsonSchema(Task.Task),
95
+ });
96
+
97
+ // Create a view for Project-Projects.
98
+ const projectView = View.make({
99
+ query: Query.select(Filter.type(Pipeline.Pipeline)),
100
+ jsonSchema: Type.toJsonSchema(Pipeline.Pipeline),
101
+ });
102
+
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()],
108
+ }),
109
+ jsonSchema: Type.toJsonSchema(Message.Message),
110
+ });
111
+
112
+ // Create pipline with columns.
113
+ const pipeline = Pipeline.make({
114
+ columns: [
115
+ {
116
+ name: 'Contacts',
117
+ view: Ref.make(personView),
118
+ order: [],
119
+ },
120
+ {
121
+ name: 'Organizations',
122
+ view: Ref.make(organizationView),
123
+ order: [],
124
+ },
125
+ {
126
+ name: 'Tasks',
127
+ view: Ref.make(taskView),
128
+ order: [],
129
+ },
130
+ {
131
+ name: 'Projects',
132
+ view: Ref.make(projectView),
133
+ order: [],
134
+ },
135
+ {
136
+ name: 'Messages',
137
+ view: Ref.make(messageView),
138
+ order: [],
139
+ },
140
+ ],
141
+ });
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
+ {
151
+ name: faker.company.name(),
152
+ website: faker.internet.url(),
153
+ description: faker.lorem.paragraph(),
154
+ image: faker.image.url(),
155
+ },
156
+ {
157
+ tags: faker.datatype.boolean() ? [Obj.getDXN(tag).toString()] : [],
158
+ },
159
+ );
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
+ {
168
+ title: faker.lorem.sentence(),
169
+ status: faker.helpers.arrayElement(['todo', 'in-progress', 'done']) as any,
170
+ priority: faker.helpers.arrayElement(['low', 'medium', 'high']) as any,
171
+ },
172
+ {
173
+ tags: faker.datatype.boolean() ? [Obj.getDXN(tag).toString()] : [],
174
+ },
175
+ );
176
+ space.db.add(task);
177
+ });
178
+
179
+ // Generate sample Contacts
180
+ const factory = createObjectFactory(space.db, faker as any);
181
+ yield* Effect.promise(() => factory([{ type: Person.Person, count: 12 }]));
182
+
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
+ }),
209
+ }),
210
+
211
+ InboxPlugin(),
212
+ PreviewPlugin(),
213
+ ],
214
+ }),
215
+ ],
216
+ parameters: {
217
+ layout: 'fullscreen',
218
+ translations: [...translations, ...stackTranslations],
219
+ },
220
+ } satisfies Meta<typeof PipelineContainer>;
221
+
222
+ export default meta;
223
+
224
+ type Story = StoryObj<typeof meta>;
225
+
226
+ export const Default: Story = {};
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback } from 'react';
6
+
7
+ import { useOperationInvoker } from '@dxos/app-framework/ui';
8
+ import { Surface } from '@dxos/app-framework/ui';
9
+ import { type SurfaceComponentProps } from '@dxos/app-toolkit/ui';
10
+ import { Obj } from '@dxos/echo';
11
+ import { ATTENDABLE_PATH_SEPARATOR, DeckOperation } from '@dxos/plugin-deck/types';
12
+ import { Layout } from '@dxos/react-ui';
13
+ import { useAttention } from '@dxos/react-ui-attention';
14
+ import { type Pipeline as PipelineType } from '@dxos/types';
15
+
16
+ import { type ItemProps, PipelineComponent } from './PipelineComponent';
17
+
18
+ export type PipelineContainerProps = SurfaceComponentProps<PipelineType.Pipeline>;
19
+
20
+ export const PipelineContainer = ({ role, subject: pipeline }: PipelineContainerProps) => {
21
+ const { invokePromise } = useOperationInvoker();
22
+ const attendableId = Obj.getDXN(pipeline).toString();
23
+ const { hasAttention } = useAttention(attendableId);
24
+
25
+ const handleColumnAdd = useCallback(
26
+ () =>
27
+ invokePromise(DeckOperation.ChangeCompanion, {
28
+ primary: attendableId,
29
+ companion: `${attendableId}${ATTENDABLE_PATH_SEPARATOR}settings`,
30
+ }),
31
+ [invokePromise, attendableId],
32
+ );
33
+
34
+ return (
35
+ <Layout.Main role={role} toolbar>
36
+ <PipelineComponent.Root Item={PipelineItem} onAddColumn={handleColumnAdd}>
37
+ <PipelineComponent.Toolbar disabled={!hasAttention} />
38
+ <PipelineComponent.Content pipeline={pipeline} />
39
+ </PipelineComponent.Root>
40
+ </Layout.Main>
41
+ );
42
+ };
43
+
44
+ const PipelineItem = ({ item, projectionModel }: ItemProps) => {
45
+ return (
46
+ <Surface.Surface
47
+ role='card--content'
48
+ data={{
49
+ subject: item,
50
+ projection: projectionModel,
51
+ }}
52
+ limit={1}
53
+ />
54
+ );
55
+ };
56
+
57
+ export default PipelineContainer;
@@ -0,0 +1,230 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Schema from 'effect/Schema';
6
+ import React, { useCallback, useMemo, useRef, useState } from 'react';
7
+
8
+ import { DXN, Filter, Obj, Query, type QueryAST, Ref, Tag, Type } from '@dxos/echo';
9
+ import { type Mutable } from '@dxos/echo/internal';
10
+ import { useTypeOptions } from '@dxos/plugin-space';
11
+ import { resolveSchemaWithRegistry } from '@dxos/plugin-space';
12
+ import { getSpace, useQuery } from '@dxos/react-client/echo';
13
+ import { IconButton, type ThemedClassName, useAsyncEffect, useTranslation } from '@dxos/react-ui';
14
+ import { Form, ViewEditor } from '@dxos/react-ui-form';
15
+ import { List } from '@dxos/react-ui-list';
16
+ import { type ProjectionModel, View } from '@dxos/schema';
17
+ import { Pipeline, Task } from '@dxos/types';
18
+ import { inputTextLabel, mx, osTranslations, subtleHover } from '@dxos/ui-theme';
19
+ import { arrayMove } from '@dxos/util';
20
+
21
+ import { meta } from '../meta';
22
+
23
+ const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
24
+ const listItemGrid = 'grid grid-cols-subgrid col-span-5';
25
+
26
+ const ColumnFormSchema = Pipeline.Column.pipe(Schema.mutable, Schema.pick('name'));
27
+
28
+ // TODO(burdon): Standardize Object/Plugin settings.
29
+ export type PipelineObjectSettingsProps = ThemedClassName<{
30
+ pipeline: Pipeline.Pipeline;
31
+ }>;
32
+
33
+ /**
34
+ * Supports editing the pipeline view.
35
+ */
36
+ export const PipelineObjectSettings = ({ classNames, pipeline }: PipelineObjectSettingsProps) => {
37
+ const { t } = useTranslation(meta.id);
38
+ const space = getSpace(pipeline);
39
+ const [expandedId, setExpandedId] = useState<string>();
40
+ const column = useMemo(
41
+ () => pipeline.columns.find((column) => column.view.dxn.toString() === expandedId),
42
+ [pipeline.columns, expandedId],
43
+ );
44
+ const view = column?.view.target;
45
+ const [schema, setSchema] = useState<Schema.Schema.AnyNoContext>(() => Schema.Struct({}));
46
+ const projectionRef = useRef<ProjectionModel>(null);
47
+ const tags = useQuery(space?.db, Filter.type(Tag.Tag));
48
+ const types = useTypeOptions({
49
+ space,
50
+ annotation: {
51
+ location: ['database', 'runtime'],
52
+ kind: ['user'],
53
+ registered: ['registered', 'unregistered'],
54
+ },
55
+ });
56
+
57
+ useAsyncEffect(async () => {
58
+ if (!view?.query || !space) {
59
+ return;
60
+ }
61
+
62
+ const foundSchema = await resolveSchemaWithRegistry(space.db.schemaRegistry, view.query.ast);
63
+ if (foundSchema && foundSchema !== schema) {
64
+ setSchema(() => foundSchema);
65
+ }
66
+ }, [space, view, schema]);
67
+
68
+ const handleMove = useCallback(
69
+ (fromIndex: number, toIndex: number) =>
70
+ Obj.change(pipeline, (p) => {
71
+ arrayMove(p.columns, fromIndex, toIndex);
72
+ }),
73
+ [pipeline],
74
+ );
75
+
76
+ const handleQueryChanged = useCallback(
77
+ async (newQuery: QueryAST.Query, target?: string) => {
78
+ if (!view || !space) {
79
+ return;
80
+ }
81
+
82
+ 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>;
86
+ });
87
+ const newSchema = await resolveSchemaWithRegistry(space.db.schemaRegistry, query.ast);
88
+ if (!newSchema) {
89
+ return;
90
+ }
91
+
92
+ const newView = View.make({
93
+ query,
94
+ jsonSchema: Type.toJsonSchema(newSchema),
95
+ });
96
+ Obj.change(view, (v) => {
97
+ v.projection = Obj.getSnapshot(newView).projection as Mutable<typeof v.projection>;
98
+ });
99
+
100
+ setSchema(() => newSchema);
101
+ },
102
+ [view, schema],
103
+ );
104
+
105
+ const handleToggleField = useCallback((column: Pipeline.Column) => {
106
+ setExpandedId((prevExpandedId) =>
107
+ prevExpandedId === column.view.dxn.toString() ? undefined : column.view.dxn.toString(),
108
+ );
109
+ }, []);
110
+
111
+ const handleDelete = useCallback(
112
+ async (column: Pipeline.Column) => {
113
+ if (column.view.dxn.toString() === expandedId) {
114
+ setExpandedId(undefined);
115
+ }
116
+
117
+ const index = pipeline.columns.findIndex((l) => l === column);
118
+ const viewToRemove = await column.view.load();
119
+ Obj.change(pipeline, (p) => {
120
+ p.columns.splice(index, 1);
121
+ });
122
+ space?.db.remove(viewToRemove);
123
+ },
124
+ [expandedId, pipeline.columns, space],
125
+ );
126
+
127
+ const handleAdd = useCallback(() => {
128
+ const newView = View.make({
129
+ query: Query.select(Filter.type(Task.Task)),
130
+ jsonSchema: Type.toJsonSchema(Task.Task),
131
+ });
132
+ Obj.change(pipeline, (p) => {
133
+ p.columns.push({
134
+ name: 'Tasks',
135
+ // Type assertion needed due to QueryAST type variance.
136
+ view: Ref.make(newView) as (typeof p.columns)[number]['view'],
137
+ order: [],
138
+ });
139
+ });
140
+ 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
+ );
154
+
155
+ return (
156
+ <div role='none' className={mx('plb-cardSpacingBlock overflow-y-auto', classNames)}>
157
+ <h2 className={mx(inputTextLabel)}>{t('views label')}</h2>
158
+
159
+ <List.Root<Pipeline.Column>
160
+ items={pipeline.columns}
161
+ isItem={Schema.is(Pipeline.Column)}
162
+ getId={(column) => column.view.dxn.toString()}
163
+ onMove={handleMove}
164
+ >
165
+ {({ items: columns }) => (
166
+ <>
167
+ <div role='list' className={mx(listGrid)}>
168
+ {columns.map((column) => (
169
+ <List.Item<Pipeline.Column>
170
+ key={column.view.dxn.toString()}
171
+ item={column}
172
+ classNames={listItemGrid}
173
+ aria-expanded={expandedId === column.view.dxn.toString()}
174
+ >
175
+ <div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
176
+ <List.ItemDragHandle />
177
+ <List.ItemTitle onClick={() => handleToggleField(column)}>{column.name}</List.ItemTitle>
178
+ <List.ItemDeleteButton
179
+ label={t('delete view label')}
180
+ autoHide={false}
181
+ onClick={() => handleDelete(column)}
182
+ data-testid='view.delete'
183
+ />
184
+ <IconButton
185
+ iconOnly
186
+ variant='ghost'
187
+ label={t('toggle expand label', { ns: osTranslations })}
188
+ icon={
189
+ expandedId === column.view.dxn.toString()
190
+ ? 'ph--caret-down--regular'
191
+ : 'ph--caret-right--regular'
192
+ }
193
+ onClick={() => handleToggleField(column)}
194
+ />
195
+ </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}>
199
+ <Form.Content>
200
+ <Form.FieldSet />
201
+ </Form.Content>
202
+ </Form.Root>
203
+ <ViewEditor
204
+ ref={projectionRef}
205
+ mode='tag'
206
+ readonly
207
+ schema={schema}
208
+ view={view}
209
+ registry={space?.db.schemaRegistry}
210
+ tags={tags}
211
+ types={types}
212
+ onQueryChanged={handleQueryChanged}
213
+ />
214
+ </div>
215
+ )}
216
+ </List.Item>
217
+ ))}
218
+ </div>
219
+ </>
220
+ )}
221
+ </List.Root>
222
+
223
+ <div role='none' className='mlb-cardSpacingBlock'>
224
+ <IconButton icon='ph--plus--regular' label={t('add view label')} onClick={handleAdd} classNames='is-full' />
225
+ </div>
226
+ </div>
227
+ );
228
+ };
229
+
230
+ export default PipelineObjectSettings;
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
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'));
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './meta';
6
+ export * from './PipelinePlugin';
package/src/meta.ts ADDED
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { type Plugin } from '@dxos/app-framework';
6
+ import { trim } from '@dxos/util';
7
+
8
+ export const meta: Plugin.Meta = {
9
+ id: 'dxos.org/plugin/pipeline',
10
+ name: 'Pipelines',
11
+ description: trim`
12
+ Pipelines for organizing tasks, milestones, and deliverables.
13
+ Track progress across multiple pipelines and coordinate team activities in a structured environment.
14
+ `,
15
+ icon: 'ph--path--regular',
16
+ iconHue: 'purple',
17
+ source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-pipeline',
18
+ tags: ['labs'],
19
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './templates';