@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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# gallery-demo
|
|
2
|
+
|
|
3
|
+
This is a File Gallery Walrus application.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Upload multiple files to Walrus
|
|
8
|
+
- Grid view of all uploaded files
|
|
9
|
+
- Persistent index (localStorage)
|
|
10
|
+
- Delete files from gallery
|
|
11
|
+
- File metadata display (name, size, upload date)
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
1. Click "Choose File" and select a file
|
|
16
|
+
2. Click "Add to Gallery" to upload
|
|
17
|
+
3. Files appear in the grid below
|
|
18
|
+
4. Click "Delete" to remove files from gallery
|
|
19
|
+
5. Gallery index persists in localStorage
|
|
20
|
+
|
|
21
|
+
## Code Structure
|
|
22
|
+
|
|
23
|
+
- `GalleryGrid.tsx` - Grid layout for files
|
|
24
|
+
- `FileCard.tsx` - Individual file display
|
|
25
|
+
- `UploadModal.tsx` - Upload UI
|
|
26
|
+
- `index-manager.ts` - localStorage persistence
|
|
27
|
+
- `App.tsx` - Main app layout
|
|
28
|
+
|
|
29
|
+
## Gallery Index Format
|
|
30
|
+
|
|
31
|
+
The gallery maintains a local index in localStorage:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"version": "1.0",
|
|
36
|
+
"items": [
|
|
37
|
+
{
|
|
38
|
+
"blobId": "abc123...",
|
|
39
|
+
"name": "photo.jpg",
|
|
40
|
+
"size": 102400,
|
|
41
|
+
"contentType": "image/jpeg",
|
|
42
|
+
"uploadedAt": 1705449600000
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"lastModified": 1705449600000
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Deploy to Walrus Sites
|
|
50
|
+
|
|
51
|
+
### First-time Setup
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm setup-walrus-deploy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This will automatically:
|
|
58
|
+
- Install Bun (if not already installed)
|
|
59
|
+
- Download site-builder binary for your OS
|
|
60
|
+
- Clone Walrus Sites portal to `~/.walrus/portal`
|
|
61
|
+
- Add deployment scripts to package.json
|
|
62
|
+
|
|
63
|
+
### Configure SUI Private Key
|
|
64
|
+
|
|
65
|
+
Edit the portal configuration:
|
|
66
|
+
|
|
67
|
+
**Linux/macOS:**
|
|
68
|
+
```bash
|
|
69
|
+
nano ~/.walrus/portal/.env
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Windows:**
|
|
73
|
+
```bash
|
|
74
|
+
notepad %USERPROFILE%\.walrus\portal\.env
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Add your private key:
|
|
78
|
+
```env
|
|
79
|
+
SUI_PRIVATE_KEY=0x...
|
|
80
|
+
WALRUS_NETWORK=testnet
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Build & Deploy
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Build production bundle
|
|
87
|
+
pnpm build
|
|
88
|
+
|
|
89
|
+
# Deploy to Walrus Sites (testnet, 10 epochs)
|
|
90
|
+
pnpm deploy:walrus
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Preview Locally
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pnpm walrus:portal
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This starts the local portal server to preview your deployed site.
|
|
100
|
+
|
|
101
|
+
## Available Scripts
|
|
102
|
+
|
|
103
|
+
- `pnpm dev` - Start development server
|
|
104
|
+
- `pnpm build` - Build for production
|
|
105
|
+
- `pnpm setup-walrus-deploy` - One-time deployment setup
|
|
106
|
+
- `pnpm deploy:walrus` - Deploy to Walrus Sites
|
|
107
|
+
- `pnpm walrus:portal` - Start local portal preview
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gallery-demo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Walrus application scaffolded with create-walrus-app",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"dev": "vite",
|
|
10
|
+
"lint": "eslint . --ext .ts,.tsx",
|
|
11
|
+
"preview": "vite preview",
|
|
12
|
+
"type-check": "tsc --noEmit",
|
|
13
|
+
"setup-walrus-deploy": "bash scripts/setup-walrus-deploy.sh"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@mysten/dapp-kit": "^0.14.0",
|
|
17
|
+
"@mysten/sui": "^1.10.0",
|
|
18
|
+
"@mysten/walrus": "^0.9.0",
|
|
19
|
+
"@tanstack/react-query": "^5.17.0",
|
|
20
|
+
"react": "^18.2.0",
|
|
21
|
+
"react-dom": "^18.2.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/react": "^18.2.48",
|
|
25
|
+
"@types/react-dom": "^18.2.18",
|
|
26
|
+
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
27
|
+
"@typescript-eslint/parser": "^6.19.0",
|
|
28
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
29
|
+
"eslint": "^8.56.0",
|
|
30
|
+
"eslint-plugin-react": "^7.33.2",
|
|
31
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
32
|
+
"typescript": "^5.3.3",
|
|
33
|
+
"vite": "^5.0.11"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# setup-walrus-deploy.sh - Zero-config Walrus Sites deployment setup (testnet)
|
|
3
|
+
# Auto-installs dependencies, downloads tools, clones portal
|
|
4
|
+
# Supports: Linux, macOS, Windows (Git Bash/WSL)
|
|
5
|
+
|
|
6
|
+
set -e # Exit on error
|
|
7
|
+
|
|
8
|
+
echo "🦭 Walrus Sites Zero-Config Setup (testnet)"
|
|
9
|
+
echo ""
|
|
10
|
+
|
|
11
|
+
# ============================================================================
|
|
12
|
+
# 1. Detect OS & Architecture
|
|
13
|
+
# ============================================================================
|
|
14
|
+
detect_os() {
|
|
15
|
+
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
16
|
+
ARCH=$(uname -m)
|
|
17
|
+
|
|
18
|
+
case "$OS" in
|
|
19
|
+
linux*) OS_TYPE="linux" ;;
|
|
20
|
+
darwin*) OS_TYPE="macos" ;;
|
|
21
|
+
mingw*|msys*|cygwin*) OS_TYPE="windows" ;;
|
|
22
|
+
*)
|
|
23
|
+
echo "❌ Unsupported OS: $OS"
|
|
24
|
+
exit 1
|
|
25
|
+
;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
echo "✅ Detected: $OS_TYPE ($ARCH)"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# ============================================================================
|
|
32
|
+
# 2. Auto-install Bun (if not exists)
|
|
33
|
+
# ============================================================================
|
|
34
|
+
setup_bun() {
|
|
35
|
+
if command -v bun &>/dev/null; then
|
|
36
|
+
echo "✅ Bun already installed: $(bun --version)"
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "📥 Installing Bun..."
|
|
41
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
42
|
+
# Windows (PowerShell install via Git Bash)
|
|
43
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
44
|
+
else
|
|
45
|
+
# Linux/macOS
|
|
46
|
+
curl -fsSL https://bun.sh/install | bash
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Add to PATH for current session
|
|
50
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
51
|
+
export PATH="$USERPROFILE/.bun/bin:$PATH"
|
|
52
|
+
else
|
|
53
|
+
export BUN_INSTALL="$HOME/.bun"
|
|
54
|
+
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Verify installation
|
|
58
|
+
if command -v bun &>/dev/null; then
|
|
59
|
+
echo "✅ Bun installed: $(bun --version)"
|
|
60
|
+
else
|
|
61
|
+
echo "⚠️ Bun installed but not in PATH. Restart terminal or run:"
|
|
62
|
+
echo " export PATH=\"\$HOME/.bun/bin:\$PATH\""
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ============================================================================
|
|
67
|
+
# 3. Download site-builder binary (if not exists)
|
|
68
|
+
# ============================================================================
|
|
69
|
+
setup_site_builder() {
|
|
70
|
+
# Set install directory based on OS
|
|
71
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
72
|
+
WALRUS_BIN="$USERPROFILE/.walrus/bin"
|
|
73
|
+
SITE_BUILDER="$WALRUS_BIN/site-builder.exe"
|
|
74
|
+
else
|
|
75
|
+
WALRUS_BIN="$HOME/.walrus/bin"
|
|
76
|
+
SITE_BUILDER="$WALRUS_BIN/site-builder"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Check if already exists
|
|
80
|
+
if [ -f "$SITE_BUILDER" ]; then
|
|
81
|
+
echo "✅ site-builder already exists: $SITE_BUILDER"
|
|
82
|
+
chmod +x "$SITE_BUILDER" 2>/dev/null || true
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
echo "📥 Downloading site-builder for $OS_TYPE..."
|
|
87
|
+
mkdir -p "$WALRUS_BIN"
|
|
88
|
+
|
|
89
|
+
# Select binary based on OS
|
|
90
|
+
case "$OS_TYPE" in
|
|
91
|
+
linux) BINARY_NAME="site-builder-linux" ;;
|
|
92
|
+
macos) BINARY_NAME="site-builder-macos" ;;
|
|
93
|
+
windows) BINARY_NAME="site-builder-windows.exe" ;;
|
|
94
|
+
esac
|
|
95
|
+
|
|
96
|
+
DOWNLOAD_URL="https://github.com/MystenLabs/walrus-sites/releases/latest/download/$BINARY_NAME"
|
|
97
|
+
|
|
98
|
+
# Download with retry
|
|
99
|
+
if ! curl -fsSL -o "$SITE_BUILDER" "$DOWNLOAD_URL"; then
|
|
100
|
+
echo "❌ Failed to download site-builder from: $DOWNLOAD_URL"
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
chmod +x "$SITE_BUILDER"
|
|
105
|
+
echo "✅ site-builder installed: $SITE_BUILDER"
|
|
106
|
+
|
|
107
|
+
# Add to PATH hint (won't persist after script)
|
|
108
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
109
|
+
export PATH="$USERPROFILE/.walrus/bin:$PATH"
|
|
110
|
+
else
|
|
111
|
+
export PATH="$HOME/.walrus/bin:$PATH"
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# ============================================================================
|
|
116
|
+
# 4. Clone Walrus Portal (if not exists)
|
|
117
|
+
# ============================================================================
|
|
118
|
+
setup_portal() {
|
|
119
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
120
|
+
PORTAL_DIR="$USERPROFILE/.walrus/portal"
|
|
121
|
+
else
|
|
122
|
+
PORTAL_DIR="$HOME/.walrus/portal"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ -d "$PORTAL_DIR" ]; then
|
|
126
|
+
echo "✅ Portal already exists: $PORTAL_DIR"
|
|
127
|
+
echo " Updating..."
|
|
128
|
+
cd "$PORTAL_DIR"
|
|
129
|
+
git pull --quiet || echo "⚠️ Git pull failed (may be offline)"
|
|
130
|
+
else
|
|
131
|
+
echo "📂 Cloning Walrus Sites portal..."
|
|
132
|
+
mkdir -p "$(dirname "$PORTAL_DIR")"
|
|
133
|
+
|
|
134
|
+
# Clone with depth=1 for faster download
|
|
135
|
+
if ! git clone --depth=1 https://github.com/MystenLabs/walrus-sites.git "$PORTAL_DIR"; then
|
|
136
|
+
echo "❌ Failed to clone portal repository"
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
cd "$PORTAL_DIR"
|
|
141
|
+
echo "✅ Portal cloned to: $PORTAL_DIR"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Setup .env if not exists
|
|
145
|
+
if [ ! -f ".env" ]; then
|
|
146
|
+
if [ -f ".env.example" ]; then
|
|
147
|
+
cp .env.example .env
|
|
148
|
+
echo "✅ Created .env from .env.example"
|
|
149
|
+
else
|
|
150
|
+
# Create minimal .env
|
|
151
|
+
cat > .env << 'EOF'
|
|
152
|
+
# Walrus Portal Configuration (Testnet)
|
|
153
|
+
WALRUS_NETWORK=testnet
|
|
154
|
+
SUI_PRIVATE_KEY=
|
|
155
|
+
|
|
156
|
+
# Optional: Uncomment to customize
|
|
157
|
+
# PORTAL_PORT=3000
|
|
158
|
+
EOF
|
|
159
|
+
echo "✅ Created .env template"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
echo ""
|
|
163
|
+
echo "⚠️ ACTION REQUIRED:"
|
|
164
|
+
echo " Edit $PORTAL_DIR/.env"
|
|
165
|
+
echo " Add your SUI_PRIVATE_KEY=0x..."
|
|
166
|
+
echo ""
|
|
167
|
+
else
|
|
168
|
+
echo "✅ .env already configured"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# Install portal dependencies
|
|
172
|
+
echo "📦 Installing portal dependencies..."
|
|
173
|
+
if ! bun install --silent; then
|
|
174
|
+
echo "❌ Bun install failed"
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
echo "✅ Portal dependencies installed"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# ============================================================================
|
|
182
|
+
# 5. Add npm scripts to project package.json
|
|
183
|
+
# ============================================================================
|
|
184
|
+
add_project_scripts() {
|
|
185
|
+
PROJECT_DIR="${1:-.}" # Default to current directory
|
|
186
|
+
cd "$PROJECT_DIR" || { echo "❌ Invalid project directory"; exit 1; }
|
|
187
|
+
|
|
188
|
+
if [ ! -f "package.json" ]; then
|
|
189
|
+
echo "❌ No package.json found in $PROJECT_DIR"
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
echo "📝 Adding Walrus deploy scripts to package.json..."
|
|
194
|
+
|
|
195
|
+
# Use Node.js to safely modify package.json (guaranteed to exist in Node projects)
|
|
196
|
+
node -e "
|
|
197
|
+
const fs = require('fs');
|
|
198
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
199
|
+
|
|
200
|
+
pkg.scripts = pkg.scripts || {};
|
|
201
|
+
|
|
202
|
+
// Add scripts (only if not already exists)
|
|
203
|
+
if (!pkg.scripts['setup-walrus-deploy']) {
|
|
204
|
+
pkg.scripts['setup-walrus-deploy'] = 'bash scripts/setup-walrus-deploy.sh';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!pkg.scripts['deploy:walrus']) {
|
|
208
|
+
const siteBuilderPath = process.platform === 'win32'
|
|
209
|
+
? '%USERPROFILE%/.walrus/bin/site-builder.exe'
|
|
210
|
+
: '~/.walrus/bin/site-builder';
|
|
211
|
+
pkg.scripts['deploy:walrus'] = siteBuilderPath + ' --context=testnet deploy ./dist --epochs 10';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!pkg.scripts['walrus:portal']) {
|
|
215
|
+
const portalPath = process.platform === 'win32'
|
|
216
|
+
? 'cd %USERPROFILE%/.walrus/portal'
|
|
217
|
+
: 'cd ~/.walrus/portal';
|
|
218
|
+
pkg.scripts['walrus:portal'] = portalPath + ' && bun run server';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\\n');
|
|
222
|
+
" || {
|
|
223
|
+
echo "❌ Failed to update package.json"
|
|
224
|
+
exit 1
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
echo "✅ Scripts added to package.json:"
|
|
228
|
+
echo " - setup-walrus-deploy"
|
|
229
|
+
echo " - deploy:walrus"
|
|
230
|
+
echo " - walrus:portal"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# ============================================================================
|
|
234
|
+
# Main Execution
|
|
235
|
+
# ============================================================================
|
|
236
|
+
main() {
|
|
237
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
238
|
+
|
|
239
|
+
# Trap errors
|
|
240
|
+
trap 'echo "❌ Setup failed at line $LINENO"' ERR
|
|
241
|
+
|
|
242
|
+
# Prerequisites check
|
|
243
|
+
if ! command -v git &>/dev/null; then
|
|
244
|
+
echo "❌ Git not found. Install: https://git-scm.com"
|
|
245
|
+
exit 1
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
if ! command -v node &>/dev/null; then
|
|
249
|
+
echo "❌ Node.js not found. Install: https://nodejs.org"
|
|
250
|
+
exit 1
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Run setup steps
|
|
254
|
+
detect_os
|
|
255
|
+
setup_bun
|
|
256
|
+
setup_site_builder
|
|
257
|
+
setup_portal
|
|
258
|
+
add_project_scripts "$@"
|
|
259
|
+
|
|
260
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
261
|
+
echo ""
|
|
262
|
+
echo "🎉 Setup Complete!"
|
|
263
|
+
echo ""
|
|
264
|
+
echo "Next Steps:"
|
|
265
|
+
echo " 1. Configure your SUI private key:"
|
|
266
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
267
|
+
echo " notepad %USERPROFILE%\\.walrus\\portal\\.env"
|
|
268
|
+
else
|
|
269
|
+
echo " nano ~/.walrus/portal/.env"
|
|
270
|
+
fi
|
|
271
|
+
echo " Add: SUI_PRIVATE_KEY=0x..."
|
|
272
|
+
echo ""
|
|
273
|
+
echo " 2. Build your project:"
|
|
274
|
+
echo " pnpm build"
|
|
275
|
+
echo ""
|
|
276
|
+
echo " 3. Deploy to Walrus Sites:"
|
|
277
|
+
echo " pnpm deploy:walrus"
|
|
278
|
+
echo ""
|
|
279
|
+
echo " 4. (Optional) Preview locally:"
|
|
280
|
+
echo " pnpm walrus:portal"
|
|
281
|
+
echo ""
|
|
282
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Run main with all script arguments
|
|
286
|
+
main "$@"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { AppLayout } from './components/layout/app-layout.js';
|
|
3
|
+
import { GalleryGrid } from './components/features/gallery-grid.js';
|
|
4
|
+
import { UploadModal } from './components/features/upload-modal.js';
|
|
5
|
+
import './index.css';
|
|
6
|
+
|
|
7
|
+
function App() {
|
|
8
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<AppLayout>
|
|
12
|
+
<div className="gallery-app">
|
|
13
|
+
<h2><span className="text-accent">🖼️</span> File Gallery</h2>
|
|
14
|
+
<p className="text-secondary">Upload and manage multiple files on <span className="text-accent">Walrus</span></p>
|
|
15
|
+
|
|
16
|
+
<UploadModal onSuccess={() => setRefreshKey((k) => k + 1)} />
|
|
17
|
+
<GalleryGrid key={refreshKey} />
|
|
18
|
+
</div>
|
|
19
|
+
</AppLayout>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default App;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { removeItem } from '../../utils/index-manager.js';
|
|
3
|
+
import type { GalleryItem } from '../../lib/walrus/types.js';
|
|
4
|
+
import {
|
|
5
|
+
generatePreviewUrl,
|
|
6
|
+
isImageType,
|
|
7
|
+
revokePreviewUrl,
|
|
8
|
+
} from '../../utils/preview-generator.js';
|
|
9
|
+
|
|
10
|
+
interface FileCardProps {
|
|
11
|
+
item: GalleryItem;
|
|
12
|
+
onDelete: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function FileCard({ item, onDelete }: FileCardProps) {
|
|
16
|
+
const [previewUrl, setPreviewUrl] = useState<string | null>(
|
|
17
|
+
item.previewUrl || null
|
|
18
|
+
);
|
|
19
|
+
const [isLoadingPreview, setIsLoadingPreview] = useState(false);
|
|
20
|
+
|
|
21
|
+
// Load preview if it's an image and we don't have a preview URL yet
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!previewUrl && isImageType(item.contentType)) {
|
|
24
|
+
setIsLoadingPreview(true);
|
|
25
|
+
generatePreviewUrl(item.blobId, item.contentType)
|
|
26
|
+
.then((url) => setPreviewUrl(url))
|
|
27
|
+
.catch((error) => {
|
|
28
|
+
console.error('Failed to load preview:', error);
|
|
29
|
+
})
|
|
30
|
+
.finally(() => setIsLoadingPreview(false));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Cleanup: revoke object URL when component unmounts
|
|
34
|
+
return () => {
|
|
35
|
+
if (previewUrl) {
|
|
36
|
+
revokePreviewUrl(previewUrl);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}, [item.blobId, item.contentType, previewUrl]);
|
|
40
|
+
|
|
41
|
+
const handleDelete = () => {
|
|
42
|
+
if (confirm(`Delete ${item.name}?`)) {
|
|
43
|
+
// Revoke preview URL before deleting
|
|
44
|
+
if (previewUrl) {
|
|
45
|
+
revokePreviewUrl(previewUrl);
|
|
46
|
+
}
|
|
47
|
+
removeItem(item.blobId);
|
|
48
|
+
onDelete();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const formatBytes = (bytes: number): string => {
|
|
53
|
+
if (bytes === 0) return '0 Bytes';
|
|
54
|
+
const k = 1024;
|
|
55
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
56
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
57
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const formatDate = (timestamp: number): string => {
|
|
61
|
+
return new Date(timestamp).toLocaleString();
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="file-card">
|
|
66
|
+
{/* Image Preview */}
|
|
67
|
+
{isImageType(item.contentType) && (
|
|
68
|
+
<div className="image-preview">
|
|
69
|
+
{isLoadingPreview ? (
|
|
70
|
+
<div className="loading-placeholder">Loading preview...</div>
|
|
71
|
+
) : previewUrl ? (
|
|
72
|
+
<img src={previewUrl} alt={item.name} />
|
|
73
|
+
) : (
|
|
74
|
+
<div className="error-placeholder">Preview not available</div>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
{/* File Info */}
|
|
80
|
+
<h4>{item.name}</h4>
|
|
81
|
+
<p className="text-secondary">Size: {formatBytes(item.size)}</p>
|
|
82
|
+
<p className="text-secondary">Uploaded: {formatDate(item.uploadedAt)}</p>
|
|
83
|
+
<p className="blob-id text-accent">{item.blobId.slice(0, 16)}...</p>
|
|
84
|
+
<button onClick={handleDelete} className="btn-danger">
|
|
85
|
+
Delete
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
-
import { FileCard } from './
|
|
3
|
-
import { loadIndex } from '
|
|
4
|
-
import type { GalleryItem } from '
|
|
2
|
+
import { FileCard } from './file-card.js';
|
|
3
|
+
import { loadIndex } from '../../utils/index-manager.js';
|
|
4
|
+
import type { GalleryItem } from '../../lib/walrus/types.js';
|
|
5
5
|
|
|
6
6
|
export function GalleryGrid() {
|
|
7
7
|
const [items, setItems] = useState<GalleryItem[]>([]);
|
|
@@ -19,7 +19,7 @@ export function GalleryGrid() {
|
|
|
19
19
|
return (
|
|
20
20
|
<div className="gallery-grid">
|
|
21
21
|
{items.length === 0 ? (
|
|
22
|
-
<p>No files yet. Upload your first file!</p>
|
|
22
|
+
<p className="text-secondary">No files yet. Upload your first file!</p>
|
|
23
23
|
) : (
|
|
24
24
|
items.map((item) => (
|
|
25
25
|
<FileCard key={item.blobId} item={item} onDelete={refreshGallery} />
|
|
@@ -27,4 +27,4 @@ export function GalleryGrid() {
|
|
|
27
27
|
)}
|
|
28
28
|
</div>
|
|
29
29
|
);
|
|
30
|
-
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useUpload } from '../../hooks/use-upload.js';
|
|
3
|
+
import { addItem } from '../../utils/index-manager.js';
|
|
4
|
+
import { generatePreviewUrl, isImageType } from '../../utils/preview-generator.js';
|
|
5
|
+
|
|
6
|
+
interface UploadModalProps {
|
|
7
|
+
onSuccess: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function UploadModal({ onSuccess }: UploadModalProps) {
|
|
11
|
+
const [file, setFile] = useState<File | null>(null);
|
|
12
|
+
const upload = useUpload();
|
|
13
|
+
|
|
14
|
+
const handleUpload = async () => {
|
|
15
|
+
if (!file) return;
|
|
16
|
+
|
|
17
|
+
upload.mutate(
|
|
18
|
+
{ file, options: { epochs: 1 } },
|
|
19
|
+
{
|
|
20
|
+
onSuccess: async (data) => {
|
|
21
|
+
// Generate preview URL for images
|
|
22
|
+
let previewUrl: string | undefined;
|
|
23
|
+
if (isImageType(file.type)) {
|
|
24
|
+
try {
|
|
25
|
+
previewUrl = await generatePreviewUrl(data.blobId, file.type);
|
|
26
|
+
console.log('Preview URL generated:', previewUrl);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Failed to generate preview URL:', error);
|
|
29
|
+
// Continue without preview - FileCard will try to load it later
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Add item to gallery with preview URL
|
|
34
|
+
addItem({
|
|
35
|
+
blobId: data.blobId,
|
|
36
|
+
name: file.name,
|
|
37
|
+
size: file.size,
|
|
38
|
+
contentType: file.type,
|
|
39
|
+
uploadedAt: Date.now(),
|
|
40
|
+
previewUrl,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
setFile(null);
|
|
44
|
+
onSuccess();
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="upload-modal">
|
|
52
|
+
<input
|
|
53
|
+
type="file"
|
|
54
|
+
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
|
55
|
+
className="file-input"
|
|
56
|
+
/>
|
|
57
|
+
<button
|
|
58
|
+
onClick={handleUpload}
|
|
59
|
+
disabled={!file || upload.isPending}
|
|
60
|
+
className="btn-primary"
|
|
61
|
+
>
|
|
62
|
+
{upload.isPending ? 'Uploading...' : 'Add to Gallery'}
|
|
63
|
+
</button>
|
|
64
|
+
{upload.error && (
|
|
65
|
+
<p className="text-error">Error: {upload.error.message}</p>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { WalletConnect } from '../features/wallet-connect.js';
|
|
3
|
+
|
|
4
|
+
interface LayoutProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function AppLayout({ children }: LayoutProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="app-layout">
|
|
11
|
+
<header className="app-header">
|
|
12
|
+
<h1><span className="text-secondary">🌊</span> <span className="text-accent">Walrus</span> App</h1>
|
|
13
|
+
<WalletConnect />
|
|
14
|
+
</header>
|
|
15
|
+
<main className="app-main">{children}</main>
|
|
16
|
+
<footer className="app-footer">
|
|
17
|
+
<p className="text-secondary">Powered by <span className="text-accent">Walrus</span> & <span style={{ color: 'var(--walrus-accent-blue)' }}>Sui</span></p>
|
|
18
|
+
</footer>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
-
}
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { storageAdapter } from '../lib/walrus/index.js';
|
|
19
3
|
|
|
20
4
|
export function useDownload(blobId: string | null) {
|
|
21
5
|
return useQuery({
|