@dxos/plugin-kanban 0.8.4-main.fcfe5033a5 → 0.8.4-staging.60fe92afc8
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/LICENSE +102 -5
- package/PLUGIN.mdl +398 -0
- package/README.md +1 -1
- package/dist/lib/neutral/KanbanArticle-T4CPKAZH.mjs +132 -0
- package/dist/lib/neutral/KanbanArticle-T4CPKAZH.mjs.map +7 -0
- package/dist/lib/neutral/KanbanPlugin.mjs +44 -0
- package/dist/lib/neutral/KanbanPlugin.mjs.map +7 -0
- package/dist/lib/neutral/KanbanPlugin.node.mjs +27 -0
- package/dist/lib/neutral/KanbanPlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/KanbanPlugin.workerd.mjs +21 -0
- package/dist/lib/neutral/KanbanPlugin.workerd.mjs.map +7 -0
- package/dist/lib/neutral/KanbanSettings-5WOS4CUE.mjs +83 -0
- package/dist/lib/neutral/KanbanSettings-5WOS4CUE.mjs.map +7 -0
- package/dist/lib/neutral/blueprint-definition-6DV3Q5MC.mjs +15 -0
- package/dist/lib/neutral/blueprint-definition-6DV3Q5MC.mjs.map +7 -0
- package/dist/lib/neutral/blueprints/index.mjs +8 -0
- package/dist/lib/neutral/capabilities/index.mjs +17 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-6ZHHQWO5.mjs +39 -0
- package/dist/lib/neutral/chunk-6ZHHQWO5.mjs.map +7 -0
- package/dist/lib/neutral/chunk-DAKIZO46.mjs +246 -0
- package/dist/lib/neutral/chunk-DAKIZO46.mjs.map +7 -0
- package/dist/lib/neutral/chunk-M5ISZWZU.mjs +8 -0
- package/dist/lib/neutral/chunk-M5ISZWZU.mjs.map +7 -0
- package/dist/lib/{browser/blueprints/index.mjs → neutral/chunk-ZTQW5KQS.mjs} +4 -5
- package/dist/lib/neutral/chunk-ZTQW5KQS.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +243 -0
- package/dist/lib/neutral/components/index.mjs.map +7 -0
- package/dist/lib/neutral/containers/index.mjs +11 -0
- package/dist/lib/neutral/containers/index.mjs.map +7 -0
- package/dist/lib/neutral/create-object-DKBSI46K.mjs +40 -0
- package/dist/lib/neutral/create-object-DKBSI46K.mjs.map +7 -0
- package/dist/lib/{browser/delete-card-VPNVIWOA.mjs → neutral/delete-card-VNAV3CZV.mjs} +6 -14
- package/dist/lib/neutral/delete-card-VNAV3CZV.mjs.map +7 -0
- package/dist/lib/neutral/delete-card-field-XHOLGS6L.mjs +39 -0
- package/dist/lib/neutral/delete-card-field-XHOLGS6L.mjs.map +7 -0
- package/dist/lib/neutral/hooks/index.mjs +431 -0
- package/dist/lib/neutral/hooks/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +34 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +8 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs +13 -0
- package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +8 -0
- package/dist/lib/neutral/operations/index.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +16 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/react-surface-VZEVEJL5.mjs +91 -0
- package/dist/lib/neutral/react-surface-VZEVEJL5.mjs.map +7 -0
- package/dist/lib/{browser/restore-card-4GG2RYKR.mjs → neutral/restore-card-EKVEPATL.mjs} +6 -14
- package/dist/lib/neutral/restore-card-EKVEPATL.mjs.map +7 -0
- package/dist/lib/neutral/restore-card-field-TQCTGGNO.mjs +37 -0
- package/dist/lib/neutral/restore-card-field-TQCTGGNO.mjs.map +7 -0
- package/dist/lib/neutral/testing/index.mjs +62 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +44 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +22 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs +42 -0
- package/dist/lib/neutral/undo-mappings-6CHW6BOF.mjs.map +7 -0
- 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/KanbanPlugin.workerd.d.ts +4 -0
- package/dist/types/src/KanbanPlugin.workerd.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 +1 -1
- package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -1
- package/dist/types/src/capabilities/create-object.d.ts +11 -0
- package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +9 -2
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- 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 +9 -29
- 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 +2 -3
- package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts +2 -3
- package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/context.d.ts +38 -0
- package/dist/types/src/components/KanbanBoard/context.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts +6 -0
- package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts.map +1 -0
- package/dist/types/src/containers/{KanbanContainer/KanbanContainer.stories.d.ts → KanbanArticle/KanbanArticle.stories.d.ts} +26 -26
- package/dist/types/src/containers/KanbanArticle/KanbanArticle.stories.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanArticle/index.d.ts +2 -0
- package/dist/types/src/containers/KanbanArticle/index.d.ts.map +1 -0
- 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 +2 -2
- 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 +3 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/delete-card-field.d.ts +3 -3
- package/dist/types/src/operations/delete-card-field.d.ts.map +1 -1
- package/dist/types/src/operations/delete-card.d.ts +3 -3
- package/dist/types/src/operations/delete-card.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts +1 -2
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/operations/restore-card-field.d.ts +3 -3
- package/dist/types/src/operations/restore-card-field.d.ts.map +1 -1
- package/dist/types/src/operations/restore-card.d.ts +3 -3
- 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/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- 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 +66 -9
- package/dist/types/src/types/Kanban.d.ts.map +1 -1
- package/dist/types/src/{operations/definitions.d.ts → types/KanbanOperation.d.ts} +4 -4
- package/dist/types/src/types/KanbanOperation.d.ts.map +1 -0
- 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/index.d.ts +2 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- 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 +8 -4
- package/dist/types/src/util/arrangement.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +110 -76
- package/src/KanbanPlugin.node.ts +21 -0
- package/src/KanbanPlugin.test.ts +31 -0
- package/src/KanbanPlugin.tsx +12 -39
- package/src/KanbanPlugin.workerd.ts +18 -0
- package/src/blueprints/kanban-blueprint.ts +2 -3
- package/src/capabilities/artifact-definition.ts +10 -9
- package/src/capabilities/blueprint-definition.ts +6 -4
- package/src/capabilities/create-object.ts +40 -0
- package/src/capabilities/index.ts +9 -2
- package/src/capabilities/operation-handler.ts +1 -1
- package/src/capabilities/react-surface.tsx +31 -14
- package/src/capabilities/undo-mappings.ts +1 -1
- package/src/components/KanbanBoard/KanbanBoard.stories.tsx +9 -6
- package/src/components/KanbanBoard/KanbanBoard.tsx +22 -51
- package/src/components/KanbanBoard/KanbanCard.tsx +75 -60
- package/src/components/KanbanBoard/KanbanColumn.tsx +16 -13
- package/src/components/KanbanBoard/context.ts +54 -0
- package/src/containers/{KanbanContainer/KanbanContainer.stories.tsx → KanbanArticle/KanbanArticle.stories.tsx} +48 -42
- package/src/containers/KanbanArticle/KanbanArticle.tsx +179 -0
- package/src/containers/KanbanArticle/index.ts +5 -0
- package/src/containers/KanbanSettings/KanbanSettings.tsx +94 -0
- package/src/containers/KanbanSettings/index.ts +5 -0
- package/src/containers/index.ts +2 -2
- 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} +14 -19
- package/src/hooks/useKanbanBoardModel.ts +19 -7
- package/src/hooks/useProjectionModel.ts +4 -4
- package/src/index.ts +3 -2
- package/src/meta.ts +22 -5
- package/src/operations/delete-card-field.ts +11 -16
- package/src/operations/delete-card.ts +3 -3
- package/src/operations/index.ts +1 -3
- package/src/operations/restore-card-field.ts +11 -16
- package/src/operations/restore-card.ts +3 -3
- package/src/playwright/smoke.spec.ts +3 -3
- package/src/plugin.ts +11 -0
- package/src/testing/KanbanCardTileSimple.tsx +6 -6
- package/src/types/Kanban.ts +86 -23
- package/src/{operations/definitions.ts → types/KanbanOperation.ts} +25 -9
- package/src/types/index.ts +3 -1
- package/src/types/schema.ts +20 -2
- package/src/types/types.ts +2 -2
- package/src/util/arrangement.test.ts +22 -13
- package/src/util/arrangement.ts +25 -15
- package/src/vite-env.d.ts +10 -0
- package/dist/lib/browser/blueprints/index.mjs.map +0 -7
- package/dist/lib/browser/chunk-A3PBV3S5.mjs +0 -105
- 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 +0 -50
- package/dist/lib/browser/delete-card-field-4HHF2GYX.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -121
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/operations/index.mjs +0 -13
- package/dist/lib/browser/operations/index.mjs.map +0 -7
- package/dist/lib/browser/restore-card-4GG2RYKR.mjs.map +0 -7
- package/dist/lib/browser/restore-card-field-3T26ACYX.mjs +0 -48
- package/dist/lib/browser/restore-card-field-3T26ACYX.mjs.map +0 -7
- package/dist/lib/browser/types/index.mjs +0 -100
- package/dist/lib/browser/types/index.mjs.map +0 -7
- package/dist/lib/node-esm/blueprints/index.mjs +0 -28
- package/dist/lib/node-esm/blueprints/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6LELYA2G.mjs +0 -106
- package/dist/lib/node-esm/chunk-6LELYA2G.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/delete-card-5PW5OMFN.mjs +0 -33
- package/dist/lib/node-esm/delete-card-5PW5OMFN.mjs.map +0 -7
- package/dist/lib/node-esm/delete-card-field-KPJU2AQ3.mjs +0 -51
- package/dist/lib/node-esm/delete-card-field-KPJU2AQ3.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -122
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/operations/index.mjs +0 -14
- package/dist/lib/node-esm/operations/index.mjs.map +0 -7
- package/dist/lib/node-esm/restore-card-X2TKMU5A.mjs +0 -30
- package/dist/lib/node-esm/restore-card-X2TKMU5A.mjs.map +0 -7
- package/dist/lib/node-esm/restore-card-field-IUTL4RTR.mjs +0 -49
- package/dist/lib/node-esm/restore-card-field-IUTL4RTR.mjs.map +0 -7
- package/dist/lib/node-esm/types/index.mjs +0 -101
- package/dist/lib/node-esm/types/index.mjs.map +0 -7
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts +0 -6
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts.map +0 -1
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts.map +0 -1
- package/dist/types/src/containers/KanbanContainer/index.d.ts +0 -2
- package/dist/types/src/containers/KanbanContainer/index.d.ts.map +0 -1
- 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/dist/types/src/operations/definitions.d.ts.map +0 -1
- package/src/containers/KanbanContainer/KanbanContainer.tsx +0 -96
- package/src/containers/KanbanContainer/index.ts +0 -5
- package/src/containers/KanbanViewEditor/KanbanViewEditor.tsx +0 -63
- package/src/containers/KanbanViewEditor/index.ts +0 -5
- /package/dist/lib/{browser/chunk-J5LGTIGS.mjs.map → neutral/blueprints/index.mjs.map} +0 -0
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
- /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import React, { useCallback, useContext, useMemo } from 'react';
|
|
7
|
+
|
|
8
|
+
import { useCapabilities, useOperationInvoker } from '@dxos/app-framework/ui';
|
|
9
|
+
import { AppCapabilities } from '@dxos/app-toolkit';
|
|
10
|
+
import { useSchemaFilter, type AppSurface } from '@dxos/app-toolkit/ui';
|
|
11
|
+
import { Filter, Obj, Query, type Ref, Type } from '@dxos/echo';
|
|
12
|
+
import { useObject, useType } from '@dxos/react-client/echo';
|
|
13
|
+
import { Panel, Toolbar } from '@dxos/react-ui';
|
|
14
|
+
import { getTagFromQuery, getTypeURIFromQuery } from '@dxos/schema';
|
|
15
|
+
|
|
16
|
+
import { KanbanBoard } from '#components';
|
|
17
|
+
import { useEchoChangeCallback, useItemsProjection, useProjectionModel } from '#hooks';
|
|
18
|
+
import { KanbanOperation } from '#types';
|
|
19
|
+
import { Kanban } from '#types';
|
|
20
|
+
|
|
21
|
+
export type KanbanArticleProps = AppSurface.ObjectArticleProps<Kanban.Kanban>;
|
|
22
|
+
|
|
23
|
+
export const KanbanArticle = (props: KanbanArticleProps) => {
|
|
24
|
+
// Branch on `kanban.spec.kind`: view-variant runs a typename query through
|
|
25
|
+
// `useProjectionModel`; items-variant dereferences `kanban.spec.items` and
|
|
26
|
+
// uses a stub projection from `useItemsProjection`.
|
|
27
|
+
return Kanban.isKanbanItems(props.subject) ? (
|
|
28
|
+
<ItemsKanbanArticle {...props} subject={props.subject} />
|
|
29
|
+
) : (
|
|
30
|
+
<ViewKanbanArticle {...props} />
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ViewKanbanArticle = ({ role, subject: object }: KanbanArticleProps) => {
|
|
35
|
+
const registry = useContext(RegistryContext);
|
|
36
|
+
const schemas = useCapabilities(AppCapabilities.Schema);
|
|
37
|
+
const db = Obj.getDatabase(object);
|
|
38
|
+
const { invokePromise } = useOperationInvoker();
|
|
39
|
+
const [view] = useObject(object.spec.kind === 'view' ? object.spec.view : undefined);
|
|
40
|
+
const typeUri = view?.query ? getTypeURIFromQuery(view.query.ast) : undefined;
|
|
41
|
+
const tag = view?.query ? getTagFromQuery(view.query.ast) : undefined;
|
|
42
|
+
|
|
43
|
+
const schemaFromDb = useType(db, typeUri);
|
|
44
|
+
const cardSchema = useMemo(
|
|
45
|
+
() => schemaFromDb ?? schemas.flat().find((schema) => Type.getURI(schema) === typeUri),
|
|
46
|
+
[schemaFromDb, schemas, typeUri],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const baseFilter = useSchemaFilter(cardSchema);
|
|
50
|
+
const items = useMemo(() => {
|
|
51
|
+
if (!db) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const query = tag ? Query.select(baseFilter).select(Filter.tag(tag)) : Query.select(baseFilter);
|
|
55
|
+
return db.query(query).atom;
|
|
56
|
+
}, [db, baseFilter, tag]);
|
|
57
|
+
|
|
58
|
+
const projection = useProjectionModel(cardSchema, object, registry);
|
|
59
|
+
const change = useEchoChangeCallback(object);
|
|
60
|
+
|
|
61
|
+
const pivotFieldId = view?.projection?.pivotFieldId;
|
|
62
|
+
const columnFieldPath =
|
|
63
|
+
projection && pivotFieldId ? projection.tryGetFieldProjection(pivotFieldId)?.props.property : undefined;
|
|
64
|
+
|
|
65
|
+
const handleCardAdd = useCallback(
|
|
66
|
+
(columnValue: string | undefined) => {
|
|
67
|
+
if (db && cardSchema && columnFieldPath) {
|
|
68
|
+
const card = Obj.make(Type.assertObject(cardSchema), {
|
|
69
|
+
[columnFieldPath]: columnValue,
|
|
70
|
+
});
|
|
71
|
+
db.add(card);
|
|
72
|
+
return card.id;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[db, cardSchema, columnFieldPath],
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const handleCardRemove = useCallback(
|
|
79
|
+
(card: { id: string }) => {
|
|
80
|
+
void invokePromise(KanbanOperation.DeleteCard, { card });
|
|
81
|
+
},
|
|
82
|
+
[invokePromise],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!object || !db || !items || !projection || !change) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Panel.Root role={role}>
|
|
91
|
+
<Panel.Toolbar asChild>
|
|
92
|
+
<Toolbar.Root />
|
|
93
|
+
</Panel.Toolbar>
|
|
94
|
+
<KanbanBoard.Root
|
|
95
|
+
kanban={object}
|
|
96
|
+
projection={projection}
|
|
97
|
+
items={items}
|
|
98
|
+
change={change}
|
|
99
|
+
onCardAdd={handleCardAdd}
|
|
100
|
+
onCardRemove={handleCardRemove}
|
|
101
|
+
>
|
|
102
|
+
<Panel.Content asChild>
|
|
103
|
+
<KanbanBoard.Content />
|
|
104
|
+
</Panel.Content>
|
|
105
|
+
</KanbanBoard.Root>
|
|
106
|
+
</Panel.Root>
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
type ItemsKanbanArticleProps = Omit<KanbanArticleProps, 'subject'> & { subject: Kanban.KanbanItems };
|
|
111
|
+
|
|
112
|
+
const ItemsKanbanArticle = ({ role, subject: object }: ItemsKanbanArticleProps) => {
|
|
113
|
+
const db = Obj.getDatabase(object);
|
|
114
|
+
const projection = useItemsProjection(object);
|
|
115
|
+
const change = useEchoChangeCallback(object);
|
|
116
|
+
|
|
117
|
+
// TODO(wittjosiah): pass refs (not loaded objects) through to the kanban
|
|
118
|
+
// board and let `KanbanCard` subscribe to its own ref via `useObject`.
|
|
119
|
+
// Today this atom subscribes to *every* item — any one changing causes the
|
|
120
|
+
// container (and the model's per-column atoms) to recompute. With cards
|
|
121
|
+
// subscribing themselves, the container only needs the refs and the
|
|
122
|
+
// per-card render is independent. Requires:
|
|
123
|
+
// - `KanbanCard` to accept `Ref<Obj.Unknown>` as `data` and call
|
|
124
|
+
// `useObject(ref)` internally.
|
|
125
|
+
// - The model to handle a ref-bearing item shape (id from
|
|
126
|
+
// `ref.dxn.asEchoDXN()?.echoUri`) and use arrangement-only ordering
|
|
127
|
+
// for items-variant (no pivot-value fallback, since refs don't expose
|
|
128
|
+
// the pivot field without loading).
|
|
129
|
+
// - `Mosaic.isItem` to accept the ref wrapper alongside `Obj.isObject`.
|
|
130
|
+
const itemsAtom = useMemo(
|
|
131
|
+
() =>
|
|
132
|
+
Atom.make((get) => {
|
|
133
|
+
const out: Obj.Unknown[] = [];
|
|
134
|
+
for (const ref of object.spec.items as ReadonlyArray<Ref.Ref<Obj.Unknown>>) {
|
|
135
|
+
const target = get(Obj.atom(ref));
|
|
136
|
+
if (target == null) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// Drop soft-deleted cards (e.g. Trello-closed cards). The ref
|
|
140
|
+
// stays in `spec.items` so arrangement is preserved, but the card
|
|
141
|
+
// shouldn't render.
|
|
142
|
+
if (Obj.isDeleted(target)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
out.push(target as unknown as Obj.Unknown);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}),
|
|
149
|
+
[object.spec.items],
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const handleCardRemove = useCallback(() => undefined, []);
|
|
153
|
+
|
|
154
|
+
if (!object || !db || !change) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// TODO(wittjosiah): wire `onCardAdd` to the create-object flow so
|
|
159
|
+
// users can add items directly from the kanban (currently the column's
|
|
160
|
+
// "+" button is hidden because `onCardAdd` is undefined).
|
|
161
|
+
return (
|
|
162
|
+
<Panel.Root role={role}>
|
|
163
|
+
<Panel.Toolbar asChild>
|
|
164
|
+
<Toolbar.Root />
|
|
165
|
+
</Panel.Toolbar>
|
|
166
|
+
<KanbanBoard.Root
|
|
167
|
+
kanban={object}
|
|
168
|
+
projection={projection}
|
|
169
|
+
items={itemsAtom}
|
|
170
|
+
change={change}
|
|
171
|
+
onCardRemove={handleCardRemove}
|
|
172
|
+
>
|
|
173
|
+
<Panel.Content asChild>
|
|
174
|
+
<KanbanBoard.Content />
|
|
175
|
+
</Panel.Content>
|
|
176
|
+
</KanbanBoard.Root>
|
|
177
|
+
</Panel.Root>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
@@ -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/Format';
|
|
11
|
+
import { useObject, useType } from '@dxos/react-client/echo';
|
|
12
|
+
import { Form, type FormFieldMap, SelectField } from '@dxos/react-ui-form';
|
|
13
|
+
import { getTypeURIFromQuery } 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 currentTypeUri = view?.query ? getTypeURIFromQuery(view.query.ast) : undefined;
|
|
34
|
+
const schema = useType(db, currentTypeUri);
|
|
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
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ComponentType, lazy } from 'react';
|
|
6
6
|
|
|
7
|
-
export const
|
|
8
|
-
export const
|
|
7
|
+
export const KanbanArticle: ComponentType<any> = lazy(() => import('./KanbanArticle'));
|
|
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
|
+
};
|
|
@@ -7,10 +7,10 @@ import { act, renderHook } from '@testing-library/react';
|
|
|
7
7
|
import * as Schema from 'effect/Schema';
|
|
8
8
|
import { beforeEach, describe, test } from 'vitest';
|
|
9
9
|
|
|
10
|
-
import { Filter, JsonSchema, Obj, Query, Type } from '@dxos/echo';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
10
|
+
import { DXN, Filter, JsonSchema, Obj, Query, Type, type View } from '@dxos/echo';
|
|
11
|
+
import { Format, FormatAnnotation } from '@dxos/echo/Format';
|
|
12
|
+
import { PropertyMetaAnnotationId } from '@dxos/echo/internal';
|
|
13
|
+
import { EntityId } from '@dxos/keys';
|
|
14
14
|
import { ProjectionModel, ViewModel, createDirectChangeCallback } from '@dxos/schema';
|
|
15
15
|
|
|
16
16
|
import { Kanban } from '#types';
|
|
@@ -36,14 +36,9 @@ const KanbanTaskSchema = Schema.Struct({
|
|
|
36
36
|
}),
|
|
37
37
|
Schema.optional,
|
|
38
38
|
),
|
|
39
|
-
}).pipe(
|
|
40
|
-
Type.object({
|
|
41
|
-
typename: 'com.example.type.kanban-task',
|
|
42
|
-
version: '0.1.0',
|
|
43
|
-
}),
|
|
44
|
-
);
|
|
39
|
+
}).pipe(Type.makeObject(DXN.make('com.example.type.kanbanTask', '0.1.0')));
|
|
45
40
|
|
|
46
|
-
type KanbanTask =
|
|
41
|
+
type KanbanTask = Type.InstanceType<typeof KanbanTaskSchema>;
|
|
47
42
|
|
|
48
43
|
describe('useKanbanBoardModel', () => {
|
|
49
44
|
let registry: Registry.Registry;
|
|
@@ -146,7 +141,7 @@ describe('useKanbanBoardModel', () => {
|
|
|
146
141
|
expect(orderBefore).toEqual(['__uncategorized__', 'a', 'b']);
|
|
147
142
|
|
|
148
143
|
act(() => {
|
|
149
|
-
Obj.
|
|
144
|
+
Obj.update(kanban, (kanban) => {
|
|
150
145
|
kanban.arrangement.order = ['b', 'a'];
|
|
151
146
|
});
|
|
152
147
|
});
|
|
@@ -160,15 +155,15 @@ describe('useKanbanBoardModel', () => {
|
|
|
160
155
|
|
|
161
156
|
test('getItems returns items in column ordered by arrangement ids', ({ expect }) => {
|
|
162
157
|
const item1 = Obj.make(KanbanTaskSchema, {
|
|
163
|
-
id:
|
|
158
|
+
id: EntityId.random(),
|
|
164
159
|
status: 'a',
|
|
165
160
|
});
|
|
166
161
|
const item2 = Obj.make(KanbanTaskSchema, {
|
|
167
|
-
id:
|
|
162
|
+
id: EntityId.random(),
|
|
168
163
|
status: 'a',
|
|
169
164
|
});
|
|
170
165
|
const item3 = Obj.make(KanbanTaskSchema, {
|
|
171
|
-
id:
|
|
166
|
+
id: EntityId.random(),
|
|
172
167
|
status: 'a',
|
|
173
168
|
});
|
|
174
169
|
const items: KanbanTask[] = [item1, item2, item3];
|
|
@@ -187,7 +182,7 @@ describe('useKanbanBoardModel', () => {
|
|
|
187
182
|
});
|
|
188
183
|
|
|
189
184
|
act(() => {
|
|
190
|
-
Obj.
|
|
185
|
+
Obj.update(kanban, (kanban) => {
|
|
191
186
|
kanban.arrangement.columns['a'] = {
|
|
192
187
|
ids: [item3.id, item1.id, item2.id],
|
|
193
188
|
};
|
|
@@ -201,11 +196,11 @@ describe('useKanbanBoardModel', () => {
|
|
|
201
196
|
|
|
202
197
|
test('subscribing to one column items atom does not fire when another column changes', ({ expect }) => {
|
|
203
198
|
const itemA = Obj.make(KanbanTaskSchema, {
|
|
204
|
-
id:
|
|
199
|
+
id: EntityId.random(),
|
|
205
200
|
status: 'a',
|
|
206
201
|
});
|
|
207
202
|
const itemB = Obj.make(KanbanTaskSchema, {
|
|
208
|
-
id:
|
|
203
|
+
id: EntityId.random(),
|
|
209
204
|
status: 'b',
|
|
210
205
|
});
|
|
211
206
|
const items: KanbanTask[] = [itemA, itemB];
|
|
@@ -225,7 +220,7 @@ describe('useKanbanBoardModel', () => {
|
|
|
225
220
|
});
|
|
226
221
|
|
|
227
222
|
act(() => {
|
|
228
|
-
Obj.
|
|
223
|
+
Obj.update(kanban, (kanban) => {
|
|
229
224
|
kanban.arrangement.columns['b'] = { ids: [itemB.id] };
|
|
230
225
|
});
|
|
231
226
|
});
|
|
@@ -6,7 +6,6 @@ import { Atom, type Registry } from '@effect-atom/atom-react';
|
|
|
6
6
|
import { useMemo } from 'react';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import { AtomObj } from '@dxos/echo-atom';
|
|
10
9
|
import type { BoardModel } from '@dxos/react-ui-mosaic';
|
|
11
10
|
import type { ProjectionModel } from '@dxos/schema';
|
|
12
11
|
|
|
@@ -36,16 +35,29 @@ export function useKanbanBoardModel<T extends BaseKanbanItem = BaseKanbanItem>(
|
|
|
36
35
|
registry: Registry.Registry,
|
|
37
36
|
): BoardModel<ColumnStructure, T> {
|
|
38
37
|
// Source atoms: reactive reads from the kanban object; items come from the passed-in atom (e.g. AtomQuery or in-memory).
|
|
39
|
-
const arrangementAtom = useMemo(() =>
|
|
38
|
+
const arrangementAtom = useMemo(() => Obj.atomProperty(kanban, 'arrangement'), [kanban]);
|
|
40
39
|
const viewSnapshotAtom = useMemo(
|
|
41
|
-
() =>
|
|
42
|
-
|
|
40
|
+
() =>
|
|
41
|
+
kanban?.spec?.kind === 'view' && kanban.spec.view
|
|
42
|
+
? Obj.atom(kanban.spec.view)
|
|
43
|
+
: Atom.make<undefined>(() => undefined),
|
|
44
|
+
[kanban?.spec],
|
|
43
45
|
);
|
|
44
46
|
|
|
45
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Only changes when the discriminator-relevant pivot input changes.
|
|
49
|
+
* View-variant: derived from `view.projection.pivotFieldId`.
|
|
50
|
+
* Items-variant: the kanban's `spec.pivotField` (the property name itself acts as the field id).
|
|
51
|
+
*/
|
|
46
52
|
const pivotFieldIdAtom = useMemo(
|
|
47
|
-
() =>
|
|
48
|
-
|
|
53
|
+
() =>
|
|
54
|
+
Atom.make((get) => {
|
|
55
|
+
if (kanban?.spec.kind === 'items') {
|
|
56
|
+
return kanban.spec.pivotField;
|
|
57
|
+
}
|
|
58
|
+
return get(viewSnapshotAtom)?.projection?.pivotFieldId as string | undefined;
|
|
59
|
+
}),
|
|
60
|
+
[kanban?.spec, viewSnapshotAtom],
|
|
49
61
|
);
|
|
50
62
|
|
|
51
63
|
// Effective per-column ids: from kanban.arrangement.columns; empty when arrangement has no columns.
|
|
@@ -30,17 +30,17 @@ 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
|
}
|
|
41
41
|
|
|
42
|
-
const jsonSchema = Type.
|
|
43
|
-
const change = createEchoChangeCallback(view, Type.
|
|
42
|
+
const jsonSchema = Type.getDatabase(schema) != null ? schema.jsonSchema : JsonSchema.toJsonSchema(schema);
|
|
43
|
+
const change = createEchoChangeCallback(view, Type.getDatabase(schema) != null ? schema : undefined);
|
|
44
44
|
|
|
45
45
|
const projection = new ProjectionModel({ registry, view, baseSchema: jsonSchema, change });
|
|
46
46
|
projection.normalizeView();
|
package/src/index.ts
CHANGED
package/src/meta.ts
CHANGED
|
@@ -2,18 +2,35 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Plugin } from '@dxos/app-framework';
|
|
6
|
+
import { DXN } from '@dxos/echo';
|
|
6
7
|
import { trim } from '@dxos/util';
|
|
7
8
|
|
|
8
|
-
export const meta
|
|
9
|
-
|
|
9
|
+
export const meta = Plugin.makeMeta({
|
|
10
|
+
key: DXN.make('org.dxos.plugin.kanban'),
|
|
10
11
|
name: 'Kanban',
|
|
12
|
+
author: 'DXOS',
|
|
11
13
|
description: trim`
|
|
12
14
|
Visual project management using customizable kanban boards to track workflow progress.
|
|
13
|
-
Organize
|
|
15
|
+
Organize any ECHO type into drag-and-drop columns defined by a single-select field, move cards
|
|
16
|
+
between stages, and reorder both columns and cards — all changes are persisted to ECHO and
|
|
17
|
+
replicated to collaborators in real time.
|
|
18
|
+
|
|
19
|
+
Kanban boards come in two variants: view-variant boards source their cards through a linked
|
|
20
|
+
View query (any ECHO type can be pivoted on a single-select field), while items-variant boards
|
|
21
|
+
own their cards explicitly and are used by external sync integrations such as Linear or GitHub.
|
|
22
|
+
Both variants share the same arrangement storage and drag-and-drop mechanics.
|
|
23
|
+
|
|
24
|
+
Board settings let users choose which single-select field drives the column layout and
|
|
25
|
+
optionally hide the uncategorized column for items that have no pivot value.
|
|
26
|
+
Field-level undo support allows deleted card fields to be restored without data loss.
|
|
27
|
+
|
|
28
|
+
The plugin contributes a Kanban blueprint that exposes board creation and card management
|
|
29
|
+
to AI agents, enabling automated project tracking workflows.
|
|
14
30
|
`,
|
|
15
31
|
icon: 'ph--kanban--regular',
|
|
16
32
|
iconHue: 'green',
|
|
17
33
|
source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-kanban',
|
|
34
|
+
spec: 'PLUGIN.mdl',
|
|
18
35
|
screenshots: ['https://dxos.network/plugin-details-kanban-dark.png'],
|
|
19
|
-
};
|
|
36
|
+
});
|
|
@@ -3,34 +3,29 @@
|
|
|
3
3
|
import * as Effect from 'effect/Effect';
|
|
4
4
|
|
|
5
5
|
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { Operation } from '@dxos/compute';
|
|
7
|
+
import { Obj, Type } from '@dxos/echo';
|
|
8
8
|
import { invariant } from '@dxos/invariant';
|
|
9
|
-
import {
|
|
10
|
-
import { ProjectionModel, createEchoChangeCallback, getTypenameFromQuery } from '@dxos/schema';
|
|
9
|
+
import { ProjectionModel, createEchoChangeCallback, getTypeURIFromQuery } from '@dxos/schema';
|
|
11
10
|
|
|
12
|
-
import {
|
|
11
|
+
import { KanbanOperation } from '../types';
|
|
13
12
|
|
|
14
|
-
const handler: Operation.WithHandler<typeof DeleteCardField> = DeleteCardField.pipe(
|
|
13
|
+
const handler: Operation.WithHandler<typeof KanbanOperation.DeleteCardField> = KanbanOperation.DeleteCardField.pipe(
|
|
15
14
|
Operation.withHandler(
|
|
16
15
|
Effect.fnUntraced(function* ({ view, fieldId }) {
|
|
17
16
|
const registry = yield* Capability.get(Capabilities.AtomRegistry);
|
|
18
17
|
const db = Obj.getDatabase(view);
|
|
19
18
|
invariant(db, 'Database not found');
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
typename: getTypenameFromQuery(view.query.ast)!,
|
|
24
|
-
location: ['database', 'runtime'],
|
|
25
|
-
})
|
|
26
|
-
.first(),
|
|
27
|
-
);
|
|
19
|
+
const types = yield* Effect.sync(() => db.graph.registry.list().filter(Type.isType));
|
|
20
|
+
const schema = types.find((t) => Type.getURI(t) === getTypeURIFromQuery(view.query.ast));
|
|
21
|
+
invariant(schema, 'Schema not found');
|
|
28
22
|
|
|
23
|
+
invariant(Type.isType(schema), 'expected stored Type.Type for card schema');
|
|
29
24
|
const projection = new ProjectionModel({
|
|
30
25
|
registry,
|
|
31
26
|
view,
|
|
32
|
-
baseSchema:
|
|
33
|
-
change: createEchoChangeCallback(view, schema
|
|
27
|
+
baseSchema: schema.jsonSchema,
|
|
28
|
+
change: createEchoChangeCallback(view, schema),
|
|
34
29
|
});
|
|
35
30
|
|
|
36
31
|
const result = projection.deleteFieldProjection(fieldId);
|
|
@@ -2,13 +2,13 @@
|
|
|
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
|
-
import {
|
|
9
|
+
import { KanbanOperation } from '../types';
|
|
10
10
|
|
|
11
|
-
const handler: Operation.WithHandler<typeof DeleteCard> = DeleteCard.pipe(
|
|
11
|
+
const handler: Operation.WithHandler<typeof KanbanOperation.DeleteCard> = KanbanOperation.DeleteCard.pipe(
|
|
12
12
|
Operation.withHandler(({ card }) =>
|
|
13
13
|
Effect.sync(() => {
|
|
14
14
|
const db = Obj.getDatabase(card);
|
package/src/operations/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// Copyright 2025 DXOS.org
|
|
2
2
|
|
|
3
|
-
import { OperationHandlerSet } from '@dxos/
|
|
4
|
-
|
|
5
|
-
export * as KanbanOperation from './definitions';
|
|
3
|
+
import { OperationHandlerSet } from '@dxos/compute';
|
|
6
4
|
|
|
7
5
|
export const KanbanOperationHandlerSet = OperationHandlerSet.lazy(
|
|
8
6
|
() => import('./delete-card'),
|