@blu1606/create-walrus-app 1.0.0 → 2.0.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 +2 -15
- package/dist/post-install/messages.js +1 -1
- 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/{templates/gallery → presets/react-mysten-gallery}/README.md +25 -22
- package/presets/react-mysten-gallery/package.json +34 -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/index.html +13 -0
- package/{templates/react → presets/react-mysten-simple-upload}/package.json +13 -11
- 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/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/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
- /package/{templates/simple-upload → presets/react-mysten-simple-upload}/README.md +0 -0
|
@@ -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,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;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { formatBytes, formatDate } from '../utils/format.js';
|
|
2
|
-
import { removeItem } from '../utils/index-manager.js';
|
|
3
|
-
import type { GalleryItem } from '../types/gallery.js';
|
|
4
|
-
|
|
5
|
-
interface FileCardProps {
|
|
6
|
-
item: GalleryItem;
|
|
7
|
-
onDelete: () => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function FileCard({ item, onDelete }: FileCardProps) {
|
|
11
|
-
const handleDelete = () => {
|
|
12
|
-
if (confirm(`Delete ${item.name}?`)) {
|
|
13
|
-
removeItem(item.blobId);
|
|
14
|
-
onDelete();
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<div className="file-card">
|
|
20
|
-
<h4>{item.name}</h4>
|
|
21
|
-
<p>Size: {formatBytes(item.size)}</p>
|
|
22
|
-
<p>Uploaded: {formatDate(item.uploadedAt)}</p>
|
|
23
|
-
<p className="blob-id">Blob ID: {item.blobId.slice(0, 12)}...</p>
|
|
24
|
-
<button onClick={handleDelete}>Delete</button>
|
|
25
|
-
</div>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useUpload } from '../hooks/useStorage.js';
|
|
3
|
-
import { addItem } from '../utils/index-manager.js';
|
|
4
|
-
|
|
5
|
-
interface UploadModalProps {
|
|
6
|
-
onSuccess: () => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function UploadModal({ onSuccess }: UploadModalProps) {
|
|
10
|
-
const [file, setFile] = useState<File | null>(null);
|
|
11
|
-
const upload = useUpload();
|
|
12
|
-
|
|
13
|
-
const handleUpload = async () => {
|
|
14
|
-
if (!file) return;
|
|
15
|
-
|
|
16
|
-
upload.mutate(
|
|
17
|
-
{ file, options: { epochs: 1 } },
|
|
18
|
-
{
|
|
19
|
-
onSuccess: async (data) => {
|
|
20
|
-
addItem({
|
|
21
|
-
blobId: data.blobId,
|
|
22
|
-
name: file.name,
|
|
23
|
-
size: file.size,
|
|
24
|
-
contentType: file.type,
|
|
25
|
-
uploadedAt: Date.now(),
|
|
26
|
-
});
|
|
27
|
-
setFile(null);
|
|
28
|
-
onSuccess();
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div className="upload-modal">
|
|
36
|
-
<input
|
|
37
|
-
type="file"
|
|
38
|
-
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
|
39
|
-
/>
|
|
40
|
-
<button onClick={handleUpload} disabled={!file || upload.isPending}>
|
|
41
|
-
{upload.isPending ? 'Uploading...' : 'Add to Gallery'}
|
|
42
|
-
</button>
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
.gallery-app {
|
|
2
|
-
max-width: 1200px;
|
|
3
|
-
margin: 0 auto;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.upload-modal {
|
|
7
|
-
margin: 2rem 0;
|
|
8
|
-
padding: 1.5rem;
|
|
9
|
-
border: 1px solid #333;
|
|
10
|
-
border-radius: 8px;
|
|
11
|
-
display: flex;
|
|
12
|
-
gap: 1rem;
|
|
13
|
-
align-items: center;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.gallery-grid {
|
|
17
|
-
display: grid;
|
|
18
|
-
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
19
|
-
gap: 1.5rem;
|
|
20
|
-
margin: 2rem 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.file-card {
|
|
24
|
-
padding: 1.5rem;
|
|
25
|
-
border: 1px solid #333;
|
|
26
|
-
border-radius: 8px;
|
|
27
|
-
background: #1a1a1a;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.file-card h4 {
|
|
31
|
-
margin: 0 0 1rem 0;
|
|
32
|
-
color: #fff;
|
|
33
|
-
overflow: hidden;
|
|
34
|
-
text-overflow: ellipsis;
|
|
35
|
-
white-space: nowrap;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.file-card p {
|
|
39
|
-
margin: 0.5rem 0;
|
|
40
|
-
font-size: 0.9rem;
|
|
41
|
-
color: #aaa;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.file-card .blob-id {
|
|
45
|
-
font-family: monospace;
|
|
46
|
-
font-size: 0.8rem;
|
|
47
|
-
color: #888;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
.file-card button {
|
|
51
|
-
margin-top: 1rem;
|
|
52
|
-
width: 100%;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
button:disabled {
|
|
56
|
-
opacity: 0.5;
|
|
57
|
-
cursor: not-allowed;
|
|
58
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": [
|
|
3
|
-
"eslint:recommended",
|
|
4
|
-
"plugin:@typescript-eslint/recommended",
|
|
5
|
-
"plugin:react/recommended",
|
|
6
|
-
"plugin:react-hooks/recommended"
|
|
7
|
-
],
|
|
8
|
-
"parser": "@typescript-eslint/parser",
|
|
9
|
-
"parserOptions": {
|
|
10
|
-
"ecmaVersion": 2020,
|
|
11
|
-
"sourceType": "module",
|
|
12
|
-
"ecmaFeatures": {
|
|
13
|
-
"jsx": true
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"plugins": ["@typescript-eslint", "react", "react-hooks"],
|
|
17
|
-
"rules": {
|
|
18
|
-
"no-console": "warn",
|
|
19
|
-
"react/react-in-jsx-scope": "off"
|
|
20
|
-
},
|
|
21
|
-
"settings": {
|
|
22
|
-
"react": {
|
|
23
|
-
"version": "detect"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|