@dxos/plugin-kanban 0.8.4-main.c85a9c8dae → 0.8.4-main.d9fc60f731
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-V3UCZQB6.mjs +133 -0
- package/dist/lib/neutral/KanbanArticle-V3UCZQB6.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/{browser/KanbanViewEditor-IH5CJ6BW.mjs → neutral/KanbanSettings-Q2RJW4U4.mjs} +38 -21
- package/dist/lib/neutral/KanbanSettings-Q2RJW4U4.mjs.map +7 -0
- package/dist/lib/{browser/blueprint-definition-HFEKGFJK.mjs → neutral/blueprint-definition-FHVIEKTQ.mjs} +4 -6
- package/dist/lib/neutral/blueprint-definition-FHVIEKTQ.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/blueprints/index.mjs +1 -1
- 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-PNGVV67W.mjs +246 -0
- package/dist/lib/neutral/chunk-PNGVV67W.mjs.map +7 -0
- package/dist/lib/neutral/chunk-UWUD7L3O.mjs +8 -0
- package/dist/lib/neutral/chunk-UWUD7L3O.mjs.map +7 -0
- package/dist/lib/{browser/chunk-TLYZSC7O.mjs → neutral/chunk-ZTQW5KQS.mjs} +3 -6
- package/dist/lib/neutral/chunk-ZTQW5KQS.mjs.map +7 -0
- package/dist/lib/{browser/KanbanContainer-BCXSJ6KS.mjs → neutral/components/index.mjs} +81 -142
- 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/neutral/delete-card-4QF3TKXO.mjs +24 -0
- package/dist/lib/neutral/delete-card-4QF3TKXO.mjs.map +7 -0
- package/dist/lib/neutral/delete-card-field-5M4ZHKLO.mjs +39 -0
- package/dist/lib/neutral/delete-card-field-5M4ZHKLO.mjs.map +7 -0
- package/dist/lib/{node-esm/chunk-CSL3HF2X.mjs → neutral/hooks/index.mjs} +128 -82
- package/dist/lib/neutral/hooks/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +34 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +8 -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/plugin.mjs +16 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/{browser/react-surface-FOMOGFVW.mjs → neutral/react-surface-MROAQQQ6.mjs} +30 -38
- package/dist/lib/neutral/react-surface-MROAQQQ6.mjs.map +7 -0
- package/dist/lib/neutral/restore-card-5NI7HMIJ.mjs +21 -0
- package/dist/lib/neutral/restore-card-5NI7HMIJ.mjs.map +7 -0
- package/dist/lib/neutral/restore-card-field-QM2R67J5.mjs +37 -0
- package/dist/lib/neutral/restore-card-field-QM2R67J5.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/{browser → neutral}/types/index.mjs +7 -5
- 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 -0
- package/dist/types/src/capabilities/blueprint-definition.d.ts +6 -0
- package/dist/types/src/capabilities/blueprint-definition.d.ts.map +1 -0
- 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 +12 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/undo-mappings.d.ts +5 -0
- package/dist/types/src/capabilities/undo-mappings.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +17 -38
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts +48 -46
- 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/KanbanArticle/KanbanArticle.stories.d.ts +79 -0
- 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 +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 +1 -1
- package/dist/types/src/hooks/useKanbanBoardModel.d.ts.map +1 -1
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts +2 -2
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +2 -2
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +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 +5 -0
- package/dist/types/src/operations/delete-card-field.d.ts.map +1 -0
- package/dist/types/src/operations/delete-card.d.ts +5 -0
- package/dist/types/src/operations/delete-card.d.ts.map +1 -0
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/restore-card-field.d.ts +5 -0
- package/dist/types/src/operations/restore-card-field.d.ts.map +1 -0
- package/dist/types/src/operations/restore-card.d.ts +5 -0
- package/dist/types/src/operations/restore-card.d.ts.map +1 -0
- 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/testing/KanbanCardTileSimple.d.ts +4 -2
- package/dist/types/src/testing/KanbanCardTileSimple.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +48 -46
- 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/types/KanbanOperation.d.ts +52 -0
- 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 -104
- 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 +115 -63
- package/src/KanbanPlugin.node.ts +21 -0
- package/src/KanbanPlugin.test.ts +31 -0
- package/src/KanbanPlugin.tsx +15 -29
- package/src/KanbanPlugin.workerd.ts +18 -0
- package/src/blueprints/kanban-blueprint.ts +4 -8
- package/src/capabilities/{artifact-definition/artifact-definition.ts → artifact-definition.ts} +12 -11
- package/src/capabilities/{blueprint-definition/blueprint-definition.ts → blueprint-definition.ts} +3 -1
- package/src/capabilities/create-object.ts +40 -0
- package/src/capabilities/index.ts +12 -3
- package/src/capabilities/operation-handler.ts +14 -0
- package/src/capabilities/{react-surface/react-surface.tsx → react-surface.tsx} +35 -19
- package/src/capabilities/undo-mappings.ts +34 -0
- package/src/components/KanbanBoard/KanbanBoard.stories.tsx +22 -19
- package/src/components/KanbanBoard/KanbanBoard.tsx +43 -63
- package/src/components/KanbanBoard/KanbanCard.tsx +77 -63
- package/src/components/KanbanBoard/KanbanColumn.tsx +19 -16
- package/src/components/KanbanBoard/context.ts +54 -0
- package/src/containers/{KanbanContainer/KanbanContainer.stories.tsx → KanbanArticle/KanbanArticle.stories.tsx} +67 -53
- package/src/containers/KanbanArticle/KanbanArticle.tsx +180 -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 +3 -3
- package/src/hooks/useItemsProjection.ts +44 -0
- package/src/hooks/{useKanbanBoardModel.test.ts → useKanbanBoardModel.browser.test.ts} +13 -19
- package/src/hooks/useKanbanBoardModel.ts +20 -6
- package/src/hooks/useKanbanColumnEventHandler.ts +1 -1
- package/src/hooks/useKanbanItemEventHandler.ts +1 -1
- package/src/hooks/useProjectionModel.ts +5 -5
- package/src/index.ts +3 -2
- package/src/meta.ts +22 -5
- package/src/operations/delete-card-field.ts +42 -0
- package/src/operations/delete-card.ts +23 -0
- package/src/operations/index.ts +10 -0
- package/src/operations/restore-card-field.ts +36 -0
- package/src/operations/restore-card.ts +21 -0
- package/src/playwright/smoke.spec.ts +3 -3
- package/src/plugin.ts +11 -0
- package/src/testing/KanbanCardTileSimple.tsx +29 -23
- package/src/translations.ts +26 -25
- package/src/types/Kanban.ts +85 -18
- package/src/types/KanbanOperation.ts +79 -0
- package/src/types/index.ts +3 -1
- package/src/types/schema.ts +20 -78
- package/src/types/types.ts +2 -2
- package/src/util/arrangement.test.ts +23 -14
- package/src/util/arrangement.ts +25 -15
- package/src/vite-env.d.ts +10 -0
- package/dist/lib/browser/KanbanContainer-BCXSJ6KS.mjs.map +0 -7
- package/dist/lib/browser/KanbanViewEditor-IH5CJ6BW.mjs.map +0 -7
- package/dist/lib/browser/blueprint-definition-HFEKGFJK.mjs.map +0 -7
- package/dist/lib/browser/chunk-QSWCFMEB.mjs +0 -385
- package/dist/lib/browser/chunk-QSWCFMEB.mjs.map +0 -7
- package/dist/lib/browser/chunk-RNFIFE2P.mjs +0 -213
- package/dist/lib/browser/chunk-RNFIFE2P.mjs.map +0 -7
- package/dist/lib/browser/chunk-TLYZSC7O.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -101
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/operation-resolver-BRA2OHUE.mjs +0 -162
- package/dist/lib/browser/operation-resolver-BRA2OHUE.mjs.map +0 -7
- package/dist/lib/browser/react-surface-FOMOGFVW.mjs.map +0 -7
- package/dist/lib/node-esm/KanbanContainer-EHRTLE7M.mjs +0 -305
- package/dist/lib/node-esm/KanbanContainer-EHRTLE7M.mjs.map +0 -7
- package/dist/lib/node-esm/KanbanViewEditor-WDACFC35.mjs +0 -67
- package/dist/lib/node-esm/KanbanViewEditor-WDACFC35.mjs.map +0 -7
- package/dist/lib/node-esm/blueprint-definition-NARBX32U.mjs +0 -18
- package/dist/lib/node-esm/blueprint-definition-NARBX32U.mjs.map +0 -7
- package/dist/lib/node-esm/blueprints/index.mjs +0 -9
- package/dist/lib/node-esm/chunk-4AWDHQVY.mjs +0 -214
- package/dist/lib/node-esm/chunk-4AWDHQVY.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CSL3HF2X.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-X3UJUQIV.mjs +0 -31
- package/dist/lib/node-esm/chunk-X3UJUQIV.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -102
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/operation-resolver-UEQ64LCN.mjs +0 -163
- package/dist/lib/node-esm/operation-resolver-UEQ64LCN.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-AITKFRBE.mjs +0 -100
- package/dist/lib/node-esm/react-surface-AITKFRBE.mjs.map +0 -7
- package/dist/lib/node-esm/types/index.mjs +0 -21
- package/dist/types/src/capabilities/artifact-definition/artifact-definition.d.ts.map +0 -1
- package/dist/types/src/capabilities/artifact-definition/index.d.ts +0 -3
- package/dist/types/src/capabilities/artifact-definition/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts +0 -6
- package/dist/types/src/capabilities/blueprint-definition/blueprint-definition.d.ts.map +0 -1
- package/dist/types/src/capabilities/blueprint-definition/index.d.ts +0 -3
- package/dist/types/src/capabilities/blueprint-definition/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
- 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 +0 -77
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts.map +0 -1
- package/dist/types/src/containers/KanbanContainer/index.d.ts +0 -3
- 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 -3
- 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/capabilities/artifact-definition/index.ts +0 -7
- package/src/capabilities/blueprint-definition/index.ts +0 -7
- package/src/capabilities/operation-resolver/index.ts +0 -7
- package/src/capabilities/operation-resolver/operation-resolver.ts +0 -133
- package/src/capabilities/react-surface/index.ts +0 -7
- package/src/containers/KanbanContainer/KanbanContainer.tsx +0 -87
- package/src/containers/KanbanContainer/index.ts +0 -7
- package/src/containers/KanbanViewEditor/KanbanViewEditor.tsx +0 -63
- package/src/containers/KanbanViewEditor/index.ts +0 -7
- /package/dist/lib/{browser → neutral}/blueprints/index.mjs.map +0 -0
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs +0 -0
- /package/dist/lib/{browser → neutral}/chunk-J5LGTIGS.mjs.map +0 -0
- /package/dist/lib/{browser/types → neutral}/index.mjs.map +0 -0
- /package/dist/lib/{node-esm/blueprints/index.mjs.map → neutral/meta.mjs.map} +0 -0
- /package/dist/lib/{node-esm/types → neutral/operations}/index.mjs.map +0 -0
- /package/dist/lib/{node-esm/chunk-HSLMI22Q.mjs.map → neutral/types/index.mjs.map} +0 -0
- /package/dist/types/src/capabilities/{artifact-definition/artifact-definition.d.ts → artifact-definition.d.ts} +0 -0
- /package/dist/types/src/capabilities/{react-surface/react-surface.d.ts → react-surface.d.ts} +0 -0
|
@@ -10,36 +10,40 @@ import { expect, waitFor, within } from 'storybook/test';
|
|
|
10
10
|
|
|
11
11
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
12
12
|
import { Surface } from '@dxos/app-framework/ui';
|
|
13
|
-
import {
|
|
14
|
-
import { View } from '@dxos/echo';
|
|
13
|
+
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
14
|
+
import { Filter, Obj, type QueryAST, Type, View } from '@dxos/echo';
|
|
15
15
|
import { type Mutable } from '@dxos/echo/internal';
|
|
16
16
|
import { invariant } from '@dxos/invariant';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import {
|
|
17
|
+
// `/plugin` entrypoints used here for the same reason as `corePlugins()` —
|
|
18
|
+
// see `@dxos/plugin-testing/src/core.ts` for the rationale.
|
|
19
|
+
import { ClientPlugin } from '@dxos/plugin-client/testing';
|
|
20
|
+
import { initializeIdentity } from '@dxos/plugin-client/testing';
|
|
21
|
+
import { PreviewPlugin } from '@dxos/plugin-preview/testing';
|
|
22
|
+
import { SpacePlugin } from '@dxos/plugin-space/testing';
|
|
20
23
|
import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
24
|
+
import { random } from '@dxos/random';
|
|
25
|
+
import { type Space, useQuery, useType, useSpaces } from '@dxos/react-client/echo';
|
|
24
26
|
import { ViewEditor } from '@dxos/react-ui-form';
|
|
25
|
-
import {
|
|
27
|
+
import { Syntax } from '@dxos/react-ui-syntax-highlighter';
|
|
28
|
+
import { withLayout } from '@dxos/react-ui/testing';
|
|
26
29
|
import { ViewModel, getTypenameFromQuery } from '@dxos/schema';
|
|
27
30
|
// TODO(wittjosiah): Replace with echo/testing.
|
|
28
31
|
import { Organization, Person } from '@dxos/types';
|
|
29
32
|
|
|
30
|
-
import { useProjectionModel } from '
|
|
33
|
+
import { useProjectionModel } from '#hooks';
|
|
34
|
+
import { translations } from '#translations';
|
|
35
|
+
import { Kanban } from '#types';
|
|
36
|
+
|
|
31
37
|
import { KanbanPlugin } from '../../KanbanPlugin';
|
|
32
|
-
import { translations } from '../../translations';
|
|
33
|
-
import { Kanban } from '../../types';
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
random.seed(0);
|
|
36
40
|
|
|
37
41
|
const createOrg = (status?: Organization.Organization['status']) => ({
|
|
38
|
-
name:
|
|
39
|
-
description:
|
|
40
|
-
image:
|
|
41
|
-
website:
|
|
42
|
-
status: (status ??
|
|
42
|
+
name: random.commerce.productName(),
|
|
43
|
+
description: random.lorem.paragraph(),
|
|
44
|
+
image: random.image.url(),
|
|
45
|
+
website: random.internet.url(),
|
|
46
|
+
status: (status ?? random.helpers.arrayElement(Organization.StatusOptions).id) as Organization.Organization['status'],
|
|
43
47
|
});
|
|
44
48
|
|
|
45
49
|
//
|
|
@@ -53,7 +57,7 @@ type ClientSetupOptions = {
|
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Creates the standard plugin manager decorator with client configuration.
|
|
56
|
-
* Includes KanbanPlugin so the Surface resolves to
|
|
60
|
+
* Includes KanbanPlugin so the Surface resolves to KanbanArticle.
|
|
57
61
|
*/
|
|
58
62
|
const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions): Decorator =>
|
|
59
63
|
withPluginManager({
|
|
@@ -63,7 +67,7 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
|
|
|
63
67
|
types: [...types, View.View, Kanban.Kanban],
|
|
64
68
|
onClientInitialized: ({ client }) =>
|
|
65
69
|
Effect.gen(function* () {
|
|
66
|
-
yield*
|
|
70
|
+
yield* initializeIdentity(client);
|
|
67
71
|
const space = yield* Effect.promise(() => client.spaces.create());
|
|
68
72
|
yield* Effect.promise(() => space.waitUntilReady());
|
|
69
73
|
yield* Effect.promise(() => onSpaceCreated?.(space) ?? Promise.resolve());
|
|
@@ -77,59 +81,67 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
|
|
|
77
81
|
});
|
|
78
82
|
|
|
79
83
|
/**
|
|
80
|
-
* Renders the first Kanban in the space via Surface (resolves to
|
|
81
|
-
* with a sidebar containing ViewEditor and
|
|
84
|
+
* Renders the first Kanban in the space via Surface (resolves to KanbanArticle),
|
|
85
|
+
* with a sidebar containing ViewEditor and Json filter.
|
|
82
86
|
*/
|
|
83
87
|
const DefaultComponent = () => {
|
|
84
88
|
const registry = useContext(RegistryContext);
|
|
85
89
|
const spaces = useSpaces();
|
|
86
90
|
const space = spaces[spaces.length - 1];
|
|
87
91
|
const [kanban] = useQuery(space?.db, Filter.type(Kanban.Kanban));
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
const
|
|
92
|
+
const viewRef = kanban && kanban.spec.kind === 'view' ? kanban.spec.view : undefined;
|
|
93
|
+
const view = viewRef?.target;
|
|
94
|
+
const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
|
|
95
|
+
const type = useType(space?.db, typename);
|
|
96
|
+
const projection = useProjectionModel(type, kanban, registry);
|
|
91
97
|
|
|
92
|
-
const data = useMemo(() => (kanban ? { subject: kanban } :
|
|
98
|
+
const data = useMemo(() => (kanban ? { subject: kanban, attendableId: 'story' } : undefined), [kanban]);
|
|
93
99
|
|
|
94
100
|
const handleUpdateQuery = useCallback(
|
|
95
101
|
(newQuery: QueryAST.Query) => {
|
|
96
|
-
invariant(
|
|
97
|
-
invariant(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Obj.change(kanban.view.target, (view) => {
|
|
102
|
+
invariant(type);
|
|
103
|
+
invariant(view);
|
|
104
|
+
// NOTE: persisted Type.Type typename is immutable; only the view's
|
|
105
|
+
// query is updated here.
|
|
106
|
+
Obj.update(view, (view) => {
|
|
102
107
|
view.query.ast = newQuery as Mutable<QueryAST.Query>;
|
|
103
108
|
});
|
|
104
109
|
},
|
|
105
|
-
[
|
|
110
|
+
[view, type],
|
|
106
111
|
);
|
|
107
112
|
|
|
108
113
|
const handleDeleteField = useCallback(
|
|
109
114
|
(fieldId: string) => {
|
|
110
|
-
if (
|
|
115
|
+
if (type && Type.getDatabase(type) != null && projection) {
|
|
111
116
|
projection.deleteFieldProjection(fieldId);
|
|
112
117
|
}
|
|
113
118
|
},
|
|
114
|
-
[
|
|
119
|
+
[type, projection],
|
|
115
120
|
);
|
|
116
121
|
|
|
117
|
-
if (!
|
|
122
|
+
if (!type || !view) {
|
|
118
123
|
return null;
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
return (
|
|
122
127
|
<div className='grow grid grid-cols-[1fr_350px] overflow-hidden h-full w-full'>
|
|
123
|
-
<Surface.Surface
|
|
128
|
+
<Surface.Surface type={AppSurface.Article} data={data} limit={1} />
|
|
124
129
|
<div className='flex flex-col h-full overflow-hidden border-l border-separator'>
|
|
125
130
|
<ViewEditor
|
|
126
|
-
registry={space?.db.
|
|
127
|
-
|
|
128
|
-
view={
|
|
131
|
+
registry={space?.db.graph.registry}
|
|
132
|
+
type={type}
|
|
133
|
+
view={view}
|
|
129
134
|
onQueryChanged={handleUpdateQuery}
|
|
130
|
-
onDelete={
|
|
135
|
+
onDelete={type && Type.getDatabase(type) != null ? handleDeleteField : undefined}
|
|
131
136
|
/>
|
|
132
|
-
<
|
|
137
|
+
<Syntax.Root data={{ view, schema: Type.getSchema(type) }}>
|
|
138
|
+
<Syntax.Content>
|
|
139
|
+
<Syntax.Filter />
|
|
140
|
+
<Syntax.Viewport>
|
|
141
|
+
<Syntax.Code classNames='text-xs' />
|
|
142
|
+
</Syntax.Viewport>
|
|
143
|
+
</Syntax.Content>
|
|
144
|
+
</Syntax.Root>
|
|
133
145
|
</div>
|
|
134
146
|
</div>
|
|
135
147
|
);
|
|
@@ -143,7 +155,7 @@ const meta = {
|
|
|
143
155
|
title: 'plugins/plugin-kanban/containers/Kanban',
|
|
144
156
|
component: DefaultComponent,
|
|
145
157
|
render: () => <DefaultComponent />,
|
|
146
|
-
decorators: [
|
|
158
|
+
decorators: [withLayout({ layout: 'fullscreen' })],
|
|
147
159
|
parameters: {
|
|
148
160
|
layout: 'fullscreen',
|
|
149
161
|
translations,
|
|
@@ -165,7 +177,7 @@ export const Default: Story = {
|
|
|
165
177
|
onSpaceCreated: async (space) => {
|
|
166
178
|
const { view } = await ViewModel.makeFromDatabase({
|
|
167
179
|
db: space.db,
|
|
168
|
-
typename: Organization.Organization
|
|
180
|
+
typename: Type.getTypename(Organization.Organization),
|
|
169
181
|
pivotFieldName: 'status',
|
|
170
182
|
});
|
|
171
183
|
const kanban = Kanban.make({ view });
|
|
@@ -182,9 +194,9 @@ export const Default: Story = {
|
|
|
182
194
|
|
|
183
195
|
// Wait for the kanban columns to render by finding the status tags.
|
|
184
196
|
// Organization.StatusOptions: prospect, qualified, active, commit, reject.
|
|
185
|
-
const activeTag = await canvas.findByText('Active', undefined, { timeout:
|
|
186
|
-
const prospectTag = await canvas.findByText('Prospect', undefined, { timeout:
|
|
187
|
-
const commitTag = await canvas.findByText('Commit', undefined, { timeout:
|
|
197
|
+
const activeTag = await canvas.findByText('Active', undefined, { timeout: 12_000 });
|
|
198
|
+
const prospectTag = await canvas.findByText('Prospect', undefined, { timeout: 12_000 });
|
|
199
|
+
const commitTag = await canvas.findByText('Commit', undefined, { timeout: 12_000 });
|
|
188
200
|
|
|
189
201
|
// Verify all expected columns are rendered.
|
|
190
202
|
await expect(activeTag).toBeTruthy();
|
|
@@ -209,7 +221,7 @@ export const Default: Story = {
|
|
|
209
221
|
await expect(activeCards.length).toBeGreaterThan(0);
|
|
210
222
|
await expect(prospectCards.length).toBeGreaterThan(0);
|
|
211
223
|
|
|
212
|
-
// Verify cards have drag handles (Card.
|
|
224
|
+
// Verify cards have drag handles (Card.Header includes drag handle).
|
|
213
225
|
const firstActiveCard = activeCards[0];
|
|
214
226
|
const buttons = firstActiveCard.querySelectorAll('button');
|
|
215
227
|
await expect(buttons.length).toBeGreaterThan(0);
|
|
@@ -226,7 +238,7 @@ export const Default: Story = {
|
|
|
226
238
|
};
|
|
227
239
|
|
|
228
240
|
/**
|
|
229
|
-
* Story variant that uses a
|
|
241
|
+
* Story variant that uses a database-stored Type.Type entity (mutable schema).
|
|
230
242
|
* This allows testing schema mutations like adding/removing fields.
|
|
231
243
|
*/
|
|
232
244
|
// TODO(wittjosiah): Card previews (e.g., OrganizationCard) are type-specific and hard-coded.
|
|
@@ -237,12 +249,14 @@ export const MutableSchema: Story = {
|
|
|
237
249
|
decorators: [
|
|
238
250
|
withKanbanPlugins({
|
|
239
251
|
onSpaceCreated: async (space) => {
|
|
240
|
-
//
|
|
241
|
-
const
|
|
252
|
+
// Persist the schema in the database to make it mutable (stored Type.Type).
|
|
253
|
+
const type = await space.db.addType(Organization.Organization);
|
|
242
254
|
|
|
243
255
|
const { view } = await ViewModel.makeFromDatabase({
|
|
244
256
|
db: space.db,
|
|
245
|
-
|
|
257
|
+
// `db.addType` returns a persisted `Type.Type` entity; its typename lives in the
|
|
258
|
+
// type metadata, so read it via `Type.getTypename` rather than a `.typename` prop.
|
|
259
|
+
typename: Type.getTypename(type),
|
|
246
260
|
pivotFieldName: 'status',
|
|
247
261
|
});
|
|
248
262
|
const kanban = Kanban.make({ view });
|
|
@@ -256,7 +270,7 @@ export const MutableSchema: Story = {
|
|
|
256
270
|
...Array.from({ length: 1 }, () => createOrg('commit')),
|
|
257
271
|
...Array.from({ length: 1 }, () => createOrg('reject')),
|
|
258
272
|
];
|
|
259
|
-
requiredOrgs.forEach((org) => space.db.add(Obj.make(
|
|
273
|
+
requiredOrgs.forEach((org) => space.db.add(Obj.make(type, org)));
|
|
260
274
|
},
|
|
261
275
|
}),
|
|
262
276
|
],
|
|
@@ -0,0 +1,180 @@
|
|
|
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 { AtomObj, AtomQuery } from '@dxos/echo-atom';
|
|
13
|
+
import { useObject, useType } from '@dxos/react-client/echo';
|
|
14
|
+
import { Panel, Toolbar } from '@dxos/react-ui';
|
|
15
|
+
import { getTagFromQuery, getTypenameFromQuery } from '@dxos/schema';
|
|
16
|
+
|
|
17
|
+
import { KanbanBoard } from '#components';
|
|
18
|
+
import { useEchoChangeCallback, useItemsProjection, useProjectionModel } from '#hooks';
|
|
19
|
+
import { KanbanOperation } from '#types';
|
|
20
|
+
import { Kanban } from '#types';
|
|
21
|
+
|
|
22
|
+
export type KanbanArticleProps = AppSurface.ObjectArticleProps<Kanban.Kanban>;
|
|
23
|
+
|
|
24
|
+
export const KanbanArticle = (props: KanbanArticleProps) => {
|
|
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
|
+
<ItemsKanbanArticle {...props} subject={props.subject} />
|
|
30
|
+
) : (
|
|
31
|
+
<ViewKanbanArticle {...props} />
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const ViewKanbanArticle = ({ role, subject: object }: KanbanArticleProps) => {
|
|
36
|
+
const registry = useContext(RegistryContext);
|
|
37
|
+
const schemas = useCapabilities(AppCapabilities.Schema);
|
|
38
|
+
const db = Obj.getDatabase(object);
|
|
39
|
+
const { invokePromise } = useOperationInvoker();
|
|
40
|
+
const [view] = useObject(object.spec.kind === 'view' ? object.spec.view : undefined);
|
|
41
|
+
const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
|
|
42
|
+
const tag = view?.query ? getTagFromQuery(view.query.ast) : undefined;
|
|
43
|
+
|
|
44
|
+
const schemaFromDb = useType(db, typename);
|
|
45
|
+
const cardSchema = useMemo(
|
|
46
|
+
() => schemaFromDb ?? schemas.flat().find((schema) => Type.getTypename(schema) === typename),
|
|
47
|
+
[schemaFromDb, schemas, typename],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const baseFilter = useSchemaFilter(cardSchema);
|
|
51
|
+
const items = useMemo(() => {
|
|
52
|
+
if (!db) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const query = tag ? Query.select(baseFilter).select(Filter.tag(tag)) : Query.select(baseFilter);
|
|
56
|
+
return AtomQuery.make(db, query);
|
|
57
|
+
}, [db, baseFilter, tag]);
|
|
58
|
+
|
|
59
|
+
const projection = useProjectionModel(cardSchema, object, registry);
|
|
60
|
+
const change = useEchoChangeCallback(object);
|
|
61
|
+
|
|
62
|
+
const pivotFieldId = view?.projection?.pivotFieldId;
|
|
63
|
+
const columnFieldPath =
|
|
64
|
+
projection && pivotFieldId ? projection.tryGetFieldProjection(pivotFieldId)?.props.property : undefined;
|
|
65
|
+
|
|
66
|
+
const handleCardAdd = useCallback(
|
|
67
|
+
(columnValue: string | undefined) => {
|
|
68
|
+
if (db && cardSchema && columnFieldPath) {
|
|
69
|
+
const card = Obj.make(Type.assertObject(cardSchema), {
|
|
70
|
+
[columnFieldPath]: columnValue,
|
|
71
|
+
});
|
|
72
|
+
db.add(card);
|
|
73
|
+
return card.id;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[db, cardSchema, columnFieldPath],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const handleCardRemove = useCallback(
|
|
80
|
+
(card: { id: string }) => {
|
|
81
|
+
void invokePromise(KanbanOperation.DeleteCard, { card });
|
|
82
|
+
},
|
|
83
|
+
[invokePromise],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (!object || !db || !items || !projection || !change) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Panel.Root role={role}>
|
|
92
|
+
<Panel.Toolbar asChild>
|
|
93
|
+
<Toolbar.Root />
|
|
94
|
+
</Panel.Toolbar>
|
|
95
|
+
<KanbanBoard.Root
|
|
96
|
+
kanban={object}
|
|
97
|
+
projection={projection}
|
|
98
|
+
items={items}
|
|
99
|
+
change={change}
|
|
100
|
+
onCardAdd={handleCardAdd}
|
|
101
|
+
onCardRemove={handleCardRemove}
|
|
102
|
+
>
|
|
103
|
+
<Panel.Content asChild>
|
|
104
|
+
<KanbanBoard.Content />
|
|
105
|
+
</Panel.Content>
|
|
106
|
+
</KanbanBoard.Root>
|
|
107
|
+
</Panel.Root>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type ItemsKanbanArticleProps = Omit<KanbanArticleProps, 'subject'> & { subject: Kanban.KanbanItems };
|
|
112
|
+
|
|
113
|
+
const ItemsKanbanArticle = ({ role, subject: object }: ItemsKanbanArticleProps) => {
|
|
114
|
+
const db = Obj.getDatabase(object);
|
|
115
|
+
const projection = useItemsProjection(object);
|
|
116
|
+
const change = useEchoChangeCallback(object);
|
|
117
|
+
|
|
118
|
+
// TODO(wittjosiah): pass refs (not loaded objects) through to the kanban
|
|
119
|
+
// board and let `KanbanCard` subscribe to its own ref via `useObject`.
|
|
120
|
+
// Today this atom subscribes to *every* item — any one changing causes the
|
|
121
|
+
// container (and the model's per-column atoms) to recompute. With cards
|
|
122
|
+
// subscribing themselves, the container only needs the refs and the
|
|
123
|
+
// per-card render is independent. Requires:
|
|
124
|
+
// - `KanbanCard` to accept `Ref<Obj.Unknown>` as `data` and call
|
|
125
|
+
// `useObject(ref)` internally.
|
|
126
|
+
// - The model to handle a ref-bearing item shape (id from
|
|
127
|
+
// `ref.dxn.asEchoDXN()?.echoUri`) and use arrangement-only ordering
|
|
128
|
+
// for items-variant (no pivot-value fallback, since refs don't expose
|
|
129
|
+
// the pivot field without loading).
|
|
130
|
+
// - `Mosaic.isItem` to accept the ref wrapper alongside `Obj.isObject`.
|
|
131
|
+
const itemsAtom = useMemo(
|
|
132
|
+
() =>
|
|
133
|
+
Atom.make((get) => {
|
|
134
|
+
const out: Obj.Unknown[] = [];
|
|
135
|
+
for (const ref of object.spec.items as ReadonlyArray<Ref.Ref<Obj.Unknown>>) {
|
|
136
|
+
const target = get(AtomObj.make(ref));
|
|
137
|
+
if (target == null) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// Drop soft-deleted cards (e.g. Trello-closed cards). The ref
|
|
141
|
+
// stays in `spec.items` so arrangement is preserved, but the card
|
|
142
|
+
// shouldn't render.
|
|
143
|
+
if (Obj.isDeleted(target)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
out.push(target as unknown as Obj.Unknown);
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}),
|
|
150
|
+
[object.spec.items],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const handleCardRemove = useCallback(() => undefined, []);
|
|
154
|
+
|
|
155
|
+
if (!object || !db || !change) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// TODO(wittjosiah): wire `onCardAdd` to the create-object flow so
|
|
160
|
+
// users can add items directly from the kanban (currently the column's
|
|
161
|
+
// "+" button is hidden because `onCardAdd` is undefined).
|
|
162
|
+
return (
|
|
163
|
+
<Panel.Root role={role}>
|
|
164
|
+
<Panel.Toolbar asChild>
|
|
165
|
+
<Toolbar.Root />
|
|
166
|
+
</Panel.Toolbar>
|
|
167
|
+
<KanbanBoard.Root
|
|
168
|
+
kanban={object}
|
|
169
|
+
projection={projection}
|
|
170
|
+
items={itemsAtom}
|
|
171
|
+
change={change}
|
|
172
|
+
onCardRemove={handleCardRemove}
|
|
173
|
+
>
|
|
174
|
+
<Panel.Content asChild>
|
|
175
|
+
<KanbanBoard.Content />
|
|
176
|
+
</Panel.Content>
|
|
177
|
+
</KanbanBoard.Root>
|
|
178
|
+
</Panel.Root>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
@@ -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, useType } 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 = useType(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
|
@@ -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
|
@@ -6,16 +6,16 @@ import { useMemo } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { Obj } from '@dxos/echo';
|
|
8
8
|
|
|
9
|
-
import { type Kanban, type KanbanChangeCallback } from '
|
|
9
|
+
import { type Kanban, type KanbanChangeCallback } from '#types';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Creates a change callback for ECHO-backed kanban and items (plain function, no hooks).
|
|
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
|
+
};
|