@dxos/plugin-kanban 0.6.8-main.046e6cf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +15 -0
- package/dist/lib/browser/KanbanMain-OVUL576T.mjs +444 -0
- package/dist/lib/browser/KanbanMain-OVUL576T.mjs.map +7 -0
- package/dist/lib/browser/chunk-DMDAZVOX.mjs +21 -0
- package/dist/lib/browser/chunk-DMDAZVOX.mjs.map +7 -0
- package/dist/lib/browser/chunk-LEPZRV4E.mjs +47 -0
- package/dist/lib/browser/chunk-LEPZRV4E.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +176 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/browser/meta.mjs +9 -0
- package/dist/lib/browser/meta.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +14 -0
- package/dist/lib/browser/types/index.mjs.map +7 -0
- package/dist/lib/node/KanbanMain-RSRZLAM5.cjs +453 -0
- package/dist/lib/node/KanbanMain-RSRZLAM5.cjs.map +7 -0
- package/dist/lib/node/chunk-CJTEPA5Z.cjs +54 -0
- package/dist/lib/node/chunk-CJTEPA5Z.cjs.map +7 -0
- package/dist/lib/node/chunk-RYK4NJNG.cjs +67 -0
- package/dist/lib/node/chunk-RYK4NJNG.cjs.map +7 -0
- package/dist/lib/node/index.cjs +192 -0
- package/dist/lib/node/index.cjs.map +7 -0
- package/dist/lib/node/meta.cjs +30 -0
- package/dist/lib/node/meta.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -0
- package/dist/lib/node/types/index.cjs +36 -0
- package/dist/lib/node/types/index.cjs.map +7 -0
- package/dist/types/src/KanbanPlugin.d.ts +4 -0
- package/dist/types/src/KanbanPlugin.d.ts.map +1 -0
- package/dist/types/src/components/KanbanBoard.d.ts +6 -0
- package/dist/types/src/components/KanbanBoard.d.ts.map +1 -0
- package/dist/types/src/components/KanbanCard.d.ts +9 -0
- package/dist/types/src/components/KanbanCard.d.ts.map +1 -0
- package/dist/types/src/components/KanbanColumn.d.ts +14 -0
- package/dist/types/src/components/KanbanColumn.d.ts.map +1 -0
- package/dist/types/src/components/KanbanMain.d.ts +7 -0
- package/dist/types/src/components/KanbanMain.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +5 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/components/util.d.ts +7 -0
- package/dist/types/src/components/util.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +15 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/sanity.test.d.ts +2 -0
- package/dist/types/src/sanity.test.d.ts.map +1 -0
- package/dist/types/src/stories/testing.d.ts +19 -0
- package/dist/types/src/stories/testing.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +20 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +3 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/types/kanban.d.ts +76 -0
- package/dist/types/src/types/kanban.d.ts.map +1 -0
- package/dist/types/src/types/types.d.ts +18 -0
- package/dist/types/src/types/types.d.ts.map +1 -0
- package/package.json +85 -0
- package/src/KanbanPlugin.tsx +112 -0
- package/src/components/KanbanBoard.tsx +195 -0
- package/src/components/KanbanCard.tsx +82 -0
- package/src/components/KanbanColumn.tsx +143 -0
- package/src/components/KanbanMain.tsx +37 -0
- package/src/components/index.ts +8 -0
- package/src/components/util.ts +38 -0
- package/src/index.ts +9 -0
- package/src/meta.tsx +19 -0
- package/src/sanity.test.ts +13 -0
- package/src/stories/testing.ts +29 -0
- package/src/translations.ts +26 -0
- package/src/types/index.ts +6 -0
- package/src/types/kanban.ts +22 -0
- package/src/types/types.ts +57 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useDroppable } from '@dnd-kit/core';
|
|
6
|
+
import { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
7
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
8
|
+
import { DotsSixVertical, X, Plus } from '@phosphor-icons/react';
|
|
9
|
+
import React, { type FC } from 'react';
|
|
10
|
+
|
|
11
|
+
import { Button, Input, useTranslation } from '@dxos/react-ui';
|
|
12
|
+
import { modalSurface, getSize, groupSurface, mx } from '@dxos/react-ui-theme';
|
|
13
|
+
import { nonNullable } from '@dxos/util';
|
|
14
|
+
|
|
15
|
+
import { KanbanCardComponent } from './KanbanCard';
|
|
16
|
+
import { useSubscription } from './util';
|
|
17
|
+
import { KANBAN_PLUGIN } from '../meta';
|
|
18
|
+
import { type KanbanColumnType, type KanbanItemType } from '../types';
|
|
19
|
+
|
|
20
|
+
export type ItemsMapper = (column: string, items: KanbanItemType[]) => KanbanItemType[];
|
|
21
|
+
|
|
22
|
+
const DeleteColumn = ({ onClick }: { onClick: () => void }) => {
|
|
23
|
+
const { t } = useTranslation(KANBAN_PLUGIN);
|
|
24
|
+
return (
|
|
25
|
+
<Button variant='ghost' onClick={onClick} classNames='plb-0 pli-0.5 -mlb-1'>
|
|
26
|
+
<span className='sr-only'>{t('delete column label')}</span>
|
|
27
|
+
<X className={getSize(4)} />
|
|
28
|
+
</Button>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const AddItem = ({ onClick }: { onClick: () => void }) => {
|
|
33
|
+
const { t } = useTranslation(KANBAN_PLUGIN);
|
|
34
|
+
return (
|
|
35
|
+
<Button variant='ghost' onClick={onClick} classNames='plb-0 pli-0.5 -mlb-1'>
|
|
36
|
+
<span className='sr-only'>{t('add item label')}</span>
|
|
37
|
+
<Plus className={getSize(4)} />
|
|
38
|
+
</Button>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// TODO(burdon): Factor out container.
|
|
43
|
+
export const KanbanColumnComponentPlaceholder: FC<{ onAdd: () => void }> = ({ onAdd }) => {
|
|
44
|
+
const { t } = useTranslation(KANBAN_PLUGIN);
|
|
45
|
+
return (
|
|
46
|
+
<div className={mx('flex flex-col justify-center shadow rounded w-[300px] h-[300px]', groupSurface)}>
|
|
47
|
+
<Button variant='ghost' onClick={onAdd} classNames='plb-0 pli-0.5 -mlb-1'>
|
|
48
|
+
<span className='sr-only'>{t('add column label')}</span>
|
|
49
|
+
<Plus className={getSize(6)} />
|
|
50
|
+
</Button>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const KanbanColumnComponent: FC<{
|
|
56
|
+
column: KanbanColumnType;
|
|
57
|
+
itemMapper?: ItemsMapper;
|
|
58
|
+
debug?: boolean; // TODO(burdon): Context.
|
|
59
|
+
onCreate?: (column: KanbanColumnType) => KanbanItemType;
|
|
60
|
+
onDelete?: () => void;
|
|
61
|
+
}> = ({ column, itemMapper, debug = false, onCreate, onDelete }) => {
|
|
62
|
+
const { t } = useTranslation(KANBAN_PLUGIN);
|
|
63
|
+
|
|
64
|
+
// TODO(wittjosiah): Remove?
|
|
65
|
+
useSubscription([column.items]);
|
|
66
|
+
const items = itemMapper?.(column.id!, column.items.filter(nonNullable)) ?? column.items!;
|
|
67
|
+
|
|
68
|
+
const { setNodeRef: setDroppableNodeRef } = useDroppable({ id: column.id! });
|
|
69
|
+
const { isDragging, attributes, listeners, transform, transition, setNodeRef } = useSortable({
|
|
70
|
+
id: column.id!,
|
|
71
|
+
data: { type: 'column' },
|
|
72
|
+
});
|
|
73
|
+
const tx = transform ? Object.assign(transform, { scaleY: 1 }) : null;
|
|
74
|
+
|
|
75
|
+
const handleAddItem = onCreate
|
|
76
|
+
? () => {
|
|
77
|
+
const item = onCreate(column);
|
|
78
|
+
column.items!.splice(column.items!.length, 0, item);
|
|
79
|
+
}
|
|
80
|
+
: undefined;
|
|
81
|
+
|
|
82
|
+
const handleDeleteItem = (id: string) => {
|
|
83
|
+
const index = column.items.filter(nonNullable).findIndex((column) => column.id === id);
|
|
84
|
+
if (index >= 0) {
|
|
85
|
+
column.items!.splice(index, 1);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
ref={setNodeRef}
|
|
92
|
+
style={{ transform: CSS.Transform.toString(tx), transition }}
|
|
93
|
+
className={mx('flex flex-col snap-center overflow-y-hidden', isDragging && 'relative z-10')}
|
|
94
|
+
>
|
|
95
|
+
{/* TODO(burdon): Width approx mobile phone width. */}
|
|
96
|
+
<div
|
|
97
|
+
className={mx(
|
|
98
|
+
'flex flex-col py-2 overflow-hidden shadow rounded w-[300px] min-h-[300px]',
|
|
99
|
+
isDragging ? modalSurface : groupSurface,
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
<div className='flex items-center mb-2 px-2'>
|
|
103
|
+
<button {...attributes} {...listeners}>
|
|
104
|
+
<DotsSixVertical className={getSize(5)} />
|
|
105
|
+
</button>
|
|
106
|
+
|
|
107
|
+
<Input.Root>
|
|
108
|
+
<Input.Label srOnly>{t('column title label')}</Input.Label>
|
|
109
|
+
<Input.TextInput
|
|
110
|
+
variant='subdued'
|
|
111
|
+
classNames='px-2'
|
|
112
|
+
placeholder={t('column title placeholder')}
|
|
113
|
+
defaultValue={column.name}
|
|
114
|
+
onChange={({ target: { value } }) => (column.name = value)}
|
|
115
|
+
/>
|
|
116
|
+
</Input.Root>
|
|
117
|
+
|
|
118
|
+
{/* TODO(burdon): Menu. */}
|
|
119
|
+
{onDelete && <DeleteColumn onClick={onDelete} />}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* TODO(burdon): Scrolling (radix; see kai/mosaic). */}
|
|
123
|
+
<SortableContext strategy={verticalListSortingStrategy} items={items.filter(nonNullable).map(({ id }) => id)}>
|
|
124
|
+
<div ref={setDroppableNodeRef} className='flex flex-col grow overflow-y-scroll space-y-2 pr-4'>
|
|
125
|
+
{items.filter(nonNullable).map((item) => (
|
|
126
|
+
<div key={item.id} id={item.id} className='flex pl-2'>
|
|
127
|
+
<KanbanCardComponent column={column} item={item} onDelete={() => handleDeleteItem(item.id)} />
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</SortableContext>
|
|
132
|
+
|
|
133
|
+
{handleAddItem && (
|
|
134
|
+
<div className='flex justify-center mt-2'>
|
|
135
|
+
<AddItem onClick={handleAddItem} />
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
{debug && <div className='px-2 text-xs text-red-800'>{column.id!.slice(0, 9)}</div>}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type FC } from 'react';
|
|
6
|
+
|
|
7
|
+
import { create } from '@dxos/echo-schema';
|
|
8
|
+
import { getSpace } from '@dxos/react-client/echo';
|
|
9
|
+
import { Main } from '@dxos/react-ui';
|
|
10
|
+
import { topbarBlockPaddingStart, fixedInsetFlexLayout, bottombarBlockPaddingEnd } from '@dxos/react-ui-theme';
|
|
11
|
+
|
|
12
|
+
import { KanbanBoard } from './KanbanBoard';
|
|
13
|
+
import { type KanbanType, KanbanColumnType, KanbanItemType, type KanbanModel } from '../types';
|
|
14
|
+
|
|
15
|
+
const KanbanMain: FC<{ kanban: KanbanType }> = ({ kanban }) => {
|
|
16
|
+
// const { t } = useTranslation(KANBAN_PLUGIN);
|
|
17
|
+
const space = getSpace(kanban);
|
|
18
|
+
if (!space) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// TODO(burdon): Should plugin create and pass in model?
|
|
23
|
+
const model: KanbanModel = {
|
|
24
|
+
root: kanban, // TODO(burdon): How to keep pure?
|
|
25
|
+
createColumn: () => space.db.add(create(KanbanColumnType, { items: [] })),
|
|
26
|
+
// TODO(burdon): Add metadata from column in the case of projections.
|
|
27
|
+
createItem: (column) => space.db.add(create(KanbanItemType, { name: '' })),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Main.Content classNames={[fixedInsetFlexLayout, topbarBlockPaddingStart, bottombarBlockPaddingEnd]}>
|
|
32
|
+
<KanbanBoard model={model} />
|
|
33
|
+
</Main.Content>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default KanbanMain;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { createSubscription } from '@dxos/react-client/echo';
|
|
8
|
+
import { nonNullable } from '@dxos/util';
|
|
9
|
+
|
|
10
|
+
import { type KanbanColumnType, type Location } from '../types';
|
|
11
|
+
|
|
12
|
+
// TODO(burdon): Factor out.
|
|
13
|
+
export const useSubscription = (data: any) => {
|
|
14
|
+
const [_, setIter] = useState([]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const handle = createSubscription(() => setIter([]));
|
|
17
|
+
handle.update(data);
|
|
18
|
+
return () => handle.unsubscribe();
|
|
19
|
+
}, []);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Find the column or item within the model.
|
|
24
|
+
*/
|
|
25
|
+
// TODO(burdon): Move to model.
|
|
26
|
+
export const findLocation = (columns: KanbanColumnType[], id: string): Location | undefined => {
|
|
27
|
+
for (const column of columns) {
|
|
28
|
+
// TODO(burdon): Need transient ID for UX.
|
|
29
|
+
if (column.id === id) {
|
|
30
|
+
return { column };
|
|
31
|
+
} else {
|
|
32
|
+
const idx = column.items.filter(nonNullable).findIndex((item) => item.id === id);
|
|
33
|
+
if (idx !== -1) {
|
|
34
|
+
return { column, item: column.items![idx], idx };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
package/src/index.ts
ADDED
package/src/meta.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Kanban, type IconProps } from '@phosphor-icons/react';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { pluginMeta } from '@dxos/app-framework';
|
|
9
|
+
|
|
10
|
+
export const KANBAN_PLUGIN = 'dxos.org/plugin/kanban';
|
|
11
|
+
|
|
12
|
+
export default pluginMeta({
|
|
13
|
+
id: KANBAN_PLUGIN,
|
|
14
|
+
name: 'Kanban',
|
|
15
|
+
description: 'Kanban board for managing tasks.',
|
|
16
|
+
tags: ['experimental'],
|
|
17
|
+
iconComponent: (props: IconProps) => <Kanban {...props} />,
|
|
18
|
+
iconSymbol: 'ph--kanban--regular',
|
|
19
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { create } from '@dxos/echo-schema';
|
|
6
|
+
import { faker } from '@dxos/random';
|
|
7
|
+
|
|
8
|
+
import { KanbanColumnType, KanbanItemType, KanbanType } from '../types';
|
|
9
|
+
|
|
10
|
+
// TODO(burdon): Types.
|
|
11
|
+
export const createKanban = () => {
|
|
12
|
+
return create(KanbanType, {
|
|
13
|
+
name: faker.lorem.words(3),
|
|
14
|
+
columns: faker.helpers.multiple(
|
|
15
|
+
() =>
|
|
16
|
+
create(KanbanColumnType, {
|
|
17
|
+
name: faker.lorem.words(3),
|
|
18
|
+
items: faker.helpers.multiple(
|
|
19
|
+
() =>
|
|
20
|
+
create(KanbanItemType, {
|
|
21
|
+
name: faker.lorem.words(faker.number.int({ min: 3, max: 24 })) + '.',
|
|
22
|
+
}),
|
|
23
|
+
{ count: faker.number.int(8) },
|
|
24
|
+
),
|
|
25
|
+
}),
|
|
26
|
+
{ count: { min: 2, max: 8 } },
|
|
27
|
+
),
|
|
28
|
+
});
|
|
29
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { KANBAN_PLUGIN } from './meta';
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
{
|
|
9
|
+
'en-US': {
|
|
10
|
+
[KANBAN_PLUGIN]: {
|
|
11
|
+
'plugin name': 'Kanban',
|
|
12
|
+
'kanban title label': 'Title',
|
|
13
|
+
'kanban title placeholder': 'New kanban',
|
|
14
|
+
'column title label': 'Column title',
|
|
15
|
+
'column title placeholder': 'New column',
|
|
16
|
+
'item title label': 'Item title',
|
|
17
|
+
'item title placeholder': 'New item',
|
|
18
|
+
'add column label': 'Add column',
|
|
19
|
+
'add item label': 'Add item',
|
|
20
|
+
'delete column label': 'Delete column',
|
|
21
|
+
'delete item label': 'Delete item',
|
|
22
|
+
'create kanban label': 'Create kanban',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Expando, ref, S, TypedObject } from '@dxos/echo-schema';
|
|
6
|
+
|
|
7
|
+
export class KanbanItemType extends TypedObject({ typename: 'dxos.org/type/KanbanItem', version: '0.1.0' })({
|
|
8
|
+
object: S.optional(ref(Expando)),
|
|
9
|
+
name: S.optional(S.String),
|
|
10
|
+
index: S.optional(S.String),
|
|
11
|
+
}) {}
|
|
12
|
+
|
|
13
|
+
export class KanbanColumnType extends TypedObject({ typename: 'dxos.org/type/KanbanColumn', version: '0.1.0' })({
|
|
14
|
+
name: S.optional(S.String),
|
|
15
|
+
index: S.optional(S.String),
|
|
16
|
+
items: S.mutable(S.Array(ref(KanbanItemType))),
|
|
17
|
+
}) {}
|
|
18
|
+
|
|
19
|
+
export class KanbanType extends TypedObject({ typename: 'dxos.org/type/Kanban', version: '0.1.0' })({
|
|
20
|
+
name: S.optional(S.String),
|
|
21
|
+
columns: S.mutable(S.Array(ref(KanbanColumnType))),
|
|
22
|
+
}) {}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
GraphBuilderProvides,
|
|
7
|
+
IntentResolverProvides,
|
|
8
|
+
MetadataRecordsProvides,
|
|
9
|
+
SurfaceProvides,
|
|
10
|
+
TranslationsProvides,
|
|
11
|
+
} from '@dxos/app-framework';
|
|
12
|
+
import { type SchemaProvides } from '@dxos/plugin-client';
|
|
13
|
+
|
|
14
|
+
import { type KanbanColumnType, type KanbanItemType, type KanbanType } from './kanban';
|
|
15
|
+
import { KANBAN_PLUGIN } from '../meta';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Kanban data model.
|
|
19
|
+
* A Kanban board is a collection of columns, each of which contains a collection of items.
|
|
20
|
+
* The layout of columns and items is controlled by models.
|
|
21
|
+
* The underlying data model may be represented by direct object relationships
|
|
22
|
+
* (e.g., a column object containing an array of ordered items) or projections constructed
|
|
23
|
+
* by the model (e.g., a query of items based on metadata within a column object).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const KANBAN_ACTION = `${KANBAN_PLUGIN}/action`;
|
|
27
|
+
|
|
28
|
+
export enum KanbanAction {
|
|
29
|
+
CREATE = `${KANBAN_ACTION}/create`,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type KanbanPluginProvides = SurfaceProvides &
|
|
33
|
+
IntentResolverProvides &
|
|
34
|
+
GraphBuilderProvides &
|
|
35
|
+
MetadataRecordsProvides &
|
|
36
|
+
TranslationsProvides &
|
|
37
|
+
SchemaProvides;
|
|
38
|
+
|
|
39
|
+
// TODO(burdon): Undo?
|
|
40
|
+
// TODO(burdon): Typescript types (replace proto with annotations?)
|
|
41
|
+
// TODO(burdon): Should pure components depend on ECHO? Relationship between ECHO object/array and Observable.
|
|
42
|
+
// TODO(burdon): Can the plugin configure the object based on the data? E.g., how are the models constructed?
|
|
43
|
+
// TODO(burdon): Create models. Simple first based on actual data.
|
|
44
|
+
// Model is always a projection since the dragging state is tentative.
|
|
45
|
+
|
|
46
|
+
// TODO(burdon): Extend model for moving items (in and across columns).
|
|
47
|
+
export interface KanbanModel {
|
|
48
|
+
root: KanbanType;
|
|
49
|
+
createColumn(): KanbanColumnType;
|
|
50
|
+
createItem(column: KanbanColumnType): KanbanItemType;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type Location = {
|
|
54
|
+
column: KanbanColumnType;
|
|
55
|
+
item?: KanbanItemType;
|
|
56
|
+
idx?: number;
|
|
57
|
+
};
|