@dxos/plugin-wnfs 0.6.14-main.f49f251
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/FileCard-HJIZDWJF.mjs +39 -0
- package/dist/lib/browser/FileCard-HJIZDWJF.mjs.map +7 -0
- package/dist/lib/browser/FileMain-WEWTX7QB.mjs +27 -0
- package/dist/lib/browser/FileMain-WEWTX7QB.mjs.map +7 -0
- package/dist/lib/browser/FileSection-BEUGJ7EI.mjs +24 -0
- package/dist/lib/browser/FileSection-BEUGJ7EI.mjs.map +7 -0
- package/dist/lib/browser/FileSlide-NYBGZEZS.mjs +21 -0
- package/dist/lib/browser/FileSlide-NYBGZEZS.mjs.map +7 -0
- package/dist/lib/browser/chunk-2I63TM7M.mjs +27 -0
- package/dist/lib/browser/chunk-2I63TM7M.mjs.map +7 -0
- package/dist/lib/browser/chunk-STEG7B5Y.mjs +17 -0
- package/dist/lib/browser/chunk-STEG7B5Y.mjs.map +7 -0
- package/dist/lib/browser/chunk-VI24OYJB.mjs +30 -0
- package/dist/lib/browser/chunk-VI24OYJB.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +517 -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 +10 -0
- package/dist/lib/browser/types/index.mjs.map +7 -0
- package/dist/types/src/WnfsPlugin.d.ts +4 -0
- package/dist/types/src/WnfsPlugin.d.ts.map +1 -0
- package/dist/types/src/blockstore.d.ts +19 -0
- package/dist/types/src/blockstore.d.ts.map +1 -0
- package/dist/types/src/common.d.ts +11 -0
- package/dist/types/src/common.d.ts.map +1 -0
- package/dist/types/src/components/FileCard.d.ts +9 -0
- package/dist/types/src/components/FileCard.d.ts.map +1 -0
- package/dist/types/src/components/FileMain.d.ts +7 -0
- package/dist/types/src/components/FileMain.d.ts.map +1 -0
- package/dist/types/src/components/FilePreview.d.ts +11 -0
- package/dist/types/src/components/FilePreview.d.ts.map +1 -0
- package/dist/types/src/components/FileSection.d.ts +8 -0
- package/dist/types/src/components/FileSection.d.ts.map +1 -0
- package/dist/types/src/components/FileSlide.d.ts +8 -0
- package/dist/types/src/components/FileSlide.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +14 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/extensions/image.d.ts +9 -0
- package/dist/types/src/extensions/image.d.ts.map +1 -0
- package/dist/types/src/extensions/image.stories.d.ts +10 -0
- package/dist/types/src/extensions/image.stories.d.ts.map +1 -0
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.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/load.d.ts +8 -0
- package/dist/types/src/load.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +10 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +11 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/file.d.ts +20 -0
- package/dist/types/src/types/file.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/types.d.ts +8 -0
- package/dist/types/src/types/types.d.ts.map +1 -0
- package/dist/types/src/upload.d.ts +10 -0
- package/dist/types/src/upload.d.ts.map +1 -0
- package/dist/types/src/wnfs-url.d.ts +2 -0
- package/dist/types/src/wnfs-url.d.ts.map +1 -0
- package/package.json +82 -0
- package/src/WnfsPlugin.tsx +102 -0
- package/src/blockstore.ts +96 -0
- package/src/common.ts +84 -0
- package/src/components/FileCard.tsx +45 -0
- package/src/components/FileMain.tsx +28 -0
- package/src/components/FilePreview.tsx +23 -0
- package/src/components/FileSection.tsx +20 -0
- package/src/components/FileSlide.tsx +20 -0
- package/src/components/index.ts +11 -0
- package/src/extensions/image.stories.tsx +142 -0
- package/src/extensions/image.ts +244 -0
- package/src/extensions/index.ts +5 -0
- package/src/index.ts +9 -0
- package/src/load.ts +63 -0
- package/src/meta.tsx +15 -0
- package/src/translations.ts +17 -0
- package/src/types/file.ts +13 -0
- package/src/types/index.ts +6 -0
- package/src/types/types.ts +27 -0
- package/src/upload.ts +46 -0
- package/src/wnfs-url.ts +7 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { BaseBlockstore } from 'blockstore-core';
|
|
6
|
+
import { IDBBlockstore } from 'blockstore-idb';
|
|
7
|
+
import type { CID } from 'multiformats';
|
|
8
|
+
|
|
9
|
+
import { storeName } from './common';
|
|
10
|
+
|
|
11
|
+
export const create = (apiHost?: string) => {
|
|
12
|
+
return new MixedBlockstore(apiHost);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A blockstore that communicates with the DXOS blob service,
|
|
17
|
+
* and which uses an indexedDB blockstore as a client cache.
|
|
18
|
+
*/
|
|
19
|
+
export class MixedBlockstore extends BaseBlockstore {
|
|
20
|
+
readonly #apiHost: string | undefined;
|
|
21
|
+
readonly #idbStore: IDBBlockstore;
|
|
22
|
+
|
|
23
|
+
constructor(apiHost?: string) {
|
|
24
|
+
super();
|
|
25
|
+
|
|
26
|
+
this.#apiHost = apiHost;
|
|
27
|
+
this.#idbStore = new IDBBlockstore(storeName());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async open() {
|
|
31
|
+
await this.#idbStore.open();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
url(apiHost: string, cid?: CID) {
|
|
35
|
+
const path = cid ? cid.toString() : '';
|
|
36
|
+
return `${apiHost}/api/file/${path}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Blockstore implementation
|
|
40
|
+
override async delete(key: CID): Promise<void> {
|
|
41
|
+
await this.#idbStore.delete(key);
|
|
42
|
+
|
|
43
|
+
if (this.#apiHost) {
|
|
44
|
+
await fetch(this.url(this.#apiHost, key), {
|
|
45
|
+
method: 'DELETE',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override async get(key: CID): Promise<Uint8Array> {
|
|
51
|
+
if (await this.#idbStore.has(key)) {
|
|
52
|
+
return await this.#idbStore.get(key);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (this.#apiHost) {
|
|
56
|
+
return await fetch(this.url(this.#apiHost, key), {
|
|
57
|
+
method: 'GET',
|
|
58
|
+
})
|
|
59
|
+
.then((r) => r.arrayBuffer())
|
|
60
|
+
.then((r) => new Uint8Array(r));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return await this.#idbStore.get(key);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override async has(key: CID): Promise<boolean> {
|
|
67
|
+
if (await this.#idbStore.has(key)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.#apiHost) {
|
|
72
|
+
return await fetch(this.url(this.#apiHost, key), {
|
|
73
|
+
method: 'HEAD',
|
|
74
|
+
}).then((r) => r.ok);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override async put(key: CID, val: Uint8Array): Promise<CID> {
|
|
81
|
+
await this.#idbStore.put(key, val);
|
|
82
|
+
|
|
83
|
+
if (this.#apiHost) {
|
|
84
|
+
await fetch(this.url(this.#apiHost, key), {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
body: val,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return key;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async destroy(): Promise<void> {
|
|
94
|
+
await this.#idbStore.destroy();
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { Blockstore } from 'interface-blockstore';
|
|
6
|
+
import { CID } from 'multiformats/cid';
|
|
7
|
+
import { sha256 } from 'multiformats/hashes/sha2';
|
|
8
|
+
import { type BlockStore as WnfsBlockStore } from 'wnfs';
|
|
9
|
+
|
|
10
|
+
import * as crypto from '@dxos/crypto';
|
|
11
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
12
|
+
|
|
13
|
+
//
|
|
14
|
+
// PATH
|
|
15
|
+
//
|
|
16
|
+
|
|
17
|
+
export const filePath = (fileName: string, space: Space) => {
|
|
18
|
+
return ['spaces', space.properties.id, 'files', fileName];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
//
|
|
22
|
+
// RNG
|
|
23
|
+
//
|
|
24
|
+
|
|
25
|
+
export class Rng {
|
|
26
|
+
randomBytes(count: number): Uint8Array {
|
|
27
|
+
return new Uint8Array(crypto.randomBytes(count));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//
|
|
32
|
+
// STORE
|
|
33
|
+
//
|
|
34
|
+
|
|
35
|
+
export const store = (blockstore: Blockstore): WnfsBlockStore => {
|
|
36
|
+
const getBlock = async (cid: Uint8Array): Promise<Uint8Array | undefined> => {
|
|
37
|
+
const decodedCid = CID.decode(cid);
|
|
38
|
+
return await blockstore.get(decodedCid);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const hasBlock = async (cid: Uint8Array): Promise<boolean> => {
|
|
42
|
+
const decodedCid = CID.decode(cid);
|
|
43
|
+
return await blockstore.has(decodedCid);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const putBlockKeyed = async (cid: Uint8Array, bytes: Uint8Array): Promise<void> => {
|
|
47
|
+
const decodedCid = CID.decode(cid);
|
|
48
|
+
await blockstore.put(decodedCid, bytes);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Don't hash blocks with the rs-wnfs default Blake 3, sha256 has better support.
|
|
52
|
+
const putBlock = async (bytes: Uint8Array, codec: number): Promise<Uint8Array> => {
|
|
53
|
+
const hash = await sha256.digest(bytes);
|
|
54
|
+
const cid = CID.create(1, codec, hash);
|
|
55
|
+
await blockstore.put(cid, bytes);
|
|
56
|
+
return cid.bytes;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return { getBlock, hasBlock, putBlockKeyed, putBlock };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const storeName = () => 'dxos/wnfs';
|
|
63
|
+
|
|
64
|
+
//
|
|
65
|
+
// OTHER
|
|
66
|
+
//
|
|
67
|
+
|
|
68
|
+
export const readFile = (file: File): Promise<Uint8Array> => {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const reader = new FileReader();
|
|
71
|
+
reader.onload = (event) => {
|
|
72
|
+
const result = event.target?.result;
|
|
73
|
+
if (result === null || result === undefined || typeof result === 'string') {
|
|
74
|
+
reject(new Error('Expected an ArrayBuffer'));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
resolve(new Uint8Array(result));
|
|
78
|
+
};
|
|
79
|
+
reader.onerror = (err) => {
|
|
80
|
+
reject(err);
|
|
81
|
+
};
|
|
82
|
+
reader.readAsArrayBuffer(file);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { forwardRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { DropdownMenu } from '@dxos/react-ui';
|
|
8
|
+
import { Card } from '@dxos/react-ui-card';
|
|
9
|
+
import type { MosaicTileComponent } from '@dxos/react-ui-mosaic';
|
|
10
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
11
|
+
|
|
12
|
+
import { type FileType } from '../types';
|
|
13
|
+
|
|
14
|
+
export type FileCardProps = {
|
|
15
|
+
id: string;
|
|
16
|
+
object: FileType;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const FileCard: MosaicTileComponent<FileCardProps> = forwardRef(
|
|
20
|
+
(
|
|
21
|
+
{ classNames, isDragging, draggableStyle, draggableProps, item: { id, object: file }, grow, onSelect, onAction },
|
|
22
|
+
forwardRef,
|
|
23
|
+
) => {
|
|
24
|
+
const url = 'TODO';
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div role='none' ref={forwardRef} className='flex w-full' style={draggableStyle}>
|
|
28
|
+
<Card.Root classNames={mx('w-full snap-center', isDragging && 'opacity-20', classNames)} grow={grow}>
|
|
29
|
+
<Card.Header onDoubleClick={() => onSelect?.()} floating={!!url}>
|
|
30
|
+
<Card.DragHandle {...draggableProps} position='left' />
|
|
31
|
+
<Card.Menu position='right'>
|
|
32
|
+
{/* TODO(burdon): Handle events/intents? */}
|
|
33
|
+
<DropdownMenu.Item onClick={() => onAction?.({ id, action: 'delete' })}>
|
|
34
|
+
<span className='grow'>Delete</span>
|
|
35
|
+
</DropdownMenu.Item>
|
|
36
|
+
</Card.Menu>
|
|
37
|
+
</Card.Header>
|
|
38
|
+
<Card.Media src={url} />
|
|
39
|
+
</Card.Root>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export default FileCard;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type FC } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Main } from '@dxos/react-ui';
|
|
8
|
+
import {
|
|
9
|
+
baseSurface,
|
|
10
|
+
topbarBlockPaddingStart,
|
|
11
|
+
fixedInsetFlexLayout,
|
|
12
|
+
bottombarBlockPaddingEnd,
|
|
13
|
+
} from '@dxos/react-ui-theme';
|
|
14
|
+
|
|
15
|
+
import { FilePreview } from './FilePreview';
|
|
16
|
+
import { type FileType } from '../types';
|
|
17
|
+
|
|
18
|
+
const FileMain: FC<{ file: FileType }> = ({ file }) => {
|
|
19
|
+
const url = 'TODO';
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Main.Content classNames={[baseSurface, fixedInsetFlexLayout, topbarBlockPaddingStart, bottombarBlockPaddingEnd]}>
|
|
23
|
+
<FilePreview type={file.type} url={url} />
|
|
24
|
+
</Main.Content>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default FileMain;
|
|
@@ -0,0 +1,23 @@
|
|
|
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/react-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
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type FC } from 'react';
|
|
6
|
+
|
|
7
|
+
import { FilePreview } from './FilePreview';
|
|
8
|
+
import { type FileType } from '../types';
|
|
9
|
+
|
|
10
|
+
const FileSection: FC<{ file: FileType; height?: number }> = ({ file, height = 400 }) => {
|
|
11
|
+
const url = 'TODO';
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ height }} className='flex w-full p-2 justify-center align-center'>
|
|
15
|
+
<FilePreview type={file.type} url={url} classNames='object-contain' />
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default FileSection;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type FC } from 'react';
|
|
6
|
+
|
|
7
|
+
import { FilePreview } from './FilePreview';
|
|
8
|
+
import { type FileType } from '../types';
|
|
9
|
+
|
|
10
|
+
const FileSlide: FC<{ file: FileType; cover?: boolean }> = ({ file, cover }) => {
|
|
11
|
+
const url = 'TODO';
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div className='h-full flex justify-center align-center'>
|
|
15
|
+
<FilePreview type={file.type} url={url} classNames={cover ? 'object-cover' : 'object-contain'} />
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default FileSlide;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
// Lazily load components for content surfaces.
|
|
8
|
+
export const FileCard = React.lazy(() => import('./FileCard'));
|
|
9
|
+
export const FileMain = React.lazy(() => import('./FileMain'));
|
|
10
|
+
export const FileSection = React.lazy(() => import('./FileSection'));
|
|
11
|
+
export const FileSlide = React.lazy(() => import('./FileSlide'));
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import type { Blockstore } from 'interface-blockstore';
|
|
8
|
+
import React, { useEffect, useState } from 'react';
|
|
9
|
+
|
|
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 {
|
|
14
|
+
createBasicExtensions,
|
|
15
|
+
createMarkdownExtensions,
|
|
16
|
+
createThemeExtensions,
|
|
17
|
+
decorateMarkdown,
|
|
18
|
+
useTextEditor,
|
|
19
|
+
} from '@dxos/react-ui-editor';
|
|
20
|
+
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
21
|
+
|
|
22
|
+
import { image } from './image';
|
|
23
|
+
import { create as createBlockstore } from '../blockstore';
|
|
24
|
+
import { filePath, store } from '../common';
|
|
25
|
+
import { loadWnfs } from '../load';
|
|
26
|
+
import { upload } from '../upload';
|
|
27
|
+
|
|
28
|
+
const Story = () => {
|
|
29
|
+
const space = useSpace();
|
|
30
|
+
|
|
31
|
+
const [blockstore, setBlockstore] = useState<Blockstore>();
|
|
32
|
+
const [amountOfUploads, setAmountOfUploads] = useState<number>(0);
|
|
33
|
+
const [images, setImages] = useState<{ url: string }[]>([]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (space) {
|
|
37
|
+
console.log('SPACE ID', space.properties.id);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const create = async () => {
|
|
41
|
+
// NOTE: We don't pass in the URL of the Blob service, so we don't use it.
|
|
42
|
+
const bs = createBlockstore();
|
|
43
|
+
await bs.open();
|
|
44
|
+
setBlockstore(bs);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
create().catch(console.error);
|
|
48
|
+
}, [space]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!space || !blockstore) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
(async () => {
|
|
56
|
+
const { directory, forest } = await loadWnfs(space, blockstore);
|
|
57
|
+
const parentPath = ['spaces', space.properties.id, 'files'];
|
|
58
|
+
const parentNode = await directory.getNode(parentPath, true, forest, store(blockstore));
|
|
59
|
+
if (!parentNode) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { result } = await directory.ls(parentPath, true, forest, store(blockstore));
|
|
64
|
+
|
|
65
|
+
const images = await Promise.all(
|
|
66
|
+
result.map(async (r: any) => {
|
|
67
|
+
const path = filePath(r.name, space);
|
|
68
|
+
const reading = await directory.read(path, true, forest, store(blockstore));
|
|
69
|
+
const bytes = reading.result;
|
|
70
|
+
const blob = new Blob([bytes]);
|
|
71
|
+
const url = URL.createObjectURL(blob);
|
|
72
|
+
return { url };
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
setImages(images);
|
|
77
|
+
})().catch(console.error);
|
|
78
|
+
}, [space, blockstore, amountOfUploads]);
|
|
79
|
+
|
|
80
|
+
const onFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
81
|
+
const file = event.target.files?.[0];
|
|
82
|
+
if (file) {
|
|
83
|
+
if (blockstore) {
|
|
84
|
+
if (space) {
|
|
85
|
+
await upload({ blockstore, file, space });
|
|
86
|
+
setAmountOfUploads(amountOfUploads + 1);
|
|
87
|
+
} else {
|
|
88
|
+
console.warn('Space is undefined.');
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
console.warn("Blockstore hasn't been initialised yet.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const { themeMode } = useThemeContext();
|
|
97
|
+
const { parentRef, focusAttributes } = useTextEditor(
|
|
98
|
+
() => ({
|
|
99
|
+
initialValue: '',
|
|
100
|
+
extensions: [
|
|
101
|
+
createBasicExtensions(),
|
|
102
|
+
createMarkdownExtensions({ themeMode }),
|
|
103
|
+
createThemeExtensions({ themeMode, syntaxHighlighting: true }),
|
|
104
|
+
blockstore && space ? [image({ blockstore, space })] : [],
|
|
105
|
+
decorateMarkdown(),
|
|
106
|
+
],
|
|
107
|
+
}),
|
|
108
|
+
[themeMode],
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const renderedImages = images.map((image, index) => (
|
|
112
|
+
<div key={index} className='mt-1'>
|
|
113
|
+
<img src={image.url} width='300' />
|
|
114
|
+
</div>
|
|
115
|
+
));
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<div className='mb-2'>
|
|
120
|
+
<input type='file' accept='image/*' onChange={onFileChange} />
|
|
121
|
+
</div>
|
|
122
|
+
<div className='w-[50rem]' ref={parentRef} {...focusAttributes} />
|
|
123
|
+
<div>
|
|
124
|
+
{images.length ? (
|
|
125
|
+
renderedImages
|
|
126
|
+
) : (
|
|
127
|
+
<p>
|
|
128
|
+
<strong className='text-green-600'>Select an image file to add to WNFS and render it.</strong>
|
|
129
|
+
</p>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
</>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export default {
|
|
137
|
+
title: 'plugins/plugin-wnfs/image',
|
|
138
|
+
render: Story,
|
|
139
|
+
decorators: [withClientProvider({ createIdentity: true, createSpace: true }), withTheme, withLayout()],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const Default = {};
|