@blu1606/create-walrus-app 1.0.0 → 2.1.0
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/dist/generator/file-ops.d.ts +8 -0
- package/dist/generator/file-ops.js +20 -0
- package/dist/generator/index.js +37 -22
- package/dist/generator/layers.d.ts +15 -2
- package/dist/generator/layers.js +38 -47
- package/dist/generator/types.d.ts +9 -1
- package/dist/index.js +1 -2
- package/dist/post-install/git.d.ts +8 -0
- package/dist/post-install/git.js +2 -0
- package/dist/post-install/index.d.ts +0 -1
- package/dist/post-install/index.js +7 -15
- package/dist/post-install/messages.js +1 -1
- package/dist/post-install/walrus-deploy.d.ts +6 -0
- package/dist/post-install/walrus-deploy.js +77 -0
- package/package.json +3 -3
- package/{templates/base → presets/react-mysten-gallery}/.env.example +31 -31
- package/presets/react-mysten-gallery/.gitkeep +4 -0
- package/presets/react-mysten-gallery/README.md +107 -0
- package/presets/react-mysten-gallery/package.json +35 -0
- package/presets/react-mysten-gallery/scripts/setup-walrus-deploy.sh +286 -0
- package/presets/react-mysten-gallery/src/App.tsx +23 -0
- package/presets/react-mysten-gallery/src/components/features/file-card.tsx +89 -0
- package/{templates/gallery/src/components/GalleryGrid.tsx → presets/react-mysten-gallery/src/components/features/gallery-grid.tsx} +5 -5
- package/presets/react-mysten-gallery/src/components/features/upload-modal.tsx +69 -0
- package/{templates/react/src/components/WalletConnect.tsx → presets/react-mysten-gallery/src/components/features/wallet-connect.tsx} +1 -1
- package/presets/react-mysten-gallery/src/components/layout/app-layout.tsx +21 -0
- package/{templates/react/src/hooks/useStorage.ts → presets/react-mysten-gallery/src/hooks/use-download.ts} +2 -18
- package/presets/react-mysten-gallery/src/hooks/use-upload.ts +49 -0
- package/{templates/react/src/hooks/useWallet.ts → presets/react-mysten-gallery/src/hooks/use-wallet.ts} +2 -7
- package/presets/react-mysten-gallery/src/index.css +384 -0
- package/presets/react-mysten-gallery/src/index.ts +17 -0
- package/presets/react-mysten-gallery/src/lib/walrus/adapter.ts +197 -0
- package/presets/react-mysten-gallery/src/lib/walrus/client.ts +87 -0
- package/presets/react-mysten-gallery/src/lib/walrus/index.ts +4 -0
- package/presets/react-mysten-gallery/src/lib/walrus/types.ts +101 -0
- package/{templates/react → presets/react-mysten-gallery}/src/main.tsx +0 -1
- package/{templates/react → presets/react-mysten-gallery}/src/providers/WalletProvider.tsx +16 -1
- package/{templates/base → presets/react-mysten-gallery}/src/utils/env.ts +41 -41
- package/{templates/gallery → presets/react-mysten-gallery}/src/utils/index-manager.ts +2 -2
- package/presets/react-mysten-gallery/src/utils/mime-type.ts +97 -0
- package/presets/react-mysten-gallery/src/utils/preview-generator.ts +134 -0
- package/{templates/react → presets/react-mysten-gallery}/tsconfig.json +20 -8
- package/presets/react-mysten-simple-upload/.env.example +31 -0
- package/presets/react-mysten-simple-upload/.gitkeep +4 -0
- package/presets/react-mysten-simple-upload/README.md +84 -0
- package/presets/react-mysten-simple-upload/index.html +13 -0
- package/{templates/react → presets/react-mysten-simple-upload}/package.json +15 -12
- package/presets/react-mysten-simple-upload/scripts/setup-walrus-deploy.sh +286 -0
- package/presets/react-mysten-simple-upload/src/App.tsx +27 -0
- package/presets/react-mysten-simple-upload/src/components/features/file-preview.tsx +73 -0
- package/{templates/simple-upload/src/components/UploadForm.tsx → presets/react-mysten-simple-upload/src/components/features/upload-form.tsx} +15 -5
- package/presets/react-mysten-simple-upload/src/components/features/wallet-connect.tsx +21 -0
- package/presets/react-mysten-simple-upload/src/components/layout/app-layout.tsx +21 -0
- package/presets/react-mysten-simple-upload/src/hooks/use-download.ts +24 -0
- package/presets/react-mysten-simple-upload/src/hooks/use-upload.ts +49 -0
- package/presets/react-mysten-simple-upload/src/hooks/use-wallet.ts +11 -0
- package/presets/react-mysten-simple-upload/src/index.css +252 -0
- package/presets/react-mysten-simple-upload/src/index.ts +16 -0
- package/presets/react-mysten-simple-upload/src/lib/walrus/adapter.ts +197 -0
- package/presets/react-mysten-simple-upload/src/lib/walrus/client.ts +87 -0
- package/presets/react-mysten-simple-upload/src/lib/walrus/index.ts +4 -0
- package/{templates/base/src/adapters/storage.ts → presets/react-mysten-simple-upload/src/lib/walrus/types.ts} +83 -58
- package/presets/react-mysten-simple-upload/src/main.tsx +16 -0
- package/presets/react-mysten-simple-upload/src/providers/QueryProvider.tsx +18 -0
- package/presets/react-mysten-simple-upload/src/providers/WalletProvider.tsx +52 -0
- package/presets/react-mysten-simple-upload/src/utils/env.ts +41 -0
- package/presets/react-mysten-simple-upload/src/utils/mime-type.ts +97 -0
- package/presets/react-mysten-simple-upload/tsconfig.json +39 -0
- package/presets/react-mysten-simple-upload/tsconfig.node.json +10 -0
- package/presets/react-mysten-simple-upload/vite.config.ts +19 -0
- package/templates/base/README.md +0 -54
- package/templates/base/package.json +0 -19
- package/templates/base/src/types/index.ts +0 -9
- package/templates/base/src/types/walrus.ts +0 -22
- package/templates/base/src/utils/format.ts +0 -29
- package/templates/base/tsconfig.json +0 -19
- package/templates/gallery/README.md +0 -44
- package/templates/gallery/package.json +0 -6
- package/templates/gallery/src/App.tsx +0 -21
- package/templates/gallery/src/components/FileCard.tsx +0 -27
- package/templates/gallery/src/components/UploadModal.tsx +0 -45
- package/templates/gallery/src/styles.css +0 -58
- package/templates/gallery/src/types/gallery.ts +0 -13
- package/templates/react/.eslintrc.json +0 -26
- package/templates/react/README.md +0 -80
- package/templates/react/src/App.tsx +0 -14
- package/templates/react/src/components/Layout.tsx +0 -21
- package/templates/react/src/dapp-kit.css +0 -1
- package/templates/react/src/index.css +0 -50
- package/templates/react/src/index.ts +0 -10
- package/templates/sdk-mysten/README.md +0 -65
- package/templates/sdk-mysten/package.json +0 -14
- package/templates/sdk-mysten/src/adapter.ts +0 -80
- package/templates/sdk-mysten/src/client.ts +0 -45
- package/templates/sdk-mysten/src/config.ts +0 -33
- package/templates/sdk-mysten/src/index.ts +0 -11
- package/templates/sdk-mysten/src/types.ts +0 -19
- package/templates/sdk-mysten/test/adapter.test.ts +0 -20
- package/templates/simple-upload/README.md +0 -24
- package/templates/simple-upload/package.json +0 -6
- package/templates/simple-upload/src/App.tsx +0 -27
- package/templates/simple-upload/src/components/FilePreview.tsx +0 -40
- package/templates/simple-upload/src/styles.css +0 -33
- /package/{templates/react → presets/react-mysten-gallery}/index.html +0 -0
- /package/{templates/react → presets/react-mysten-gallery}/src/providers/QueryProvider.tsx +0 -0
- /package/{templates/react → presets/react-mysten-gallery}/tsconfig.node.json +0 -0
- /package/{templates/react → presets/react-mysten-gallery}/vite.config.ts +0 -0
|
@@ -1,58 +1,83 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Universal storage adapter interface for Walrus
|
|
3
|
-
*
|
|
4
|
-
* This interface abstracts SDK-specific implementations,
|
|
5
|
-
* allowing use case layers to work with any Walrus SDK.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
*
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Universal storage adapter interface for Walrus
|
|
3
|
+
*
|
|
4
|
+
* This interface abstracts SDK-specific implementations,
|
|
5
|
+
* allowing use case layers to work with any Walrus SDK.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SuiClient } from '@mysten/sui/client';
|
|
9
|
+
import type { Transaction } from '@mysten/sui/transactions';
|
|
10
|
+
|
|
11
|
+
export interface BlobMetadata {
|
|
12
|
+
blobId: string;
|
|
13
|
+
size: number;
|
|
14
|
+
contentType?: string;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
/** Original filename with extension */
|
|
18
|
+
fileName?: string;
|
|
19
|
+
/** All tags from WalrusFile */
|
|
20
|
+
tags?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SignAndExecuteTransactionArgs {
|
|
24
|
+
transaction: Transaction;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SignAndExecuteTransactionResult {
|
|
28
|
+
digest: string;
|
|
29
|
+
effects?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UploadOptions {
|
|
33
|
+
/** Number of epochs to store (Walrus-specific) */
|
|
34
|
+
epochs?: number;
|
|
35
|
+
/** MIME type of the content */
|
|
36
|
+
contentType?: string;
|
|
37
|
+
/** Sui client with Walrus extension (from useSuiClient hook) */
|
|
38
|
+
client?: SuiClient;
|
|
39
|
+
/** Wallet signer for authenticated uploads with address and transaction signing */
|
|
40
|
+
signer?: {
|
|
41
|
+
address: string;
|
|
42
|
+
signAndExecuteTransaction: (args: SignAndExecuteTransactionArgs) => Promise<SignAndExecuteTransactionResult>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DownloadOptions {
|
|
47
|
+
/** Byte range (for large files) */
|
|
48
|
+
range?: { start: number; end: number };
|
|
49
|
+
/** Output format - auto-detects if not specified */
|
|
50
|
+
format?: 'bytes' | 'text' | 'json';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface StorageAdapter {
|
|
54
|
+
/**
|
|
55
|
+
* Upload data to Walrus storage
|
|
56
|
+
* @param data - File or raw bytes to upload
|
|
57
|
+
* @param options - Upload configuration
|
|
58
|
+
* @returns Blob ID (permanent reference)
|
|
59
|
+
*/
|
|
60
|
+
upload(data: File | Uint8Array, options?: UploadOptions): Promise<string>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Download blob data by ID
|
|
64
|
+
* @param blobId - Unique blob identifier
|
|
65
|
+
* @param options - Download configuration
|
|
66
|
+
* @returns Blob data in requested format (Uint8Array, string, or JSON)
|
|
67
|
+
*/
|
|
68
|
+
download(blobId: string, options?: DownloadOptions): Promise<Uint8Array | string | unknown>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get blob metadata without downloading content
|
|
72
|
+
* @param blobId - Unique blob identifier
|
|
73
|
+
* @returns Metadata object
|
|
74
|
+
*/
|
|
75
|
+
getMetadata(blobId: string): Promise<BlobMetadata>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if blob exists
|
|
79
|
+
* @param blobId - Unique blob identifier
|
|
80
|
+
* @returns True if blob is accessible
|
|
81
|
+
*/
|
|
82
|
+
exists(blobId: string): Promise<boolean>;
|
|
83
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { QueryProvider } from './providers/QueryProvider.js';
|
|
4
|
+
import { WalletProvider } from './providers/WalletProvider.js';
|
|
5
|
+
import App from './App.js';
|
|
6
|
+
import './index.css';
|
|
7
|
+
|
|
8
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
9
|
+
<React.StrictMode>
|
|
10
|
+
<QueryProvider>
|
|
11
|
+
<WalletProvider>
|
|
12
|
+
<App />
|
|
13
|
+
</WalletProvider>
|
|
14
|
+
</QueryProvider>
|
|
15
|
+
</React.StrictMode>
|
|
16
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
const queryClient = new QueryClient({
|
|
5
|
+
defaultOptions: {
|
|
6
|
+
queries: {
|
|
7
|
+
refetchOnWindowFocus: false,
|
|
8
|
+
retry: 1,
|
|
9
|
+
staleTime: 5 * 60 * 1000,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export function QueryProvider({ children }: { children: ReactNode }) {
|
|
15
|
+
return (
|
|
16
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNetworkConfig,
|
|
3
|
+
SuiClientProvider,
|
|
4
|
+
WalletProvider as SuiWalletProvider,
|
|
5
|
+
} from '@mysten/dapp-kit';
|
|
6
|
+
import { getFullnodeUrl, SuiClient } from '@mysten/sui/client';
|
|
7
|
+
import { walrus } from '@mysten/walrus';
|
|
8
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
9
|
+
import { ReactNode } from 'react';
|
|
10
|
+
import { loadEnv } from '../utils/env.js';
|
|
11
|
+
|
|
12
|
+
const env = loadEnv();
|
|
13
|
+
|
|
14
|
+
const validatedNetwork =
|
|
15
|
+
env.suiNetwork === 'mainnet' || env.suiNetwork === 'testnet'
|
|
16
|
+
? env.suiNetwork
|
|
17
|
+
: 'testnet';
|
|
18
|
+
|
|
19
|
+
const { networkConfig } = createNetworkConfig({
|
|
20
|
+
[validatedNetwork]: {
|
|
21
|
+
url: env.suiRpc || getFullnodeUrl(validatedNetwork),
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const walletQueryClient = new QueryClient();
|
|
26
|
+
|
|
27
|
+
export function WalletProvider({ children }: { children: ReactNode }) {
|
|
28
|
+
return (
|
|
29
|
+
<QueryClientProvider client={walletQueryClient}>
|
|
30
|
+
<SuiClientProvider
|
|
31
|
+
networks={networkConfig}
|
|
32
|
+
defaultNetwork={validatedNetwork}
|
|
33
|
+
createClient={(network: string | number, config: { readonly url: string }) => {
|
|
34
|
+
// Create SuiClient with Walrus extension
|
|
35
|
+
const client = new SuiClient({
|
|
36
|
+
url: config.url,
|
|
37
|
+
}).$extend(
|
|
38
|
+
walrus({
|
|
39
|
+
network: network as 'testnet' | 'mainnet',
|
|
40
|
+
wasmUrl: 'https://unpkg.com/@mysten/walrus-wasm@latest/web/walrus_wasm_bg.wasm',
|
|
41
|
+
// No uploadRelay - upload directly to storage nodes
|
|
42
|
+
// This avoids tip payment requirements but uses ~2200 requests
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
return client;
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
<SuiWalletProvider>{children}</SuiWalletProvider>
|
|
49
|
+
</SuiClientProvider>
|
|
50
|
+
</QueryClientProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface EnvConfig {
|
|
2
|
+
walrusNetwork: string;
|
|
3
|
+
walrusAggregator: string;
|
|
4
|
+
walrusPublisher: string;
|
|
5
|
+
suiNetwork: string;
|
|
6
|
+
suiRpc: string;
|
|
7
|
+
blockberryKey?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function loadEnv(): EnvConfig {
|
|
11
|
+
const getEnv = (key: string, required = true): string => {
|
|
12
|
+
const value = import.meta.env[key];
|
|
13
|
+
if (required && !value) {
|
|
14
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
15
|
+
}
|
|
16
|
+
return value || '';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
walrusNetwork: getEnv('VITE_WALRUS_NETWORK'),
|
|
21
|
+
walrusAggregator: getEnv('VITE_WALRUS_AGGREGATOR'),
|
|
22
|
+
walrusPublisher: getEnv('VITE_WALRUS_PUBLISHER'),
|
|
23
|
+
suiNetwork: getEnv('VITE_SUI_NETWORK'),
|
|
24
|
+
suiRpc: getEnv('VITE_SUI_RPC'),
|
|
25
|
+
blockberryKey: getEnv('VITE_BLOCKBERRY_KEY', false),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function validateEnv(config: EnvConfig): void {
|
|
30
|
+
if (!['testnet', 'mainnet', 'devnet'].includes(config.walrusNetwork)) {
|
|
31
|
+
throw new Error(`Invalid WALRUS_NETWORK: ${config.walrusNetwork}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!config.walrusAggregator.startsWith('http')) {
|
|
35
|
+
throw new Error('WALRUS_AGGREGATOR must be a valid HTTP URL');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!config.walrusPublisher.startsWith('http')) {
|
|
39
|
+
throw new Error('WALRUS_PUBLISHER must be a valid HTTP URL');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIME type detection from file extensions
|
|
3
|
+
* Handles common file types that browsers may not auto-detect
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const MIME_TYPES: Record<string, string> = {
|
|
7
|
+
// Text formats
|
|
8
|
+
'.txt': 'text/plain',
|
|
9
|
+
'.md': 'text/markdown',
|
|
10
|
+
'.markdown': 'text/markdown',
|
|
11
|
+
'.csv': 'text/csv',
|
|
12
|
+
'.html': 'text/html',
|
|
13
|
+
'.css': 'text/css',
|
|
14
|
+
'.js': 'text/javascript',
|
|
15
|
+
'.ts': 'text/typescript',
|
|
16
|
+
'.json': 'application/json',
|
|
17
|
+
'.xml': 'application/xml',
|
|
18
|
+
|
|
19
|
+
// Images
|
|
20
|
+
'.jpg': 'image/jpeg',
|
|
21
|
+
'.jpeg': 'image/jpeg',
|
|
22
|
+
'.png': 'image/png',
|
|
23
|
+
'.gif': 'image/gif',
|
|
24
|
+
'.svg': 'image/svg+xml',
|
|
25
|
+
'.webp': 'image/webp',
|
|
26
|
+
'.ico': 'image/x-icon',
|
|
27
|
+
|
|
28
|
+
// Documents
|
|
29
|
+
'.pdf': 'application/pdf',
|
|
30
|
+
'.doc': 'application/msword',
|
|
31
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
32
|
+
'.xls': 'application/vnd.ms-excel',
|
|
33
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
34
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
|
35
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
36
|
+
|
|
37
|
+
// Archives
|
|
38
|
+
'.zip': 'application/zip',
|
|
39
|
+
'.rar': 'application/x-rar-compressed',
|
|
40
|
+
'.tar': 'application/x-tar',
|
|
41
|
+
'.gz': 'application/gzip',
|
|
42
|
+
'.7z': 'application/x-7z-compressed',
|
|
43
|
+
|
|
44
|
+
// Audio
|
|
45
|
+
'.mp3': 'audio/mpeg',
|
|
46
|
+
'.wav': 'audio/wav',
|
|
47
|
+
'.ogg': 'audio/ogg',
|
|
48
|
+
'.m4a': 'audio/mp4',
|
|
49
|
+
|
|
50
|
+
// Video
|
|
51
|
+
'.mp4': 'video/mp4',
|
|
52
|
+
'.webm': 'video/webm',
|
|
53
|
+
'.avi': 'video/x-msvideo',
|
|
54
|
+
'.mov': 'video/quicktime',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Detect MIME type from filename
|
|
59
|
+
* @param fileName - File name with extension
|
|
60
|
+
* @returns MIME type or null if not found
|
|
61
|
+
*/
|
|
62
|
+
export function getMimeTypeFromFileName(fileName: string): string | null {
|
|
63
|
+
const extension = fileName.toLowerCase().match(/\.[^.]+$/)?.[0];
|
|
64
|
+
if (!extension) return null;
|
|
65
|
+
|
|
66
|
+
return MIME_TYPES[extension] || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get content type with fallback logic
|
|
71
|
+
* Priority: explicit contentType > File.type > extension detection > fallback
|
|
72
|
+
* @param file - File object, Uint8Array, or filename
|
|
73
|
+
* @param explicitType - Explicitly provided content type
|
|
74
|
+
* @returns Best-match content type
|
|
75
|
+
*/
|
|
76
|
+
export function getContentType(
|
|
77
|
+
file: File | Uint8Array | string,
|
|
78
|
+
explicitType?: string
|
|
79
|
+
): string {
|
|
80
|
+
// Priority 1: Explicit content type
|
|
81
|
+
if (explicitType) return explicitType;
|
|
82
|
+
|
|
83
|
+
// Priority 2: File.type (if not empty and not generic)
|
|
84
|
+
if (file instanceof File && file.type && file.type !== 'application/octet-stream') {
|
|
85
|
+
return file.type;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Priority 3: Detect from filename extension
|
|
89
|
+
const fileName = typeof file === 'string' ? file : file instanceof File ? file.name : '';
|
|
90
|
+
if (fileName) {
|
|
91
|
+
const detectedType = getMimeTypeFromFileName(fileName);
|
|
92
|
+
if (detectedType) return detectedType;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fallback: Generic binary
|
|
96
|
+
return 'application/octet-stream';
|
|
97
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": [
|
|
6
|
+
"ES2020",
|
|
7
|
+
"DOM",
|
|
8
|
+
"DOM.Iterable"
|
|
9
|
+
],
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"jsx": "react-jsx",
|
|
18
|
+
"types": [
|
|
19
|
+
"vite/client"
|
|
20
|
+
],
|
|
21
|
+
"strict": true,
|
|
22
|
+
"noUnusedLocals": true,
|
|
23
|
+
"noUnusedParameters": true,
|
|
24
|
+
"noFallthroughCasesInSwitch": true,
|
|
25
|
+
"paths": {
|
|
26
|
+
"@/*": [
|
|
27
|
+
"./src/*"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": [
|
|
32
|
+
"src"
|
|
33
|
+
],
|
|
34
|
+
"references": [
|
|
35
|
+
{
|
|
36
|
+
"path": "./tsconfig.node.json"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
server: {
|
|
7
|
+
port: 3000,
|
|
8
|
+
open: true,
|
|
9
|
+
},
|
|
10
|
+
build: {
|
|
11
|
+
target: 'esnext',
|
|
12
|
+
outDir: 'dist',
|
|
13
|
+
},
|
|
14
|
+
resolve: {
|
|
15
|
+
alias: {
|
|
16
|
+
'@': '/src',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
package/templates/base/README.md
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# {{projectName}}
|
|
2
|
-
|
|
3
|
-
This is a Walrus application generated by `create-walrus-app`.
|
|
4
|
-
|
|
5
|
-
## What's Included
|
|
6
|
-
|
|
7
|
-
### Adapter Interface
|
|
8
|
-
|
|
9
|
-
- `src/adapters/storage.ts` - Universal SDK-agnostic interface
|
|
10
|
-
- Allows use case code to work with any Walrus SDK
|
|
11
|
-
|
|
12
|
-
### Type Definitions
|
|
13
|
-
|
|
14
|
-
- `src/types/walrus.ts` - Walrus-specific types
|
|
15
|
-
- `src/types/index.ts` - Common utility types
|
|
16
|
-
|
|
17
|
-
### Utilities
|
|
18
|
-
|
|
19
|
-
- `src/utils/env.ts` - Environment validation
|
|
20
|
-
- `src/utils/format.ts` - Formatting helpers
|
|
21
|
-
|
|
22
|
-
### Configuration
|
|
23
|
-
|
|
24
|
-
- `.env.example` - Environment template
|
|
25
|
-
- `tsconfig.json` - TypeScript strict mode config
|
|
26
|
-
- `package.json` - Base dependencies
|
|
27
|
-
|
|
28
|
-
## Layer Composition
|
|
29
|
-
|
|
30
|
-
This base layer is **always included** and combined with:
|
|
31
|
-
|
|
32
|
-
1. **SDK Layer** (e.g., `sdk-mysten/`) - Implements `StorageAdapter`
|
|
33
|
-
2. **Framework Layer** (e.g., `react/`) - UI framework setup
|
|
34
|
-
3. **Use Case Layer** (e.g., `simple-upload/`) - Application logic
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
Base + SDK + Framework + UseCase = Your App
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Environment Setup
|
|
41
|
-
|
|
42
|
-
1. Copy `.env.example` to `.env`
|
|
43
|
-
2. Fill in required values:
|
|
44
|
-
- Walrus network URLs
|
|
45
|
-
- Sui RPC endpoint
|
|
46
|
-
3. Optional: Add Blockberry API key
|
|
47
|
-
|
|
48
|
-
## Next Steps
|
|
49
|
-
|
|
50
|
-
This base layer is completed by:
|
|
51
|
-
|
|
52
|
-
- **Phase 4**: SDK implementation
|
|
53
|
-
- **Phase 5**: Framework setup
|
|
54
|
-
- **Phase 6**: Use case logic
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{projectName}}",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"dev": "echo 'Override by framework layer'",
|
|
8
|
-
"build": "echo 'Override by framework layer'",
|
|
9
|
-
"preview": "echo 'Override by framework layer'",
|
|
10
|
-
"lint": "eslint . --ext .ts,.tsx",
|
|
11
|
-
"type-check": "tsc --noEmit"
|
|
12
|
-
},
|
|
13
|
-
"devDependencies": {
|
|
14
|
-
"typescript": "^5.3.3",
|
|
15
|
-
"eslint": "^8.56.0",
|
|
16
|
-
"@typescript-eslint/parser": "^6.19.1",
|
|
17
|
-
"@typescript-eslint/eslint-plugin": "^6.19.1"
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export type WalrusNetwork = 'testnet' | 'mainnet' | 'devnet';
|
|
2
|
-
|
|
3
|
-
export interface WalrusConfig {
|
|
4
|
-
network: WalrusNetwork;
|
|
5
|
-
publisherUrl: string;
|
|
6
|
-
aggregatorUrl: string;
|
|
7
|
-
suiRpcUrl: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface BlobInfo {
|
|
11
|
-
blobId: string;
|
|
12
|
-
name?: string;
|
|
13
|
-
size: number;
|
|
14
|
-
contentType?: string;
|
|
15
|
-
uploadedAt: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface StorageStats {
|
|
19
|
-
totalBlobs: number;
|
|
20
|
-
totalSize: number;
|
|
21
|
-
usedEpochs: number;
|
|
22
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format bytes to human-readable size
|
|
3
|
-
*/
|
|
4
|
-
export function formatBytes(bytes: number): string {
|
|
5
|
-
if (bytes === 0) return '0 Bytes';
|
|
6
|
-
|
|
7
|
-
const k = 1024;
|
|
8
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
9
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
10
|
-
|
|
11
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Format blob ID for display (truncate middle)
|
|
16
|
-
*/
|
|
17
|
-
export function formatBlobId(blobId: string, length = 12): string {
|
|
18
|
-
if (blobId.length <= length) return blobId;
|
|
19
|
-
|
|
20
|
-
const part = Math.floor((length - 3) / 2);
|
|
21
|
-
return `${blobId.slice(0, part)}...${blobId.slice(-part)}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Format timestamp to locale string
|
|
26
|
-
*/
|
|
27
|
-
export function formatDate(timestamp: number): string {
|
|
28
|
-
return new Date(timestamp).toLocaleString();
|
|
29
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
5
|
-
"module": "ESNext",
|
|
6
|
-
"moduleResolution": "bundler",
|
|
7
|
-
"resolveJsonModule": true,
|
|
8
|
-
"allowImportingTsExtensions": true,
|
|
9
|
-
"strict": true,
|
|
10
|
-
"noUnusedLocals": true,
|
|
11
|
-
"noUnusedParameters": true,
|
|
12
|
-
"noFallthroughCasesInSwitch": true,
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"skipLibCheck": true,
|
|
15
|
-
"types": ["vite/client"]
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# File Gallery Use Case
|
|
2
|
-
|
|
3
|
-
Manage multiple files with a persistent index.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- Upload multiple files
|
|
8
|
-
- Grid view of all files
|
|
9
|
-
- Local index (localStorage)
|
|
10
|
-
- Delete files from gallery
|
|
11
|
-
- File metadata display
|
|
12
|
-
|
|
13
|
-
## Index Format
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"version": "1.0",
|
|
18
|
-
"items": [
|
|
19
|
-
{
|
|
20
|
-
"blobId": "abc123...",
|
|
21
|
-
"name": "photo.jpg",
|
|
22
|
-
"size": 102400,
|
|
23
|
-
"contentType": "image/jpeg",
|
|
24
|
-
"uploadedAt": 1705449600000
|
|
25
|
-
}
|
|
26
|
-
],
|
|
27
|
-
"lastModified": 1705449600000
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Usage
|
|
32
|
-
|
|
33
|
-
1. Click "Choose File" to select a file
|
|
34
|
-
2. Click "Add to Gallery" to upload
|
|
35
|
-
3. Files appear in the grid below
|
|
36
|
-
4. Click "Delete" to remove files from gallery
|
|
37
|
-
|
|
38
|
-
## Code Structure
|
|
39
|
-
|
|
40
|
-
- `GalleryGrid.tsx` - Grid layout for files
|
|
41
|
-
- `FileCard.tsx` - Individual file display
|
|
42
|
-
- `UploadModal.tsx` - Upload UI
|
|
43
|
-
- `index-manager.ts` - localStorage persistence
|
|
44
|
-
- `gallery.ts` - Type definitions
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { Layout } from './components/Layout.js';
|
|
3
|
-
import { GalleryGrid } from './components/GalleryGrid.js';
|
|
4
|
-
import { UploadModal } from './components/UploadModal.js';
|
|
5
|
-
import './styles.css';
|
|
6
|
-
|
|
7
|
-
function App() {
|
|
8
|
-
const [refreshKey, setRefreshKey] = useState(0);
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<Layout>
|
|
12
|
-
<div className="gallery-app">
|
|
13
|
-
<h2>🖼️ File Gallery</h2>
|
|
14
|
-
<UploadModal onSuccess={() => setRefreshKey((k) => k + 1)} />
|
|
15
|
-
<GalleryGrid key={refreshKey} />
|
|
16
|
-
</div>
|
|
17
|
-
</Layout>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default App;
|