@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.
Files changed (101) hide show
  1. package/dist/generator/file-ops.d.ts +8 -0
  2. package/dist/generator/file-ops.js +20 -0
  3. package/dist/generator/index.js +37 -22
  4. package/dist/generator/layers.d.ts +15 -2
  5. package/dist/generator/layers.js +38 -47
  6. package/dist/generator/types.d.ts +9 -1
  7. package/dist/index.js +1 -2
  8. package/dist/post-install/git.d.ts +8 -0
  9. package/dist/post-install/git.js +2 -0
  10. package/dist/post-install/index.d.ts +0 -1
  11. package/dist/post-install/index.js +2 -15
  12. package/dist/post-install/messages.js +1 -1
  13. package/package.json +3 -3
  14. package/{templates/base → presets/react-mysten-gallery}/.env.example +31 -31
  15. package/presets/react-mysten-gallery/.gitkeep +4 -0
  16. package/{templates/gallery → presets/react-mysten-gallery}/README.md +25 -22
  17. package/presets/react-mysten-gallery/package.json +34 -0
  18. package/presets/react-mysten-gallery/src/App.tsx +23 -0
  19. package/presets/react-mysten-gallery/src/components/features/file-card.tsx +89 -0
  20. package/{templates/gallery/src/components/GalleryGrid.tsx → presets/react-mysten-gallery/src/components/features/gallery-grid.tsx} +5 -5
  21. package/presets/react-mysten-gallery/src/components/features/upload-modal.tsx +69 -0
  22. package/{templates/react/src/components/WalletConnect.tsx → presets/react-mysten-gallery/src/components/features/wallet-connect.tsx} +1 -1
  23. package/presets/react-mysten-gallery/src/components/layout/app-layout.tsx +21 -0
  24. package/{templates/react/src/hooks/useStorage.ts → presets/react-mysten-gallery/src/hooks/use-download.ts} +2 -18
  25. package/presets/react-mysten-gallery/src/hooks/use-upload.ts +49 -0
  26. package/{templates/react/src/hooks/useWallet.ts → presets/react-mysten-gallery/src/hooks/use-wallet.ts} +2 -7
  27. package/presets/react-mysten-gallery/src/index.css +384 -0
  28. package/presets/react-mysten-gallery/src/index.ts +17 -0
  29. package/presets/react-mysten-gallery/src/lib/walrus/adapter.ts +197 -0
  30. package/presets/react-mysten-gallery/src/lib/walrus/client.ts +87 -0
  31. package/presets/react-mysten-gallery/src/lib/walrus/index.ts +4 -0
  32. package/presets/react-mysten-gallery/src/lib/walrus/types.ts +101 -0
  33. package/{templates/react → presets/react-mysten-gallery}/src/main.tsx +0 -1
  34. package/{templates/react → presets/react-mysten-gallery}/src/providers/WalletProvider.tsx +16 -1
  35. package/{templates/base → presets/react-mysten-gallery}/src/utils/env.ts +41 -41
  36. package/{templates/gallery → presets/react-mysten-gallery}/src/utils/index-manager.ts +2 -2
  37. package/presets/react-mysten-gallery/src/utils/mime-type.ts +97 -0
  38. package/presets/react-mysten-gallery/src/utils/preview-generator.ts +134 -0
  39. package/{templates/react → presets/react-mysten-gallery}/tsconfig.json +20 -8
  40. package/presets/react-mysten-simple-upload/.env.example +31 -0
  41. package/presets/react-mysten-simple-upload/.gitkeep +4 -0
  42. package/presets/react-mysten-simple-upload/index.html +13 -0
  43. package/{templates/react → presets/react-mysten-simple-upload}/package.json +13 -11
  44. package/presets/react-mysten-simple-upload/src/App.tsx +27 -0
  45. package/presets/react-mysten-simple-upload/src/components/features/file-preview.tsx +73 -0
  46. package/{templates/simple-upload/src/components/UploadForm.tsx → presets/react-mysten-simple-upload/src/components/features/upload-form.tsx} +15 -5
  47. package/presets/react-mysten-simple-upload/src/components/features/wallet-connect.tsx +21 -0
  48. package/presets/react-mysten-simple-upload/src/components/layout/app-layout.tsx +21 -0
  49. package/presets/react-mysten-simple-upload/src/hooks/use-download.ts +24 -0
  50. package/presets/react-mysten-simple-upload/src/hooks/use-upload.ts +49 -0
  51. package/presets/react-mysten-simple-upload/src/hooks/use-wallet.ts +11 -0
  52. package/presets/react-mysten-simple-upload/src/index.css +252 -0
  53. package/presets/react-mysten-simple-upload/src/index.ts +16 -0
  54. package/presets/react-mysten-simple-upload/src/lib/walrus/adapter.ts +197 -0
  55. package/presets/react-mysten-simple-upload/src/lib/walrus/client.ts +87 -0
  56. package/presets/react-mysten-simple-upload/src/lib/walrus/index.ts +4 -0
  57. package/{templates/base/src/adapters/storage.ts → presets/react-mysten-simple-upload/src/lib/walrus/types.ts} +83 -58
  58. package/presets/react-mysten-simple-upload/src/main.tsx +16 -0
  59. package/presets/react-mysten-simple-upload/src/providers/QueryProvider.tsx +18 -0
  60. package/presets/react-mysten-simple-upload/src/providers/WalletProvider.tsx +52 -0
  61. package/presets/react-mysten-simple-upload/src/utils/env.ts +41 -0
  62. package/presets/react-mysten-simple-upload/src/utils/mime-type.ts +97 -0
  63. package/presets/react-mysten-simple-upload/tsconfig.json +39 -0
  64. package/presets/react-mysten-simple-upload/tsconfig.node.json +10 -0
  65. package/presets/react-mysten-simple-upload/vite.config.ts +19 -0
  66. package/templates/base/README.md +0 -54
  67. package/templates/base/package.json +0 -19
  68. package/templates/base/src/types/index.ts +0 -9
  69. package/templates/base/src/types/walrus.ts +0 -22
  70. package/templates/base/src/utils/format.ts +0 -29
  71. package/templates/base/tsconfig.json +0 -19
  72. package/templates/gallery/package.json +0 -6
  73. package/templates/gallery/src/App.tsx +0 -21
  74. package/templates/gallery/src/components/FileCard.tsx +0 -27
  75. package/templates/gallery/src/components/UploadModal.tsx +0 -45
  76. package/templates/gallery/src/styles.css +0 -58
  77. package/templates/gallery/src/types/gallery.ts +0 -13
  78. package/templates/react/.eslintrc.json +0 -26
  79. package/templates/react/README.md +0 -80
  80. package/templates/react/src/App.tsx +0 -14
  81. package/templates/react/src/components/Layout.tsx +0 -21
  82. package/templates/react/src/dapp-kit.css +0 -1
  83. package/templates/react/src/index.css +0 -50
  84. package/templates/react/src/index.ts +0 -10
  85. package/templates/sdk-mysten/README.md +0 -65
  86. package/templates/sdk-mysten/package.json +0 -14
  87. package/templates/sdk-mysten/src/adapter.ts +0 -80
  88. package/templates/sdk-mysten/src/client.ts +0 -45
  89. package/templates/sdk-mysten/src/config.ts +0 -33
  90. package/templates/sdk-mysten/src/index.ts +0 -11
  91. package/templates/sdk-mysten/src/types.ts +0 -19
  92. package/templates/sdk-mysten/test/adapter.test.ts +0 -20
  93. package/templates/simple-upload/package.json +0 -6
  94. package/templates/simple-upload/src/App.tsx +0 -27
  95. package/templates/simple-upload/src/components/FilePreview.tsx +0 -40
  96. package/templates/simple-upload/src/styles.css +0 -33
  97. /package/{templates/react → presets/react-mysten-gallery}/index.html +0 -0
  98. /package/{templates/react → presets/react-mysten-gallery}/src/providers/QueryProvider.tsx +0 -0
  99. /package/{templates/react → presets/react-mysten-gallery}/tsconfig.node.json +0 -0
  100. /package/{templates/react → presets/react-mysten-gallery}/vite.config.ts +0 -0
  101. /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,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
@@ -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
+ });
@@ -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,9 +0,0 @@
1
- export * from './walrus.js';
2
-
3
- export interface Result<T, E = Error> {
4
- success: boolean;
5
- data?: T;
6
- error?: E;
7
- }
8
-
9
- export type AsyncResult<T, E = Error> = Promise<Result<T, E>>;
@@ -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,6 +0,0 @@
1
- {
2
- "name": "{{projectName}}",
3
- "version": "0.1.0",
4
- "private": true,
5
- "dependencies": {}
6
- }
@@ -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,13 +0,0 @@
1
- export interface GalleryItem {
2
- blobId: string;
3
- name: string;
4
- size: number;
5
- contentType: string;
6
- uploadedAt: number;
7
- }
8
-
9
- export interface GalleryIndex {
10
- version: '1.0';
11
- items: GalleryItem[];
12
- lastModified: number;
13
- }
@@ -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
- }