@dxos/plugin-kanban 0.10.0 → 0.11.1
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/KanbanPlugin.mjs +18 -0
- package/dist/lib/KanbanPlugin.mjs.map +1 -0
- package/dist/lib/KanbanPlugin.node.mjs +11 -0
- package/dist/lib/KanbanPlugin.node.mjs.map +1 -0
- package/dist/lib/KanbanPlugin.workerd.mjs +11 -0
- package/dist/lib/KanbanPlugin.workerd.mjs.map +1 -0
- package/dist/lib/capabilities/node.mjs +10 -0
- package/dist/lib/capabilities/node.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +11 -0
- package/dist/lib/capabilities.mjs.map +1 -0
- package/dist/lib/chunk-KanbanArticle.mjs +121 -0
- package/dist/lib/chunk-KanbanArticle.mjs.map +1 -0
- package/dist/lib/chunk-KanbanProperties.mjs +73 -0
- package/dist/lib/chunk-KanbanProperties.mjs.map +1 -0
- package/dist/lib/chunk-create-object.mjs +42 -0
- package/dist/lib/chunk-create-object.mjs.map +1 -0
- package/dist/lib/chunk-delete-card-field.mjs +50 -0
- package/dist/lib/chunk-delete-card-field.mjs.map +1 -0
- package/dist/lib/chunk-delete-card.mjs +23 -0
- package/dist/lib/chunk-delete-card.mjs.map +1 -0
- package/dist/lib/{neutral/chunk-V4AVOJVU.mjs → chunk-meta.mjs} +21 -29
- package/dist/lib/chunk-meta.mjs.map +1 -0
- package/dist/lib/chunk-operation-handler.mjs +11 -0
- package/dist/lib/chunk-operation-handler.mjs.map +1 -0
- package/dist/lib/chunk-react-surface.mjs +67 -0
- package/dist/lib/chunk-react-surface.mjs.map +1 -0
- package/dist/lib/chunk-restore-card-field.mjs +48 -0
- package/dist/lib/chunk-restore-card-field.mjs.map +1 -0
- package/dist/lib/chunk-restore-card.mjs +22 -0
- package/dist/lib/chunk-restore-card.mjs.map +1 -0
- package/dist/lib/chunk-skill-definition.mjs +10 -0
- package/dist/lib/chunk-skill-definition.mjs.map +1 -0
- package/dist/lib/chunk-skills.mjs +20 -0
- package/dist/lib/chunk-skills.mjs.map +1 -0
- package/dist/lib/chunk-types.mjs +231 -0
- package/dist/lib/chunk-types.mjs.map +1 -0
- package/dist/lib/chunk-undo-mappings.mjs +25 -0
- package/dist/lib/chunk-undo-mappings.mjs.map +1 -0
- package/dist/lib/components.mjs +227 -0
- package/dist/lib/components.mjs.map +1 -0
- package/dist/lib/containers.mjs +8 -0
- package/dist/lib/containers.mjs.map +1 -0
- package/dist/lib/hooks.mjs +394 -0
- package/dist/lib/hooks.mjs.map +1 -0
- package/dist/lib/index.mjs +5 -0
- package/dist/lib/meta.mjs +2 -0
- package/dist/lib/operations.mjs +7 -0
- package/dist/lib/operations.mjs.map +1 -0
- package/dist/lib/plugin.mjs +9 -0
- package/dist/lib/plugin.mjs.map +1 -0
- package/dist/lib/skills.mjs +2 -0
- package/dist/lib/testing.mjs +72 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/translations.mjs +38 -0
- package/dist/lib/translations.mjs.map +1 -0
- package/dist/lib/types.mjs +2 -0
- package/dist/types/src/capabilities/PivotColumnField.d.ts +12 -0
- package/dist/types/src/capabilities/PivotColumnField.d.ts.map +1 -0
- package/dist/types/src/capabilities/artifact-definition.d.ts +1 -1
- package/dist/types/src/capabilities/create-object.d.ts +1 -1
- package/dist/types/src/capabilities/node.d.ts +7 -0
- package/dist/types/src/capabilities/node.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-handler.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/capabilities/undo-mappings.d.ts +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts +3 -4
- package/dist/types/src/components/KanbanBoard/KanbanBoard.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanBoard.stories.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/KanbanCard.d.ts.map +1 -1
- package/dist/types/src/components/KanbanBoard/index.d.ts +1 -0
- package/dist/types/src/components/KanbanBoard/index.d.ts.map +1 -1
- package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts +4 -1
- package/dist/types/src/containers/KanbanArticle/KanbanArticle.d.ts.map +1 -1
- package/dist/types/src/containers/KanbanArticle/KanbanArticle.stories.d.ts.map +1 -1
- package/dist/types/src/containers/KanbanProperties/KanbanProperties.d.ts +4 -8
- package/dist/types/src/containers/KanbanProperties/KanbanProperties.d.ts.map +1 -1
- 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 +4 -3
- package/dist/types/src/hooks/useKanbanColumnEventHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useKanbanItemEventHandler.d.ts +4 -3
- 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/testing/KanbanCardTileSimple.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +1 -0
- package/package.json +58 -51
- package/src/capabilities/PivotColumnField.tsx +56 -0
- package/src/capabilities/node.ts +18 -0
- package/src/capabilities/react-surface.ts +45 -0
- package/src/components/KanbanBoard/KanbanBoard.stories.tsx +15 -4
- package/src/components/KanbanBoard/KanbanBoard.tsx +0 -3
- package/src/components/KanbanBoard/KanbanCard.tsx +5 -3
- package/src/components/KanbanBoard/index.ts +2 -0
- package/src/containers/KanbanArticle/KanbanArticle.stories.tsx +2 -1
- package/src/containers/KanbanArticle/KanbanArticle.tsx +3 -1
- package/src/containers/KanbanProperties/KanbanProperties.tsx +3 -1
- package/src/hooks/useItemsProjection.ts +1 -1
- package/src/hooks/useKanbanBoardModel.browser.test.ts +1 -1
- package/src/hooks/useKanbanBoardModel.ts +1 -1
- package/src/hooks/useKanbanColumnEventHandler.ts +6 -5
- package/src/hooks/useKanbanItemEventHandler.ts +6 -7
- package/src/hooks/useProjectionModel.ts +1 -1
- package/src/testing/KanbanCardTileSimple.tsx +2 -1
- package/dist/lib/neutral/KanbanArticle-JQMQQHRB.mjs +0 -132
- package/dist/lib/neutral/KanbanArticle-JQMQQHRB.mjs.map +0 -7
- package/dist/lib/neutral/KanbanPlugin.mjs +0 -44
- package/dist/lib/neutral/KanbanPlugin.mjs.map +0 -7
- package/dist/lib/neutral/KanbanPlugin.node.mjs +0 -27
- package/dist/lib/neutral/KanbanPlugin.node.mjs.map +0 -7
- package/dist/lib/neutral/KanbanPlugin.workerd.mjs +0 -21
- package/dist/lib/neutral/KanbanPlugin.workerd.mjs.map +0 -7
- package/dist/lib/neutral/KanbanProperties-W7MZNZVJ.mjs +0 -83
- package/dist/lib/neutral/KanbanProperties-W7MZNZVJ.mjs.map +0 -7
- package/dist/lib/neutral/capabilities/index.mjs +0 -17
- package/dist/lib/neutral/capabilities/index.mjs.map +0 -7
- package/dist/lib/neutral/chunk-7TVVF45P.mjs +0 -247
- package/dist/lib/neutral/chunk-7TVVF45P.mjs.map +0 -7
- package/dist/lib/neutral/chunk-7YLGUKDJ.mjs +0 -8
- package/dist/lib/neutral/chunk-7YLGUKDJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-CFPH5R6P.mjs +0 -26
- package/dist/lib/neutral/chunk-CFPH5R6P.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-V4AVOJVU.mjs.map +0 -7
- package/dist/lib/neutral/components/index.mjs +0 -245
- package/dist/lib/neutral/components/index.mjs.map +0 -7
- package/dist/lib/neutral/containers/index.mjs +0 -11
- package/dist/lib/neutral/containers/index.mjs.map +0 -7
- package/dist/lib/neutral/create-object-DIIFRS57.mjs +0 -45
- package/dist/lib/neutral/create-object-DIIFRS57.mjs.map +0 -7
- package/dist/lib/neutral/delete-card-TK2G6YHX.mjs +0 -24
- package/dist/lib/neutral/delete-card-TK2G6YHX.mjs.map +0 -7
- package/dist/lib/neutral/delete-card-field-LGWPBOUW.mjs +0 -39
- package/dist/lib/neutral/delete-card-field-LGWPBOUW.mjs.map +0 -7
- package/dist/lib/neutral/hooks/index.mjs +0 -431
- package/dist/lib/neutral/hooks/index.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -34
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/meta.mjs +0 -8
- package/dist/lib/neutral/meta.mjs.map +0 -7
- package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs +0 -13
- package/dist/lib/neutral/operation-handler-B7IW6MXU.mjs.map +0 -7
- package/dist/lib/neutral/operations/index.mjs +0 -8
- package/dist/lib/neutral/operations/index.mjs.map +0 -7
- package/dist/lib/neutral/plugin.mjs +0 -16
- package/dist/lib/neutral/plugin.mjs.map +0 -7
- package/dist/lib/neutral/react-surface-FQGXRR3X.mjs +0 -89
- package/dist/lib/neutral/react-surface-FQGXRR3X.mjs.map +0 -7
- package/dist/lib/neutral/restore-card-3O6PVN7J.mjs +0 -21
- package/dist/lib/neutral/restore-card-3O6PVN7J.mjs.map +0 -7
- package/dist/lib/neutral/restore-card-field-5G4DVYYY.mjs +0 -37
- package/dist/lib/neutral/restore-card-field-5G4DVYYY.mjs.map +0 -7
- package/dist/lib/neutral/skill-definition-YGTCKXGP.mjs +0 -15
- package/dist/lib/neutral/skill-definition-YGTCKXGP.mjs.map +0 -7
- package/dist/lib/neutral/skills/index.mjs +0 -8
- package/dist/lib/neutral/skills/index.mjs.map +0 -7
- package/dist/lib/neutral/testing/index.mjs +0 -64
- package/dist/lib/neutral/testing/index.mjs.map +0 -7
- package/dist/lib/neutral/translations.mjs +0 -44
- package/dist/lib/neutral/translations.mjs.map +0 -7
- package/dist/lib/neutral/types/index.mjs +0 -22
- package/dist/lib/neutral/types/index.mjs.map +0 -7
- package/dist/lib/neutral/undo-mappings-EZTHO4UL.mjs +0 -42
- package/dist/lib/neutral/undo-mappings-EZTHO4UL.mjs.map +0 -7
- package/src/capabilities/react-surface.tsx +0 -95
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Obj } from "@dxos/echo";
|
|
2
|
+
import { meta } from "#meta";
|
|
3
|
+
import { UNCATEGORIZED_ATTRIBUTES, UNCATEGORIZED_VALUE } from "#types";
|
|
4
|
+
import { createContext } from "@radix-ui/react-context";
|
|
5
|
+
import { RegistryContext } from "@effect-atom/atom-react";
|
|
6
|
+
import { forwardRef, useCallback, useContext, useMemo, useState } from "react";
|
|
7
|
+
import { Card, IconButton, composable, composableProps, useTranslation } from "@dxos/react-ui";
|
|
8
|
+
import { Board, Focus, Mosaic, useBoard } from "@dxos/react-ui-mosaic";
|
|
9
|
+
import { useKanbanBoardModel, useKanbanColumnEventHandler, useKanbanItemEventHandler } from "#hooks";
|
|
10
|
+
import { Surface } from "@dxos/app-framework/ui";
|
|
11
|
+
import { AppSurface, useCardPivot, useObjectMenuItems } from "@dxos/app-toolkit/ui";
|
|
12
|
+
import { Menu, createMenuAction } from "@dxos/react-ui-menu";
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
var [KanbanBoardContext, useKanbanBoard] = createContext("KanbanBoard.Context", {
|
|
15
|
+
kanbanId: "never",
|
|
16
|
+
projection: void 0,
|
|
17
|
+
columnFieldPath: void 0,
|
|
18
|
+
change: {
|
|
19
|
+
kanban: () => {},
|
|
20
|
+
setItemField: () => {}
|
|
21
|
+
},
|
|
22
|
+
pivotFieldId: void 0,
|
|
23
|
+
getPivotAttributes: (id) => id === UNCATEGORIZED_VALUE ? UNCATEGORIZED_ATTRIBUTES : {
|
|
24
|
+
title: id,
|
|
25
|
+
color: "neutral"
|
|
26
|
+
},
|
|
27
|
+
itemTile: (() => null)
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/components/KanbanBoard/KanbanCard.tsx
|
|
31
|
+
var KANBAN_CARD_TILE_NAME = "KanbanBoard.Card";
|
|
32
|
+
/**
|
|
33
|
+
* Mosaic Tile for Kanban card.
|
|
34
|
+
* Uses Surface for content; requires plugin manager context.
|
|
35
|
+
*/
|
|
36
|
+
var KanbanCard = forwardRef(({ data, location, debug, draggable }, forwardedRef) => {
|
|
37
|
+
const { t } = useTranslation(meta.profile.key);
|
|
38
|
+
const { model } = useBoard(KANBAN_CARD_TILE_NAME);
|
|
39
|
+
const { projection, columnFieldPath, onCardRemove } = useKanbanBoard(KANBAN_CARD_TILE_NAME);
|
|
40
|
+
const [dragHandle, setDragHandle] = useState(null);
|
|
41
|
+
const dragHandleRef = useCallback((el) => setDragHandle(el), []);
|
|
42
|
+
const [cardRef, pivotId] = useCardPivot();
|
|
43
|
+
const objectMenuItems = useObjectMenuItems(data, pivotId);
|
|
44
|
+
const menuItems = useMemo(() => [...objectMenuItems, ...onCardRemove ? [createMenuAction("remove", () => onCardRemove(data), {
|
|
45
|
+
label: t("remove-card.label"),
|
|
46
|
+
icon: "ph--trash--regular"
|
|
47
|
+
})] : []], [
|
|
48
|
+
objectMenuItems,
|
|
49
|
+
onCardRemove,
|
|
50
|
+
data,
|
|
51
|
+
t
|
|
52
|
+
]);
|
|
53
|
+
return /* @__PURE__ */ jsx(Menu.Root, { children: /* @__PURE__ */ jsx(Mosaic.Tile, {
|
|
54
|
+
asChild: true,
|
|
55
|
+
id: model.getItemId(data),
|
|
56
|
+
data,
|
|
57
|
+
location,
|
|
58
|
+
debug,
|
|
59
|
+
draggable,
|
|
60
|
+
dragHandle,
|
|
61
|
+
children: /* @__PURE__ */ jsx(Focus.Item, {
|
|
62
|
+
asChild: true,
|
|
63
|
+
children: /* @__PURE__ */ jsxs(Card.Root, {
|
|
64
|
+
ref: forwardedRef,
|
|
65
|
+
"data-testid": "board-item",
|
|
66
|
+
children: [/* @__PURE__ */ jsxs(Card.Header, {
|
|
67
|
+
ref: cardRef,
|
|
68
|
+
children: [
|
|
69
|
+
/* @__PURE__ */ jsx(Card.DragHandle, {
|
|
70
|
+
ref: dragHandleRef,
|
|
71
|
+
testId: "mosaicBoard.cardDragHandle"
|
|
72
|
+
}),
|
|
73
|
+
/* @__PURE__ */ jsx(Card.Title, {
|
|
74
|
+
"data-testid": "mosaicBoard.cardTitle",
|
|
75
|
+
children: Obj.getLabel(data)
|
|
76
|
+
}),
|
|
77
|
+
/* @__PURE__ */ jsxs(Card.Block, {
|
|
78
|
+
end: true,
|
|
79
|
+
children: [/* @__PURE__ */ jsx(Menu.Trigger, {
|
|
80
|
+
asChild: true,
|
|
81
|
+
disabled: !menuItems?.length,
|
|
82
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
83
|
+
iconOnly: true,
|
|
84
|
+
variant: "ghost",
|
|
85
|
+
icon: "ph--dots-three-vertical--regular",
|
|
86
|
+
label: t("action-menu.label")
|
|
87
|
+
})
|
|
88
|
+
}), /* @__PURE__ */ jsx(Menu.Content, { items: menuItems })]
|
|
89
|
+
})
|
|
90
|
+
]
|
|
91
|
+
}), /* @__PURE__ */ jsx(Card.Body, { children: projection && /* @__PURE__ */ jsx(Surface.Surface, {
|
|
92
|
+
type: AppSurface.CardContent,
|
|
93
|
+
limit: 1,
|
|
94
|
+
data: {
|
|
95
|
+
subject: data,
|
|
96
|
+
projection,
|
|
97
|
+
ignorePaths: columnFieldPath ? [columnFieldPath] : void 0
|
|
98
|
+
}
|
|
99
|
+
}) })]
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
}) });
|
|
103
|
+
});
|
|
104
|
+
KanbanCard.displayName = KANBAN_CARD_TILE_NAME;
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/components/KanbanBoard/KanbanColumn.tsx
|
|
107
|
+
var KANBAN_COLUMN_NAME = "KanbanBoard.Column";
|
|
108
|
+
/**
|
|
109
|
+
* Mosaic Tile for Kanban column.
|
|
110
|
+
*/
|
|
111
|
+
var KanbanColumn = forwardRef(({ data: column, location, debug, draggable }, forwardedRef) => {
|
|
112
|
+
const { model } = useBoard(KANBAN_COLUMN_NAME);
|
|
113
|
+
const { columnFieldPath, change, onCardAdd, getPivotAttributes, itemTile } = useKanbanBoard(KANBAN_COLUMN_NAME);
|
|
114
|
+
const { title } = getPivotAttributes(column.columnValue);
|
|
115
|
+
const uncategorized = column.columnValue === UNCATEGORIZED_VALUE;
|
|
116
|
+
const [dragHandle, setDragHandle] = useState(null);
|
|
117
|
+
const eventHandler = useKanbanItemEventHandler({
|
|
118
|
+
column,
|
|
119
|
+
columnFieldPath,
|
|
120
|
+
model,
|
|
121
|
+
change
|
|
122
|
+
});
|
|
123
|
+
return /* @__PURE__ */ jsxs(Board.Column.Root, {
|
|
124
|
+
data: column,
|
|
125
|
+
location,
|
|
126
|
+
classNames: "grid grid-rows-[var(--dx-rail-action)_1fr_var(--dx-rail-action)]",
|
|
127
|
+
debug,
|
|
128
|
+
draggable,
|
|
129
|
+
dragHandle,
|
|
130
|
+
ref: forwardedRef,
|
|
131
|
+
children: [
|
|
132
|
+
uncategorized ? /* @__PURE__ */ jsx("div", {
|
|
133
|
+
className: "border-b border-separator p-2",
|
|
134
|
+
"data-testid": "board-column-header",
|
|
135
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
136
|
+
className: "font-medium",
|
|
137
|
+
children: title
|
|
138
|
+
})
|
|
139
|
+
}) : /* @__PURE__ */ jsx(Board.Column.Header, {
|
|
140
|
+
label: title,
|
|
141
|
+
dragHandleRef: setDragHandle
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ jsx(Board.Column.Body, {
|
|
144
|
+
data: column,
|
|
145
|
+
eventHandler,
|
|
146
|
+
Tile: itemTile
|
|
147
|
+
}),
|
|
148
|
+
/* @__PURE__ */ jsx(Board.Column.Footer, { onAdd: onCardAdd ? () => onCardAdd(column.columnValue === UNCATEGORIZED_VALUE ? void 0 : column.columnValue) : void 0 })
|
|
149
|
+
]
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
KanbanColumn.displayName = KANBAN_COLUMN_NAME;
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/components/KanbanBoard/KanbanBoard.tsx
|
|
155
|
+
var KANBAN_BOARD_ROOT = "KanbanBoard.Root";
|
|
156
|
+
var KanbanBoardRoot = ({ children, kanban, projection, items, change, itemTile = KanbanCard, onCardAdd, onCardRemove }) => {
|
|
157
|
+
const registry = useContext(RegistryContext);
|
|
158
|
+
const { t } = useTranslation(meta.profile.key);
|
|
159
|
+
const model = useKanbanBoardModel(kanban, projection, items, registry);
|
|
160
|
+
const columns = model?.getColumns?.() ?? [];
|
|
161
|
+
const pivotFieldId = (kanban?.spec.kind === "view" ? kanban.spec.view.target : void 0)?.projection?.pivotFieldId;
|
|
162
|
+
const columnFieldPath = useMemo(() => {
|
|
163
|
+
if (kanban?.spec.kind === "items") return kanban.spec.pivotField;
|
|
164
|
+
if (pivotFieldId === void 0 || !projection) return;
|
|
165
|
+
return projection.tryGetFieldProjection(pivotFieldId)?.props.property;
|
|
166
|
+
}, [
|
|
167
|
+
kanban?.spec,
|
|
168
|
+
projection,
|
|
169
|
+
pivotFieldId
|
|
170
|
+
]);
|
|
171
|
+
const getPivotAttributes = useCallback((columnValue) => {
|
|
172
|
+
if (columnValue === UNCATEGORIZED_VALUE) return UNCATEGORIZED_ATTRIBUTES;
|
|
173
|
+
return (projection?.tryGetFieldProjection(pivotFieldId ?? "")?.props.options ?? []).find((option) => option.id === columnValue) ?? {
|
|
174
|
+
title: columnValue,
|
|
175
|
+
color: "neutral"
|
|
176
|
+
};
|
|
177
|
+
}, [projection, pivotFieldId]);
|
|
178
|
+
if (columns.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
179
|
+
className: "flex flex-1 items-center justify-center p-8 text-center text-description",
|
|
180
|
+
children: t("select-pivot.placeholder")
|
|
181
|
+
});
|
|
182
|
+
return /* @__PURE__ */ jsx(KanbanBoardContext, {
|
|
183
|
+
kanbanId: Obj.getURI(kanban),
|
|
184
|
+
projection,
|
|
185
|
+
columnFieldPath,
|
|
186
|
+
pivotFieldId,
|
|
187
|
+
getPivotAttributes,
|
|
188
|
+
itemTile,
|
|
189
|
+
change,
|
|
190
|
+
onCardAdd,
|
|
191
|
+
onCardRemove,
|
|
192
|
+
children: /* @__PURE__ */ jsx(Board.Root, {
|
|
193
|
+
model,
|
|
194
|
+
children
|
|
195
|
+
})
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
KanbanBoardRoot.displayName = KANBAN_BOARD_ROOT;
|
|
199
|
+
var KANBAN_BOARD_CONTENT = "KanbanBoard.Content";
|
|
200
|
+
var KanbanBoardContent = composable((props, forwardedRef) => {
|
|
201
|
+
const { model } = useBoard(KANBAN_BOARD_CONTENT);
|
|
202
|
+
const { kanbanId, projection, pivotFieldId, change } = useKanbanBoard(KANBAN_BOARD_CONTENT);
|
|
203
|
+
const columnEventHandler = useKanbanColumnEventHandler({
|
|
204
|
+
id: `${kanbanId}-columns`,
|
|
205
|
+
model,
|
|
206
|
+
projection: projection ?? void 0,
|
|
207
|
+
pivotFieldId: pivotFieldId ?? void 0,
|
|
208
|
+
change
|
|
209
|
+
});
|
|
210
|
+
return /* @__PURE__ */ jsx(Board.Content, {
|
|
211
|
+
...composableProps(props),
|
|
212
|
+
ref: forwardedRef,
|
|
213
|
+
eventHandler: columnEventHandler,
|
|
214
|
+
Tile: KanbanColumn
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
KanbanBoardContent.displayName = KANBAN_BOARD_CONTENT;
|
|
218
|
+
var KanbanBoard = {
|
|
219
|
+
Root: KanbanBoardRoot,
|
|
220
|
+
Content: KanbanBoardContent,
|
|
221
|
+
Column: KanbanColumn,
|
|
222
|
+
Card: KanbanCard
|
|
223
|
+
};
|
|
224
|
+
//#endregion
|
|
225
|
+
export { KanbanBoard, KanbanBoardContent, KanbanBoardRoot, useKanbanBoard };
|
|
226
|
+
|
|
227
|
+
//# sourceMappingURL=components.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.mjs","names":[],"sources":["../../src/components/KanbanBoard/context.ts","../../src/components/KanbanBoard/KanbanCard.tsx","../../src/components/KanbanBoard/KanbanColumn.tsx","../../src/components/KanbanBoard/KanbanBoard.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { createContext } from '@radix-ui/react-context';\nimport { type ComponentType } from 'react';\n\nimport { type Obj } from '@dxos/echo';\nimport { type MosaicTileProps } from '@dxos/react-ui-mosaic';\nimport { type ProjectionModel } from '@dxos/schema';\n\nimport { type ColumnStructure, type KanbanChangeCallback, UNCATEGORIZED_ATTRIBUTES, UNCATEGORIZED_VALUE } from '#types';\n\nconst KANBAN_BOARD_NAME = 'KanbanBoard.Context';\n\n/**\n * Card tile props. Defined here (rather than in `KanbanCard.tsx`) so the\n * context type can reference it without forming a cycle through that\n * module's runtime exports — webkit treats the resulting TDZ as a hard\n * error while other engines are lenient.\n */\nexport type KanbanCardProps = Pick<MosaicTileProps<Obj.Unknown>, 'location' | 'data' | 'debug' | 'draggable'>;\n\n/**\n * Column tile props. See note on {@link KanbanCardProps}.\n */\nexport type KanbanColumnProps = Pick<MosaicTileProps<ColumnStructure>, 'location' | 'data' | 'debug' | 'draggable'>;\n\n/**\n * Context value for the Kanban board.\n * Items are Echo objects (Obj.Unknown).\n */\nexport type KanbanBoardContextValue = {\n kanbanId: string;\n projection: ProjectionModel | undefined;\n columnFieldPath: string | undefined;\n change: KanbanChangeCallback<Obj.Unknown>;\n pivotFieldId: string | undefined;\n getPivotAttributes: (columnValue: string) => { title: string; color: string };\n itemTile?: ComponentType<KanbanCardProps>; // TODO(burdon): Prop.\n onCardAdd?: (columnValue: string | undefined) => string | undefined;\n onCardRemove?: (card: Obj.Unknown) => void;\n};\n\nexport const [KanbanBoardContext, useKanbanBoard] = createContext<KanbanBoardContextValue>(KANBAN_BOARD_NAME, {\n kanbanId: 'never',\n projection: undefined,\n columnFieldPath: undefined,\n change: { kanban: () => {}, setItemField: () => {} },\n pivotFieldId: undefined,\n getPivotAttributes: (id: string) =>\n id === UNCATEGORIZED_VALUE ? UNCATEGORIZED_ATTRIBUTES : { title: id, color: 'neutral' },\n itemTile: (() => null) as ComponentType<KanbanCardProps>,\n});\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { forwardRef, useCallback, useMemo, useState } from 'react';\n\nimport { Surface } from '@dxos/app-framework/ui';\nimport { AppSurface, useCardPivot, useObjectMenuItems } from '@dxos/app-toolkit/ui';\nimport { Obj } from '@dxos/echo';\nimport { Card, IconButton, useTranslation } from '@dxos/react-ui';\nimport { Menu, createMenuAction } from '@dxos/react-ui-menu';\nimport { Focus, Mosaic, useBoard } from '@dxos/react-ui-mosaic';\n\nimport { meta } from '#meta';\n\nimport { type KanbanCardProps, useKanbanBoard } from './context';\n\nexport { type KanbanCardProps };\n\nconst KANBAN_CARD_TILE_NAME = 'KanbanBoard.Card';\n\n/**\n * Mosaic Tile for Kanban card.\n * Uses Surface for content; requires plugin manager context.\n */\nexport const KanbanCard = forwardRef<HTMLDivElement, KanbanCardProps>(\n ({ data, location, debug, draggable }, forwardedRef) => {\n const { t } = useTranslation(meta.profile.key);\n const { model } = useBoard(KANBAN_CARD_TILE_NAME);\n const { projection, columnFieldPath, onCardRemove } = useKanbanBoard(KANBAN_CARD_TILE_NAME);\n const [dragHandle, setDragHandle] = useState<HTMLButtonElement | null>(null);\n const dragHandleRef = useCallback((el: HTMLButtonElement | null) => setDragHandle(el), []);\n\n // Card.Root already takes the forwarded ref; walk from the header to resolve the origin plank.\n const [cardRef, pivotId] = useCardPivot();\n const objectMenuItems = useObjectMenuItems(data, pivotId);\n\n const menuItems = useMemo(\n () => [\n ...objectMenuItems,\n ...(onCardRemove\n ? [\n createMenuAction('remove', () => onCardRemove(data), {\n label: t('remove-card.label'),\n icon: 'ph--trash--regular',\n }),\n ]\n : []),\n ],\n [objectMenuItems, onCardRemove, data, t],\n );\n\n return (\n <Menu.Root>\n <Mosaic.Tile\n asChild\n id={model.getItemId(data)}\n data={data}\n location={location}\n debug={debug}\n draggable={draggable}\n dragHandle={dragHandle}\n >\n <Focus.Item asChild>\n <Card.Root ref={forwardedRef} data-testid='board-item'>\n <Card.Header ref={cardRef}>\n <Card.DragHandle ref={dragHandleRef} testId='mosaicBoard.cardDragHandle' />\n <Card.Title data-testid='mosaicBoard.cardTitle'>{Obj.getLabel(data)}</Card.Title>\n {/* TODO(wittjosiah): Reconcile with Card.Menu. */}\n <Card.Block end>\n <Menu.Trigger asChild disabled={!menuItems?.length}>\n <IconButton\n iconOnly\n variant='ghost'\n icon='ph--dots-three-vertical--regular'\n label={t('action-menu.label')}\n />\n </Menu.Trigger>\n <Menu.Content items={menuItems} />\n </Card.Block>\n </Card.Header>\n <Card.Body>\n {projection && (\n <Surface.Surface\n type={AppSurface.CardContent}\n limit={1}\n data={{\n subject: data,\n projection,\n // Hide the pivot field: its value is already conveyed by\n // which column the card sits in.\n ignorePaths: columnFieldPath ? [columnFieldPath] : undefined,\n }}\n />\n )}\n </Card.Body>\n </Card.Root>\n </Focus.Item>\n </Mosaic.Tile>\n </Menu.Root>\n );\n },\n);\n\nKanbanCard.displayName = KANBAN_CARD_TILE_NAME;\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { FC, forwardRef, useState } from 'react';\n\nimport type { Obj } from '@dxos/echo';\nimport { Board, type MosaicTileProps, useBoard } from '@dxos/react-ui-mosaic';\n\nimport { useKanbanItemEventHandler } from '#hooks';\nimport { type ColumnStructure, UNCATEGORIZED_VALUE } from '#types';\n\nimport { type KanbanColumnProps, useKanbanBoard } from './context';\n\nexport { type KanbanColumnProps };\n\nconst KANBAN_COLUMN_NAME = 'KanbanBoard.Column';\n\n/**\n * Mosaic Tile for Kanban column.\n */\nexport const KanbanColumn = forwardRef<HTMLDivElement, KanbanColumnProps>(\n ({ data: column, location, debug, draggable }, forwardedRef) => {\n const { model } = useBoard<ColumnStructure, Obj.Unknown>(KANBAN_COLUMN_NAME);\n const { columnFieldPath, change, onCardAdd, getPivotAttributes, itemTile } = useKanbanBoard(KANBAN_COLUMN_NAME);\n\n const { title } = getPivotAttributes(column.columnValue);\n const uncategorized = column.columnValue === UNCATEGORIZED_VALUE;\n const [dragHandle, setDragHandle] = useState<HTMLButtonElement | null>(null);\n\n const eventHandler = useKanbanItemEventHandler({\n column,\n columnFieldPath,\n model,\n change,\n });\n\n return (\n <Board.Column.Root\n data={column}\n location={location}\n classNames='grid grid-rows-[var(--dx-rail-action)_1fr_var(--dx-rail-action)]'\n debug={debug}\n draggable={draggable}\n dragHandle={dragHandle}\n ref={forwardedRef}\n >\n {uncategorized ? (\n <div className='border-b border-separator p-2' data-testid='board-column-header'>\n <span className='font-medium'>{title}</span>\n </div>\n ) : (\n <Board.Column.Header label={title} dragHandleRef={setDragHandle} />\n )}\n <Board.Column.Body\n data={column}\n eventHandler={eventHandler}\n Tile={itemTile as FC<MosaicTileProps<Obj.Unknown>>}\n />\n <Board.Column.Footer\n onAdd={\n onCardAdd\n ? () => onCardAdd(column.columnValue === UNCATEGORIZED_VALUE ? undefined : column.columnValue)\n : undefined\n }\n />\n </Board.Column.Root>\n );\n },\n);\n\nKanbanColumn.displayName = KANBAN_COLUMN_NAME;\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Atom, RegistryContext } from '@effect-atom/atom-react';\nimport React, { type ComponentPropsWithoutRef, type PropsWithChildren, useCallback, useContext, useMemo } from 'react';\n\nimport { Obj } from '@dxos/echo';\nimport { useTranslation } from '@dxos/react-ui';\nimport { composable, composableProps } from '@dxos/react-ui';\nimport { Board, useBoard } from '@dxos/react-ui-mosaic';\nimport type { ProjectionModel } from '@dxos/schema';\n\nimport { useKanbanBoardModel, useKanbanColumnEventHandler } from '#hooks';\nimport { meta } from '#meta';\nimport { type Kanban, UNCATEGORIZED_ATTRIBUTES, UNCATEGORIZED_VALUE } from '#types';\n\nimport {\n KanbanBoardContext,\n type KanbanBoardContextValue,\n type KanbanCardProps,\n type KanbanColumnProps,\n useKanbanBoard,\n} from './context';\nimport { KanbanCard } from './KanbanCard';\nimport { KanbanColumn } from './KanbanColumn';\n\n// TODO(burdon): Rename Kanban.\n\n//\n// Root\n//\n\nconst KANBAN_BOARD_ROOT = 'KanbanBoard.Root';\n\ntype KanbanBoardRootProps = PropsWithChildren<\n {\n kanban: Kanban.Kanban;\n /** Required when providing context; Root derives columnFieldPath, pivotFieldId, getPivotAttributes from kanban + projection. */\n projection: ProjectionModel;\n /** Atom of items (e.g. from AtomQuery for DB, or Atom.make([]) for in-memory). */\n items: Atom.Atom<Obj.Unknown[]>;\n onCardAdd?: (columnValue: string | undefined) => string | undefined;\n onCardRemove?: (card: Obj.Unknown) => void;\n } & Pick<KanbanBoardContextValue, 'change' | 'itemTile'> &\n ComponentPropsWithoutRef<'div'>\n>;\n\nexport const KanbanBoardRoot = ({\n children,\n kanban,\n projection,\n items,\n change,\n itemTile = KanbanCard,\n onCardAdd,\n onCardRemove,\n}: KanbanBoardRootProps) => {\n const registry = useContext(RegistryContext);\n const { t } = useTranslation(meta.profile.key);\n const model = useKanbanBoardModel(kanban, projection, items, registry);\n const columns = model?.getColumns?.() ?? [];\n const view = kanban?.spec.kind === 'view' ? kanban.spec.view.target : undefined;\n const pivotFieldId = view?.projection?.pivotFieldId;\n const columnFieldPath = useMemo(() => {\n // Items-variant kanbans use the property name itself as the pivot field\n // (no view/projection translation layer).\n if (kanban?.spec.kind === 'items') {\n return kanban.spec.pivotField;\n }\n if (pivotFieldId === undefined || !projection) {\n return undefined;\n }\n\n return projection.tryGetFieldProjection(pivotFieldId)?.props.property;\n }, [kanban?.spec, projection, pivotFieldId]);\n\n const getPivotAttributes = useCallback<KanbanBoardContextValue['getPivotAttributes']>(\n (columnValue) => {\n if (columnValue === UNCATEGORIZED_VALUE) {\n return UNCATEGORIZED_ATTRIBUTES;\n }\n\n const options = projection?.tryGetFieldProjection(pivotFieldId ?? '')?.props.options ?? [];\n const option = options.find((option) => option.id === columnValue);\n return option ?? ({ title: columnValue, color: 'neutral' } as const);\n },\n [projection, pivotFieldId],\n );\n\n if (columns.length === 0) {\n return (\n <div className='flex flex-1 items-center justify-center p-8 text-center text-description'>\n {t('select-pivot.placeholder')}\n </div>\n );\n }\n\n return (\n <KanbanBoardContext\n kanbanId={Obj.getURI(kanban)}\n projection={projection}\n columnFieldPath={columnFieldPath}\n pivotFieldId={pivotFieldId}\n getPivotAttributes={getPivotAttributes}\n itemTile={itemTile}\n change={change}\n onCardAdd={onCardAdd}\n onCardRemove={onCardRemove}\n >\n <Board.Root model={model}>{children}</Board.Root>\n </KanbanBoardContext>\n );\n};\n\nKanbanBoardRoot.displayName = KANBAN_BOARD_ROOT;\n\n//\n// KanbanBoardContent\n//\n\nconst KANBAN_BOARD_CONTENT = 'KanbanBoard.Content';\n\ntype KanbanBoardContentProps = {};\n\nexport const KanbanBoardContent = composable<HTMLDivElement, KanbanBoardContentProps>((props, forwardedRef) => {\n const { model } = useBoard(KANBAN_BOARD_CONTENT);\n const { kanbanId, projection, pivotFieldId, change } = useKanbanBoard(KANBAN_BOARD_CONTENT);\n\n const columnEventHandler = useKanbanColumnEventHandler({\n id: `${kanbanId}-columns`,\n model,\n projection: projection ?? undefined,\n pivotFieldId: pivotFieldId ?? undefined,\n change,\n });\n\n return (\n <Board.Content\n {...composableProps(props)}\n ref={forwardedRef}\n eventHandler={columnEventHandler}\n Tile={KanbanColumn}\n />\n );\n});\n\nKanbanBoardContent.displayName = KANBAN_BOARD_CONTENT;\n\n//\n// KanbanBoard\n//\n\nexport const KanbanBoard = {\n Root: KanbanBoardRoot,\n Content: KanbanBoardContent,\n Column: KanbanColumn,\n Card: KanbanCard,\n};\n\nexport type { KanbanBoardRootProps as KanbanBoardProps, KanbanCardProps, KanbanColumnProps };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { lazy } from "react";
|
|
2
|
+
//#region src/containers/index.ts
|
|
3
|
+
var KanbanArticle = lazy(() => import("./chunk-KanbanArticle.mjs"));
|
|
4
|
+
var KanbanProperties = lazy(() => import("./chunk-KanbanProperties.mjs"));
|
|
5
|
+
//#endregion
|
|
6
|
+
export { KanbanArticle, KanbanProperties };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=containers.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"containers.mjs","names":[],"sources":["../../src/containers/index.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { type ComponentType, lazy } from 'react';\n\nexport const KanbanArticle: ComponentType<any> = lazy(() => import('./KanbanArticle'));\nexport const KanbanProperties: ComponentType<any> = lazy(() => import('./KanbanProperties'));\n"],"mappings":";;AAMA,IAAa,gBAAoC,WAAW,OAAO,4BAAkB;AACrF,IAAa,mBAAuC,WAAW,OAAO,+BAAqB"}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { arrayMove } from "@dxos/util";
|
|
2
|
+
import { JsonSchema, Obj, Type } from "@dxos/echo";
|
|
3
|
+
import { ProjectionModel, createEchoChangeCallback as createEchoChangeCallback$1 } from "@dxos/schema";
|
|
4
|
+
import { UNCATEGORIZED_VALUE } from "#types";
|
|
5
|
+
import { useMemo, useState } from "react";
|
|
6
|
+
import { useAsyncEffect } from "@dxos/react-ui";
|
|
7
|
+
import { Atom } from "@effect-atom/atom";
|
|
8
|
+
import { log } from "@dxos/log";
|
|
9
|
+
//#region src/hooks/useEchoChangeCallback.ts
|
|
10
|
+
/**
|
|
11
|
+
* Creates a change callback for ECHO-backed kanban and items (plain function, no hooks).
|
|
12
|
+
* Use this when the kanban and items are stored in the ECHO database.
|
|
13
|
+
*/
|
|
14
|
+
var createEchoChangeCallback = (kanban) => ({
|
|
15
|
+
kanban: (mutate) => Obj.update(kanban, (kanban) => mutate(kanban)),
|
|
16
|
+
setItemField: (item, field, value) => {
|
|
17
|
+
Obj.update(item, (item) => {
|
|
18
|
+
item[field] = value;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Returns a memoized ECHO change callback for the given kanban.
|
|
24
|
+
* Returns null when kanban is undefined.
|
|
25
|
+
*/
|
|
26
|
+
var useEchoChangeCallback = (kanban) => useMemo(() => kanban ? createEchoChangeCallback(kanban) : null, [kanban]);
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/hooks/useItemsProjection.ts
|
|
29
|
+
/**
|
|
30
|
+
* Minimal `ProjectionModel` for `spec.kind === 'items'` (no View). Supplies `pivotField`
|
|
31
|
+
* and column options from `arrangement.columns` keys—written by sync so columns exist
|
|
32
|
+
* before refs hydrate. Stubs `getFieldProjections` / `getHiddenProperties` for shared
|
|
33
|
+
* board/card UI; hides the pivot on the card body (column shows it); Expando cards render title only.
|
|
34
|
+
*/
|
|
35
|
+
var useItemsProjection = (kanban) => {
|
|
36
|
+
return useMemo(() => {
|
|
37
|
+
const pivotField = kanban.spec.pivotField;
|
|
38
|
+
const options = Object.keys(kanban.arrangement?.columns ?? {}).map((id) => ({
|
|
39
|
+
id,
|
|
40
|
+
title: id,
|
|
41
|
+
color: "neutral"
|
|
42
|
+
}));
|
|
43
|
+
const fieldProjection = {
|
|
44
|
+
field: {
|
|
45
|
+
id: pivotField,
|
|
46
|
+
path: pivotField
|
|
47
|
+
},
|
|
48
|
+
props: {
|
|
49
|
+
property: pivotField,
|
|
50
|
+
options
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
fields: Atom.make(() => [fieldProjection.field]),
|
|
55
|
+
tryGetFieldProjection: (id) => id === pivotField ? fieldProjection : void 0,
|
|
56
|
+
getFieldProjections: () => [],
|
|
57
|
+
getHiddenProperties: () => [pivotField]
|
|
58
|
+
};
|
|
59
|
+
}, [kanban.arrangement?.columns, kanban.spec.pivotField]);
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/util/arrangement.ts
|
|
63
|
+
/**
|
|
64
|
+
* Column order from a raw arrangement object (e.g. when working with arrangement only).
|
|
65
|
+
*
|
|
66
|
+
* @param arrangement - Optional arrangement; reads order.
|
|
67
|
+
* @returns Mutable copy of order array, or empty if arrangement/order missing or empty.
|
|
68
|
+
*/
|
|
69
|
+
var getOrderFromArrangement = (arrangement) => {
|
|
70
|
+
const order = arrangement?.order;
|
|
71
|
+
if (order != null && Array.isArray(order) && order.length > 0) return [...order];
|
|
72
|
+
return [];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Per-column card id order and optional hidden flag from a raw arrangement object.
|
|
76
|
+
*
|
|
77
|
+
* Returns mutable copies so schema readonly does not leak to callers.
|
|
78
|
+
*
|
|
79
|
+
* @param arrangement - Optional arrangement; reads columns.
|
|
80
|
+
* @returns Map of column value → { ids, hidden? }; empty object if not set.
|
|
81
|
+
*/
|
|
82
|
+
var getOrderByColumnFromArrangement = (arrangement) => {
|
|
83
|
+
const columns = arrangement?.columns;
|
|
84
|
+
if (columns != null && Object.keys(columns).length > 0) return Object.fromEntries(Object.entries(columns).map(([key, entry]) => {
|
|
85
|
+
const rawIds = entry.ids;
|
|
86
|
+
return [key, {
|
|
87
|
+
ids: Array.isArray(rawIds) ? [...rawIds] : [],
|
|
88
|
+
...entry.hidden !== void 0 && { hidden: entry.hidden }
|
|
89
|
+
}];
|
|
90
|
+
}));
|
|
91
|
+
return {};
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Builds the ordered list of columns (value + ids) for the board model.
|
|
95
|
+
*
|
|
96
|
+
* Column order: uncategorized first, then effectiveOrder, then any
|
|
97
|
+
* selectOption ids not yet in order. Each entry gets the ids from
|
|
98
|
+
* effectiveByColumn for that column (or empty). Columns whose entry has
|
|
99
|
+
* `hidden: true` are dropped from the result — currently only the
|
|
100
|
+
* "Uncategorized" column is toggleable from settings, but the filter is
|
|
101
|
+
* generic so any column can be hidden via the same flag.
|
|
102
|
+
*
|
|
103
|
+
* @param effectiveOrder - Column order from arrangement (or previous merge).
|
|
104
|
+
* @param effectiveByColumn - Per-column card id order + hidden flag from arrangement.
|
|
105
|
+
* @param selectOptions - Defines valid column ids; any missing from order are appended.
|
|
106
|
+
* @returns ColumnStructure array in display order.
|
|
107
|
+
*/
|
|
108
|
+
var computeColumnStructure = (effectiveOrder, effectiveByColumn, selectOptions) => {
|
|
109
|
+
const order = [...effectiveOrder];
|
|
110
|
+
if (!order.includes(UNCATEGORIZED_VALUE)) order.unshift(UNCATEGORIZED_VALUE);
|
|
111
|
+
for (const opt of selectOptions) if (opt.id !== UNCATEGORIZED_VALUE && !order.includes(opt.id)) order.push(opt.id);
|
|
112
|
+
return order.filter((columnValue) => effectiveByColumn[columnValue]?.hidden !== true).map((columnValue) => ({
|
|
113
|
+
columnValue,
|
|
114
|
+
ids: effectiveByColumn[columnValue]?.ids ?? []
|
|
115
|
+
}));
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Orders items for a single column: partition by saved order vs new, sort by orderMap, concatenate.
|
|
119
|
+
* Uses explicit null/undefined check for itemColumn so empty string is handled consistently.
|
|
120
|
+
*
|
|
121
|
+
* @param items - All items to filter.
|
|
122
|
+
* @param ids - Saved id order for this column.
|
|
123
|
+
* @param columnValue - Column id (e.g. UNCATEGORIZED_VALUE or option id).
|
|
124
|
+
* @param pivotPath - Item property key for column value (undefined = no pivot).
|
|
125
|
+
* @param validColumnValues - Set of valid option ids.
|
|
126
|
+
* @returns Items belonging to this column in display order.
|
|
127
|
+
*/
|
|
128
|
+
var orderItemsInColumn = (items, ids, columnValue, pivotPath, validColumnValues) => {
|
|
129
|
+
const orderMap = new Map(ids.map((id, index) => [id, index]));
|
|
130
|
+
const cardsWithExistingOrder = [];
|
|
131
|
+
const newCards = [];
|
|
132
|
+
for (const item of items) {
|
|
133
|
+
const itemColumn = pivotPath !== void 0 ? item[pivotPath] : void 0;
|
|
134
|
+
const isValidColumn = itemColumn != null && validColumnValues.has(itemColumn);
|
|
135
|
+
if (columnValue === UNCATEGORIZED_VALUE ? !isValidColumn : itemColumn === columnValue) (orderMap.has(item.id) ? cardsWithExistingOrder : newCards).push(item);
|
|
136
|
+
}
|
|
137
|
+
cardsWithExistingOrder.sort((a, b) => {
|
|
138
|
+
return (orderMap.get(a.id) ?? Infinity) - (orderMap.get(b.id) ?? Infinity);
|
|
139
|
+
});
|
|
140
|
+
return [...cardsWithExistingOrder, ...newCards];
|
|
141
|
+
};
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/hooks/useKanbanBoardModel.ts
|
|
144
|
+
/**
|
|
145
|
+
* Builds a board model that maps kanban arrangement and projection onto columns and per-column items.
|
|
146
|
+
*
|
|
147
|
+
* @template T - Item type (must have id; defaults to BaseKanbanItem).
|
|
148
|
+
* @param kanban - Kanban object (arrangement, view).
|
|
149
|
+
* @param projection - ProjectionModel for pivot field and options.
|
|
150
|
+
* @param itemsAtom - Atom holding the full item list.
|
|
151
|
+
* @param registry - Registry for reading atom values.
|
|
152
|
+
* @returns BoardModel with columns atom, items family, and getColumns/getItems.
|
|
153
|
+
*/
|
|
154
|
+
function useKanbanBoardModel(kanban, projection, itemsAtom, registry) {
|
|
155
|
+
const arrangementAtom = useMemo(() => Obj.atomProperty(kanban, "arrangement"), [kanban]);
|
|
156
|
+
const viewSnapshotAtom = useMemo(() => kanban?.spec?.kind === "view" && kanban.spec.view ? Obj.atom(kanban.spec.view) : Atom.make(() => void 0), [kanban?.spec]);
|
|
157
|
+
/**
|
|
158
|
+
* Only changes when the discriminator-relevant pivot input changes.
|
|
159
|
+
* View-variant: derived from `view.projection.pivotFieldId`.
|
|
160
|
+
* Items-variant: the kanban's `spec.pivotField` (the property name itself acts as the field id).
|
|
161
|
+
*/
|
|
162
|
+
const pivotFieldIdAtom = useMemo(() => Atom.make((get) => {
|
|
163
|
+
if (kanban?.spec.kind === "items") return kanban.spec.pivotField;
|
|
164
|
+
return get(viewSnapshotAtom)?.projection?.pivotFieldId;
|
|
165
|
+
}), [kanban?.spec, viewSnapshotAtom]);
|
|
166
|
+
const effectiveByColumnAtom = useMemo(() => Atom.make((get) => getOrderByColumnFromArrangement(get(arrangementAtom))), [arrangementAtom]);
|
|
167
|
+
const columnsAtom = useMemo(() => Atom.make((get) => {
|
|
168
|
+
const pivotFieldId = get(pivotFieldIdAtom);
|
|
169
|
+
if (pivotFieldId === void 0) return [];
|
|
170
|
+
get(projection.fields);
|
|
171
|
+
const fieldProj = projection.tryGetFieldProjection(pivotFieldId);
|
|
172
|
+
if (!fieldProj) return [];
|
|
173
|
+
const selectOptions = fieldProj.props.options ?? [];
|
|
174
|
+
if (selectOptions.length === 0) return [];
|
|
175
|
+
const arrangement = get(arrangementAtom);
|
|
176
|
+
return computeColumnStructure(getOrderFromArrangement(arrangement), getOrderByColumnFromArrangement(arrangement), selectOptions);
|
|
177
|
+
}), [
|
|
178
|
+
pivotFieldIdAtom,
|
|
179
|
+
arrangementAtom,
|
|
180
|
+
projection
|
|
181
|
+
]);
|
|
182
|
+
const columnArrangementAtomFamily = useMemo(() => Atom.family((columnValue) => Atom.make((get) => {
|
|
183
|
+
return {
|
|
184
|
+
columnValue,
|
|
185
|
+
ids: [...get(effectiveByColumnAtom)[columnValue]?.ids ?? []]
|
|
186
|
+
};
|
|
187
|
+
})), [effectiveByColumnAtom]);
|
|
188
|
+
const itemsAtomFamily = useMemo(() => Atom.family((columnValue) => Atom.make((get) => {
|
|
189
|
+
const columnArr = get(columnArrangementAtomFamily(columnValue));
|
|
190
|
+
const allItems = get(itemsAtom);
|
|
191
|
+
const pivotFieldId = get(pivotFieldIdAtom);
|
|
192
|
+
if (pivotFieldId === void 0) return [];
|
|
193
|
+
get(projection.fields);
|
|
194
|
+
const fieldProj = projection.tryGetFieldProjection(pivotFieldId);
|
|
195
|
+
if (!fieldProj) return [];
|
|
196
|
+
const selectOptions = fieldProj.props.options ?? [];
|
|
197
|
+
const pivotPath = fieldProj.props.property;
|
|
198
|
+
const validColumnValues = new Set(selectOptions.map((opt) => opt.id));
|
|
199
|
+
return orderItemsInColumn(allItems, columnArr.ids, columnValue, pivotPath, validColumnValues);
|
|
200
|
+
})), [
|
|
201
|
+
columnArrangementAtomFamily,
|
|
202
|
+
itemsAtom,
|
|
203
|
+
pivotFieldIdAtom,
|
|
204
|
+
projection
|
|
205
|
+
]);
|
|
206
|
+
return useMemo(() => ({
|
|
207
|
+
getColumnId: (data) => data.columnValue,
|
|
208
|
+
getItemId: (data) => data.id,
|
|
209
|
+
isColumn: (obj) => typeof obj === "object" && obj !== null && "columnValue" in obj && "ids" in obj,
|
|
210
|
+
isItem: (obj) => Obj.isObject(obj),
|
|
211
|
+
columns: columnsAtom,
|
|
212
|
+
items: (column) => itemsAtomFamily(column.columnValue),
|
|
213
|
+
getColumns: () => registry.get(columnsAtom) ?? [],
|
|
214
|
+
getItems: (column) => registry.get(itemsAtomFamily(column.columnValue)) ?? []
|
|
215
|
+
}), [
|
|
216
|
+
columnsAtom,
|
|
217
|
+
itemsAtomFamily,
|
|
218
|
+
registry
|
|
219
|
+
]);
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/hooks/useKanbanColumnEventHandler.ts
|
|
223
|
+
/**
|
|
224
|
+
* Builds the column drag-and-drop handler for the kanban board (reorder columns).
|
|
225
|
+
*
|
|
226
|
+
* @template T - Item type (extends BaseKanbanItem).
|
|
227
|
+
* @param id - Handler id.
|
|
228
|
+
* @param model - Board model for getColumns / getColumnId.
|
|
229
|
+
* @param projection - ProjectionModel for pivot field options (column order).
|
|
230
|
+
* @param pivotFieldId - Pivot field id; undefined disables drop.
|
|
231
|
+
* @param change - Callback to persist kanban.arrangement.order.
|
|
232
|
+
* @returns DndContainerHandler for column tiles.
|
|
233
|
+
*/
|
|
234
|
+
function useKanbanColumnEventHandler({ id, model, projection, pivotFieldId, change }) {
|
|
235
|
+
return useMemo(() => ({
|
|
236
|
+
id,
|
|
237
|
+
canDrop: ({ source }) => {
|
|
238
|
+
if (!projection) return false;
|
|
239
|
+
const data = source.data;
|
|
240
|
+
const columnValue = model.getColumnId(data);
|
|
241
|
+
return model.isColumn(source.data) && columnValue !== UNCATEGORIZED_VALUE && source.id !== UNCATEGORIZED_VALUE;
|
|
242
|
+
},
|
|
243
|
+
onDrop: ({ source, target }) => {
|
|
244
|
+
if (!projection || pivotFieldId === void 0) return;
|
|
245
|
+
const sourceColumnData = source.data;
|
|
246
|
+
const sourceColumnId = model.getColumnId(sourceColumnData);
|
|
247
|
+
if (sourceColumnId === UNCATEGORIZED_VALUE) return;
|
|
248
|
+
const currentColumns = model.getColumns();
|
|
249
|
+
const sourceIndex = currentColumns.findIndex((c) => model.getColumnId(c) === sourceColumnId);
|
|
250
|
+
if (sourceIndex === -1) return;
|
|
251
|
+
let targetIndex;
|
|
252
|
+
if (target?.type === "tile" || target?.type === "placeholder") targetIndex = typeof target.location === "number" ? Math.floor(target.location) : -1;
|
|
253
|
+
else if (target?.type === "container") targetIndex = currentColumns.length;
|
|
254
|
+
else return;
|
|
255
|
+
if (targetIndex < 0) return;
|
|
256
|
+
const reorderedColumnIds = arrayMove([...currentColumns.map((c) => model.getColumnId(c))], sourceIndex, targetIndex);
|
|
257
|
+
const fieldProjection = projection.getFieldProjection(pivotFieldId);
|
|
258
|
+
const currentOptions = [...fieldProjection.props.options ?? []];
|
|
259
|
+
const optionsInNewOrder = reorderedColumnIds.map((columnId) => currentOptions.find((o) => o.id === columnId)).filter((o) => o != null);
|
|
260
|
+
projection.setFieldProjection({
|
|
261
|
+
...fieldProjection,
|
|
262
|
+
props: {
|
|
263
|
+
...fieldProjection.props,
|
|
264
|
+
options: optionsInNewOrder
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
change.kanban((kanban) => {
|
|
268
|
+
kanban.arrangement.order = reorderedColumnIds.filter((columnId) => columnId !== UNCATEGORIZED_VALUE);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}), [
|
|
272
|
+
id,
|
|
273
|
+
model,
|
|
274
|
+
projection,
|
|
275
|
+
pivotFieldId,
|
|
276
|
+
change
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/hooks/useKanbanItemEventHandler.ts
|
|
281
|
+
function findColumn(id, arrangement) {
|
|
282
|
+
return arrangement.find(({ columnValue, cards }) => columnValue === id || cards.some((card) => card.id === id));
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Builds the item drag-and-drop handler for a single column (reorder and move between columns).
|
|
286
|
+
*
|
|
287
|
+
* @template T - Item type (extends BaseKanbanItem).
|
|
288
|
+
* @param column - Column structure for this tile.
|
|
289
|
+
* @param columnFieldPath - Item property path for the pivot field (used when moving to another column).
|
|
290
|
+
* @param model - Board model for getColumns / getItems.
|
|
291
|
+
* @param change - Callback to persist arrangement and item field updates.
|
|
292
|
+
* @returns DndContainerHandler for item tiles in this column.
|
|
293
|
+
*/
|
|
294
|
+
function useKanbanItemEventHandler({ column, columnFieldPath, model, change }) {
|
|
295
|
+
return useMemo(() => ({
|
|
296
|
+
id: column.columnValue,
|
|
297
|
+
canDrop: ({ source }) => model.isItem(source.data),
|
|
298
|
+
onTake: ({ source }, cb) => {
|
|
299
|
+
cb(source.data);
|
|
300
|
+
},
|
|
301
|
+
onDrop: ({ source, target }) => {
|
|
302
|
+
const currentArrangement = model.getColumns().map((col) => ({
|
|
303
|
+
columnValue: col.columnValue,
|
|
304
|
+
cards: model.getItems(col) ?? []
|
|
305
|
+
}));
|
|
306
|
+
const sourceColumnInSnapshot = findColumn(source.id, currentArrangement);
|
|
307
|
+
if (!sourceColumnInSnapshot) return;
|
|
308
|
+
const workingArrangement = currentArrangement.map((col) => ({
|
|
309
|
+
columnValue: col.columnValue,
|
|
310
|
+
cards: [...col.cards]
|
|
311
|
+
}));
|
|
312
|
+
const sourceColumnInWorking = workingArrangement.find((c) => c.columnValue === sourceColumnInSnapshot.columnValue || c.cards.some((card) => card.id === source.id));
|
|
313
|
+
const targetColumnInWorking = workingArrangement.find((c) => c.columnValue === column.columnValue);
|
|
314
|
+
if (!sourceColumnInWorking || !targetColumnInWorking) return;
|
|
315
|
+
const sourceIndex = sourceColumnInWorking.cards.findIndex((card) => card.id === source.id);
|
|
316
|
+
if (sourceIndex === -1) return;
|
|
317
|
+
const [movedCard] = sourceColumnInWorking.cards.splice(sourceIndex, 1);
|
|
318
|
+
if (columnFieldPath !== void 0) {
|
|
319
|
+
const newValue = targetColumnInWorking.columnValue === UNCATEGORIZED_VALUE ? void 0 : targetColumnInWorking.columnValue;
|
|
320
|
+
change.setItemField(movedCard, columnFieldPath, newValue);
|
|
321
|
+
}
|
|
322
|
+
const existingTargetIndex = target?.type === "tile" ? targetColumnInWorking.cards.findIndex((card) => model.getItemId(card) === target.id) : -1;
|
|
323
|
+
const closestEdge = target?.type === "placeholder" && typeof target.location === "number" ? target.location <= targetColumnInWorking.cards.length / 2 ? "top" : "bottom" : "bottom";
|
|
324
|
+
let insertIndex;
|
|
325
|
+
if (target?.type === "placeholder" && typeof target.location === "number") insertIndex = Math.max(0, Math.min(targetColumnInWorking.cards.length, Math.floor(target.location)));
|
|
326
|
+
else if (target?.type === "container" || existingTargetIndex === -1) insertIndex = targetColumnInWorking.cards.length;
|
|
327
|
+
else if (sourceColumnInWorking.columnValue === targetColumnInWorking.columnValue) {
|
|
328
|
+
insertIndex = closestEdge === "bottom" ? existingTargetIndex + 1 : existingTargetIndex;
|
|
329
|
+
if (sourceIndex < existingTargetIndex) insertIndex -= 1;
|
|
330
|
+
} else insertIndex = closestEdge === "bottom" ? existingTargetIndex + 1 : existingTargetIndex;
|
|
331
|
+
targetColumnInWorking.cards.splice(insertIndex, 0, movedCard);
|
|
332
|
+
change.kanban((kanban) => {
|
|
333
|
+
kanban.arrangement = {
|
|
334
|
+
order: workingArrangement.map(({ columnValue }) => columnValue),
|
|
335
|
+
columns: Object.fromEntries(workingArrangement.map(({ columnValue, cards }) => [columnValue, { ids: cards.map((c) => c.id) }]))
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}), [
|
|
340
|
+
column,
|
|
341
|
+
columnFieldPath,
|
|
342
|
+
model,
|
|
343
|
+
change
|
|
344
|
+
]);
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/hooks/useProjectionModel.ts
|
|
348
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-kanban/src/hooks/useProjectionModel.ts";
|
|
349
|
+
/**
|
|
350
|
+
* Loads the kanban view and builds a ProjectionModel for field projections and pivot.
|
|
351
|
+
*
|
|
352
|
+
* @template S - Entity schema type.
|
|
353
|
+
* @param schema - Echo schema for the viewed type (or undefined).
|
|
354
|
+
* @param kanban - Kanban object whose view to load (or undefined).
|
|
355
|
+
* @param registry - Atom registry for reactive state.
|
|
356
|
+
* @returns ProjectionModel when loaded, or undefined while loading or when schema/kanban are missing.
|
|
357
|
+
*/
|
|
358
|
+
var useProjectionModel = (schema, kanban, registry) => {
|
|
359
|
+
const [projection, setProjection] = useState();
|
|
360
|
+
useAsyncEffect(async (controller) => {
|
|
361
|
+
if (!schema || !kanban || kanban.spec.kind !== "view") return;
|
|
362
|
+
try {
|
|
363
|
+
const view = await kanban.spec.view.load();
|
|
364
|
+
if (controller.signal.aborted) return;
|
|
365
|
+
const projection = new ProjectionModel({
|
|
366
|
+
registry,
|
|
367
|
+
view,
|
|
368
|
+
baseSchema: Type.getDatabase(schema) != null ? schema.jsonSchema : JsonSchema.toJsonSchema(schema),
|
|
369
|
+
change: createEchoChangeCallback$1(view, Type.getDatabase(schema) != null ? schema : void 0)
|
|
370
|
+
});
|
|
371
|
+
projection.normalizeView();
|
|
372
|
+
if (!controller.signal.aborted) setProjection(projection);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
log.catch(err, {
|
|
375
|
+
schema,
|
|
376
|
+
kanban
|
|
377
|
+
}, {
|
|
378
|
+
"~LogMeta": "~LogMeta",
|
|
379
|
+
F: __dxlog_file,
|
|
380
|
+
L: 51,
|
|
381
|
+
S: void 0
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}, [
|
|
385
|
+
schema,
|
|
386
|
+
kanban,
|
|
387
|
+
registry
|
|
388
|
+
]);
|
|
389
|
+
return projection;
|
|
390
|
+
};
|
|
391
|
+
//#endregion
|
|
392
|
+
export { createEchoChangeCallback, useEchoChangeCallback, useItemsProjection, useKanbanBoardModel, useKanbanColumnEventHandler, useKanbanItemEventHandler, useProjectionModel };
|
|
393
|
+
|
|
394
|
+
//# sourceMappingURL=hooks.mjs.map
|