@dxos/plugin-wnfs 0.8.4-main.c85a9c8dae → 0.8.4-main.d9fc60f731
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 +102 -5
- package/PLUGIN.mdl +371 -0
- package/README.md +2 -2
- package/dist/lib/neutral/WnfsPlugin.mjs +51 -0
- package/dist/lib/neutral/WnfsPlugin.mjs.map +7 -0
- package/dist/lib/neutral/WnfsPlugin.node.mjs +12 -0
- package/dist/lib/neutral/WnfsPlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/WnfsPlugin.workerd.mjs +12 -0
- package/dist/lib/neutral/WnfsPlugin.workerd.mjs.map +7 -0
- package/dist/lib/neutral/backend-4HPTHS33.mjs +47 -0
- package/dist/lib/neutral/backend-4HPTHS33.mjs.map +7 -0
- package/dist/lib/{browser/blockstore-Q66HQJ4Q.mjs → neutral/blockstore-U6SPNL2C.mjs} +7 -8
- package/dist/lib/neutral/blockstore-U6SPNL2C.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +13 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-KUQHRT52.mjs +39 -0
- package/dist/lib/neutral/chunk-KUQHRT52.mjs.map +7 -0
- package/dist/lib/{browser/chunk-UC4HMNZZ.mjs → neutral/chunk-L33MOD23.mjs} +80 -30
- package/dist/lib/neutral/chunk-L33MOD23.mjs.map +7 -0
- package/dist/lib/neutral/chunk-QODCKFTY.mjs +19 -0
- package/dist/lib/neutral/chunk-QODCKFTY.mjs.map +7 -0
- package/dist/lib/neutral/helpers/index.mjs +26 -0
- package/dist/lib/neutral/helpers/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +36 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +8 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +12 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +17 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +8 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/lib/neutral/url-resolver-BIRH7WHL.mjs +47 -0
- package/dist/lib/neutral/url-resolver-BIRH7WHL.mjs.map +7 -0
- package/dist/types/src/WnfsPlugin.d.ts +1 -0
- package/dist/types/src/WnfsPlugin.d.ts.map +1 -1
- package/dist/types/src/WnfsPlugin.node.d.ts +4 -0
- package/dist/types/src/WnfsPlugin.node.d.ts.map +1 -0
- package/dist/types/src/WnfsPlugin.test.d.ts +2 -0
- package/dist/types/src/WnfsPlugin.test.d.ts.map +1 -0
- package/dist/types/src/WnfsPlugin.workerd.d.ts +4 -0
- package/dist/types/src/WnfsPlugin.workerd.d.ts.map +1 -0
- package/dist/types/src/annotations.d.ts +7 -0
- package/dist/types/src/annotations.d.ts.map +1 -0
- package/dist/types/src/blockstore.d.ts.map +1 -1
- package/dist/types/src/capabilities/backend.d.ts +7 -0
- package/dist/types/src/capabilities/backend.d.ts.map +1 -0
- package/dist/types/src/capabilities/blockstore.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +5 -5
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/url-resolver.d.ts +7 -0
- package/dist/types/src/capabilities/url-resolver.d.ts.map +1 -0
- package/dist/types/src/helpers/common.d.ts.map +1 -1
- package/dist/types/src/helpers/load.d.ts +1 -1
- package/dist/types/src/helpers/load.d.ts.map +1 -1
- package/dist/types/src/helpers/upload.d.ts +1 -1
- package/dist/types/src/helpers/upload.d.ts.map +1 -1
- package/dist/types/src/helpers/wnfs-url.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +3 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/plugin.d.ts +3 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +2 -15
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/WnfsCapabilities.d.ts +11 -0
- package/dist/types/src/types/WnfsCapabilities.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -3
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +77 -48
- package/src/WnfsPlugin.node.ts +11 -0
- package/src/WnfsPlugin.test.ts +20 -0
- package/src/WnfsPlugin.tsx +16 -41
- package/src/WnfsPlugin.workerd.ts +11 -0
- package/src/annotations.ts +16 -0
- package/src/blockstore.ts +1 -1
- package/src/capabilities/backend.ts +43 -0
- package/src/capabilities/{blockstore/blockstore.ts → blockstore.ts} +3 -2
- package/src/capabilities/index.ts +14 -5
- package/src/capabilities/url-resolver.ts +44 -0
- package/src/helpers/load.ts +33 -19
- package/src/helpers/upload.ts +13 -4
- package/src/index.ts +3 -2
- package/src/meta.ts +22 -6
- package/src/plugin.ts +9 -0
- package/src/translations.ts +2 -14
- package/src/types/WnfsCapabilities.ts +18 -0
- package/src/types/index.ts +1 -3
- package/src/vite-env.d.ts +10 -0
- package/dist/lib/browser/FileContainer-EILNV5LV.mjs +0 -74
- package/dist/lib/browser/FileContainer-EILNV5LV.mjs.map +0 -7
- package/dist/lib/browser/blockstore-Q66HQJ4Q.mjs.map +0 -7
- package/dist/lib/browser/chunk-BXGCJRL2.mjs +0 -155
- package/dist/lib/browser/chunk-BXGCJRL2.mjs.map +0 -7
- package/dist/lib/browser/chunk-HXVPBJ3A.mjs +0 -56
- package/dist/lib/browser/chunk-HXVPBJ3A.mjs.map +0 -7
- package/dist/lib/browser/chunk-UC4HMNZZ.mjs.map +0 -7
- package/dist/lib/browser/file-uploader-QL4DTOMM.mjs +0 -35
- package/dist/lib/browser/file-uploader-QL4DTOMM.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -117
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/markdown-AYCVG6TJ.mjs +0 -201
- package/dist/lib/browser/markdown-AYCVG6TJ.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/operation-resolver-YYWUF6EY.mjs +0 -99
- package/dist/lib/browser/operation-resolver-YYWUF6EY.mjs.map +0 -7
- package/dist/lib/browser/react-surface-3LKVDPH7.mjs +0 -60
- package/dist/lib/browser/react-surface-3LKVDPH7.mjs.map +0 -7
- package/dist/lib/browser/types/index.mjs +0 -13
- package/dist/types/src/capabilities/blockstore/blockstore.d.ts.map +0 -1
- package/dist/types/src/capabilities/blockstore/index.d.ts +0 -3
- package/dist/types/src/capabilities/blockstore/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/file-uploader/file-uploader.d.ts +0 -6
- package/dist/types/src/capabilities/file-uploader/file-uploader.d.ts.map +0 -1
- package/dist/types/src/capabilities/file-uploader/index.d.ts +0 -3
- package/dist/types/src/capabilities/file-uploader/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/markdown/index.d.ts +0 -3
- package/dist/types/src/capabilities/markdown/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/markdown/markdown.d.ts +0 -5
- package/dist/types/src/capabilities/markdown/markdown.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts +0 -5
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
- package/dist/types/src/components/FileInput.d.ts +0 -8
- package/dist/types/src/components/FileInput.d.ts.map +0 -1
- package/dist/types/src/components/FilePreview.d.ts +0 -11
- package/dist/types/src/components/FilePreview.d.ts.map +0 -1
- package/dist/types/src/components/index.d.ts +0 -3
- package/dist/types/src/components/index.d.ts.map +0 -1
- package/dist/types/src/containers/FileContainer/FileContainer.d.ts +0 -7
- package/dist/types/src/containers/FileContainer/FileContainer.d.ts.map +0 -1
- package/dist/types/src/containers/FileContainer/index.d.ts +0 -3
- package/dist/types/src/containers/FileContainer/index.d.ts.map +0 -1
- package/dist/types/src/containers/index.d.ts +0 -3
- package/dist/types/src/containers/index.d.ts.map +0 -1
- package/dist/types/src/extensions/image.d.ts +0 -14
- package/dist/types/src/extensions/image.d.ts.map +0 -1
- package/dist/types/src/extensions/image.stories.d.ts +0 -11
- package/dist/types/src/extensions/image.stories.d.ts.map +0 -1
- package/dist/types/src/extensions/index.d.ts +0 -2
- package/dist/types/src/extensions/index.d.ts.map +0 -1
- package/dist/types/src/types/File.d.ts +0 -16
- package/dist/types/src/types/File.d.ts.map +0 -1
- package/dist/types/src/types/capabilities.d.ts +0 -13
- package/dist/types/src/types/capabilities.d.ts.map +0 -1
- package/dist/types/src/types/types.d.ts +0 -112
- package/dist/types/src/types/types.d.ts.map +0 -1
- package/src/capabilities/blockstore/index.ts +0 -7
- package/src/capabilities/file-uploader/file-uploader.ts +0 -34
- package/src/capabilities/file-uploader/index.ts +0 -7
- package/src/capabilities/markdown/index.ts +0 -7
- package/src/capabilities/markdown/markdown.ts +0 -33
- package/src/capabilities/operation-resolver/index.ts +0 -7
- package/src/capabilities/operation-resolver/operation-resolver.ts +0 -62
- package/src/capabilities/react-surface/index.ts +0 -7
- package/src/capabilities/react-surface/react-surface.tsx +0 -51
- package/src/components/FileInput.tsx +0 -47
- package/src/components/FilePreview.tsx +0 -23
- package/src/components/index.ts +0 -6
- package/src/containers/FileContainer/FileContainer.tsx +0 -58
- package/src/containers/FileContainer/index.ts +0 -7
- package/src/containers/index.ts +0 -7
- package/src/extensions/image.stories.tsx +0 -104
- package/src/extensions/image.tsx +0 -203
- package/src/extensions/index.ts +0 -5
- package/src/types/File.ts +0 -24
- package/src/types/capabilities.ts +0 -18
- package/src/types/types.ts +0 -81
- /package/dist/lib/{browser/types/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
- /package/dist/types/src/capabilities/{blockstore/blockstore.d.ts → blockstore.d.ts} +0 -0
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import * as Effect from 'effect/Effect';
|
|
6
|
-
import type * as Schema from 'effect/Schema';
|
|
7
|
-
import React, { useCallback } from 'react';
|
|
8
|
-
|
|
9
|
-
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
10
|
-
import { Surface } from '@dxos/app-framework/ui';
|
|
11
|
-
import { Obj } from '@dxos/echo';
|
|
12
|
-
import { findAnnotation } from '@dxos/effect';
|
|
13
|
-
import { type FormFieldComponentProps } from '@dxos/react-ui-form';
|
|
14
|
-
|
|
15
|
-
import { FileInput } from '../../components';
|
|
16
|
-
import { FileContainer } from '../../containers';
|
|
17
|
-
import { meta } from '../../meta';
|
|
18
|
-
import { WnfsAction, WnfsFile } from '../../types';
|
|
19
|
-
|
|
20
|
-
export default Capability.makeModule(() =>
|
|
21
|
-
Effect.succeed(
|
|
22
|
-
Capability.contributes(Capabilities.ReactSurface, [
|
|
23
|
-
Surface.create({
|
|
24
|
-
id: `${meta.id}/article`,
|
|
25
|
-
role: ['article', 'section', 'slide'],
|
|
26
|
-
filter: (data): data is { subject: WnfsFile.File } => Obj.instanceOf(WnfsFile.File, data.subject),
|
|
27
|
-
component: ({ data, role }) => <FileContainer role={role} subject={data.subject} />,
|
|
28
|
-
}),
|
|
29
|
-
Surface.create({
|
|
30
|
-
id: `${meta.id}/create-form`,
|
|
31
|
-
role: 'form-input',
|
|
32
|
-
filter: (data): data is { prop: string; schema: Schema.Schema.Any } => {
|
|
33
|
-
const annotation = findAnnotation<boolean>(
|
|
34
|
-
(data.schema as Schema.Schema.All).ast,
|
|
35
|
-
WnfsAction.UploadAnnotationId,
|
|
36
|
-
);
|
|
37
|
-
return !!annotation;
|
|
38
|
-
},
|
|
39
|
-
component: ({ data: { schema }, ...props }) => {
|
|
40
|
-
const inputProps = props as unknown as FormFieldComponentProps;
|
|
41
|
-
const handleChange = useCallback(
|
|
42
|
-
(file: File) => inputProps.onValueChange?.(inputProps.type, file),
|
|
43
|
-
[inputProps.type, inputProps.onValueChange],
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
return <FileInput schema={schema} onChange={handleChange} />;
|
|
47
|
-
},
|
|
48
|
-
}),
|
|
49
|
-
]),
|
|
50
|
-
),
|
|
51
|
-
);
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import type * as Schema from 'effect/Schema';
|
|
6
|
-
import React, { useCallback } from 'react';
|
|
7
|
-
import { useDropzone } from 'react-dropzone';
|
|
8
|
-
|
|
9
|
-
import { findAnnotation } from '@dxos/effect';
|
|
10
|
-
import { useTranslation } from '@dxos/react-ui';
|
|
11
|
-
import { mx } from '@dxos/ui-theme';
|
|
12
|
-
|
|
13
|
-
import { meta } from '../meta';
|
|
14
|
-
import { WnfsAction } from '../types';
|
|
15
|
-
|
|
16
|
-
export type FileInputProps = {
|
|
17
|
-
schema: Schema.Schema.Any;
|
|
18
|
-
onChange: (file: File) => void;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const FileInput = ({ schema, onChange }: FileInputProps) => {
|
|
22
|
-
const { t } = useTranslation(meta.id);
|
|
23
|
-
const accept = findAnnotation<Record<string, string[]>>(schema.ast, WnfsAction.UploadAnnotationId);
|
|
24
|
-
|
|
25
|
-
const onDropAccepted = useCallback((files: File[]) => onChange?.(files[0]), [onChange]);
|
|
26
|
-
|
|
27
|
-
const { acceptedFiles, getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({
|
|
28
|
-
multiple: false,
|
|
29
|
-
accept,
|
|
30
|
-
onDropAccepted,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<div
|
|
35
|
-
{...getRootProps()}
|
|
36
|
-
className={mx(
|
|
37
|
-
'flex flex-col items-center p-8 border border-separator',
|
|
38
|
-
isFocused && 'focus-ring',
|
|
39
|
-
isDragAccept && 'bg-attention-surface',
|
|
40
|
-
isDragReject && 'border-rose-fill',
|
|
41
|
-
)}
|
|
42
|
-
>
|
|
43
|
-
<input {...getInputProps()} />
|
|
44
|
-
{acceptedFiles[0] ? <p>{acceptedFiles[0].name}</p> : <p>{t('file input placeholder')}</p>}
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import React from 'react';
|
|
6
|
-
|
|
7
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
|
-
import { mx } from '@dxos/ui-theme';
|
|
9
|
-
|
|
10
|
-
export type FilePreviewProps = ThemedClassName<{ type: string; url: string }>;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* File/content preview iframe.
|
|
14
|
-
*/
|
|
15
|
-
export const FilePreview = ({ type, url, classNames }: FilePreviewProps) => {
|
|
16
|
-
if (type.startsWith('image/')) {
|
|
17
|
-
return <img className={mx('w-full h-full object-contain', classNames)} src={url} />;
|
|
18
|
-
} else if (type.startsWith('video/')) {
|
|
19
|
-
return <video className={mx('w-full h-full object-contain', classNames)} src={url} controls />;
|
|
20
|
-
} else {
|
|
21
|
-
return <iframe className={mx('w-full h-full overflow-auto', classNames)} src={url} />;
|
|
22
|
-
}
|
|
23
|
-
};
|
package/src/components/index.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import React, { useState } from 'react';
|
|
6
|
-
|
|
7
|
-
import { useCapability } from '@dxos/app-framework/ui';
|
|
8
|
-
import { type SurfaceComponentProps } from '@dxos/app-toolkit/ui';
|
|
9
|
-
import { invariant } from '@dxos/invariant';
|
|
10
|
-
import { getSpace } from '@dxos/react-client/echo';
|
|
11
|
-
import { Panel, useAsyncEffect } from '@dxos/react-ui';
|
|
12
|
-
|
|
13
|
-
import { FilePreview } from '../../components';
|
|
14
|
-
import { filePath, getBlobUrl, loadWnfs, wnfsUrl } from '../../helpers';
|
|
15
|
-
import { WnfsCapabilities, type WnfsFile } from '../../types';
|
|
16
|
-
|
|
17
|
-
export type FileContainerProps = SurfaceComponentProps<WnfsFile.File>;
|
|
18
|
-
|
|
19
|
-
export const FileContainer = ({ role, subject: file }: FileContainerProps) => {
|
|
20
|
-
const blockstore = useCapability(WnfsCapabilities.Blockstore);
|
|
21
|
-
const instances = useCapability(WnfsCapabilities.Instances);
|
|
22
|
-
const [blobUrl, setBlobUrl] = useState<string>();
|
|
23
|
-
|
|
24
|
-
useAsyncEffect(async () => {
|
|
25
|
-
const space = getSpace(file);
|
|
26
|
-
invariant(space);
|
|
27
|
-
const { directory, forest } = await loadWnfs({
|
|
28
|
-
blockstore,
|
|
29
|
-
instances,
|
|
30
|
-
space,
|
|
31
|
-
});
|
|
32
|
-
const path = filePath(file.cid.toString(), space);
|
|
33
|
-
const url = await getBlobUrl({
|
|
34
|
-
wnfsUrl: wnfsUrl(path),
|
|
35
|
-
blockstore,
|
|
36
|
-
directory,
|
|
37
|
-
forest,
|
|
38
|
-
type: file.type,
|
|
39
|
-
});
|
|
40
|
-
setBlobUrl(url);
|
|
41
|
-
}, [file]);
|
|
42
|
-
|
|
43
|
-
if (!blobUrl) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<Panel.Root role={role} className='dx-article'>
|
|
49
|
-
<Panel.Content asChild>
|
|
50
|
-
<Panel.Content asChild>
|
|
51
|
-
<FilePreview type={file.type} url={blobUrl} />
|
|
52
|
-
</Panel.Content>
|
|
53
|
-
</Panel.Content>
|
|
54
|
-
</Panel.Root>
|
|
55
|
-
);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export default FileContainer;
|
package/src/containers/index.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
-
import type { Blockstore } from 'interface-blockstore';
|
|
7
|
-
import React, { type ChangeEvent, useEffect, useState } from 'react';
|
|
8
|
-
|
|
9
|
-
import { faker } from '@dxos/random';
|
|
10
|
-
import { useSpace } from '@dxos/react-client/echo';
|
|
11
|
-
import { withClientProvider } from '@dxos/react-client/testing';
|
|
12
|
-
import { useThemeContext } from '@dxos/react-ui';
|
|
13
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
14
|
-
import { useTextEditor } from '@dxos/react-ui-editor';
|
|
15
|
-
import {
|
|
16
|
-
createBasicExtensions,
|
|
17
|
-
createMarkdownExtensions,
|
|
18
|
-
createThemeExtensions,
|
|
19
|
-
decorateMarkdown,
|
|
20
|
-
processEditorPayload,
|
|
21
|
-
} from '@dxos/ui-editor';
|
|
22
|
-
|
|
23
|
-
import { create as createBlockstore } from '../blockstore';
|
|
24
|
-
import { upload } from '../helpers';
|
|
25
|
-
|
|
26
|
-
import { image } from './image';
|
|
27
|
-
|
|
28
|
-
faker.seed(1);
|
|
29
|
-
const initialValue = faker.lorem.paragraphs(100);
|
|
30
|
-
const instances = {};
|
|
31
|
-
|
|
32
|
-
const DefaultStory = () => {
|
|
33
|
-
const { themeMode } = useThemeContext();
|
|
34
|
-
const space = useSpace();
|
|
35
|
-
const [blockstore, setBlockstore] = useState<Blockstore>();
|
|
36
|
-
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
if (space) {
|
|
39
|
-
console.log('SPACE ID', space.id);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const create = async () => {
|
|
43
|
-
// NOTE: We don't pass in the URL of the Blob service, so we don't use it.
|
|
44
|
-
const bs = createBlockstore();
|
|
45
|
-
await bs.open();
|
|
46
|
-
setBlockstore(bs);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
create().catch(console.error);
|
|
50
|
-
}, [space]);
|
|
51
|
-
|
|
52
|
-
const { parentRef, view, focusAttributes } = useTextEditor(
|
|
53
|
-
() => ({
|
|
54
|
-
initialValue,
|
|
55
|
-
extensions: [
|
|
56
|
-
createBasicExtensions(),
|
|
57
|
-
createMarkdownExtensions(),
|
|
58
|
-
createThemeExtensions({ themeMode, syntaxHighlighting: true }),
|
|
59
|
-
blockstore && space ? [image({ blockstore, instances, space })] : [],
|
|
60
|
-
decorateMarkdown(),
|
|
61
|
-
],
|
|
62
|
-
}),
|
|
63
|
-
[themeMode, blockstore, space],
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const onFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
|
67
|
-
const file = event.target.files?.[0];
|
|
68
|
-
if (file) {
|
|
69
|
-
if (blockstore) {
|
|
70
|
-
if (space) {
|
|
71
|
-
const info = await upload({ blockstore, file, space });
|
|
72
|
-
if (info && view) {
|
|
73
|
-
processEditorPayload(view, { type: 'image', data: info.url });
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
console.warn('Space is undefined.');
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
console.warn("Blockstore hasn't been initialised yet.");
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<>
|
|
86
|
-
<div className='mb-2'>
|
|
87
|
-
<input type='file' accept='image/*' onChange={onFileChange} />
|
|
88
|
-
</div>
|
|
89
|
-
<div className='w-[50rem]' ref={parentRef} {...focusAttributes} />
|
|
90
|
-
</>
|
|
91
|
-
);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const meta = {
|
|
95
|
-
title: 'plugins/plugin-wnfs/extensions/image',
|
|
96
|
-
render: DefaultStory,
|
|
97
|
-
decorators: [withTheme(), withClientProvider({ createIdentity: true, createSpace: true })],
|
|
98
|
-
} satisfies Meta<typeof DefaultStory>;
|
|
99
|
-
|
|
100
|
-
export default meta;
|
|
101
|
-
|
|
102
|
-
type Story = StoryObj<typeof meta>;
|
|
103
|
-
|
|
104
|
-
export const Default: Story = {};
|
package/src/extensions/image.tsx
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { syntaxTree } from '@codemirror/language';
|
|
6
|
-
import { type EditorState, type Extension, type Range, StateField } from '@codemirror/state';
|
|
7
|
-
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
|
8
|
-
import type { Blockstore } from 'interface-blockstore';
|
|
9
|
-
import React from 'react';
|
|
10
|
-
import { createRoot } from 'react-dom/client';
|
|
11
|
-
|
|
12
|
-
import { type Space } from '@dxos/react-client/echo';
|
|
13
|
-
import { Status, ThemeProvider } from '@dxos/react-ui';
|
|
14
|
-
import { focusField } from '@dxos/ui-editor';
|
|
15
|
-
import { defaultTx } from '@dxos/ui-theme';
|
|
16
|
-
import { type MaybePromise } from '@dxos/util';
|
|
17
|
-
|
|
18
|
-
import { getBlobUrl, getPathFromUrl, loadWnfs } from '../helpers';
|
|
19
|
-
import { type WnfsCapabilities } from '../types';
|
|
20
|
-
|
|
21
|
-
const WAIT_UNTIL_LOADER = 1500;
|
|
22
|
-
|
|
23
|
-
export type ImageOptions = {
|
|
24
|
-
blockstore: Blockstore;
|
|
25
|
-
instances: WnfsCapabilities.Instances;
|
|
26
|
-
space: Space;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Create WNFS image decorations.
|
|
31
|
-
*/
|
|
32
|
-
export const image = (options: ImageOptions): Extension[] => {
|
|
33
|
-
const blobUrlCache: Record<string, string> = {};
|
|
34
|
-
const preloaded = new Set<string>();
|
|
35
|
-
|
|
36
|
-
const preload = (url: string) => {
|
|
37
|
-
if (!preloaded.has(url)) {
|
|
38
|
-
const img = document.createElement('img');
|
|
39
|
-
img.src = url;
|
|
40
|
-
preloaded.add(url);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
return [
|
|
45
|
-
StateField.define({
|
|
46
|
-
create: (state) => {
|
|
47
|
-
return Decoration.set(
|
|
48
|
-
buildDecorations({ from: 0, to: state.doc.length, state, blobUrlCache, preload, options }),
|
|
49
|
-
);
|
|
50
|
-
},
|
|
51
|
-
update: (value, tr) => {
|
|
52
|
-
if (!tr.docChanged && !tr.selection) {
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Find range of changes and cursor changes.
|
|
57
|
-
const cursor = tr.state.selection.main.head;
|
|
58
|
-
const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
|
|
59
|
-
let from = Math.min(cursor, oldCursor);
|
|
60
|
-
let to = Math.max(cursor, oldCursor);
|
|
61
|
-
tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
|
|
62
|
-
from = Math.min(from, fromB);
|
|
63
|
-
to = Math.max(to, toB);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Expand to cover lines.
|
|
67
|
-
from = tr.state.doc.lineAt(from).from;
|
|
68
|
-
to = tr.state.doc.lineAt(to).to;
|
|
69
|
-
|
|
70
|
-
return value.map(tr.changes).update({
|
|
71
|
-
filterFrom: from,
|
|
72
|
-
filterTo: to,
|
|
73
|
-
filter: () => false,
|
|
74
|
-
add: buildDecorations({ from, to, state: tr.state, blobUrlCache, preload, options }),
|
|
75
|
-
});
|
|
76
|
-
},
|
|
77
|
-
provide: (field) => EditorView.decorations.from(field),
|
|
78
|
-
}),
|
|
79
|
-
];
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const buildDecorations = ({
|
|
83
|
-
state,
|
|
84
|
-
from,
|
|
85
|
-
to,
|
|
86
|
-
blobUrlCache,
|
|
87
|
-
preload,
|
|
88
|
-
options: { blockstore, instances, space },
|
|
89
|
-
}: {
|
|
90
|
-
state: EditorState;
|
|
91
|
-
from: number;
|
|
92
|
-
to: number;
|
|
93
|
-
blobUrlCache: Record<string, string>;
|
|
94
|
-
preload: (url: string) => void;
|
|
95
|
-
options: ImageOptions;
|
|
96
|
-
}) => {
|
|
97
|
-
const decorations: Range<Decoration>[] = [];
|
|
98
|
-
const cursor = state.selection.main.head;
|
|
99
|
-
syntaxTree(state).iterate({
|
|
100
|
-
enter: (node) => {
|
|
101
|
-
if (node.name !== 'Image') {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const urlNode = node.node.getChild('URL');
|
|
106
|
-
if (!urlNode) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const hide = state.readOnly || cursor < node.from || cursor > node.to;
|
|
111
|
-
const wnfsUrl = state.sliceDoc(urlNode.from, urlNode.to);
|
|
112
|
-
if (!wnfsUrl.startsWith('wnfs://')) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const path = getPathFromUrl(wnfsUrl);
|
|
117
|
-
|
|
118
|
-
// Cannot load images from other spaces
|
|
119
|
-
const spaceId = path[1];
|
|
120
|
-
if (spaceId !== space.id) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Load image contents into memory blob
|
|
125
|
-
const blobUrlPromise = (async () => {
|
|
126
|
-
const { directory, forest } = await loadWnfs({ blockstore, instances, space });
|
|
127
|
-
const url = await getBlobUrl({ wnfsUrl, blockstore, directory, forest });
|
|
128
|
-
blobUrlCache[path.join('/')] = url;
|
|
129
|
-
preload(url);
|
|
130
|
-
return url;
|
|
131
|
-
})();
|
|
132
|
-
|
|
133
|
-
decorations.push(
|
|
134
|
-
Decoration.replace({
|
|
135
|
-
block: true, // Prevent cursor from entering.
|
|
136
|
-
widget: new WnfsImageWidget(wnfsUrl, blobUrlCache[path.join('/')] ?? blobUrlPromise),
|
|
137
|
-
}).range(hide ? node.from : node.to, node.to),
|
|
138
|
-
);
|
|
139
|
-
},
|
|
140
|
-
from,
|
|
141
|
-
to,
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
return decorations;
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const createImg = (view: EditorView, url: string) => {
|
|
148
|
-
const img = document.createElement('img');
|
|
149
|
-
img.setAttribute('src', url);
|
|
150
|
-
img.setAttribute('class', 'cm-image-with-loader');
|
|
151
|
-
// If focused, hide image until successfully loaded to avoid flickering effects.
|
|
152
|
-
if (view.state.field(focusField)) {
|
|
153
|
-
img.onload = () => img.classList.add('cm-loaded-image');
|
|
154
|
-
} else {
|
|
155
|
-
img.classList.add('cm-loaded-image');
|
|
156
|
-
}
|
|
157
|
-
return img;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
class WnfsImageWidget extends WidgetType {
|
|
161
|
-
constructor(
|
|
162
|
-
readonly _wnfsUrl: string,
|
|
163
|
-
readonly _url: MaybePromise<string>,
|
|
164
|
-
) {
|
|
165
|
-
super();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
override eq(other: this) {
|
|
169
|
-
return this._wnfsUrl === other._wnfsUrl;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
override toDOM(view: EditorView) {
|
|
173
|
-
if (typeof this._url === 'string') {
|
|
174
|
-
return createImg(view, this._url);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const widget = document.createElement('div');
|
|
178
|
-
widget.className = 'cm-image-wrapper';
|
|
179
|
-
const loader = document.createElement('div');
|
|
180
|
-
loader.className = 'mx-auto transition-opacity';
|
|
181
|
-
let loaderAdded = false;
|
|
182
|
-
|
|
183
|
-
const timeout = setTimeout(() => {
|
|
184
|
-
const root = createRoot(loader);
|
|
185
|
-
root.render(
|
|
186
|
-
<ThemeProvider tx={defaultTx}>
|
|
187
|
-
<Status indeterminate />
|
|
188
|
-
</ThemeProvider>,
|
|
189
|
-
);
|
|
190
|
-
widget.appendChild(loader);
|
|
191
|
-
loaderAdded = true;
|
|
192
|
-
}, WAIT_UNTIL_LOADER);
|
|
193
|
-
|
|
194
|
-
void this._url.then((url) => {
|
|
195
|
-
clearTimeout(timeout);
|
|
196
|
-
const img = createImg(view, url);
|
|
197
|
-
loaderAdded && widget.removeChild(loader);
|
|
198
|
-
widget.appendChild(img);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
return widget;
|
|
202
|
-
}
|
|
203
|
-
}
|
package/src/extensions/index.ts
DELETED
package/src/types/File.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import * as Schema from 'effect/Schema';
|
|
6
|
-
|
|
7
|
-
import { Obj, Type } from '@dxos/echo';
|
|
8
|
-
import { FormInputAnnotation } from '@dxos/echo/internal';
|
|
9
|
-
|
|
10
|
-
export const File = Schema.Struct({
|
|
11
|
-
name: Schema.String.pipe(Schema.optional),
|
|
12
|
-
type: Schema.String.pipe(FormInputAnnotation.set(false)),
|
|
13
|
-
cid: Schema.String.pipe(FormInputAnnotation.set(false)),
|
|
14
|
-
timestamp: Schema.String.pipe(FormInputAnnotation.set(false), Schema.optional),
|
|
15
|
-
}).pipe(
|
|
16
|
-
Type.object({
|
|
17
|
-
typename: 'dxos.org/type/File',
|
|
18
|
-
version: '0.1.0',
|
|
19
|
-
}),
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
export type File = Schema.Schema.Type<typeof File>;
|
|
23
|
-
|
|
24
|
-
export const make = (props: Obj.MakeProps<typeof File>) => Obj.make(File, props);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { type Blockstore } from 'interface-blockstore';
|
|
6
|
-
import { type PrivateDirectory, type PrivateForest } from 'wnfs';
|
|
7
|
-
|
|
8
|
-
import { Capability } from '@dxos/app-framework';
|
|
9
|
-
import { type SpaceId } from '@dxos/keys';
|
|
10
|
-
|
|
11
|
-
import { meta } from '../meta';
|
|
12
|
-
|
|
13
|
-
export namespace WnfsCapabilities {
|
|
14
|
-
export const Blockstore = Capability.make<Blockstore>(`${meta.id}/capability/blockstore`);
|
|
15
|
-
|
|
16
|
-
export type Instances = Record<SpaceId, { directory: PrivateDirectory; forest: PrivateForest }>;
|
|
17
|
-
export const Instances = Capability.make<Instances>(`${meta.id}/capability/instances`);
|
|
18
|
-
}
|
package/src/types/types.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2025 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import * as Schema from 'effect/Schema';
|
|
6
|
-
|
|
7
|
-
import { Capability } from '@dxos/app-framework';
|
|
8
|
-
import { FileInfoSchema } from '@dxos/app-toolkit';
|
|
9
|
-
import { Database } from '@dxos/echo';
|
|
10
|
-
import { Collection } from '@dxos/echo';
|
|
11
|
-
import { Operation } from '@dxos/operation';
|
|
12
|
-
import { SpaceSchema } from '@dxos/react-client/echo';
|
|
13
|
-
|
|
14
|
-
import { meta } from '../meta';
|
|
15
|
-
|
|
16
|
-
import * as File from './File';
|
|
17
|
-
|
|
18
|
-
export namespace WnfsAction {
|
|
19
|
-
export const UploadAnnotationId = Symbol.for(`${meta.id}/annotation/upload`);
|
|
20
|
-
|
|
21
|
-
export const UploadFileSchema = Schema.Struct({
|
|
22
|
-
file: Schema.instanceOf(File.File).annotations({
|
|
23
|
-
[UploadAnnotationId]: {
|
|
24
|
-
// Accept file types.
|
|
25
|
-
'image/*': ['.jpg', '.jpeg', '.png', '.gif'],
|
|
26
|
-
'application/pdf': ['.pdf'],
|
|
27
|
-
// TODO(wittjosiah): Add more file types.
|
|
28
|
-
},
|
|
29
|
-
}),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export type UploadFileForm = Schema.Schema.Type<typeof UploadFileSchema>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const WNFS_OPERATION = `${meta.id}/operation`;
|
|
36
|
-
|
|
37
|
-
export namespace WnfsOperation {
|
|
38
|
-
export const OnCreateSpace = Operation.make({
|
|
39
|
-
meta: { key: `${WNFS_OPERATION}/on-create-space`, name: 'On Create Space' },
|
|
40
|
-
services: [Capability.Service],
|
|
41
|
-
schema: {
|
|
42
|
-
input: Schema.Struct({
|
|
43
|
-
space: SpaceSchema,
|
|
44
|
-
rootCollection: Collection.Collection,
|
|
45
|
-
isDefault: Schema.optional(Schema.Boolean),
|
|
46
|
-
}),
|
|
47
|
-
output: Schema.Void,
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
export const Create = Operation.make({
|
|
52
|
-
meta: { key: `${WNFS_OPERATION}/create`, name: 'Create WNFS File' },
|
|
53
|
-
services: [Capability.Service],
|
|
54
|
-
schema: {
|
|
55
|
-
input: FileInfoSchema.pick('name', 'type', 'cid').pipe(Schema.required),
|
|
56
|
-
output: Schema.Struct({
|
|
57
|
-
object: File.File,
|
|
58
|
-
}),
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
export const Upload = Operation.make({
|
|
63
|
-
meta: { key: `${WNFS_OPERATION}/upload`, name: 'Upload File' },
|
|
64
|
-
services: [Capability.Service],
|
|
65
|
-
schema: {
|
|
66
|
-
input: Schema.extend(WnfsAction.UploadFileSchema, Schema.Struct({ db: Database.Database })),
|
|
67
|
-
output: Schema.required(FileInfoSchema),
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
export const CreateFile = Operation.make({
|
|
72
|
-
meta: { key: `${WNFS_OPERATION}/create-file`, name: 'Create File' },
|
|
73
|
-
services: [Capability.Service],
|
|
74
|
-
schema: {
|
|
75
|
-
input: Schema.extend(WnfsAction.UploadFileSchema, Schema.Struct({ db: Database.Database })),
|
|
76
|
-
output: Schema.Struct({
|
|
77
|
-
object: File.File,
|
|
78
|
-
}),
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
}
|
|
File without changes
|
|
File without changes
|