@dxos/plugin-stack 0.6.14-staging.8758a12 → 0.6.14-staging.9b873ce

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.
Files changed (63) hide show
  1. package/dist/lib/browser/StackMain-Z2DSTC42.mjs +390 -0
  2. package/dist/lib/browser/StackMain-Z2DSTC42.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-ONAKM24Z.mjs → chunk-DS5KGEJG.mjs} +5 -2
  4. package/dist/lib/browser/chunk-DS5KGEJG.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +18 -8
  6. package/dist/lib/browser/index.mjs.map +3 -3
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/meta.mjs +1 -1
  9. package/dist/lib/node/StackMain-YK5QQBXA.cjs +402 -0
  10. package/dist/lib/node/StackMain-YK5QQBXA.cjs.map +7 -0
  11. package/dist/lib/node/{chunk-O5SQVKNW.cjs → chunk-PA5PRDM6.cjs} +8 -5
  12. package/dist/lib/node/chunk-PA5PRDM6.cjs.map +7 -0
  13. package/dist/lib/node/index.cjs +29 -19
  14. package/dist/lib/node/index.cjs.map +3 -3
  15. package/dist/lib/node/meta.cjs +4 -4
  16. package/dist/lib/node/meta.cjs.map +1 -1
  17. package/dist/lib/node/meta.json +1 -1
  18. package/dist/lib/node-esm/StackMain-GCZYK7GH.mjs +391 -0
  19. package/dist/lib/node-esm/StackMain-GCZYK7GH.mjs.map +7 -0
  20. package/dist/lib/node-esm/{chunk-J57BXQJY.mjs → chunk-JEQMG2E2.mjs} +5 -2
  21. package/dist/lib/node-esm/chunk-JEQMG2E2.mjs.map +7 -0
  22. package/dist/lib/node-esm/index.mjs +18 -8
  23. package/dist/lib/node-esm/index.mjs.map +3 -3
  24. package/dist/lib/node-esm/meta.json +1 -1
  25. package/dist/lib/node-esm/meta.mjs +1 -1
  26. package/dist/types/src/StackPlugin.d.ts.map +1 -1
  27. package/dist/types/src/components/AddSectionDialog.d.ts +1 -1
  28. package/dist/types/src/components/AddSectionDialog.d.ts.map +1 -1
  29. package/dist/types/src/components/CaretDownUp.d.ts +3 -0
  30. package/dist/types/src/components/CaretDownUp.d.ts.map +1 -0
  31. package/dist/types/src/components/StackContext.d.ts +10 -0
  32. package/dist/types/src/components/StackContext.d.ts.map +1 -0
  33. package/dist/types/src/components/StackMain.d.ts +1 -2
  34. package/dist/types/src/components/StackMain.d.ts.map +1 -1
  35. package/dist/types/src/components/StackSection.d.ts +4 -0
  36. package/dist/types/src/components/StackSection.d.ts.map +1 -0
  37. package/dist/types/src/components/index.d.ts +1 -2
  38. package/dist/types/src/components/index.d.ts.map +1 -1
  39. package/dist/types/src/meta.d.ts +2 -0
  40. package/dist/types/src/meta.d.ts.map +1 -1
  41. package/dist/types/src/translations.d.ts +17 -8
  42. package/dist/types/src/translations.d.ts.map +1 -1
  43. package/dist/types/src/types/types.d.ts +24 -0
  44. package/dist/types/src/types/types.d.ts.map +1 -1
  45. package/package.json +24 -23
  46. package/src/StackPlugin.tsx +3 -11
  47. package/src/components/AddSectionDialog.tsx +1 -2
  48. package/src/components/CaretDownUp.tsx +30 -0
  49. package/src/components/StackContext.tsx +23 -0
  50. package/src/components/StackMain.tsx +49 -111
  51. package/src/components/StackSection.tsx +118 -0
  52. package/src/meta.ts +3 -0
  53. package/src/translations.ts +9 -0
  54. package/src/types/types.ts +30 -0
  55. package/dist/lib/browser/StackMain-KKC3XIAY.mjs +0 -277
  56. package/dist/lib/browser/StackMain-KKC3XIAY.mjs.map +0 -7
  57. package/dist/lib/browser/chunk-ONAKM24Z.mjs.map +0 -7
  58. package/dist/lib/node/StackMain-ME6LYFGI.cjs +0 -296
  59. package/dist/lib/node/StackMain-ME6LYFGI.cjs.map +0 -7
  60. package/dist/lib/node/chunk-O5SQVKNW.cjs.map +0 -7
  61. package/dist/lib/node-esm/StackMain-D3P3PIMI.mjs +0 -278
  62. package/dist/lib/node-esm/StackMain-D3P3PIMI.mjs.map +0 -7
  63. package/dist/lib/node-esm/chunk-J57BXQJY.mjs.map +0 -7
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { createContext, useContext } from 'react';
6
+
7
+ import { type AddSectionPosition } from '../types';
8
+
9
+ export type StackContextValue = {
10
+ onCollapse: (id: string, collapsed: boolean) => void;
11
+ onNavigate: (id: string) => Promise<void>;
12
+ onDelete: (id: string) => Promise<void>;
13
+ onAdd: (id: string, position: AddSectionPosition) => void;
14
+ };
15
+
16
+ export const StackContext = createContext<StackContextValue>({
17
+ onCollapse: () => {},
18
+ onNavigate: async () => {},
19
+ onDelete: async () => {},
20
+ onAdd: () => {},
21
+ });
22
+
23
+ export const useStack = () => useContext(StackContext);
@@ -8,43 +8,38 @@ import React, { useEffect, useMemo, useState } from 'react';
8
8
  import {
9
9
  LayoutAction,
10
10
  NavigationAction,
11
- Surface,
12
11
  parseMetadataResolverPlugin,
13
12
  useIntentDispatcher,
14
13
  useResolvePlugin,
15
14
  } from '@dxos/app-framework';
