@dxos/plugin-kanban 0.8.4-main.9be5663bfe → 0.8.4-main.abd8ff62ef
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.
- package/dist/lib/browser/blueprints/index.mjs +1 -1
- package/dist/lib/browser/blueprints/index.mjs.map +3 -3
- package/dist/lib/browser/{chunk-A3PBV3S5.mjs → chunk-T32TEM55.mjs} +2 -2
- package/dist/lib/browser/chunk-T32TEM55.mjs.map +7 -0
- package/dist/lib/browser/{delete-card-VPNVIWOA.mjs → delete-card-7OSCNCW2.mjs} +4 -12
- package/dist/lib/browser/delete-card-7OSCNCW2.mjs.map +7 -0
- package/dist/lib/browser/{delete-card-field-4HHF2GYX.mjs → delete-card-field-NSB2RE3Z.mjs} +4 -12
- package/dist/lib/browser/delete-card-field-NSB2RE3Z.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +5 -97
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/operations/index.mjs +3 -3
- package/dist/lib/browser/operations/index.mjs.map +3 -3
- package/dist/lib/browser/{restore-card-4GG2RYKR.mjs → restore-card-FO3WERIE.mjs} +4 -12
- package/dist/lib/browser/restore-card-FO3WERIE.mjs.map +7 -0
- package/dist/lib/browser/{restore-card-field-3T26ACYX.mjs → restore-card-field-U5XYEEOW.mjs} +4 -12
- package/dist/lib/browser/restore-card-field-U5XYEEOW.mjs.map +7 -0
- package/dist/lib/browser/translations.mjs +44 -0
- package/dist/lib/browser/translations.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +70 -10
- package/dist/lib/browser/types/index.mjs.map +3 -3
- package/dist/lib/node-esm/blueprints/index.mjs +1 -1
- package/dist/lib/node-esm/blueprints/index.mjs.map +3 -3
- package/dist/lib/node-esm/{chunk-6LELYA2G.mjs → chunk-W2RNFBMZ.mjs} +2 -2
- package/dist/lib/node-esm/chunk-W2RNFBMZ.mjs.map +7 -0
- package/dist/lib/node-esm/{delete-card-5PW5OMFN.mjs → delete-card-ZIREL6HN.mjs} +4 -12
- package/dist/lib/node-esm/delete-card-ZIREL6HN.mjs.map +7 -0
- package/dist/lib/node-esm/{delete-card-field-KPJU2AQ3.mjs → delete-card-field-IPTEGVPP.mjs} +4 -12
- package/dist/lib/node-esm/delete-card-field-IPTEGVPP.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +5 -97
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/operations/index.mjs +3 -3
- package/dist/lib/node-esm/operations/index.mjs.map +3 -3
- package/dist/lib/node-esm/{restore-card-X2TKMU5A.mjs → restore-card-WJJ4YB7K.mjs} +4 -12
- package/dist/lib/node-esm/restore-card-WJJ4YB7K.mjs.map +7 -0
- package/dist/lib/node-esm/{restore-card-field-IUTL4RTR.mjs → restore-card-field-L24WJXAW.mjs} +4 -12
- package/dist/lib/node-esm/restore-card-field-L24WJXAW.mjs.map +7 -0
- package/dist/lib/node-esm/translations.mjs +45 -0
- package/dist/lib/node-esm/translations.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +70 -10
- package/dist/lib/node-esm/types/index.mjs.map +3 -3
- package/dist/types/src/KanbanPlugin.d.ts +1 -0
- package/dist/types/src/KanbanPlugin.d.ts.map +1 -1
- package/dist/types/src/KanbanPlugin.node.d.ts +4 -0
- package/dist/types/src/KanbanPlugin.node.d.ts.map +1 -0
- package/dist/types/src/KanbanPlugin.test.d.ts +2 -0
- package/dist/types/src/KanbanPlugin.test.d.ts.map +1 -0
- package/dist/types/src/blueprints/kanban-blueprint.d.ts +2 -2
- package/dist/types/src/blueprints/kanban-blueprint.d.ts.map +1 -1
- package/dist/types/src/capabilities/artifact-definition.d.ts.map +1 -1
- package/dist/types/src/capabilities/blueprint-definition.d.ts +2 -2
- package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +3 -2
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/migrations.d.ts +5 -0
- package/dist/types/src/capabilities/migrations.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/undo-mappings.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +6 -6
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts +24 -24
- package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts.map +1 -1
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts +2 -2
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts.map +1 -1
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts +24 -24
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts.map +1 -1
- package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts +13 -0
- package/dist/types/src/containers/KanbanSettings/KanbanSettings.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanSettings/index.d.ts +2 -0
- package/dist/types/src/containers/KanbanSettings/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +1 -1
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useEchoChangeCallback.d.ts.map +1 -1
- package/dist/types/src/hooks/useItemsProjection.d.ts +10 -0
- package/dist/types/src/hooks/useItemsProjection.d.ts.map +1 -0
- package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts +2 -0
- package/dist/types/src/hooks/useKanbanBoardModel.browser.test.d.ts.map +1 -0
- package/dist/types/src/hooks/useKanbanBoardModel.d.ts.map +1 -1
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts +1 -1
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +1 -1
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/operations/definitions.d.ts +2 -2
- package/dist/types/src/operations/definitions.d.ts.map +1 -1
- package/dist/types/src/operations/delete-card-field.d.ts +1 -1
- package/dist/types/src/operations/delete-card-field.d.ts.map +1 -1
- package/dist/types/src/operations/delete-card.d.ts +1 -1
- package/dist/types/src/operations/delete-card.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts +1 -1
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/operations/restore-card-field.d.ts +1 -1
- package/dist/types/src/operations/restore-card-field.d.ts.map +1 -1
- package/dist/types/src/operations/restore-card.d.ts +1 -1
- package/dist/types/src/operations/restore-card.d.ts.map +1 -1
- package/dist/types/src/playwright/board-manager.d.ts.map +1 -1
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +24 -24
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Kanban.d.ts +78 -6
- package/dist/types/src/types/Kanban.d.ts.map +1 -1
- package/dist/types/src/types/constants.d.ts +3 -3
- package/dist/types/src/types/constants.d.ts.map +1 -1
- package/dist/types/src/types/migrations.test.d.ts +2 -0
- package/dist/types/src/types/migrations.test.d.ts.map +1 -0
- package/dist/types/src/types/schema.d.ts +15 -1
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +2 -2
- package/dist/types/src/util/arrangement.d.ts +7 -3
- package/dist/types/src/util/arrangement.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -50
- package/src/KanbanPlugin.node.ts +55 -0
- package/src/KanbanPlugin.test.ts +31 -0
- package/src/KanbanPlugin.tsx +11 -5
- package/src/blueprints/kanban-blueprint.ts +2 -3
- package/src/capabilities/artifact-definition.ts +1 -1
- package/src/capabilities/blueprint-definition.ts +2 -0
- package/src/capabilities/index.ts +3 -1
- package/src/capabilities/migrations.ts +35 -0
- package/src/capabilities/operation-handler.ts +1 -1
- package/src/capabilities/react-surface.tsx +16 -8
- package/src/components/KanbanBoard/KanbanBoard.stories.tsx +7 -3
- package/src/components/KanbanBoard/KanbanBoard.tsx +7 -2
- package/src/components/KanbanBoard/KanbanCard.tsx +15 -3
- package/src/components/KanbanBoard/KanbanColumn.tsx +7 -5
- package/src/containers/KanbanContainer/KanbanContainer.stories.tsx +21 -16
- package/src/containers/KanbanContainer/KanbanContainer.tsx +89 -7
- package/src/containers/KanbanSettings/KanbanSettings.tsx +94 -0
- package/src/containers/KanbanSettings/index.ts +5 -0
- package/src/containers/index.ts +1 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useEchoChangeCallback.ts +2 -2
- package/src/hooks/useItemsProjection.ts +44 -0
- package/src/hooks/{useKanbanBoardModel.test.ts → useKanbanBoardModel.browser.test.ts} +3 -3
- package/src/hooks/useKanbanBoardModel.ts +18 -5
- package/src/hooks/useProjectionModel.ts +2 -2
- package/src/index.ts +6 -2
- package/src/operations/definitions.ts +1 -1
- package/src/operations/delete-card-field.ts +1 -1
- package/src/operations/delete-card.ts +1 -1
- package/src/operations/index.ts +1 -1
- package/src/operations/restore-card-field.ts +1 -1
- package/src/operations/restore-card.ts +1 -1
- package/src/types/Kanban.ts +92 -12
- package/src/types/migrations.test.ts +83 -0
- package/src/types/schema.ts +19 -1
- package/src/types/types.ts +2 -2
- package/src/util/arrangement.test.ts +10 -0
- package/src/util/arrangement.ts +24 -14
- package/dist/lib/browser/chunk-A3PBV3S5.mjs.map +0 -7
- package/dist/lib/browser/delete-card-VPNVIWOA.mjs.map +0 -7
- package/dist/lib/browser/delete-card-field-4HHF2GYX.mjs.map +0 -7
- package/dist/lib/browser/restore-card-4GG2RYKR.mjs.map +0 -7
- package/dist/lib/browser/restore-card-field-3T26ACYX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6LELYA2G.mjs.map +0 -7
- package/dist/lib/node-esm/delete-card-5PW5OMFN.mjs.map +0 -7
- package/dist/lib/node-esm/delete-card-field-KPJU2AQ3.mjs.map +0 -7
- package/dist/lib/node-esm/restore-card-X2TKMU5A.mjs.map +0 -7
- package/dist/lib/node-esm/restore-card-field-IUTL4RTR.mjs.map +0 -7
- package/dist/types/src/containers/KanbanViewEditor/KanbanViewEditor.d.ts +0 -6
- package/dist/types/src/containers/KanbanViewEditor/KanbanViewEditor.d.ts.map +0 -1
- package/dist/types/src/containers/KanbanViewEditor/index.d.ts +0 -2
- package/dist/types/src/containers/KanbanViewEditor/index.d.ts.map +0 -1
- package/dist/types/src/hooks/useKanbanBoardModel.test.d.ts +0 -2
- package/dist/types/src/hooks/useKanbanBoardModel.test.d.ts.map +0 -1
- package/src/containers/KanbanViewEditor/KanbanViewEditor.tsx +0 -63
- package/src/containers/KanbanViewEditor/index.ts +0 -5
|
@@ -2,31 +2,42 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { RegistryContext } from '@effect-atom/atom-react';
|
|
5
|
+
import { Atom, RegistryContext } from '@effect-atom/atom-react';
|
|
6
6
|
import React, { useCallback, useContext, useMemo } from 'react';
|
|
7
7
|
|
|
8
8
|
import { useCapabilities, useOperationInvoker } from '@dxos/app-framework/ui';
|
|
9
9
|
import { AppCapabilities } from '@dxos/app-toolkit';
|
|
10
10
|
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
11
|
-
import { Filter, Obj, Query, Type } from '@dxos/echo';
|
|
12
|
-
import { AtomQuery } from '@dxos/echo-atom';
|
|
11
|
+
import { Filter, Obj, Query, type Ref, Type } from '@dxos/echo';
|
|
12
|
+
import { AtomObj, AtomQuery } from '@dxos/echo-atom';
|
|
13
13
|
import { useObject, useSchema } from '@dxos/react-client/echo';
|
|
14
14
|
import { Panel, Toolbar } from '@dxos/react-ui';
|
|
15
15
|
import { getTagFromQuery, getTypenameFromQuery } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { KanbanBoard } from '#components';
|
|
18
|
-
import { useEchoChangeCallback, useProjectionModel } from '#hooks';
|
|
18
|
+
import { useEchoChangeCallback, useItemsProjection, useProjectionModel } from '#hooks';
|
|
19
19
|
import { KanbanOperation } from '#operations';
|
|
20
|
-
import {
|
|
20
|
+
import { Kanban } from '#types';
|
|
21
21
|
|
|
22
22
|
export type KanbanContainerProps = AppSurface.ObjectArticleProps<Kanban.Kanban>;
|
|
23
23
|
|
|
24
|
-
export const KanbanContainer = (
|
|
24
|
+
export const KanbanContainer = (props: KanbanContainerProps) => {
|
|
25
|
+
// Branch on `kanban.spec.kind`: view-variant runs a typename query through
|
|
26
|
+
// `useProjectionModel`; items-variant dereferences `kanban.spec.items` and
|
|
27
|
+
// uses a stub projection from `useItemsProjection`.
|
|
28
|
+
return Kanban.isKanbanItems(props.subject) ? (
|
|
29
|
+
<ItemsKanbanContainer {...props} subject={props.subject} />
|
|
30
|
+
) : (
|
|
31
|
+
<ViewKanbanContainer {...props} />
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const ViewKanbanContainer = ({ role, subject: object }: KanbanContainerProps) => {
|
|
25
36
|
const registry = useContext(RegistryContext);
|
|
26
37
|
const schemas = useCapabilities(AppCapabilities.Schema);
|
|
27
38
|
const db = Obj.getDatabase(object);
|
|
28
39
|
const { invokePromise } = useOperationInvoker();
|
|
29
|
-
const [view] = useObject(object.view);
|
|
40
|
+
const [view] = useObject(object.spec.kind === 'view' ? object.spec.view : undefined);
|
|
30
41
|
const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
|
|
31
42
|
const tag = view?.query ? getTagFromQuery(view.query.ast) : undefined;
|
|
32
43
|
|
|
@@ -94,3 +105,74 @@ export const KanbanContainer = ({ role, subject: object }: KanbanContainerProps)
|
|
|
94
105
|
</Panel.Root>
|
|
95
106
|
);
|
|
96
107
|
};
|
|
108
|
+
|
|
109
|
+
type ItemsKanbanContainerProps = Omit<KanbanContainerProps, 'subject'> & { subject: Kanban.KanbanItems };
|
|
110
|
+
|
|
111
|
+
const ItemsKanbanContainer = ({ role, subject: object }: ItemsKanbanContainerProps) => {
|
|
112
|
+
const db = Obj.getDatabase(object);
|
|
113
|
+
const projection = useItemsProjection(object);
|
|
114
|
+
const change = useEchoChangeCallback(object);
|
|
115
|
+
|
|
116
|
+
// TODO(wittjosiah): pass refs (not loaded objects) through to the kanban
|
|
117
|
+
// board and let `KanbanCard` subscribe to its own ref via `useObject`.
|
|
118
|
+
// Today this atom subscribes to *every* item — any one changing causes the
|
|
119
|
+
// container (and the model's per-column atoms) to recompute. With cards
|
|
120
|
+
// subscribing themselves, the container only needs the refs and the
|
|
121
|
+
// per-card render is independent. Requires:
|
|
122
|
+
// - `KanbanCard` to accept `Ref<Obj.Unknown>` as `data` and call
|
|
123
|
+
// `useObject(ref)` internally.
|
|
124
|
+
// - The model to handle a ref-bearing item shape (id from
|
|
125
|
+
// `ref.dxn.asEchoDXN()?.echoId`) and use arrangement-only ordering
|
|
126
|
+
// for items-variant (no pivot-value fallback, since refs don't expose
|
|
127
|
+
// the pivot field without loading).
|
|
128
|
+
// - `Mosaic.isItem` to accept the ref wrapper alongside `Obj.isObject`.
|
|
129
|
+
const itemsAtom = useMemo(
|
|
130
|
+
() =>
|
|
131
|
+
Atom.make((get) => {
|
|
132
|
+
const out: Obj.Unknown[] = [];
|
|
133
|
+
for (const ref of object.spec.items as ReadonlyArray<Ref.Ref<Obj.Unknown>>) {
|
|
134
|
+
const target = get(AtomObj.make(ref));
|
|
135
|
+
if (target == null) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Drop soft-deleted cards (e.g. Trello-closed cards). The ref
|
|
139
|
+
// stays in `spec.items` so arrangement is preserved, but the card
|
|
140
|
+
// shouldn't render.
|
|
141
|
+
if (Obj.isDeleted(target)) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
out.push(target as unknown as Obj.Unknown);
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}),
|
|
148
|
+
[object.spec.items],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const handleCardRemove = useCallback(() => undefined, []);
|
|
152
|
+
|
|
153
|
+
if (!object || !db || !change) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// TODO(wittjosiah): wire `onCardAdd` to the create-object flow so
|
|
158
|
+
// users can add items directly from the kanban (currently the column's
|
|
159
|
+
// "+" button is hidden because `onCardAdd` is undefined).
|
|
160
|
+
return (
|
|
161
|
+
<Panel.Root role={role}>
|
|
162
|
+
<Panel.Toolbar asChild>
|
|
163
|
+
<Toolbar.Root />
|
|
164
|
+
</Panel.Toolbar>
|
|
165
|
+
<KanbanBoard.Root
|
|
166
|
+
kanban={object}
|
|
167
|
+
projection={projection}
|
|
168
|
+
items={itemsAtom}
|
|
169
|
+
change={change}
|
|
170
|
+
onCardRemove={handleCardRemove}
|
|
171
|
+
>
|
|
172
|
+
<Panel.Content asChild>
|
|
173
|
+
<KanbanBoard.Content />
|
|
174
|
+
</Panel.Content>
|
|
175
|
+
</KanbanBoard.Root>
|
|
176
|
+
</Panel.Root>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import React, { useCallback, useContext, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
9
|
+
import { Obj } from '@dxos/echo';
|
|
10
|
+
import { Format } from '@dxos/echo/internal';
|
|
11
|
+
import { useObject, useSchema } from '@dxos/react-client/echo';
|
|
12
|
+
import { Form, type FormFieldMap, SelectField } from '@dxos/react-ui-form';
|
|
13
|
+
import { getTypenameFromQuery } from '@dxos/schema';
|
|
14
|
+
|
|
15
|
+
import { useProjectionModel } from '#hooks';
|
|
16
|
+
import { type Kanban, KanbanSettingsSchema, KanbanViewSettingsSchema, UNCATEGORIZED_VALUE } from '#types';
|
|
17
|
+
|
|
18
|
+
export type KanbanSettingsProps = AppSurface.ObjectPropertiesProps<Kanban.Kanban>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Settings panel for a Kanban. Renders fields common to every kanban
|
|
22
|
+
* (currently the "Hide uncategorized column" toggle); for view-variant
|
|
23
|
+
* kanbans an additional "Column field" picker drives the View's pivot
|
|
24
|
+
* field. Items-variant kanbans use a hardcoded `spec.pivotField`, so that
|
|
25
|
+
* field is omitted there.
|
|
26
|
+
*/
|
|
27
|
+
export const KanbanSettings = ({ subject: object }: KanbanSettingsProps) => {
|
|
28
|
+
const registry = useContext(RegistryContext);
|
|
29
|
+
const db = Obj.getDatabase(object);
|
|
30
|
+
const isView = object.spec.kind === 'view';
|
|
31
|
+
const [view, updateView] = useObject(object.spec.kind === 'view' ? object.spec.view : undefined);
|
|
32
|
+
const [, updateKanban] = useObject(object);
|
|
33
|
+
const currentTypename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
|
|
34
|
+
const schema = useSchema(db, currentTypename);
|
|
35
|
+
const projection = useProjectionModel(schema, object, registry);
|
|
36
|
+
|
|
37
|
+
const fieldProjections = projection?.getFieldProjections() ?? [];
|
|
38
|
+
const selectFields = useMemo(
|
|
39
|
+
() =>
|
|
40
|
+
fieldProjections
|
|
41
|
+
.filter((field) => field.props.format === Format.TypeFormat.SingleSelect)
|
|
42
|
+
.map(({ field }) => ({ value: field.id, label: field.path })),
|
|
43
|
+
[fieldProjections],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const hideUncategorized = object.arrangement.columns[UNCATEGORIZED_VALUE]?.hidden ?? false;
|
|
47
|
+
|
|
48
|
+
const handleValuesChanged = useCallback(
|
|
49
|
+
(values: Partial<{ columnFieldId: string; hideUncategorized: boolean }>) => {
|
|
50
|
+
if (isView && values.columnFieldId != null) {
|
|
51
|
+
updateView((view) => {
|
|
52
|
+
view.projection.pivotFieldId = values.columnFieldId!;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (values.hideUncategorized !== undefined) {
|
|
56
|
+
updateKanban((kanban) => {
|
|
57
|
+
const existing = kanban.arrangement.columns[UNCATEGORIZED_VALUE];
|
|
58
|
+
if (existing) {
|
|
59
|
+
existing.hidden = values.hideUncategorized;
|
|
60
|
+
} else {
|
|
61
|
+
kanban.arrangement.columns[UNCATEGORIZED_VALUE] = {
|
|
62
|
+
ids: [],
|
|
63
|
+
hidden: values.hideUncategorized,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[isView, updateView, updateKanban],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const initialValues = useMemo(
|
|
73
|
+
() => ({
|
|
74
|
+
...(isView ? { columnFieldId: view?.projection.pivotFieldId } : {}),
|
|
75
|
+
hideUncategorized,
|
|
76
|
+
}),
|
|
77
|
+
[isView, view?.projection.pivotFieldId, hideUncategorized],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const fieldMap: FormFieldMap = useMemo(
|
|
81
|
+
() => ({ columnFieldId: (props) => <SelectField {...props} options={selectFields} /> }),
|
|
82
|
+
[selectFields],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Schema is picked by `kanban.spec.kind` — they have different shapes,
|
|
86
|
+
// so cast for `Form.Root`'s single-schema prop.
|
|
87
|
+
const settingsSchema = (isView ? KanbanViewSettingsSchema : KanbanSettingsSchema) as any;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Form.Root schema={settingsSchema} values={initialValues} fieldMap={fieldMap} onValuesChanged={handleValuesChanged}>
|
|
91
|
+
<Form.FieldSet />
|
|
92
|
+
</Form.Root>
|
|
93
|
+
);
|
|
94
|
+
};
|
package/src/containers/index.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
import { type ComponentType, lazy } from 'react';
|
|
6
6
|
|
|
7
7
|
export const KanbanContainer: ComponentType<any> = lazy(() => import('./KanbanContainer'));
|
|
8
|
-
export const
|
|
8
|
+
export const KanbanSettings: ComponentType<any> = lazy(() => import('./KanbanSettings'));
|
package/src/hooks/index.ts
CHANGED
|
@@ -13,9 +13,9 @@ import { type Kanban, type KanbanChangeCallback } from '#types';
|
|
|
13
13
|
* Use this when the kanban and items are stored in the ECHO database.
|
|
14
14
|
*/
|
|
15
15
|
export const createEchoChangeCallback = <T extends Obj.Unknown>(kanban: Kanban.Kanban): KanbanChangeCallback<T> => ({
|
|
16
|
-
kanban: (mutate) => Obj.
|
|
16
|
+
kanban: (mutate) => Obj.update(kanban, (kanban) => mutate(kanban)),
|
|
17
17
|
setItemField: (item, field, value) => {
|
|
18
|
-
Obj.
|
|
18
|
+
Obj.update(item, (item: any) => {
|
|
19
19
|
item[field] = value;
|
|
20
20
|
});
|
|
21
21
|
},
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import type { ProjectionModel } from '@dxos/schema';
|
|
9
|
+
|
|
10
|
+
import { type Kanban } from '#types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal `ProjectionModel` for `spec.kind === 'items'` (no View). Supplies `pivotField`
|
|
14
|
+
* and column options from `arrangement.columns` keys—written by sync so columns exist
|
|
15
|
+
* before refs hydrate. Stubs `getFieldProjections` / `getHiddenProperties` for shared
|
|
16
|
+
* board/card UI; hides the pivot on the card body (column shows it); Expando cards render title only.
|
|
17
|
+
*/
|
|
18
|
+
export const useItemsProjection = (kanban: Kanban.KanbanItems): ProjectionModel => {
|
|
19
|
+
return useMemo(() => {
|
|
20
|
+
const pivotField = kanban.spec.pivotField;
|
|
21
|
+
|
|
22
|
+
const optionIds = Object.keys(kanban.arrangement?.columns ?? {});
|
|
23
|
+
const options = optionIds.map((id) => ({ id, title: id, color: 'neutral' as const }));
|
|
24
|
+
|
|
25
|
+
const fieldProjection: any = {
|
|
26
|
+
field: { id: pivotField, path: pivotField },
|
|
27
|
+
props: { property: pivotField, options },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const fields = Atom.make(() => [fieldProjection.field]);
|
|
31
|
+
|
|
32
|
+
const stub: Pick<ProjectionModel, 'tryGetFieldProjection' | 'getFieldProjections' | 'getHiddenProperties'> & {
|
|
33
|
+
fields: typeof fields;
|
|
34
|
+
} = {
|
|
35
|
+
fields,
|
|
36
|
+
tryGetFieldProjection: (id: string) => (id === pivotField ? fieldProjection : undefined),
|
|
37
|
+
getFieldProjections: () => [],
|
|
38
|
+
getHiddenProperties: () => [pivotField],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// TODO(wittjosiah): Refactor ProjectionModel to be an interface that we can fulfill.
|
|
42
|
+
return stub as unknown as ProjectionModel;
|
|
43
|
+
}, [kanban.arrangement?.columns, kanban.spec.pivotField]);
|
|
44
|
+
};
|
|
@@ -146,7 +146,7 @@ describe('useKanbanBoardModel', () => {
|
|
|
146
146
|
expect(orderBefore).toEqual(['__uncategorized__', 'a', 'b']);
|
|
147
147
|
|
|
148
148
|
act(() => {
|
|
149
|
-
Obj.
|
|
149
|
+
Obj.update(kanban, (kanban) => {
|
|
150
150
|
kanban.arrangement.order = ['b', 'a'];
|
|
151
151
|
});
|
|
152
152
|
});
|
|
@@ -187,7 +187,7 @@ describe('useKanbanBoardModel', () => {
|
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
act(() => {
|
|
190
|
-
Obj.
|
|
190
|
+
Obj.update(kanban, (kanban) => {
|
|
191
191
|
kanban.arrangement.columns['a'] = {
|
|
192
192
|
ids: [item3.id, item1.id, item2.id],
|
|
193
193
|
};
|
|
@@ -225,7 +225,7 @@ describe('useKanbanBoardModel', () => {
|
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
act(() => {
|
|
228
|
-
Obj.
|
|
228
|
+
Obj.update(kanban, (kanban) => {
|
|
229
229
|
kanban.arrangement.columns['b'] = { ids: [itemB.id] };
|
|
230
230
|
});
|
|
231
231
|
});
|
|
@@ -38,14 +38,27 @@ export function useKanbanBoardModel<T extends BaseKanbanItem = BaseKanbanItem>(
|
|
|
38
38
|
// Source atoms: reactive reads from the kanban object; items come from the passed-in atom (e.g. AtomQuery or in-memory).
|
|
39
39
|
const arrangementAtom = useMemo(() => AtomObj.makeProperty(kanban, 'arrangement'), [kanban]);
|
|
40
40
|
const viewSnapshotAtom = useMemo(
|
|
41
|
-
() =>
|
|
42
|
-
|
|
41
|
+
() =>
|
|
42
|
+
kanban?.spec?.kind === 'view' && kanban.spec.view
|
|
43
|
+
? AtomObj.make(kanban.spec.view)
|
|
44
|
+
: Atom.make<undefined>(() => undefined),
|
|
45
|
+
[kanban?.spec],
|
|
43
46
|
);
|
|
44
47
|
|
|
45
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Only changes when the discriminator-relevant pivot input changes.
|
|
50
|
+
* View-variant: derived from `view.projection.pivotFieldId`.
|
|
51
|
+
* Items-variant: the kanban's `spec.pivotField` (the property name itself acts as the field id).
|
|
52
|
+
*/
|
|
46
53
|
const pivotFieldIdAtom = useMemo(
|
|
47
|
-
() =>
|
|
48
|
-
|
|
54
|
+
() =>
|
|
55
|
+
Atom.make((get) => {
|
|
56
|
+
if (kanban?.spec.kind === 'items') {
|
|
57
|
+
return kanban.spec.pivotField;
|
|
58
|
+
}
|
|
59
|
+
return get(viewSnapshotAtom)?.projection?.pivotFieldId as string | undefined;
|
|
60
|
+
}),
|
|
61
|
+
[kanban?.spec, viewSnapshotAtom],
|
|
49
62
|
);
|
|
50
63
|
|
|
51
64
|
// Effective per-column ids: from kanban.arrangement.columns; empty when arrangement has no columns.
|
|
@@ -30,11 +30,11 @@ export const useProjectionModel = <S extends Type.AnyEntity>(
|
|
|
30
30
|
|
|
31
31
|
useAsyncEffect(
|
|
32
32
|
async (controller) => {
|
|
33
|
-
if (!schema || !kanban) {
|
|
33
|
+
if (!schema || !kanban || kanban.spec.kind !== 'view') {
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
try {
|
|
37
|
-
const view = await kanban.view.load();
|
|
37
|
+
const view = await kanban.spec.view.load();
|
|
38
38
|
if (controller.signal.aborted) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { Plugin } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
import { meta } from './meta';
|
|
6
8
|
|
|
7
|
-
export
|
|
9
|
+
export const KanbanPlugin = Plugin.lazy(meta, () => import('#plugin'));
|
|
10
|
+
|
|
11
|
+
export * from './meta';
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import * as Effect from 'effect/Effect';
|
|
4
4
|
|
|
5
5
|
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
6
|
+
import { Operation } from '@dxos/compute';
|
|
6
7
|
import { JsonSchema, Obj } from '@dxos/echo';
|
|
7
8
|
import { type EchoSchema } from '@dxos/echo/internal';
|
|
8
9
|
import { invariant } from '@dxos/invariant';
|
|
9
|
-
import { Operation } from '@dxos/operation';
|
|
10
10
|
import { ProjectionModel, createEchoChangeCallback, getTypenameFromQuery } from '@dxos/schema';
|
|
11
11
|
|
|
12
12
|
import { DeleteCardField } from './definitions';
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Effect from 'effect/Effect';
|
|
4
4
|
|
|
5
|
+
import { Operation } from '@dxos/compute';
|
|
5
6
|
import { Obj } from '@dxos/echo';
|
|
6
7
|
import { invariant } from '@dxos/invariant';
|
|
7
|
-
import { Operation } from '@dxos/operation';
|
|
8
8
|
|
|
9
9
|
import { DeleteCard } from './definitions';
|
|
10
10
|
|
package/src/operations/index.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import * as Effect from 'effect/Effect';
|
|
4
4
|
|
|
5
5
|
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
6
|
+
import { Operation } from '@dxos/compute';
|
|
6
7
|
import { JsonSchema, Obj } from '@dxos/echo';
|
|
7
8
|
import { type EchoSchema } from '@dxos/echo/internal';
|
|
8
9
|
import { invariant } from '@dxos/invariant';
|
|
9
|
-
import { Operation } from '@dxos/operation';
|
|
10
10
|
import { ProjectionModel, createEchoChangeCallback, getTypenameFromQuery } from '@dxos/schema';
|
|
11
11
|
|
|
12
12
|
import { RestoreCardField } from './definitions';
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Effect from 'effect/Effect';
|
|
4
4
|
|
|
5
|
+
import { Operation } from '@dxos/compute';
|
|
5
6
|
import { Obj } from '@dxos/echo';
|
|
6
7
|
import { invariant } from '@dxos/invariant';
|
|
7
|
-
import { Operation } from '@dxos/operation';
|
|
8
8
|
|
|
9
9
|
import { RestoreCard } from './definitions';
|
|
10
10
|
|
package/src/types/Kanban.ts
CHANGED
|
@@ -9,10 +9,15 @@ import { View } from '@dxos/echo';
|
|
|
9
9
|
import { FormInputAnnotation, LabelAnnotation } from '@dxos/echo/internal';
|
|
10
10
|
import { ViewAnnotation } from '@dxos/schema';
|
|
11
11
|
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Per-column entry: ordered card ids plus an optional `hidden` flag that
|
|
14
|
+
* removes the column from the rendered board (and from the model's column
|
|
15
|
+
* list). Today only the uncategorized column is exposed in settings, but
|
|
16
|
+
* the data structure supports per-column hiding generally.
|
|
17
|
+
*/
|
|
13
18
|
const ArrangementColumnEntry = Schema.Struct({
|
|
14
19
|
ids: Schema.Array(Obj.ID),
|
|
15
|
-
hidden: Schema.
|
|
20
|
+
hidden: Schema.Boolean.pipe(Schema.optional),
|
|
16
21
|
});
|
|
17
22
|
|
|
18
23
|
/** Keyed by columnValue. */
|
|
@@ -29,43 +34,118 @@ export const Arrangement = Schema.Struct({
|
|
|
29
34
|
|
|
30
35
|
export type Arrangement = Schema.Schema.Type<typeof Arrangement>;
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
/**
|
|
38
|
+
* v1: pre-existing Kanban shape. Retained as the source for the v1→v2 migration.
|
|
39
|
+
*/
|
|
40
|
+
export const KanbanV1 = Schema.Struct({
|
|
41
|
+
name: Schema.String.pipe(Schema.optional),
|
|
42
|
+
view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false)),
|
|
43
|
+
arrangement: Arrangement,
|
|
44
|
+
}).pipe(
|
|
45
|
+
Type.object({
|
|
46
|
+
typename: 'org.dxos.type.kanban',
|
|
47
|
+
version: '0.1.0',
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
34
50
|
|
|
51
|
+
//
|
|
52
|
+
// v2 — `spec` is a discriminated union of how items are sourced.
|
|
53
|
+
//
|
|
54
|
+
// Mirrors the canonical DXOS pattern (see `Trigger.Spec` in
|
|
55
|
+
// `@dxos/functions/src/types/Trigger.ts` and `Sequence.Source` in
|
|
56
|
+
// `@dxos/plugin-zen`): the `Type.object` schema is a flat `Schema.Struct`,
|
|
57
|
+
// and the discriminated union lives one level down as a single field whose
|
|
58
|
+
// variants are tagged with a `kind` literal.
|
|
59
|
+
//
|
|
60
|
+
|
|
61
|
+
/** View-variant: items come from running the View's query (the original behaviour). */
|
|
62
|
+
export const KanbanViewSpec = Schema.Struct({
|
|
63
|
+
kind: Schema.Literal('view').pipe(FormInputAnnotation.set(false)),
|
|
35
64
|
view: Ref.Ref(View.View).pipe(FormInputAnnotation.set(false)),
|
|
65
|
+
});
|
|
66
|
+
export type KanbanViewSpec = Schema.Schema.Type<typeof KanbanViewSpec>;
|
|
67
|
+
|
|
68
|
+
/** Items-variant: kanban owns its items as an explicit ref array (used by externally-synced kanbans). */
|
|
69
|
+
export const KanbanItemsSpec = Schema.Struct({
|
|
70
|
+
kind: Schema.Literal('items').pipe(FormInputAnnotation.set(false)),
|
|
71
|
+
/** Property path on each item that drives column membership (e.g. `'listName'`). */
|
|
72
|
+
pivotField: Schema.String,
|
|
73
|
+
/** Items owned directly by the kanban. */
|
|
74
|
+
items: Schema.Array(Ref.Ref(Obj.Unknown)).pipe(FormInputAnnotation.set(false)),
|
|
75
|
+
});
|
|
76
|
+
export type KanbanItemsSpec = Schema.Schema.Type<typeof KanbanItemsSpec>;
|
|
77
|
+
|
|
78
|
+
/** Discriminated union of source specs. Distinguished by `kind`. */
|
|
79
|
+
export const KanbanSpec = Schema.Union(KanbanViewSpec, KanbanItemsSpec);
|
|
80
|
+
export type KanbanSpec = Schema.Schema.Type<typeof KanbanSpec>;
|
|
36
81
|
|
|
37
|
-
|
|
82
|
+
export const Kanban = Schema.Struct({
|
|
83
|
+
name: Schema.String.pipe(Schema.optional),
|
|
38
84
|
arrangement: Arrangement,
|
|
85
|
+
/** How this kanban sources its items. Discriminated by `spec.kind`. */
|
|
86
|
+
spec: KanbanSpec,
|
|
39
87
|
}).pipe(
|
|
40
88
|
Type.object({
|
|
41
89
|
typename: 'org.dxos.type.kanban',
|
|
42
|
-
version: '0.
|
|
90
|
+
version: '0.2.0',
|
|
43
91
|
}),
|
|
44
92
|
LabelAnnotation.set(['name']),
|
|
45
|
-
ViewAnnotation.set(
|
|
93
|
+
ViewAnnotation.set(['spec', 'view']),
|
|
46
94
|
Annotation.IconAnnotation.set({
|
|
47
95
|
icon: 'ph--kanban--regular',
|
|
48
96
|
hue: 'green',
|
|
49
97
|
}),
|
|
50
98
|
);
|
|
51
99
|
|
|
52
|
-
/** Instance type;
|
|
100
|
+
/** Instance type; narrow on `kanban.spec.kind` (or use the guards below). */
|
|
53
101
|
export interface Kanban extends Schema.Schema.Type<typeof Kanban> {}
|
|
54
102
|
|
|
55
|
-
|
|
103
|
+
/** Narrowed view-variant kanban. */
|
|
104
|
+
export type KanbanView = Kanban & { spec: KanbanViewSpec };
|
|
105
|
+
|
|
106
|
+
/** Narrowed items-variant kanban. */
|
|
107
|
+
export type KanbanItems = Kanban & { spec: KanbanItemsSpec };
|
|
108
|
+
|
|
109
|
+
export const isKanbanView = (kanban: Kanban): kanban is KanbanView => kanban.spec.kind === 'view';
|
|
110
|
+
export const isKanbanItems = (kanban: Kanban): kanban is KanbanItems => kanban.spec.kind === 'items';
|
|
111
|
+
|
|
112
|
+
type MakeViewProps = {
|
|
113
|
+
name?: string;
|
|
56
114
|
view: View.View;
|
|
115
|
+
arrangement?: Arrangement;
|
|
57
116
|
};
|
|
58
117
|
|
|
59
118
|
/**
|
|
60
|
-
* Make a kanban
|
|
119
|
+
* Make a view-variant kanban (items sourced via the View's query).
|
|
61
120
|
*/
|
|
62
|
-
export const make = (props:
|
|
121
|
+
export const make = (props: MakeViewProps): Kanban => {
|
|
63
122
|
const { name, view, arrangement } = props;
|
|
64
123
|
const order = arrangement?.order ?? [];
|
|
65
124
|
const columns = arrangement?.columns ?? {};
|
|
66
125
|
return Obj.make(Kanban, {
|
|
67
126
|
name,
|
|
68
|
-
view: Ref.make(view),
|
|
69
127
|
arrangement: { order, columns },
|
|
128
|
+
spec: { kind: 'view' as const, view: Ref.make(view) },
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
type MakeItemsProps = {
|
|
133
|
+
name?: string;
|
|
134
|
+
arrangement?: Arrangement;
|
|
135
|
+
pivotField: string;
|
|
136
|
+
items?: ReadonlyArray<Ref.Ref<Obj.Unknown>>;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Make an items-variant kanban (items list owned by the kanban itself, e.g. populated by a sync integration).
|
|
141
|
+
*/
|
|
142
|
+
export const makeItems = (props: MakeItemsProps): Kanban => {
|
|
143
|
+
const { name, arrangement, pivotField, items = [] } = props;
|
|
144
|
+
const order = arrangement?.order ?? [];
|
|
145
|
+
const columns = arrangement?.columns ?? {};
|
|
146
|
+
return Obj.make(Kanban, {
|
|
147
|
+
name,
|
|
148
|
+
arrangement: { order, columns },
|
|
149
|
+
spec: { kind: 'items' as const, pivotField, items },
|
|
70
150
|
});
|
|
71
151
|
};
|