@dxos/plugin-stack 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.
Files changed (65) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +15 -0
  3. package/dist/lib/browser/StackMain-MDOKSAYA.mjs +276 -0
  4. package/dist/lib/browser/StackMain-MDOKSAYA.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-AYDYMPJK.mjs +19 -0
  6. package/dist/lib/browser/chunk-AYDYMPJK.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-R5KXWF2W.mjs +21 -0
  8. package/dist/lib/browser/chunk-R5KXWF2W.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +285 -0
  10. package/dist/lib/browser/index.mjs.map +7 -0
  11. package/dist/lib/browser/meta.json +1 -0
  12. package/dist/lib/browser/meta.mjs +11 -0
  13. package/dist/lib/browser/meta.mjs.map +7 -0
  14. package/dist/lib/browser/types/index.mjs +9 -0
  15. package/dist/lib/browser/types/index.mjs.map +7 -0
  16. package/dist/lib/node/StackMain-AXFLSTLI.cjs +295 -0
  17. package/dist/lib/node/StackMain-AXFLSTLI.cjs.map +7 -0
  18. package/dist/lib/node/chunk-EMD6A66G.cjs +44 -0
  19. package/dist/lib/node/chunk-EMD6A66G.cjs.map +7 -0
  20. package/dist/lib/node/chunk-I42JKRXR.cjs +53 -0
  21. package/dist/lib/node/chunk-I42JKRXR.cjs.map +7 -0
  22. package/dist/lib/node/index.cjs +298 -0
  23. package/dist/lib/node/index.cjs.map +7 -0
  24. package/dist/lib/node/meta.cjs +32 -0
  25. package/dist/lib/node/meta.cjs.map +7 -0
  26. package/dist/lib/node/meta.json +1 -0
  27. package/dist/lib/node/types/index.cjs +31 -0
  28. package/dist/lib/node/types/index.cjs.map +7 -0
  29. package/dist/types/src/StackPlugin.d.ts +4 -0
  30. package/dist/types/src/StackPlugin.d.ts.map +1 -0
  31. package/dist/types/src/components/AddSection.d.ts +6 -0
  32. package/dist/types/src/components/AddSection.d.ts.map +1 -0
  33. package/dist/types/src/components/AddSectionDialog.d.ts +14 -0
  34. package/dist/types/src/components/AddSectionDialog.d.ts.map +1 -0
  35. package/dist/types/src/components/StackMain.d.ts +9 -0
  36. package/dist/types/src/components/StackMain.d.ts.map +1 -0
  37. package/dist/types/src/components/StackSettings.d.ts +6 -0
  38. package/dist/types/src/components/StackSettings.d.ts.map +1 -0
  39. package/dist/types/src/components/index.d.ts +8 -0
  40. package/dist/types/src/components/index.d.ts.map +1 -0
  41. package/dist/types/src/index.d.ts +5 -0
  42. package/dist/types/src/index.d.ts.map +1 -0
  43. package/dist/types/src/meta.d.ts +12 -0
  44. package/dist/types/src/meta.d.ts.map +1 -0
  45. package/dist/types/src/translations.d.ts +30 -0
  46. package/dist/types/src/translations.d.ts.map +1 -0
  47. package/dist/types/src/types/index.d.ts +3 -0
  48. package/dist/types/src/types/index.d.ts.map +1 -0
  49. package/dist/types/src/types/stack.d.ts +26 -0
  50. package/dist/types/src/types/stack.d.ts.map +1 -0
  51. package/dist/types/src/types/types.d.ts +34 -0
  52. package/dist/types/src/types/types.d.ts.map +1 -0
  53. package/package.json +91 -0
  54. package/src/StackPlugin.tsx +101 -0
  55. package/src/components/AddSection.tsx +102 -0
  56. package/src/components/AddSectionDialog.tsx +130 -0
  57. package/src/components/StackMain.tsx +216 -0
  58. package/src/components/StackSettings.tsx +23 -0
  59. package/src/components/index.ts +11 -0
  60. package/src/index.ts +11 -0
  61. package/src/meta.tsx +17 -0
  62. package/src/translations.ts +26 -0
  63. package/src/types/index.ts +6 -0
  64. package/src/types/stack.ts +21 -0
  65. package/src/types/types.ts +47 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/types/index.ts"],"names":[],"mappings":"AAIA,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { S } from '@dxos/echo-schema';