16
- import { create, getType, fullyQualifiedId, isReactiveObject } from '@dxos/client/echo';
15
+ import { create, getType, fullyQualifiedId } from '@dxos/client/echo';
17
16
  import { useGraph } from '@dxos/plugin-graph';
18
17
  import { SpaceAction } from '@dxos/plugin-space';
19
18
  import { type CollectionType } from '@dxos/plugin-space/types';
20
19
  import { Button, toLocalizedString, useTranslation } from '@dxos/react-ui';
21
20
  import { AttentionProvider } from '@dxos/react-ui-attention';
22
- import { type MosaicDataItem, type MosaicDropEvent, type MosaicMoveEvent, Mosaic, Path } from '@dxos/react-ui-mosaic';
23
- import {
24
- type AddSectionPosition,
25
- type CollapsedSections,
26
- Stack,
27
- type StackProps,
28
- type StackSectionItem,
29
- } from '@dxos/react-ui-stack';
21
+ import { Stack } from '@dxos/react-ui-stack';
30
22
  import { nonNullable } from '@dxos/util';
31
23
 
32
24
  import { AddSection } from './AddSection';
33
- import { SECTION_IDENTIFIER, STACK_PLUGIN } from '../meta';
34
- import { StackViewType } from '../types';
35
-
36
- const SectionContent: StackProps['SectionContent'] = ({ data }) => {
37
- // TODO(wittjosiah): Better section placeholder.
38
- return <Surface role='section' data={{ object: data }} limit={1} placeholder={<></>} />;
39
- };
25
+ import { StackContext } from './StackContext';
26
+ import { StackSection } from './StackSection';
27
+ import { STACK_PLUGIN } from '../meta';
28
+ import {
29
+ StackViewType,
30
+ type CollapsedSections,
31
+ type StackSectionMetadata,
32
+ type StackSectionView,
33
+ type StackSectionItem,
34
+ type AddSectionPosition,
35
+ } from '../types';
40
36
 
41
37
  type StackMainProps = {
42
38
  id: string;
43
39
  collection: CollectionType;
44
- separation?: boolean;
45
40
  };
46
41
 
