@blu1606/create-walrus-app 0.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/__tests__/helpers/adapter-compliance.d.ts +2 -0
- package/dist/__tests__/helpers/adapter-compliance.js +47 -0
- package/dist/__tests__/helpers/fixtures.d.ts +21 -0
- package/dist/__tests__/helpers/fixtures.js +30 -0
- package/dist/__tests__/helpers/fs-helpers.d.ts +12 -0
- package/dist/__tests__/helpers/fs-helpers.js +35 -0
- package/dist/__tests__/helpers/index.d.ts +4 -0
- package/dist/__tests__/helpers/index.js +4 -0
- package/dist/__tests__/helpers/test-hooks.d.ts +3 -0
- package/dist/__tests__/helpers/test-hooks.js +18 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.js +43 -0
- package/dist/context.test.d.ts +1 -0
- package/dist/context.test.js +98 -0
- package/dist/generator/file-ops.d.ts +12 -0
- package/dist/generator/file-ops.js +40 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +75 -0
- package/dist/generator/index.test.d.ts +1 -0
- package/dist/generator/index.test.js +143 -0
- package/dist/generator/layers.d.ts +3 -0
- package/dist/generator/layers.js +59 -0
- package/dist/generator/layers.test.d.ts +1 -0
- package/dist/generator/layers.test.js +92 -0
- package/dist/generator/merge.d.ts +14 -0
- package/dist/generator/merge.js +62 -0
- package/dist/generator/merge.test.d.ts +1 -0
- package/dist/generator/merge.test.js +79 -0
- package/dist/generator/transform.d.ts +21 -0
- package/dist/generator/transform.js +52 -0
- package/dist/generator/transform.test.d.ts +1 -0
- package/dist/generator/transform.test.js +51 -0
- package/dist/generator/types.d.ts +18 -0
- package/dist/generator/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +106 -0
- package/dist/matrix.d.ts +31 -0
- package/dist/matrix.js +31 -0
- package/dist/matrix.test.d.ts +1 -0
- package/dist/matrix.test.js +70 -0
- package/dist/post-install/git.d.ts +12 -0
- package/dist/post-install/git.js +94 -0
- package/dist/post-install/index.d.ts +16 -0
- package/dist/post-install/index.js +56 -0
- package/dist/post-install/messages.d.ts +9 -0
- package/dist/post-install/messages.js +49 -0
- package/dist/post-install/package-manager.d.ts +14 -0
- package/dist/post-install/package-manager.js +57 -0
- package/dist/post-install/validator.d.ts +14 -0
- package/dist/post-install/validator.js +114 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +115 -0
- package/dist/test-base.d.ts +1 -0
- package/dist/test-base.js +42 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/dist/types.test.d.ts +1 -0
- package/dist/types.test.js +65 -0
- package/dist/utils/detect-pm.d.ts +2 -0
- package/dist/utils/detect-pm.js +10 -0
- package/dist/utils/detect-pm.test.d.ts +1 -0
- package/dist/utils/detect-pm.test.js +52 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +7 -0
- package/dist/validator.d.ts +3 -0
- package/dist/validator.js +48 -0
- package/dist/validator.test.d.ts +1 -0
- package/dist/validator.test.js +96 -0
- package/package.json +68 -0
- package/templates/base/.env.example +31 -0
- package/templates/base/README.md +54 -0
- package/templates/base/package.json +19 -0
- package/templates/base/src/adapters/storage.ts +58 -0
- package/templates/base/src/types/index.ts +9 -0
- package/templates/base/src/types/walrus.ts +22 -0
- package/templates/base/src/utils/env.ts +41 -0
- package/templates/base/src/utils/format.ts +29 -0
- package/templates/base/tsconfig.json +19 -0
- package/templates/gallery/README.md +44 -0
- package/templates/gallery/package.json +6 -0
- package/templates/gallery/src/App.tsx +21 -0
- package/templates/gallery/src/components/FileCard.tsx +27 -0
- package/templates/gallery/src/components/GalleryGrid.tsx +30 -0
- package/templates/gallery/src/components/UploadModal.tsx +45 -0
- package/templates/gallery/src/styles.css +58 -0
- package/templates/gallery/src/types/gallery.ts +13 -0
- package/templates/gallery/src/utils/index-manager.ts +37 -0
- package/templates/react/.eslintrc.json +26 -0
- package/templates/react/README.md +80 -0
- package/templates/react/index.html +13 -0
- package/templates/react/package.json +32 -0
- package/templates/react/src/App.tsx +14 -0
- package/templates/react/src/components/Layout.tsx +21 -0
- package/templates/react/src/components/WalletConnect.tsx +21 -0
- package/templates/react/src/dapp-kit.css +1 -0
- package/templates/react/src/hooks/useStorage.ts +40 -0
- package/templates/react/src/hooks/useWallet.ts +16 -0
- package/templates/react/src/index.css +50 -0
- package/templates/react/src/index.ts +10 -0
- package/templates/react/src/main.tsx +17 -0
- package/templates/react/src/providers/QueryProvider.tsx +18 -0
- package/templates/react/src/providers/WalletProvider.tsx +37 -0
- package/templates/react/tsconfig.json +27 -0
- package/templates/react/tsconfig.node.json +10 -0
- package/templates/react/vite.config.ts +19 -0
- package/templates/sdk-mysten/README.md +65 -0
- package/templates/sdk-mysten/package.json +14 -0
- package/templates/sdk-mysten/src/adapter.ts +80 -0
- package/templates/sdk-mysten/src/client.ts +45 -0
- package/templates/sdk-mysten/src/config.ts +33 -0
- package/templates/sdk-mysten/src/index.ts +11 -0
- package/templates/sdk-mysten/src/types.ts +19 -0
- package/templates/sdk-mysten/test/adapter.test.ts +20 -0
- package/templates/simple-upload/README.md +24 -0
- package/templates/simple-upload/package.json +6 -0
- package/templates/simple-upload/src/App.tsx +27 -0
- package/templates/simple-upload/src/components/FilePreview.tsx +40 -0
- package/templates/simple-upload/src/components/UploadForm.tsx +51 -0
- package/templates/simple-upload/src/styles.css +33 -0
|
@@ -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,29 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Layout } from '../../react/src/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;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { formatBytes, formatDate } from '../../../base/src/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
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { FileCard } from './FileCard.js';
|
|
3
|
+
import { loadIndex } from '../utils/index-manager.js';
|
|
4
|
+
import type { GalleryItem } from '../types/gallery.js';
|
|
5
|
+
|
|
6
|
+
export function GalleryGrid() {
|
|
7
|
+
const [items, setItems] = useState<GalleryItem[]>([]);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const index = loadIndex();
|
|
11
|
+
setItems(index.items);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
const refreshGallery = () => {
|
|
15
|
+
const index = loadIndex();
|
|
16
|
+
setItems(index.items);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="gallery-grid">
|
|
21
|
+
{items.length === 0 ? (
|
|
22
|
+
<p>No files yet. Upload your first file!</p>
|
|
23
|
+
) : (
|
|
24
|
+
items.map((item) => (
|
|
25
|
+
<FileCard key={item.blobId} item={item} onDelete={refreshGallery} />
|
|
26
|
+
))
|
|
27
|
+
)}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useUpload } from '../../../react/src/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
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { GalleryIndex, GalleryItem } from '../types/gallery.js';
|
|
2
|
+
|
|
3
|
+
const INDEX_KEY = 'gallery-index';
|
|
4
|
+
|
|
5
|
+
export function loadIndex(): GalleryIndex {
|
|
6
|
+
try {
|
|
7
|
+
const stored = localStorage.getItem(INDEX_KEY);
|
|
8
|
+
if (!stored) {
|
|
9
|
+
return { version: '1.0', items: [], lastModified: Date.now() };
|
|
10
|
+
}
|
|
11
|
+
const parsed = JSON.parse(stored);
|
|
12
|
+
if (!parsed.version || !Array.isArray(parsed.items)) {
|
|
13
|
+
throw new Error('Invalid index format');
|
|
14
|
+
}
|
|
15
|
+
return parsed;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.warn('Failed to load gallery index, resetting:', error);
|
|
18
|
+
return { version: '1.0', items: [], lastModified: Date.now() };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function saveIndex(index: GalleryIndex): void {
|
|
23
|
+
index.lastModified = Date.now();
|
|
24
|
+
localStorage.setItem(INDEX_KEY, JSON.stringify(index));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function addItem(item: GalleryItem): void {
|
|
28
|
+
const index = loadIndex();
|
|
29
|
+
index.items.push(item);
|
|
30
|
+
saveIndex(index);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function removeItem(blobId: string): void {
|
|
34
|
+
const index = loadIndex();
|
|
35
|
+
index.items = index.items.filter((item) => item.blobId !== blobId);
|
|
36
|
+
saveIndex(index);
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# React + Vite Framework Layer
|
|
2
|
+
|
|
3
|
+
Modern React 18 application with Vite build system.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
✅ **React 18** - Hooks, Suspense, Concurrent features
|
|
8
|
+
✅ **Vite 5** - Lightning-fast HMR and builds
|
|
9
|
+
✅ **TanStack Query** - Async state management
|
|
10
|
+
✅ **@mysten/dapp-kit** - Sui wallet integration
|
|
11
|
+
✅ **TypeScript** - Full type safety
|
|
12
|
+
|
|
13
|
+
## Project Structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/
|
|
17
|
+
├── components/ # Reusable UI components
|
|
18
|
+
├── providers/ # Context providers
|
|
19
|
+
├── hooks/ # Custom React hooks
|
|
20
|
+
├── App.tsx # Root component
|
|
21
|
+
└── main.tsx # Entry point
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Custom Hooks
|
|
25
|
+
|
|
26
|
+
### `useUpload()`
|
|
27
|
+
|
|
28
|
+
Upload files to Walrus:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const upload = useUpload();
|
|
32
|
+
|
|
33
|
+
upload.mutate({ file: myFile, options: { epochs: 1 } });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `useDownload(blobId)`
|
|
37
|
+
|
|
38
|
+
Download blob data:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const { data, isLoading } = useDownload(blobId);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `useMetadata(blobId)`
|
|
45
|
+
|
|
46
|
+
Fetch blob metadata:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const { data: metadata } = useMetadata(blobId);
|
|
50
|
+
console.log(`Size: ${metadata.size} bytes`);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `useWallet()`
|
|
54
|
+
|
|
55
|
+
Access wallet state:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const { isConnected, address } = useWallet();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run dev # Start dev server (http://localhost:3000)
|
|
65
|
+
npm run build # Build for production
|
|
66
|
+
npm run preview # Preview production build
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Wallet Setup
|
|
70
|
+
|
|
71
|
+
1. Install Sui Wallet browser extension
|
|
72
|
+
2. Get testnet SUI from faucet
|
|
73
|
+
3. Connect wallet in the app
|
|
74
|
+
|
|
75
|
+
## Resources
|
|
76
|
+
|
|
77
|
+
- [React Docs](https://react.dev)
|
|
78
|
+
- [Vite Docs](https://vitejs.dev)
|
|
79
|
+
- [TanStack Query](https://tanstack.com/query)
|
|
80
|
+
- [@mysten/dapp-kit](https://sdk.mystenlabs.com/dapp-kit)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Walrus App</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"lint": "eslint . --ext .ts,.tsx",
|
|
11
|
+
"type-check": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"react": "^18.2.0",
|
|
15
|
+
"react-dom": "^18.2.0",
|
|
16
|
+
"@tanstack/react-query": "^5.17.0",
|
|
17
|
+
"@mysten/dapp-kit": "^0.14.0",
|
|
18
|
+
"@mysten/sui": "^1.10.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/react": "^18.2.48",
|
|
22
|
+
"@types/react-dom": "^18.2.18",
|
|
23
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
24
|
+
"vite": "^5.0.11",
|
|
25
|
+
"typescript": "^5.3.3",
|
|
26
|
+
"eslint": "^8.56.0",
|
|
27
|
+
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
28
|
+
"@typescript-eslint/parser": "^6.19.0",
|
|
29
|
+
"eslint-plugin-react": "^7.33.2",
|
|
30
|
+
"eslint-plugin-react-hooks": "^4.6.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Layout } from './components/Layout.js';
|
|
2
|
+
|
|
3
|
+
function App() {
|
|
4
|
+
return (
|
|
5
|
+
<Layout>
|
|
6
|
+
<div className="welcome">
|
|
7
|
+
<h2>Welcome to Walrus Starter Kit</h2>
|
|
8
|
+
<p>This app will be customized by the use case layer</p>
|
|
9
|
+
</div>
|
|
10
|
+
</Layout>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default App;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { WalletConnect } from './WalletConnect.js';
|
|
3
|
+
|
|
4
|
+
interface LayoutProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function Layout({ children }: LayoutProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="app-layout">
|
|
11
|
+
<header className="app-header">
|
|
12
|
+
<h1>🌊 Walrus App</h1>
|
|
13
|
+
<WalletConnect />
|
|
14
|
+
</header>
|
|
15
|
+
<main className="app-main">{children}</main>
|
|
16
|
+
<footer className="app-footer">
|
|
17
|
+
<p>Powered by Walrus & Sui</p>
|
|
18
|
+
</footer>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ConnectButton } from '@mysten/dapp-kit';
|
|
2
|
+
import { useWallet } from '../hooks/useWallet.js';
|
|
3
|
+
|
|
4
|
+
export function WalletConnect() {
|
|
5
|
+
const { isConnected, address } = useWallet();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div className="wallet-connect">
|
|
9
|
+
{isConnected ? (
|
|
10
|
+
<div className="wallet-info">
|
|
11
|
+
<span>
|
|
12
|
+
Connected: {address?.slice(0, 6)}...{address?.slice(-4)}
|
|
13
|
+
</span>
|
|
14
|
+
</div>
|
|
15
|
+
) : (
|
|
16
|
+
<p>Please connect your wallet</p>
|
|
17
|
+
)}
|
|
18
|
+
<ConnectButton />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import '@mysten/dapp-kit/dist/index.css';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useMutation, useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { storageAdapter } from '../index.js';
|
|
3
|
+
import type { UploadOptions } from '../adapters/storage.js';
|
|
4
|
+
|
|
5
|
+
export function useUpload() {
|
|
6
|
+
return useMutation({
|
|
7
|
+
mutationFn: async ({
|
|
8
|
+
file,
|
|
9
|
+
options,
|
|
10
|
+
}: {
|
|
11
|
+
file: File;
|
|
12
|
+
options?: UploadOptions;
|
|
13
|
+
}) => {
|
|
14
|
+
const blobId = await storageAdapter.upload(file, options);
|
|
15
|
+
return { blobId, file };
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useDownload(blobId: string | null) {
|
|
21
|
+
return useQuery({
|
|
22
|
+
queryKey: ['blob', blobId],
|
|
23
|
+
queryFn: async () => {
|
|
24
|
+
if (!blobId) throw new Error('No blob ID provided');
|
|
25
|
+
return await storageAdapter.download(blobId);
|
|
26
|
+
},
|
|
27
|
+
enabled: !!blobId,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useMetadata(blobId: string | null) {
|
|
32
|
+
return useQuery({
|
|
33
|
+
queryKey: ['metadata', blobId],
|
|
34
|
+
queryFn: async () => {
|
|
35
|
+
if (!blobId) throw new Error('No blob ID provided');
|
|
36
|
+
return await storageAdapter.getMetadata(blobId);
|
|
37
|
+
},
|
|
38
|
+
enabled: !!blobId,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCurrentAccount,
|
|
3
|
+
useSignAndExecuteTransaction,
|
|
4
|
+
} from '@mysten/dapp-kit';
|
|
5
|
+
|
|
6
|
+
export function useWallet() {
|
|
7
|
+
const currentAccount = useCurrentAccount();
|
|
8
|
+
const { mutate: signAndExecute } = useSignAndExecuteTransaction();
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
account: currentAccount,
|
|
12
|
+
isConnected: !!currentAccount,
|
|
13
|
+
address: currentAccount?.address,
|
|
14
|
+
signAndExecute,
|
|
15
|
+
};
|
|
16
|
+
}
|