2
+ export declare const SectionSchema: S.Struct<{
3
+ height: S.optional<typeof S.Number>;
4
+ custom: S.optional<S.Record$<typeof S.String, typeof S.Any>>;
5
+ }>;
6
+ declare const StackViewType_base: import("@dxos/echo-schema").AbstractTypedObject<{
7
+ sections: {
8
+ [x: string]: {
9
+ readonly height?: number | undefined;
10
+ readonly custom?: {
11
+ readonly [x: string]: any;
12
+ } | undefined;
13
+ };
14
+ };
15
+ } & {
16
+ id: string;
17
+ }, S.Struct.Encoded<{
18
+ sections: S.mutable<S.Record$<typeof S.String, S.Struct<{
19
+ height: S.optional<typeof S.Number>;
20
+ custom: S.optional<S.Record$<typeof S.String, typeof S.Any>>;
21
+ }>>>;
22
+ }>>;
23
+ export declare class StackViewType extends StackViewType_base {
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=stack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../../../../src/types/stack.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAe,MAAM,mBAAmB,CAAC;AAOnD,eAAO,MAAM,aAAa;;;EAKxB,CAAC;;;;;;;;;;;;;;;;;;AAEH,qBAAa,aAAc,SAAQ,kBAEjC;CAAG"}
@@ -0,0 +1,34 @@
1
+ import { type IconProps } from '@phosphor-icons/react';
2
+ import { type FC } from 'react';
3
+ import type { Intent, MetadataRecordsProvides, SettingsProvides, SurfaceProvides, TranslationsProvides } from '@dxos/app-framework';
4
+ import { type SchemaProvides } from '@dxos/plugin-client';
5
+ type StackSectionAction = {
6
+ id: string;
7
+ testId: string;
8
+ type: string | [string, {
9
+ ns: string;
10
+ }];
11
+ label: string | [string, {
12
+ ns: string;
13
+ }];
14
+ icon: FC<IconProps>;
15
+ };
16
+ export type StackSectionCreator = StackSectionAction & {
17
+ intent: Intent | Intent[];
18
+ };
19
+ export type StackProvides = {
20
+ stack: {
21
+ creators?: StackSectionCreator[];
22
+ };
23
+ };
24
+ export type StackState = {
25
+ creators: StackSectionCreator[];
26
+ };
27
+ export type StackSettingsProps = {
28
+ separation: boolean;
29
+ };
30
+ export type StackPluginProvides = SurfaceProvides & MetadataRecordsProvides & SettingsProvides<StackSettingsProps> & TranslationsProvides & SchemaProvides & {
31
+ stack: StackState;
32
+ };
33
+ export {};
34
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/types/types.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EACV,MAAM,EACN,uBAAuB,EACvB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,KAAK,kBAAkB,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxC,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG;IACrD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE;QACL,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;KAClC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzD,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAC/C,uBAAuB,GACvB,gBAAgB,CAAC,kBAAkB,CAAC,GACpC,oBAAoB,GACpB,cAAc,GAAG;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@dxos/plugin-stack",
3
+ "version": "0.6.8-main.046e6cf",
4
+ "description": "DXOS Surface plugin for Stack entities",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "exports": {
10
+ ".": {
11
+ "browser": "./dist/lib/browser/index.mjs",
12
+ "node": {
13
+ "default": "./dist/lib/node/index.cjs"
14
+ },
15
+ "types": "./dist/types/src/index.d.ts"
16
+ },
17
+ "./meta": {
18
+ "browser": "./dist/lib/browser/meta.mjs",
19
+ "node": {
20
+ "default": "./dist/lib/node/meta.cjs"
21
+ },
22
+ "types": "./dist/types/src/meta.d.ts"
23
+ },
24
+ "./types": {
25
+ "browser": "./dist/lib/browser/types/index.mjs",
26
+ "node": {
27
+ "default": "./dist/lib/node/types/index.cjs"
28
+ },
29
+ "types": "./dist/types/src/types/index.d.ts"
30
+ }
31
+ },
32
+ "types": "dist/types/src/index.d.ts",
33
+ "typesVersions": {
34
+ "*": {
35
+ "meta": [
36
+ "dist/types/src/meta.d.ts"
37
+ ],
38
+ "types": [
39
+ "dist/types/src/types/index.d.ts"
40
+ ]
41
+ }
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src"
46
+ ],
47
+ "dependencies": {
48
+ "@fluentui/react-tabster": "^9.19.0",
49
+ "@preact/signals-core": "^1.6.0",
50
+ "node-fetch": "^2.6.0",
51
+ "react-is": "~18.2.0",
52
+ "@dxos/app-framework": "0.6.8-main.046e6cf",
53
+ "@dxos/async": "0.6.8-main.046e6cf",
54
+ "@dxos/echo-schema": "0.6.8-main.046e6cf",
55
+ "@dxos/local-storage": "0.6.8-main.046e6cf",
56
+ "@dxos/invariant": "0.6.8-main.046e6cf",
57
+ "@dxos/plugin-client": "0.6.8-main.046e6cf",
58
+ "@dxos/plugin-graph": "0.6.8-main.046e6cf",
59
+ "@dxos/plugin-settings": "0.6.8-main.046e6cf",
60
+ "@dxos/plugin-space": "0.6.8-main.046e6cf",
61
+ "@dxos/react-client": "0.6.8-main.046e6cf",
62
+ "@dxos/react-ui-mosaic": "0.6.8-main.046e6cf",
63
+ "@dxos/plugin-ipfs": "0.6.8-main.046e6cf",
64
+ "@dxos/react-ui-searchlist": "0.6.8-main.046e6cf",
65
+ "@dxos/react-ui-stack": "0.6.8-main.046e6cf",
66
+ "@dxos/util": "0.6.8-main.046e6cf"
67
+ },
68
+ "devDependencies": {
69
+ "@babel/core": "^7.18.13",
70
+ "@phosphor-icons/react": "^2.1.5",
71
+ "@types/react": "~18.2.0",
72
+ "@types/react-dom": "~18.2.0",
73
+ "react": "~18.2.0",
74
+ "react-dom": "~18.2.0",
75
+ "vite": "^5.3.4",
76
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
77
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf",
78
+ "@dxos/storybook-utils": "0.6.8-main.046e6cf"
79
+ },
80
+ "optionalDependencies": {
81
+ "@babel/core": "^7.18.13",
82
+ "@phosphor-icons/react": "^2.1.5",
83
+ "react": "^18.0.0",
84
+ "react-dom": "^18.0.0",
85
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf",
86
+ "@dxos/react-ui": "0.6.8-main.046e6cf"
87
+ },
88
+ "publishConfig": {
89
+ "access": "public"
90
+ }
91
+ }
@@ -0,0 +1,101 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { StackSimple, type IconProps } from '@phosphor-icons/react';
6
+ import React from 'react';
7
+
8
+ import { type Plugin, type PluginDefinition } from '@dxos/app-framework';
9
+ import { create } from '@dxos/echo-schema';
10
+ import { type EchoReactiveObject } from '@dxos/echo-schema';
11
+ import { LocalStorageStore } from '@dxos/local-storage';
12
+ import { CollectionType } from '@dxos/plugin-space/types';
13
+ import { Main } from '@dxos/react-ui';
14
+ import { baseSurface, topbarBlockPaddingStart, bottombarBlockPaddingEnd } from '@dxos/react-ui-theme';
15
+
16
+ import { StackMain, StackSettings, AddSectionDialog, dataHasAddSectionDialogProps } from './components';
17
+ import meta, { SECTION_IDENTIFIER, STACK_PLUGIN } from './meta';
18
+ import translations from './translations';
19
+ import { StackViewType } from './types';
20
+ import { type StackPluginProvides, type StackProvides, type StackState, type StackSettingsProps } from './types';
21
+
22
+ export const StackPlugin = (): PluginDefinition<StackPluginProvides> => {
23
+ const settings = new LocalStorageStore<StackSettingsProps>(STACK_PLUGIN, { separation: true });
24
+ const stackState = create<StackState>({ creators: [] });
25
+
26
+ return {
27
+ meta,
28
+ ready: async (plugins) => {
29
+ settings.prop({ key: 'separation', type: LocalStorageStore.bool() });
30
+
31
+ for (const plugin of plugins) {
32
+ if (plugin.meta.id === STACK_PLUGIN) {
33
+ continue;
34
+ }
35
+
36
+ if (Array.isArray((plugin as Plugin<StackProvides>).provides?.stack?.creators)) {
37
+ stackState.creators.push(...((plugin as Plugin<StackProvides>).provides.stack.creators ?? []));
38
+ }
39
+ }
40
+ },
41
+ provides: {
42
+ settings: settings.values,
43
+ metadata: {
44
+ records: {
45
+ [StackViewType.typename]: {
46
+ placeholder: ['stack title placeholder', { ns: STACK_PLUGIN }],
47
+ icon: (props: IconProps) => <StackSimple {...props} />,
48
+ iconSymbol: 'ph--stack-simple--regular',
49
+ },
50
+ [SECTION_IDENTIFIER]: {
51
+ parse: (section: { object: EchoReactiveObject<any> }, type: string) => {
52
+ switch (type) {
53
+ case 'node':
54
+ // TODO(wittjosiah): Remove cast.
55
+ return { id: section.object.id, label: section.object.title, data: section.object };
56
+ case 'object':
57
+ return section.object;
58
+ case 'view-object':
59
+ return section;
60
+ }
61
+ },
62
+ },
63
+ },
64
+ },
65
+ translations,
66
+ echo: {
67
+ schema: [StackViewType],
68
+ },
69
+ surface: {
70
+ component: ({ data, role }) => {
71
+ switch (data.component) {
72
+ case `${STACK_PLUGIN}/AddSectionDialog`:
73
+ return dataHasAddSectionDialogProps(data) ? <AddSectionDialog {...data.subject} /> : null;
74
+ }
75
+
76
+ const primary = data.active ?? data.object;
77
+ switch (role) {
78
+ case 'main':
79
+ return primary instanceof CollectionType ? (
80
+ <Main.Content bounce classNames={[baseSurface, topbarBlockPaddingStart, bottombarBlockPaddingEnd]}>
81
+ <StackMain collection={primary} separation={settings.values.separation} />
82
+ </Main.Content>
83
+ ) : null;
84
+ case 'article':
85
+ return primary instanceof CollectionType ? (
86
+ <div role='none' className='row-span-2 overflow-auto'>
87
+ <StackMain collection={primary} separation={settings.values.separation} />
88
+ </div>
89
+ ) : null;
90
+ case 'settings': {
91
+ return data.plugin === meta.id ? <StackSettings settings={settings.values} /> : null;
92
+ }
93
+ default:
94
+ return null;
95
+ }
96
+ },
97
+ },
98
+ stack: stackState,
99
+ },
100
+ };
101
+ };
@@ -0,0 +1,102 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { useArrowNavigationGroup } from '@fluentui/react-tabster';
6
+ import { type IconProps } from '@phosphor-icons/react';
7
+ import React, { type FC, type KeyboardEvent, type MouseEvent, useCallback } from 'react';
8
+
9
+ import { usePlugin, useIntentDispatcher } from '@dxos/app-framework';
10
+ import { type CollectionType } from '@dxos/plugin-space/types';
11
+ import { toLocalizedString, useTranslation } from '@dxos/react-ui';
12
+ import { getSize, mx } from '@dxos/react-ui-theme';
13
+
14
+ import { STACK_PLUGIN } from '../meta';
15
+ import { StackViewType, type StackSectionCreator, type StackPluginProvides } from '../types';
16
+
17
+ const CreatorTile = ({
18
+ Icon,
19
+ label,
20
+ testId,
21
+ handleAdd,
22
+ }: {
23
+ Icon: FC<IconProps>;
24
+ testId: string;
25
+ label: string;
26
+ handleAdd: () => void;
27
+ }) => {
28
+ const onClick = useCallback((_: MouseEvent<HTMLDivElement>) => handleAdd(), [handleAdd]);
29
+ const onKeyDown = useCallback(
30
+ (event: KeyboardEvent<HTMLDivElement>) => {
31
+ if (event.key === 'Enter' || event.key === ' ') {
32
+ event.preventDefault();
33
+ handleAdd();
34
+ }
35
+ },
36
+ [handleAdd],
37
+ );
38
+
39
+ return (
40
+ <div
41
+ role='button'
42
+ aria-label={label}
43
+ tabIndex={0}
44
+ className={mx(
45
+ 'flex items-center w-[10rem] h-16 overflow-hidden',
46
+ 'border rounded-md separator-separator shadow-sm hover:surface-hover cursor-pointer',
47
+ )}
48
+ onClick={onClick}
49
+ onKeyDown={onKeyDown}
50
+ data-testid={testId}
51
+ >
52
+ <div className='flex h-full w-12 items-center justify-center bg-neutral-800 shrink-0 border-r separator-separator'>
53
+ <Icon weight='thin' className={getSize(6)} />
54
+ </div>
55
+ <div className='overflow-hidden items-center p-2 px-3'>
56
+ <span className='capitalize'>{label}</span>
57
+ </div>
58
+ </div>
59
+ );
60
+ };
61
+
62
+ export const AddSection = ({ collection }: { collection: CollectionType }) => {
63
+ const domAttributes = useArrowNavigationGroup({ axis: 'grid' });
64
+ const dispatch = useIntentDispatcher();
65
+ const { t } = useTranslation(STACK_PLUGIN);
66
+
67
+ const stackPlugin = usePlugin<StackPluginProvides>(STACK_PLUGIN);
68
+ const stackCreators = stackPlugin?.provides?.stack.creators ?? [];
69
+
70
+ const handleAdd = useCallback(
71
+ async (creator: StackSectionCreator) => {
72
+ const { data: section } = (await dispatch(creator.intent)) ?? {};
73
+ collection.objects = [section];
74
+ const stack = collection.views[StackViewType.typename];
75
+ if (stack) {
76
+ stack.sections[section.id] = {};
77
+ }
78
+ },
79
+ [collection, dispatch],
80
+ );
81
+
82
+ if (stackCreators.length === 0) {
83
+ return null;
84
+ }
85
+
86
+ return (
87
+ <div {...domAttributes} role='none' className='flex flex-wrap items-center p-8 gap-4'>
88
+ {stackCreators.map((creator) => {
89
+ const { id, type, icon, testId } = creator;
90
+ return (
91
+ <CreatorTile
92
+ key={id}
93
+ label={toLocalizedString(type, t)}
94
+ testId={testId}
95
+ Icon={icon}
96
+ handleAdd={() => handleAdd(creator)}
97
+ />
98
+ );
99
+ })}
100
+ </div>
101
+ );
102
+ };
@@ -0,0 +1,130 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { FilePlus } from '@phosphor-icons/react';
6
+ import React, { useCallback, useRef, useState } from 'react';
7
+
8
+ import { usePlugin, useIntent, LayoutAction, useResolvePlugin, parseFileManagerPlugin } from '@dxos/app-framework';
9
+ import { type EchoReactiveObject, create } from '@dxos/echo-schema';
10
+ import { FileType } from '@dxos/plugin-ipfs/types';
11
+ import { type CollectionType } from '@dxos/plugin-space/types';
12
+ import { getSpace } from '@dxos/react-client/echo';
13
+ import { Dialog, toLocalizedString, useTranslation } from '@dxos/react-ui';
14
+ import { Path } from '@dxos/react-ui-mosaic';
15
+ import { SearchList } from '@dxos/react-ui-searchlist';
16
+ import { type AddSectionPosition } from '@dxos/react-ui-stack';
17
+ import { getSize, mx } from '@dxos/react-ui-theme';
18
+ import { nonNullable } from '@dxos/util';
19
+
20
+ import { STACK_PLUGIN } from '../meta';
21
+ import { StackViewType, type StackPluginProvides } from '../types';
22
+
23
+ type AddSectionDialogProps = { path?: string; position: AddSectionPosition; collection: CollectionType };
24
+
25
+ export const dataHasAddSectionDialogProps = (data: any): data is { subject: AddSectionDialogProps } => {
26
+ return (
27
+ 'subject' in data &&
28
+ typeof data.subject === 'object' &&
29
+ !!data.subject &&
30
+ 'position' in data.subject &&
31
+ typeof data.subject.position === 'string' &&
32
+ 'collection' in data.subject
33
+ );
34
+ };
35
+
36
+ export const AddSectionDialog = ({ path, position, collection }: AddSectionDialogProps) => {
37
+ const { t } = useTranslation(STACK_PLUGIN);
38
+ const stackPlugin = usePlugin<StackPluginProvides>(STACK_PLUGIN);
39
+ const fileManagerPlugin = useResolvePlugin(parseFileManagerPlugin);
40
+ const fileRef = useRef<HTMLInputElement | null>(null);
41
+ const { dispatch } = useIntent();
42
+ const space = getSpace(collection);
43
+ const [pending, setPending] = useState<boolean>(false);
44
+ const [uploadError, setUploadError] = useState<Error | undefined>();
45
+
46
+ const handleAdd = useCallback(
47
+ (sectionObject: EchoReactiveObject<any>) => {
48
+ const index =
49
+ position === 'beforeAll'
50
+ ? 0
51
+ : position === 'afterAll'
52
+ ? collection.objects.length
53
+ : collection.objects.filter(nonNullable).findIndex((section) => section.id === Path.last(path!));
54
+
55
+ collection.objects.splice(index + (position === 'after' ? 1 : 0), 0, sectionObject);
56
+ const stack = collection.views[StackViewType.typename];
57
+ if (stack) {
58
+ stack.sections[sectionObject.id] = {};
59
+ }
60
+ setPending(false);
61
+ void dispatch?.({ action: LayoutAction.SET_LAYOUT, data: { element: 'dialog', state: false } });
62
+ },
63
+ [collection, path, position, dispatch],
64
+ );
65
+
66
+ const handleFileUpload =
67
+ fileManagerPlugin?.provides.file.upload && space
68
+ ? async (file: File) => {
69
+ const filename = file.name.split('.')[0];
70
+ try {
71
+ const info = await fileManagerPlugin.provides.file.upload?.(file, space);
72
+ if (info) {
73
+ const obj = create(FileType, { type: file.type, name: filename, filename, cid: info.cid });
74
+ handleAdd(obj);
75
+ }
76
+ } catch (err: any) {
77
+ setUploadError(err);
78
+ }
79
+ }
80
+ : undefined;
81
+
82
+ return (
83
+ <Dialog.Content>
84
+ <Dialog.Title>{t(`add section ${position} dialog title`)}</Dialog.Title>
85
+ <SearchList.Root label={t('add section input placeholder')}>
86
+ <SearchList.Input placeholder={t('add section input placeholder')} classNames='pli-3' disabled={pending} />
87
+ <SearchList.Content classNames='min-bs-24 bs-fit max-bs-48 overflow-auto' {...(pending && { inert: 'true' })}>
88
+ {stackPlugin?.provides?.stack.creators?.map(({ id, icon: Icon, label: propsLabel, testId, intent }) => {
89
+ const label = toLocalizedString(propsLabel, t);
90
+ return (
91
+ <SearchList.Item
92
+ value={label}
93
+ key={id}
94
+ onSelect={async () => {
95
+ setPending(true);
96
+ const { data: nextSection } = (await dispatch({ ...intent, data: { space } })) ?? {};
97
+ handleAdd(nextSection);
98
+ }}
99
+ classNames='flex items-center gap-2 pli-2'
100
+ data-testid={testId}
101
+ >
102
+ {Icon && <Icon className={mx(getSize(4), 'shrink-0')} />}
103
+ <span className='grow truncate'>{label}</span>
104
+ </SearchList.Item>
105
+ );
106
+ })}
107
+ {handleFileUpload && (
108
+ <SearchList.Item
109
+ value={t('upload file label')}
110
+ classNames='flex items-center gap-2 pli-2'
111
+ onSelect={() => fileRef.current?.click()}
112
+ >
113
+ <FilePlus />
114
+ <span className='grow truncate'>{t('upload file label')}</span>
115
+ </SearchList.Item>
116
+ )}
117
+ {uploadError && <div className='text-error-500'>{uploadError.message}</div>}
118
+ </SearchList.Content>
119
+ </SearchList.Root>
120
+ {handleFileUpload && (
121
+ <input
122
+ type='file'
123
+ className='sr-only'
124
+ ref={fileRef}
125
+ onChange={({ target: { files } }) => files && handleFileUpload(files[0])}
126
+ />
127
+ )}
128
+ </Dialog.Content>
129
+ );
130
+ };