@dxos/plugin-kanban 0.8.4-main.9735255 → 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 +23 -4
- package/dist/lib/browser/blueprints/index.mjs.map +4 -4
- package/dist/lib/browser/chunk-T32TEM55.mjs +105 -0
- package/dist/lib/browser/chunk-T32TEM55.mjs.map +7 -0
- package/dist/lib/browser/delete-card-7OSCNCW2.mjs +24 -0
- package/dist/lib/browser/delete-card-7OSCNCW2.mjs.map +7 -0
- package/dist/lib/browser/delete-card-field-NSB2RE3Z.mjs +42 -0
- package/dist/lib/browser/delete-card-field-NSB2RE3Z.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +20 -89
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/operations/index.mjs +13 -0
- package/dist/lib/browser/operations/index.mjs.map +7 -0
- package/dist/lib/browser/restore-card-FO3WERIE.mjs +21 -0
- package/dist/lib/browser/restore-card-FO3WERIE.mjs.map +7 -0
- package/dist/lib/browser/restore-card-field-U5XYEEOW.mjs +40 -0
- 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 +154 -8
- package/dist/lib/browser/types/index.mjs.map +4 -4
- package/dist/lib/node-esm/blueprints/index.mjs +23 -4
- package/dist/lib/node-esm/blueprints/index.mjs.map +4 -4
- package/dist/lib/node-esm/chunk-W2RNFBMZ.mjs +106 -0
- package/dist/lib/node-esm/chunk-W2RNFBMZ.mjs.map +7 -0
- package/dist/lib/node-esm/delete-card-ZIREL6HN.mjs +25 -0
- package/dist/lib/node-esm/delete-card-ZIREL6HN.mjs.map +7 -0
- package/dist/lib/node-esm/delete-card-field-IPTEGVPP.mjs +43 -0
- package/dist/lib/node-esm/delete-card-field-IPTEGVPP.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +20 -89
- 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 +14 -0
- package/dist/lib/node-esm/operations/index.mjs.map +7 -0
- package/dist/lib/node-esm/restore-card-WJJ4YB7K.mjs +22 -0
- package/dist/lib/node-esm/restore-card-WJJ4YB7K.mjs.map +7 -0
- package/dist/lib/node-esm/restore-card-field-L24WJXAW.mjs +41 -0
- 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 +154 -8
- package/dist/lib/node-esm/types/index.mjs.map +4 -4
- 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/index.d.ts +1 -1
- package/dist/types/src/blueprints/index.d.ts.map +1 -1
- package/dist/types/src/blueprints/kanban-blueprint.d.ts +3 -21
- package/dist/types/src/blueprints/kanban-blueprint.d.ts.map +1 -1
- package/dist/types/src/capabilities/{artifact-definition/artifact-definition.d.ts → artifact-definition.d.ts} +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/index.d.ts +7 -3
- 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 +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +5 -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 +57 -0
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts +72 -0
- package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts +10 -0
- package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts +9 -0
- package/dist/types/src/components/KanbanBoard/KanbanColumn.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard/index.d.ts +2 -0
- package/dist/types/src/components/KanbanBoard/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts +6 -0
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts +79 -0
- package/dist/types/src/containers/KanbanContainer/KanbanContainer.stories.d.ts.map +1 -0
- package/dist/types/src/containers/KanbanContainer/index.d.ts +2 -0
- package/dist/types/src/containers/KanbanContainer/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 +4 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +7 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useEchoChangeCallback.d.ts +13 -0
- package/dist/types/src/hooks/useEchoChangeCallback.d.ts.map +1 -0
- 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 +16 -0
- package/dist/types/src/hooks/useKanbanBoardModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts +22 -0
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +19 -0
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts +15 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- 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 +52 -0
- package/dist/types/src/operations/definitions.d.ts.map +1 -0
- 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 +4 -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 +5 -0
- package/dist/types/src/playwright/board-manager.d.ts.map +1 -0
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/playwright/smoke.spec.d.ts +2 -0
- package/dist/types/src/playwright/smoke.spec.d.ts.map +1 -0
- package/dist/types/src/testing/KanbanCardTileSimple.d.ts +7 -0
- package/dist/types/src/testing/KanbanCardTileSimple.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +48 -32
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Kanban.d.ts +109 -0
- package/dist/types/src/types/Kanban.d.ts.map +1 -0
- package/dist/types/src/types/constants.d.ts +6 -0
- package/dist/types/src/types/constants.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +2 -0
- package/dist/types/src/types/index.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 -104
- package/dist/types/src/types/schema.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +28 -0
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/src/util/arrangement.d.ts +72 -0
- package/dist/types/src/util/arrangement.d.ts.map +1 -0
- package/dist/types/src/util/arrangement.test.d.ts +2 -0
- package/dist/types/src/util/arrangement.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +74 -52
- package/src/KanbanPlugin.node.ts +55 -0
- package/src/KanbanPlugin.test.ts +31 -0
- package/src/KanbanPlugin.tsx +42 -24
- package/src/blueprints/index.ts +1 -1
- package/src/blueprints/kanban-blueprint.ts +12 -9
- package/src/capabilities/{artifact-definition/artifact-definition.ts → artifact-definition.ts} +11 -10
- package/src/capabilities/blueprint-definition.ts +19 -0
- package/src/capabilities/index.ts +12 -3
- package/src/capabilities/migrations.ts +35 -0
- package/src/capabilities/operation-handler.ts +14 -0
- package/src/capabilities/{react-surface/react-surface.tsx → react-surface.tsx} +31 -22
- package/src/capabilities/undo-mappings.ts +34 -0
- package/src/components/KanbanBoard/KanbanBoard.stories.tsx +146 -0
- package/src/components/KanbanBoard/KanbanBoard.tsx +198 -0
- package/src/components/KanbanBoard/KanbanCard.tsx +98 -0
- package/src/components/KanbanBoard/KanbanColumn.tsx +71 -0
- package/src/components/KanbanBoard/index.ts +5 -0
- package/src/components/index.ts +1 -2
- package/src/{components → containers/KanbanContainer}/KanbanContainer.stories.tsx +85 -96
- package/src/containers/KanbanContainer/KanbanContainer.tsx +178 -0
- package/src/containers/KanbanContainer/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 +8 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/useEchoChangeCallback.ts +30 -0
- package/src/hooks/useItemsProjection.ts +44 -0
- package/src/hooks/useKanbanBoardModel.browser.test.ts +235 -0
- package/src/hooks/useKanbanBoardModel.ts +157 -0
- package/src/hooks/useKanbanColumnEventHandler.ts +106 -0
- package/src/hooks/useKanbanItemEventHandler.ts +133 -0
- package/src/hooks/useProjectionModel.ts +58 -0
- package/src/index.ts +6 -2
- package/src/meta.ts +1 -1
- package/src/operations/definitions.ts +63 -0
- package/src/operations/delete-card-field.ts +47 -0
- package/src/operations/delete-card.ts +23 -0
- package/src/operations/index.ts +12 -0
- package/src/operations/restore-card-field.ts +41 -0
- package/src/operations/restore-card.ts +21 -0
- package/src/playwright/board-manager.ts +13 -0
- package/src/playwright/playwright.config.ts +19 -0
- package/src/playwright/smoke.spec.ts +107 -0
- package/src/testing/KanbanCardTileSimple.tsx +82 -0
- package/src/testing/index.ts +5 -0
- package/src/translations.ts +26 -18
- package/src/types/Kanban.ts +151 -0
- package/src/types/constants.ts +9 -0
- package/src/types/index.ts +2 -0
- package/src/types/migrations.test.ts +83 -0
- package/src/types/schema.ts +19 -77
- package/src/types/types.ts +35 -0
- package/src/util/arrangement.test.ts +218 -0
- package/src/util/arrangement.ts +177 -0
- package/src/util/index.ts +5 -0
- package/dist/lib/browser/blueprint-definition-T2544VMJ.mjs +0 -17
- package/dist/lib/browser/blueprint-definition-T2544VMJ.mjs.map +0 -7
- package/dist/lib/browser/chunk-L6N4ZDZ7.mjs +0 -35
- package/dist/lib/browser/chunk-L6N4ZDZ7.mjs.map +0 -7
- package/dist/lib/browser/chunk-XYQO4VL7.mjs +0 -150
- package/dist/lib/browser/chunk-XYQO4VL7.mjs.map +0 -7
- package/dist/lib/browser/operation-resolver-UEJHX42A.mjs +0 -162
- package/dist/lib/browser/operation-resolver-UEJHX42A.mjs.map +0 -7
- package/dist/lib/browser/react-surface-LFUJAPRL.mjs +0 -236
- package/dist/lib/browser/react-surface-LFUJAPRL.mjs.map +0 -7
- package/dist/lib/node-esm/blueprint-definition-APJQFSHJ.mjs +0 -18
- package/dist/lib/node-esm/blueprint-definition-APJQFSHJ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NN6JMKIT.mjs +0 -152
- package/dist/lib/node-esm/chunk-NN6JMKIT.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ZHRMUKTF.mjs +0 -36
- package/dist/lib/node-esm/chunk-ZHRMUKTF.mjs.map +0 -7
- package/dist/lib/node-esm/operation-resolver-5RPWHZCF.mjs +0 -163
- package/dist/lib/node-esm/operation-resolver-5RPWHZCF.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-7TSGBRJL.mjs +0 -237
- package/dist/lib/node-esm/react-surface-7TSGBRJL.mjs.map +0 -7
- 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 -9
- 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 +0 -5
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
- package/dist/types/src/components/KanbanContainer.d.ts +0 -6
- package/dist/types/src/components/KanbanContainer.d.ts.map +0 -1
- package/dist/types/src/components/KanbanContainer.stories.d.ts +0 -75
- package/dist/types/src/components/KanbanContainer.stories.d.ts.map +0 -1
- package/dist/types/src/components/KanbanViewEditor.d.ts +0 -8
- package/dist/types/src/components/KanbanViewEditor.d.ts.map +0 -1
- package/src/capabilities/artifact-definition/index.ts +0 -7
- package/src/capabilities/blueprint-definition/blueprint-definition.ts +0 -23
- 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/components/KanbanContainer.tsx +0 -86
- package/src/components/KanbanViewEditor.tsx +0 -59
|
@@ -5,43 +5,44 @@
|
|
|
5
5
|
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
6
|
import { type Decorator, type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
7
|
import * as Effect from 'effect/Effect';
|
|
8
|
-
import React, { useCallback, useContext } from 'react';
|
|
8
|
+
import React, { useCallback, useContext, useMemo } from 'react';
|
|
9
9
|
import { expect, waitFor, within } from 'storybook/test';
|
|
10
10
|
|
|
11
11
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
12
|
+
import { Surface } from '@dxos/app-framework/ui';
|
|
13
|
+
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
12
14
|
import { Obj, type QueryAST, Type } from '@dxos/echo';
|
|
15
|
+
import { View } from '@dxos/echo';
|
|
13
16
|
import { type Mutable } from '@dxos/echo/internal';
|
|
14
17
|
import { invariant } from '@dxos/invariant';
|
|
15
18
|
import { ClientPlugin } from '@dxos/plugin-client';
|
|
19
|
+
import { initializeIdentity } from '@dxos/plugin-client/testing';
|
|
16
20
|
import { PreviewPlugin } from '@dxos/plugin-preview';
|
|
17
|
-
import { useGlobalFilteredObjects } from '@dxos/plugin-search';
|
|
18
21
|
import { SpacePlugin } from '@dxos/plugin-space';
|
|
19
22
|
import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
|
|
20
|
-
import {
|
|
23
|
+
import { random } from '@dxos/random';
|
|
21
24
|
import { Filter, type Space, useQuery, useSchema, useSpaces } from '@dxos/react-client/echo';
|
|
22
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
23
25
|
import { ViewEditor } from '@dxos/react-ui-form';
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
useProjectionModel,
|
|
29
|
-
} from '@dxos/react-ui-kanban';
|
|
30
|
-
import { Kanban } from '@dxos/react-ui-kanban/types';
|
|
31
|
-
import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
|
|
32
|
-
import { View, getTypenameFromQuery } from '@dxos/schema';
|
|
26
|
+
import { Syntax } from '@dxos/react-ui-syntax-highlighter';
|
|
27
|
+
import { withLayout } from '@dxos/react-ui/testing';
|
|
28
|
+
import { ViewModel, getTypenameFromQuery } from '@dxos/schema';
|
|
29
|
+
// TODO(wittjosiah): Replace with echo/testing.
|
|
33
30
|
import { Organization, Person } from '@dxos/types';
|
|
34
31
|
|
|
35
|
-
import {
|
|
32
|
+
import { useProjectionModel } from '#hooks';
|
|
33
|
+
import { translations } from '#translations';
|
|
34
|
+
import { Kanban } from '#types';
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
import { KanbanPlugin } from '../../KanbanPlugin';
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
random.seed(0);
|
|
39
|
+
|
|
40
|
+
const createOrg = (status?: Organization.Organization['status']) => ({
|
|
41
|
+
name: random.commerce.productName(),
|
|
42
|
+
description: random.lorem.paragraph(),
|
|
43
|
+
image: random.image.url(),
|
|
44
|
+
website: random.internet.url(),
|
|
45
|
+
status: (status ?? random.helpers.arrayElement(Organization.StatusOptions).id) as Organization.Organization['status'],
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
//
|
|
@@ -49,12 +50,13 @@ const createOrg = () => ({
|
|
|
49
50
|
//
|
|
50
51
|
|
|
51
52
|
type ClientSetupOptions = {
|
|
52
|
-
types?: Type.
|
|
53
|
+
types?: Type.AnyEntity[];
|
|
53
54
|
onSpaceCreated?: (space: Space) => Promise<void>;
|
|
54
55
|
};
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
58
|
* Creates the standard plugin manager decorator with client configuration.
|
|
59
|
+
* Includes KanbanPlugin so the Surface resolves to KanbanContainer.
|
|
58
60
|
*/
|
|
59
61
|
const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions): Decorator =>
|
|
60
62
|
withPluginManager({
|
|
@@ -64,7 +66,7 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
|
|
|
64
66
|
types: [...types, View.View, Kanban.Kanban],
|
|
65
67
|
onClientInitialized: ({ client }) =>
|
|
66
68
|
Effect.gen(function* () {
|
|
67
|
-
yield*
|
|
69
|
+
yield* initializeIdentity(client);
|
|
68
70
|
const space = yield* Effect.promise(() => client.spaces.create());
|
|
69
71
|
yield* Effect.promise(() => space.waitUntilReady());
|
|
70
72
|
yield* Effect.promise(() => onSpaceCreated?.(space) ?? Promise.resolve());
|
|
@@ -73,61 +75,39 @@ const withKanbanPlugins = ({ types = [], onSpaceCreated }: ClientSetupOptions):
|
|
|
73
75
|
PreviewPlugin(),
|
|
74
76
|
SpacePlugin({}),
|
|
75
77
|
StorybookPlugin({}),
|
|
78
|
+
KanbanPlugin(),
|
|
76
79
|
],
|
|
77
80
|
});
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Renders the first Kanban in the space via Surface (resolves to KanbanContainer),
|
|
84
|
+
* with a sidebar containing ViewEditor and Json filter.
|
|
85
|
+
*/
|
|
83
86
|
const DefaultComponent = () => {
|
|
84
87
|
const registry = useContext(RegistryContext);
|
|
85
88
|
const spaces = useSpaces();
|
|
86
89
|
const space = spaces[spaces.length - 1];
|
|
87
|
-
const [
|
|
88
|
-
const
|
|
90
|
+
const [kanban] = useQuery(space?.db, Filter.type(Kanban.Kanban));
|
|
91
|
+
const viewRef = kanban && kanban.spec.kind === 'view' ? kanban.spec.view : undefined;
|
|
92
|
+
const view = viewRef?.target;
|
|
93
|
+
const typename = view?.query ? getTypenameFromQuery(view.query.ast) : undefined;
|
|
89
94
|
const schema = useSchema(space?.db, typename);
|
|
95
|
+
const projection = useProjectionModel(schema, kanban, registry);
|
|
90
96
|
|
|
91
|
-
const
|
|
92
|
-
const filteredObjects = useGlobalFilteredObjects(objects);
|
|
93
|
-
|
|
94
|
-
const projection = useProjectionModel(schema, object, registry);
|
|
95
|
-
const model = useKanbanModel({
|
|
96
|
-
object,
|
|
97
|
-
projection,
|
|
98
|
-
items: filteredObjects,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const handleAddCard = useCallback(
|
|
102
|
-
(columnValue: string | undefined) => {
|
|
103
|
-
const path = model?.columnFieldPath;
|
|
104
|
-
if (space && schema && Type.isObjectSchema(schema) && path) {
|
|
105
|
-
const card = Obj.make(schema, {
|
|
106
|
-
...createOrg(),
|
|
107
|
-
[path]: columnValue,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
space.db.add(card);
|
|
111
|
-
return card.id;
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
[space, schema, model],
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const handleRemoveCard = useCallback((card: { id: string }) => Obj.isObject(card) && space?.db.remove(card), [space]);
|
|
97
|
+
const data = useMemo(() => (kanban ? { subject: kanban, attendableId: 'story' } : undefined), [kanban]);
|
|
118
98
|
|
|
119
99
|
const handleUpdateQuery = useCallback(
|
|
120
100
|
(newQuery: QueryAST.Query) => {
|
|
121
101
|
invariant(schema);
|
|
122
|
-
invariant(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Obj.
|
|
127
|
-
|
|
102
|
+
invariant(view);
|
|
103
|
+
if (Type.isMutable(schema)) {
|
|
104
|
+
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
105
|
+
}
|
|
106
|
+
Obj.update(view, (view) => {
|
|
107
|
+
view.query.ast = newQuery as Mutable<QueryAST.Query>;
|
|
128
108
|
});
|
|
129
109
|
},
|
|
130
|
-
[
|
|
110
|
+
[view, schema],
|
|
131
111
|
);
|
|
132
112
|
|
|
133
113
|
const handleDeleteField = useCallback(
|
|
@@ -139,23 +119,29 @@ const DefaultComponent = () => {
|
|
|
139
119
|
[schema, projection],
|
|
140
120
|
);
|
|
141
121
|
|
|
142
|
-
if (!schema || !
|
|
122
|
+
if (!schema || !view) {
|
|
143
123
|
return null;
|
|
144
124
|
}
|
|
145
125
|
|
|
146
126
|
return (
|
|
147
|
-
<div className='grow grid grid-cols-[1fr_350px] overflow-hidden'>
|
|
148
|
-
|
|
149
|
-
<div className='flex flex-col
|
|
127
|
+
<div className='grow grid grid-cols-[1fr_350px] overflow-hidden h-full w-full'>
|
|
128
|
+
<Surface.Surface type={AppSurface.Article} data={data} limit={1} />
|
|
129
|
+
<div className='flex flex-col h-full overflow-hidden border-l border-separator'>
|
|
150
130
|
<ViewEditor
|
|
151
|
-
classNames='p-2'
|
|
152
131
|
registry={space?.db.schemaRegistry}
|
|
153
132
|
schema={schema}
|
|
154
|
-
view={
|
|
133
|
+
view={view}
|
|
155
134
|
onQueryChanged={handleUpdateQuery}
|
|
156
|
-
onDelete={Type.isMutable(schema) ? handleDeleteField : undefined}
|
|
135
|
+
onDelete={schema && Type.isMutable(schema) ? handleDeleteField : undefined}
|
|
157
136
|
/>
|
|
158
|
-
<
|
|
137
|
+
<Syntax.Root data={{ view, schema }}>
|
|
138
|
+
<Syntax.Content>
|
|
139
|
+
<Syntax.Filter />
|
|
140
|
+
<Syntax.Viewport>
|
|
141
|
+
<Syntax.Code classNames='text-xs' />
|
|
142
|
+
</Syntax.Viewport>
|
|
143
|
+
</Syntax.Content>
|
|
144
|
+
</Syntax.Root>
|
|
159
145
|
</div>
|
|
160
146
|
</div>
|
|
161
147
|
);
|
|
@@ -166,13 +152,13 @@ const DefaultComponent = () => {
|
|
|
166
152
|
//
|
|
167
153
|
|
|
168
154
|
const meta = {
|
|
169
|
-
title: 'plugins/plugin-kanban/Kanban',
|
|
155
|
+
title: 'plugins/plugin-kanban/containers/Kanban',
|
|
170
156
|
component: DefaultComponent,
|
|
171
157
|
render: () => <DefaultComponent />,
|
|
172
|
-
decorators: [
|
|
158
|
+
decorators: [withLayout({ layout: 'fullscreen' })],
|
|
173
159
|
parameters: {
|
|
174
160
|
layout: 'fullscreen',
|
|
175
|
-
translations
|
|
161
|
+
translations,
|
|
176
162
|
},
|
|
177
163
|
} satisfies Meta<typeof DefaultComponent>;
|
|
178
164
|
|
|
@@ -189,7 +175,7 @@ export const Default: Story = {
|
|
|
189
175
|
withKanbanPlugins({
|
|
190
176
|
types: [Organization.Organization, Person.Person],
|
|
191
177
|
onSpaceCreated: async (space) => {
|
|
192
|
-
const { view } = await
|
|
178
|
+
const { view } = await ViewModel.makeFromDatabase({
|
|
193
179
|
db: space.db,
|
|
194
180
|
typename: Organization.Organization.typename,
|
|
195
181
|
pivotFieldName: 'status',
|
|
@@ -197,8 +183,7 @@ export const Default: Story = {
|
|
|
197
183
|
const kanban = Kanban.make({ view });
|
|
198
184
|
space.db.add(kanban);
|
|
199
185
|
|
|
200
|
-
|
|
201
|
-
Array.from({ length: 80 }).map(() => {
|
|
186
|
+
Array.from({ length: 10 }).map(() => {
|
|
202
187
|
return space.db.add(Obj.make(Organization.Organization, createOrg()));
|
|
203
188
|
});
|
|
204
189
|
},
|
|
@@ -209,25 +194,24 @@ export const Default: Story = {
|
|
|
209
194
|
|
|
210
195
|
// Wait for the kanban columns to render by finding the status tags.
|
|
211
196
|
// Organization.StatusOptions: prospect, qualified, active, commit, reject.
|
|
212
|
-
const activeTag = await canvas.findByText('Active', undefined, { timeout:
|
|
213
|
-
const prospectTag = await canvas.findByText('Prospect', undefined, { timeout:
|
|
214
|
-
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 });
|
|
215
200
|
|
|
216
201
|
// Verify all expected columns are rendered.
|
|
217
202
|
await expect(activeTag).toBeTruthy();
|
|
218
203
|
await expect(prospectTag).toBeTruthy();
|
|
219
204
|
await expect(commitTag).toBeTruthy();
|
|
220
205
|
|
|
221
|
-
// Find the column containers.
|
|
222
|
-
const activeColumn = activeTag.closest('[data-
|
|
223
|
-
const prospectColumn = prospectTag.closest('[data-
|
|
206
|
+
// Find the column containers (Board uses data-testid="board-column").
|
|
207
|
+
const activeColumn = activeTag.closest('[data-testid="board-column"]') as HTMLElement;
|
|
208
|
+
const prospectColumn = prospectTag.closest('[data-testid="board-column"]') as HTMLElement;
|
|
224
209
|
await expect(activeColumn).toBeTruthy();
|
|
225
210
|
await expect(prospectColumn).toBeTruthy();
|
|
226
211
|
|
|
227
|
-
// Wait for cards to render in the columns.
|
|
228
|
-
// Cards have data-dx-item-id attribute from StackItem.Root.
|
|
212
|
+
// Wait for cards to render in the columns (Board items use data-testid="board-item").
|
|
229
213
|
const getColumnCards = (column: HTMLElement) =>
|
|
230
|
-
Array.from(column.querySelectorAll('[data-
|
|
214
|
+
Array.from(column.querySelectorAll('[data-testid="board-item"]')) as HTMLElement[];
|
|
231
215
|
|
|
232
216
|
await waitFor(() => expect(getColumnCards(activeColumn).length).toBeGreaterThan(0));
|
|
233
217
|
|
|
@@ -237,16 +221,16 @@ export const Default: Story = {
|
|
|
237
221
|
await expect(activeCards.length).toBeGreaterThan(0);
|
|
238
222
|
await expect(prospectCards.length).toBeGreaterThan(0);
|
|
239
223
|
|
|
240
|
-
// Verify cards have drag handles (
|
|
224
|
+
// Verify cards have drag handles (Card.Toolbar includes drag handle).
|
|
241
225
|
const firstActiveCard = activeCards[0];
|
|
242
226
|
const buttons = firstActiveCard.querySelectorAll('button');
|
|
243
227
|
await expect(buttons.length).toBeGreaterThan(0);
|
|
244
228
|
|
|
245
|
-
// Verify
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
await expect(
|
|
249
|
-
await expect(
|
|
229
|
+
// Verify add-card action exists in columns (optional footer).
|
|
230
|
+
const activeAddItem = activeColumn.querySelector('[data-testid="board-column-add-item"]');
|
|
231
|
+
const prospectAddItem = prospectColumn.querySelector('[data-testid="board-column-add-item"]');
|
|
232
|
+
await expect(activeAddItem).toBeTruthy();
|
|
233
|
+
await expect(prospectAddItem).toBeTruthy();
|
|
250
234
|
|
|
251
235
|
// TODO(wittjosiah): Get drag & drop tests working.
|
|
252
236
|
// See packages/apps/composer-app/src/playwright/stack.spec.ts for reference.
|
|
@@ -268,7 +252,7 @@ export const MutableSchema: Story = {
|
|
|
268
252
|
// Register schema in the database to make it mutable (EchoSchema).
|
|
269
253
|
const [schema] = await space.db.schemaRegistry.register([Organization.Organization]);
|
|
270
254
|
|
|
271
|
-
const { view } = await
|
|
255
|
+
const { view } = await ViewModel.makeFromDatabase({
|
|
272
256
|
db: space.db,
|
|
273
257
|
typename: schema.typename,
|
|
274
258
|
pivotFieldName: 'status',
|
|
@@ -277,9 +261,14 @@ export const MutableSchema: Story = {
|
|
|
277
261
|
space.db.add(kanban);
|
|
278
262
|
|
|
279
263
|
// Create test data using the registered schema.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
264
|
+
const requiredOrgs = [
|
|
265
|
+
...Array.from({ length: 2 }, () => createOrg('prospect')),
|
|
266
|
+
...Array.from({ length: 5 }, () => createOrg('qualified')),
|
|
267
|
+
...Array.from({ length: 1 }, () => createOrg('active')),
|
|
268
|
+
...Array.from({ length: 1 }, () => createOrg('commit')),
|
|
269
|
+
...Array.from({ length: 1 }, () => createOrg('reject')),
|
|
270
|
+
];
|
|
271
|
+
requiredOrgs.forEach((org) => space.db.add(Obj.make(schema, org)));
|
|
283
272
|
},
|
|
284
273
|
}),
|
|
285
274
|
],
|
|
@@ -0,0 +1,178 @@
|
|
|
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 { 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, useSchema } 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 '#operations';
|
|
20
|
+
import { Kanban } from '#types';
|
|
21
|
+
|
|
22
|
+
export type KanbanContainerProps = AppSurface.ObjectArticleProps<Kanban.Kanban>;
|
|
23
|
+
|
|
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) => {
|
|
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 = useSchema(db, typename);
|
|
45
|
+
const cardSchema = useMemo(
|
|
46
|
+
() => schemaFromDb ?? schemas.flat().find((schema) => Type.getTypename(schema) === typename),
|
|
47
|
+
[schemaFromDb, schemas, typename],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const items = useMemo(() => {
|
|
51
|
+
if (!db) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const baseFilter = cardSchema ? Filter.type(cardSchema) : Filter.nothing();
|
|
55
|
+
const query = tag ? Query.select(baseFilter).select(Filter.tag(tag)) : Query.select(baseFilter);
|
|
56
|
+
return AtomQuery.make(db, query);
|
|
57
|
+
}, [db, cardSchema, 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(cardSchema, { [columnFieldPath]: columnValue });
|
|
70
|
+
db.add(card);
|
|
71
|
+
return card.id;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
[db, cardSchema, columnFieldPath],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const handleCardRemove = useCallback(
|
|
78
|
+
(card: { id: string }) => {
|
|
79
|
+
void invokePromise(KanbanOperation.DeleteCard, { card });
|
|
80
|
+
},
|
|
81
|
+
[invokePromise],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (!object || !db || !items || !projection || !change) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Panel.Root role={role}>
|
|
90
|
+
<Panel.Toolbar asChild>
|
|
91
|
+
<Toolbar.Root />
|
|
92
|
+
</Panel.Toolbar>
|
|
93
|
+
<KanbanBoard.Root
|
|
94
|
+
kanban={object}
|
|
95
|
+
projection={projection}
|
|
96
|
+
items={items}
|
|
97
|
+
change={change}
|
|
98
|
+
onCardAdd={handleCardAdd}
|
|
99
|
+
onCardRemove={handleCardRemove}
|
|
100
|
+
>
|
|
101
|
+
<Panel.Content asChild>
|
|
102
|
+
<KanbanBoard.Content />
|
|
103
|
+
</Panel.Content>
|
|
104
|
+
</KanbanBoard.Root>
|
|
105
|
+
</Panel.Root>
|
|
106
|
+
);
|
|
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
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type ComponentType, lazy } from 'react';
|
|
6
|
+
|
|
7
|
+
export const KanbanContainer: ComponentType<any> = lazy(() => import('./KanbanContainer'));
|
|
8
|
+
export const KanbanSettings: ComponentType<any> = lazy(() => import('./KanbanSettings'));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
export * from './useEchoChangeCallback';
|
|
6
|
+
export * from './useItemsProjection';
|
|
7
|
+
export * from './useKanbanBoardModel';
|
|
8
|
+
export * from './useKanbanColumnEventHandler';
|
|
9
|
+
export * from './useKanbanItemEventHandler';
|
|
10
|
+
export * from './useProjectionModel';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Obj } from '@dxos/echo';
|
|
8
|
+
|
|
9
|
+
import { type Kanban, type KanbanChangeCallback } from '#types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a change callback for ECHO-backed kanban and items (plain function, no hooks).
|
|
13
|
+
* Use this when the kanban and items are stored in the ECHO database.
|
|
14
|
+
*/
|
|
15
|
+
export const createEchoChangeCallback = <T extends Obj.Unknown>(kanban: Kanban.Kanban): KanbanChangeCallback<T> => ({
|
|
16
|
+
kanban: (mutate) => Obj.update(kanban, (kanban) => mutate(kanban)),
|
|
17
|
+
setItemField: (item, field, value) => {
|
|
18
|
+
Obj.update(item, (item: any) => {
|
|
19
|
+
item[field] = value;
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns a memoized ECHO change callback for the given kanban.
|
|
26
|
+
* Returns null when kanban is undefined.
|
|
27
|
+
*/
|
|
28
|
+
export const useEchoChangeCallback = <T extends Obj.Unknown = Obj.Unknown>(
|
|
29
|
+
kanban: Kanban.Kanban | undefined,
|
|
30
|
+
): KanbanChangeCallback<T> | null => useMemo(() => (kanban ? createEchoChangeCallback<T>(kanban) : null), [kanban]);
|