47
- const StackMain = ({ id, collection, separation }: StackMainProps) => {
42
+ const StackMain = ({ id, collection }: StackMainProps) => {
48
43
  const dispatch = useIntentDispatcher();
49
44
  const { graph } = useGraph();
50
45
  const { t } = useTranslation(STACK_PLUGIN);
@@ -67,72 +62,22 @@ const StackMain = ({ id, collection, separation }: StackMainProps) => {
67
62
  .map((object) => {
68
63
  const metadata = metadataPlugin?.provides.metadata.resolver(
69
64
  getType(object)?.objectId ?? 'never',
70
- ) as StackSectionItem['metadata'];
65
+ ) as StackSectionMetadata;
71
66
  const view = {
72
67
  ...stack.sections[object.id],
73
68
  collapsed: collapsedSections[fullyQualifiedId(object)],
74
69
  title:
75
70
  (object as any)?.title ?? toLocalizedString(graph.findNode(fullyQualifiedId(object))?.properties.label, t),
76
- } as StackSectionItem['view'];
77
- return { id: fullyQualifiedId(object), object, metadata, view };
71
+ } as StackSectionView;
72
+ return { id: fullyQualifiedId(object), object, metadata, view } satisfies StackSectionItem;
78
73
  }) ?? [];
79
74
 
80
- const handleOver = ({ active, over }: MosaicMoveEvent<number>) => {
81
- // TODO(thure): Eventually Stack should handle foreign draggables.
82
- if (active.type !== SECTION_IDENTIFIER) {
83
- return 'reject';
84
- }
85
- if (Path.parent(active.path) === Path.parent(over.path)) {
86
- return 'rearrange';
87
- }
88
-
89
- const parseData = metadataPlugin?.provides.metadata.resolver(active.type)?.parse;
90
- const data = parseData ? parseData(active.item, 'object') : active.item;
91
-
92
- // TODO(wittjosiah): Prevent dropping items which don't have a section renderer?
93
- // Perhaps stack plugin should just provide a fallback section renderer.
94
- if (!isReactiveObject(data)) {
95
- return 'reject';
96
- }
97
-
98
- const exists = items.findIndex(({ id }) => id === active.item.id) >= 0;
99
- if (!exists) {
100
- return 'transfer';
101
- } else {
102
- return 'reject';
103
- }
104
- };
105
-
106
- const handleDrop = ({ operation, active, over }: MosaicDropEvent<number>) => {
107
- if (
108
- (active.path === Path.create(id, active.item.id) || active.path === id) &&
109
- (operation !== 'copy' || over.path === Path.create(id, over.item.id) || over.path === id)
110
- ) {
111
- collection.objects.splice(active.position!, 1);
112
- delete stack.sections[active.item.id];
113
- }
114
-
115
- const parseData = metadataPlugin?.provides.metadata.resolver(active.type)?.parse;
116
- const object = parseData?.(active.item, 'object');
117
- if (object && over.path === Path.create(id, over.item.id)) {
118
- collection.objects.splice(over.position!, 0, object);
119
- } else if (object && over.path === id) {
120
- collection.objects.push(object);
121
- }
122
-
123
- if (!stack.sections[object.id]) {
124
- stack.sections[object.id] = {};
125
- }
126
- };
127
-
128
- const handleDelete = async (path: string) => {
129
- const index = collection.objects
130
- .filter(nonNullable)
131
- .findIndex((section) => fullyQualifiedId(section) === Path.last(path));
75
+ const handleDelete = async (id: string) => {
76
+ const index = collection.objects.filter(nonNullable).findIndex((section) => fullyQualifiedId(section) === id);
132
77
  if (index >= 0) {
133
78
  await dispatch({
134
- action: SpaceAction.REMOVE_OBJECT,
135
- data: { object: collection.objects[index], collection },
79
+ action: SpaceAction.REMOVE_OBJECTS,
80
+ data: { objects: [collection.objects[index]], collection },
136
81
  });
137
82
 
138
83
  // TODO(wittjosiah): The section should also be removed, but needs to be restored if the action is undone.
@@ -140,56 +85,49 @@ const StackMain = ({ id, collection, separation }: StackMainProps) => {
140
85
  }
141
86
  };
142
87
 
143
- // TODO(wittjosiah): Factor out.
88
+ const handleAdd = (id: string, position: AddSectionPosition) => {
89
+ void dispatch?.({
90
+ action: LayoutAction.SET_LAYOUT,
91
+ data: {
92
+ element: 'dialog',
93
+ component: `${STACK_PLUGIN}/AddSectionDialog`,
94
+ subject: { path: id, position, collection },
95
+ },
96
+ });
97
+ };
144
98
 
145
- const handleNavigate = async (object: MosaicDataItem) => {
146
- const toId = fullyQualifiedId(object);
99
+ const handleNavigate = async (id: string) => {
147
100
  await dispatch([
148
101
  {
149
102
  action: NavigationAction.OPEN,
150
- data: { activeParts: { main: [toId] } },
103
+ data: { activeParts: { main: [id] } },
151
104
  },
152
105
  {
153
106
  action: LayoutAction.SCROLL_INTO_VIEW,
154
- data: { id: toId },
107
+ data: { id },
155
108
  },
156
109
  ]);
157
110
  };
158
111
 
159
- const handleAddSection = (path: string, position: AddSectionPosition) => {
160
- void dispatch?.({
161
- action: LayoutAction.SET_LAYOUT,
162
- data: {
163
- element: 'dialog',
164
- component: `${STACK_PLUGIN}/AddSectionDialog`,
165
- subject: { path, position, collection },
166
- },
167
- });
168
- };
169
-
170
- const handleCollapseSection = (id: string, collapsed: boolean) => {
112
+ const handleCollapse = (id: string, collapsed: boolean) => {
171
113
  setCollapsedSections((prev) => ({ ...prev, [id]: collapsed }));
172
114
  };
173
115
 
174
116
  return (
175
117
  <AttentionProvider id={id}>
176
- <Mosaic.Root>
177
- <Mosaic.DragOverlay />
178
- <Stack
179
- id={id}
180
- data-testid='main.stack'
181
- SectionContent={SectionContent}
182
- type={SECTION_IDENTIFIER}
183
- items={items}
184
- separation={separation}
185
- emptyComponent={<span data-testid='stack.empty'></span>}
186
- onDrop={handleDrop}
187
- onOver={handleOver}
188
- onDeleteSection={handleDelete}
189
- onNavigateToSection={handleNavigate}
190
- onAddSection={handleAddSection}
191
- onCollapseSection={handleCollapseSection}
192
- />
118
+ <StackContext.Provider
119
+ value={{
120
+ onCollapse: handleCollapse,
121
+ onNavigate: handleNavigate,
122
+ onDelete: handleDelete,
123
+ onAdd: handleAdd,
124
+ }}
125
+ >
126
+ <Stack orientation='vertical' size='intrinsic' id={id} data-testid='main.stack' classNames='divide-y-reverse'>
127
+ {items.map((item) => (
128
+ <StackSection key={item.id} {...item} />
129
+ ))}
130
+ </Stack>
193
131
 
194
132
  {items.length === 0 ? (
195
133
  <AddSection collection={collection} />
@@ -214,7 +152,7 @@ const StackMain = ({ id, collection, separation }: StackMainProps) => {
214
152
  </Button>
215
153
  </div>
216
154
  )}
217
- </Mosaic.Root>
155
+ </StackContext.Provider>
218
156
  </AttentionProvider>
219
157
  );
220
158
  };
@@ -0,0 +1,118 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
6
+ import React, { useState } from 'react';
7
+
8
+ import { Surface } from '@dxos/app-framework';
9
+ import { DropdownMenu, Icon, useTranslation, IconButton } from '@dxos/react-ui';
10
+ import { useAttendableAttributes } from '@dxos/react-ui-attention';
11
+ import { StackItem } from '@dxos/react-ui-stack';
12
+ import { mx, getSize, textBlockWidth } from '@dxos/react-ui-theme';
13
+
14
+ import { CaretDownUp } from './CaretDownUp';
15
+ import { useStack } from './StackContext';
16
+ import { STACK_PLUGIN } from '../meta';
17
+ import { type StackSectionItem } from '../types';
18
+
19
+ const sectionActionDimensions = 'm-1 p-1 shrink-0 min-bs-0 is-[--rail-action] bs-min';
20
+
21
+ export const StackSection = ({
22
+ id,
23
+ view,
24
+ object,
25
+ metadata: { icon = 'ph--placeholder--regular' },
26
+ }: StackSectionItem) => {
27
+ const { t } = useTranslation(STACK_PLUGIN);
28
+ const { onNavigate, onAdd, onCollapse, onDelete } = useStack();
29
+ const [optionsMenuOpen, setOptionsMenuOpen] = useState(false);
30
+ const attendableAttrs = useAttendableAttributes(id);
31
+
32
+ return (
33
+ <CollapsiblePrimitive.Root asChild open={!view.collapsed} onOpenChange={(nextOpen) => onCollapse?.(id, !nextOpen)}>
34
+ <StackItem.Root item={{ id }} role='section' {...attendableAttrs}>
35
+ <StackItem.Heading classNames='attention-surface'>
36
+ <span className='sr-only'>{view.title}</span>
37
+ <div role='none' className='sticky -block-start-px bg-[--sticky-bg]'>
38
+ <DropdownMenu.Root
39
+ {...{
40
+ open: optionsMenuOpen,
41
+ onOpenChange: setOptionsMenuOpen,
42
+ }}
43
+ >
44
+ <DropdownMenu.Trigger asChild>
45
+ <StackItem.SigilButton>
46
+ <Icon icon={icon} size={5} classNames='transition-opacity' />
47
+ </StackItem.SigilButton>
48
+ </DropdownMenu.Trigger>
49
+ <DropdownMenu.Portal>
50
+ <DropdownMenu.Content style={{ zIndex: 70 }}>
51
+ <DropdownMenu.Viewport>
52
+ {view.collapsed ? (
53
+ <DropdownMenu.Item onClick={() => onNavigate(id)} data-testid='section.navigate-to'>
54
+ <Icon icon='ph--arrow-square-out--regular' size={5} />
55
+ <span className='mis-2 grow'>{t('navigate to section label')}</span>
56
+ </DropdownMenu.Item>
57
+ ) : (
58
+ <CollapsiblePrimitive.Trigger asChild>
59
+ <DropdownMenu.Item>
60
+ <CaretDownUp className={getSize(5)} />
61
+ <span className='mis-2 grow'>{t('collapse label')}</span>
62
+ </DropdownMenu.Item>
63
+ </CollapsiblePrimitive.Trigger>
64
+ )}
65
+ <DropdownMenu.Item onClick={() => onAdd(id, 'before')} data-testid='section.add-before'>
66
+ <Icon icon='ph--arrow-line-up--regular' size={5} />
67
+ <span className='mis-2 grow'>{t('add section before label')}</span>
68
+ </DropdownMenu.Item>
69
+ <DropdownMenu.Item onClick={() => onAdd(id, 'after')} data-testid='section.add-after'>
70
+ <Icon icon='ph--arrow-line-down--regular' size={5} />
71
+ <span className='mis-2 grow'>{t('add section after label')}</span>
72
+ </DropdownMenu.Item>
73
+ <DropdownMenu.Item onClick={() => onDelete(id)} data-testid='section.remove'>
74
+ <Icon icon='ph--trash--regular' size={5} />
75
+ <span className='mis-2 grow'>{t('remove section label')}</span>
76
+ </DropdownMenu.Item>
77
+ </DropdownMenu.Viewport>
78
+ <DropdownMenu.Arrow />
79
+ </DropdownMenu.Content>
80
+ </DropdownMenu.Portal>
81
+ </DropdownMenu.Root>
82
+ {view.collapsed ? (
83
+ <CollapsiblePrimitive.Trigger asChild>
84
+ <IconButton
85
+ iconOnly
86
+ tooltipZIndex='70'
87
+ variant='ghost'
88
+ label={t('expand label')}
89
+ icon='ph--caret-up-down--regular'
90
+ classNames={sectionActionDimensions}
91
+ />
92
+ </CollapsiblePrimitive.Trigger>
93
+ ) : (
94
+ <IconButton
95
+ iconOnly
96
+ tooltipZIndex='70'
97
+ variant='ghost'
98
+ onClick={() => onNavigate(id)}
99
+ label={t('navigate to section label')}
100
+ icon='ph--arrow-square-out--regular'
101
+ data-testid='section.navigate-to'
102
+ classNames={sectionActionDimensions}
103
+ />
104
+ )}
105
+ </div>
106
+ </StackItem.Heading>
107
+ <CollapsiblePrimitive.Content>
108
+ <Surface role='section' data={{ object }} limit={1} placeholder={<></>} />
109
+ </CollapsiblePrimitive.Content>
110
+ {view.collapsed && (
111
+ <StackItem.Content toolbar={false} classNames='attention-surface'>
112
+ <h2 className={mx('flex items-center p-4 font-medium is-full', textBlockWidth)}>{view.title}</h2>
113
+ </StackItem.Content>
114
+ )}
115
+ </StackItem.Root>
116
+ </CollapsiblePrimitive.Root>
117
+ );
118
+ };
package/src/meta.ts CHANGED
@@ -10,6 +10,9 @@ export const SECTION_IDENTIFIER = 'dxos.org/type/StackSection';
10
10
  export default {
11
11
  id: STACK_PLUGIN,
12
12
  // TODO(wittjosiah): Prevents "stacks" language from being exposed to users in settings panel.
13
+ // Conside renaming this to the collection plugin and trying to factor more collections logic from the space plugin.
13
14
  name: 'Collections',
15
+ description: 'View collections of objects within the deck.',
14
16
  icon: 'ph--stack-simple--regular',
17
+ source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-stack',
15
18
  } satisfies PluginMeta;
@@ -19,6 +19,15 @@ export default [
19
19
  'add section input placeholder': 'Find the type of section to add',
20
20
  'upload file label': 'Add file',
21
21
  'add section label': 'Add section',
22
+ 'empty stack message': 'Drag items into the stack.',
23
+ 'remove section label': 'Delete',
24
+ 'navigate to section label': 'Navigate to item',
25
+ 'untitled section title': 'Untitled section',
26
+ 'add section before label': 'Add before',
27
+ 'add section after label': 'Add after',
28
+ 'expand label': 'Expand',
29
+ 'collapse label': 'Collapse',
30
+ 'drag handle label': 'Press space or enter to begin dragging this item.',
22
31
  },
23
32
  },
24
33
  },
@@ -13,6 +13,23 @@ import type {
13
13
  TranslationsProvides,
14
14
  } from '@dxos/app-framework';
15
15
  import { type SchemaProvides } from '@dxos/plugin-client';
16
+ import { type CollectionType } from '@dxos/plugin-space/types';
17
+ import { type Label } from '@dxos/react-ui';
18
+ import { type StackItemSize } from '@dxos/react-ui-stack';
19
+
20
+ export type AddSectionPosition = 'before' | 'after' | 'beforeAll' | 'afterAll';
21
+
22
+ export type CollapsedSections = Record<string, boolean>;
23
+
24
+ export type CollectionItem = CollectionType['objects'][number];
25
+
26
+ export type StackSectionView = {
27
+ title?: string;
28
+ size?: StackItemSize;
29
+ height?: number;
30
+ collapsed?: boolean;
31
+ custom?: Record<string, any>;
32
+ };
16
33
 
17
34
  // TODO(wittjosiah): Creators/choosers likely aren't stack-specific.
18
35
  // Also distinct from graph actions though, output should be inserted into current view rather than navigated to.
@@ -24,6 +41,19 @@ type StackSectionAction = {
24
41
  icon: FC<IconProps>;
25
42
  };
26
43
 
44
+ export type StackSectionMetadata = {
45
+ icon?: string;
46
+ placeholder?: Label;
47
+ viewActions?: (item: CollectionItem) => StackSectionAction;
48
+ };
49
+
50
+ export type StackSectionItem = {
51
+ id: string;
52
+ object: CollectionType['objects'][number];
53
+ view: StackSectionView;
54
+ metadata: StackSectionMetadata;
55
+ };
56
+
27
57
  export type StackSectionCreator = StackSectionAction & {
28
58
  intent: Intent | Intent[];
29
59
  };