@gallop.software/studio 2.2.2 → 2.2.4

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/README.md CHANGED
@@ -1,157 +1,120 @@
1
1
  # @gallop.software/studio
2
2
 
3
- Media manager for Gallop templates. Upload, process, and sync images to Cloudflare R2 CDN.
3
+ Standalone media manager for Gallop templates. Upload, process, and sync images to Cloudflare R2 CDN.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Floating button** in dev mode opens a full-screen media manager
7
+ - **Standalone dev server** - runs on its own port, doesn't affect your app
8
8
  - **Upload images** with automatic thumbnail generation
9
9
  - **Browse folders** with grid and list views
10
10
  - **Multi-select** for batch operations
11
- - **Sync to CDN** (Cloudflare R2) with automatic local cleanup
12
- - **Blurhash** and dominant color extraction
13
- - **Meta file** with full TypeScript types
11
+ - **Push to CDN** (Cloudflare R2) with automatic local cleanup
12
+ - **Cache purge** for custom CDN domains
13
+ - **Blurhash** generation for image placeholders
14
14
 
15
15
  ## Installation
16
16
 
17
17
  ```bash
18
- npm install @gallop.software/studio
18
+ npm install @gallop.software/studio --save-dev
19
19
  ```
20
20
 
21
21
  ## Quick Start
22
22
 
23
- ### 1. Add StudioProvider to your layout
24
-
25
- ```tsx
26
- // src/app/layout.tsx
27
- import { StudioProvider } from '@gallop.software/studio'
28
-
29
- export default function RootLayout({ children }) {
30
- return (
31
- <html>
32
- <body>
33
- {children}
34
- <StudioProvider />
35
- </body>
36
- </html>
37
- )
38
- }
39
- ```
40
-
41
- ### 2. Create API routes
42
-
43
- Create these files in your project:
44
-
45
- ```ts
46
- // src/app/api/studio/list/route.ts
47
- export { GET } from '@gallop.software/studio/api/list'
48
-
49
- // src/app/api/studio/upload/route.ts
50
- export { POST } from '@gallop.software/studio/api/upload'
51
-
52
- // src/app/api/studio/delete/route.ts
53
- export { POST } from '@gallop.software/studio/api/delete'
23
+ ### 1. Create `.env.studio`
54
24
 
55
- // src/app/api/studio/scan/route.ts
56
- export { GET } from '@gallop.software/studio/api/scan'
57
-
58
- // src/app/api/studio/sync/route.ts
59
- export { POST } from '@gallop.software/studio/api/sync'
60
-
61
- // src/app/api/studio/reprocess/route.ts
62
- export { POST } from '@gallop.software/studio/api/reprocess'
63
- ```
64
-
65
- ### 3. Configure Cloudflare R2 (optional)
66
-
67
- Add to your `.env.local`:
25
+ Create a `.env.studio` file in your project root:
68
26
 
69
27
  ```bash
28
+ # Dev site link (opens in new tab from Studio header)
29
+ STUDIO_DEV_SITE_URL=http://localhost:3000
30
+
31
+ # Cloudflare R2 Storage
70
32
  CLOUDFLARE_R2_ACCOUNT_ID=your_account_id
71
33
  CLOUDFLARE_R2_ACCESS_KEY_ID=your_access_key
72
34
  CLOUDFLARE_R2_SECRET_ACCESS_KEY=your_secret_key
73
35
  CLOUDFLARE_R2_BUCKET_NAME=your_bucket
36
+ CLOUDFLARE_R2_PUBLIC_URL=https://your-cdn.example.com
74
37
 
75
- # Default R2 URL or custom CDN domain
76
- CLOUDFLARE_R2_PUBLIC_URL=https://your-bucket.r2.dev
38
+ # Cloudflare Cache Purge (optional, for custom domains)
39
+ CLOUDFLARE_ZONE_ID=your_zone_id
40
+ CLOUDFLARE_API_TOKEN=your_api_token
77
41
  ```
78
42
 
79
- ### 4. Run your dev server
43
+ Add `.env.studio` to your `.gitignore`.
44
+
45
+ ### 2. Add script to package.json
46
+
47
+ ```json
48
+ {
49
+ "scripts": {
50
+ "studio": "studio"
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### 3. Run Studio
80
56
 
81
57
  ```bash
82
- npm run dev
58
+ npm run studio
83
59
  ```
84
60
 
85
- A floating button appears in the bottom-right corner. Click to open Studio.
61
+ Studio opens in your browser on an available port (default 3001).
86
62
 
87
- ## Using Images
63
+ ## Environment Variables
88
64
 
89
- ```tsx
90
- import { meta, getImageUrl, type ImageSize } from '@gallop.software/studio'
65
+ | Variable | Required | Description |
66
+ |----------|----------|-------------|
67
+ | `STUDIO_DEV_SITE_URL` | No | URL to your dev site (shown as link in header) |
68
+ | `CLOUDFLARE_R2_ACCOUNT_ID` | For CDN | Your Cloudflare account ID |
69
+ | `CLOUDFLARE_R2_ACCESS_KEY_ID` | For CDN | R2 API access key |
70
+ | `CLOUDFLARE_R2_SECRET_ACCESS_KEY` | For CDN | R2 API secret key |
71
+ | `CLOUDFLARE_R2_BUCKET_NAME` | For CDN | R2 bucket name |
72
+ | `CLOUDFLARE_R2_PUBLIC_URL` | For CDN | Public URL for your R2 bucket or custom domain |
73
+ | `CLOUDFLARE_ZONE_ID` | For cache purge | Cloudflare zone ID for cache purge |
74
+ | `CLOUDFLARE_API_TOKEN` | For cache purge | API token with Cache Purge permission |
91
75
 
92
- // Get image metadata
93
- const hero = meta.images['hero.jpg']
94
- console.log(hero.sizes.medium.width) // 700
95
- console.log(hero.blurhash) // "LEHV6nWB..."
76
+ ## Setting Up Cloudflare R2
96
77
 
97
- // Get resolved URL (handles CDN vs local)
98
- const url = getImageUrl('hero.jpg', 'medium')
99
- // "/images/hero-700.jpg" or "https://cdn.example.com/images/hero-700.jpg"
100
- ```
78
+ 1. Go to Cloudflare Dashboard R2 → Create bucket
79
+ 2. Go to R2 → Manage R2 API Tokens → Create API Token
80
+ 3. Copy the Access Key ID and Secret Access Key
81
+ 4. Enable public access or set up a custom domain
101
82
 
102
- ## Folder Structure
83
+ ## Setting Up Cache Purge (Custom Domains)
103
84
 
104
- Studio manages these folders:
85
+ If using a custom domain for your CDN:
105
86
 
106
- ```
107
- public/
108
- ├── originals/ # Source images (uploaded here)
109
- │ └── hero.jpg
110
- └── images/ # Generated thumbnails
111
- ├── hero.jpg # Full size (optimized)
112
- ├── hero-1400.jpg # Large
113
- ├── hero-700.jpg # Medium
114
- └── hero-300.jpg # Small
115
-
116
- _data/
117
- └── _meta.json # Image metadata
118
- ```
87
+ 1. Go to Cloudflare Dashboard → select your domain → copy Zone ID from sidebar
88
+ 2. Go to Account → API Tokens → Create Token
89
+ 3. Add permission: Zone Cache Purge → Edit
90
+ 4. Copy the token value
91
+
92
+ ## Metadata
119
93
 
120
- ## Meta Schema
94
+ Studio stores image metadata in `_data/_studio.json`:
121
95
 
122
96
  ```json
123
97
  {
124
- "$schema": "https://gallop.software/schemas/studio-meta.json",
125
- "version": 1,
126
- "generatedAt": "2026-01-24T12:00:00Z",
127
- "images": {
128
- "hero.jpg": {
129
- "original": {
130
- "path": "/originals/hero.jpg",
131
- "width": 2400,
132
- "height": 1600,
133
- "fileSize": 1245000
134
- },
135
- "sizes": {
136
- "full": { "path": "/images/hero.jpg", "width": 2400, "height": 1600 },
137
- "large": { "path": "/images/hero-1400.jpg", "width": 1400, "height": 934 },
138
- "medium": { "path": "/images/hero-700.jpg", "width": 700, "height": 467 },
139
- "small": { "path": "/images/hero-300.jpg", "width": 300, "height": 200 }
140
- },
141
- "blurhash": "LEHV6nWB2yk8pyo0adR*.7kCMdnj",
142
- "dominantColor": "#a85c32",
143
- "cdn": null
144
- }
98
+ "_cdns": ["https://your-cdn.example.com"],
99
+ "/hero.jpg": {
100
+ "o": { "w": 2400, "h": 1600 },
101
+ "b": "LEHV6nWB2yk8pyo0adR*.7kCMdnj",
102
+ "sm": { "w": 300, "h": 200 },
103
+ "md": { "w": 700, "h": 467 },
104
+ "lg": { "w": 1400, "h": 934 },
105
+ "f": { "w": 2400, "h": 1600 },
106
+ "c": 0
145
107
  }
146
108
  }
147
109
  ```
148
110
 
149
- ## CDN Workflow
150
-
151
- 1. Upload image saves to `originals/`, generates thumbnails
152
- 2. Click "Sync CDN" uploads to R2, deletes local files
153
- 3. `meta.images[key].cdn.synced` becomes `true`
154
- 4. Image component uses CDN URL automatically
111
+ | Property | Description |
112
+ |----------|-------------|
113
+ | `o` | Original dimensions `{ w, h }` |
114
+ | `b` | Blurhash string |
115
+ | `sm/md/lg/f` | Thumbnail dimensions (small/medium/large/full) |
116
+ | `c` | CDN index (references `_cdns` array) |
117
+ | `u` | Update pending flag (local file overrides cloud) |
155
118
 
156
119
  ## License
157
120
 
@@ -2675,12 +2675,9 @@ async function startServer(options) {
2675
2675
  }
2676
2676
  const app = express();
2677
2677
  process.env.STUDIO_WORKSPACE = workspace;
2678
- const envLocalPath = join(workspace, ".env.local");
2679
- const envPath = join(workspace, ".env");
2680
- if (existsSync(envLocalPath)) {
2681
- loadEnv({ path: envLocalPath, quiet: true });
2682
- } else if (existsSync(envPath)) {
2683
- loadEnv({ path: envPath, quiet: true });
2678
+ const envStudioPath = join(workspace, ".env.studio");
2679
+ if (existsSync(envStudioPath)) {
2680
+ loadEnv({ path: envStudioPath, quiet: true });
2684
2681
  }
2685
2682
  app.use((req, res, next) => {
2686
2683
  if (req.path === "/api/studio/upload") {
@@ -2724,7 +2721,7 @@ async function startServer(options) {
2724
2721
  const htmlPath = join(clientDir, "index.html");
2725
2722
  if (existsSync(htmlPath)) {
2726
2723
  let html = readFileSync(htmlPath, "utf-8");
2727
- const siteUrl = process.env.NEXT_PUBLIC_PRODUCTION_URL || "";
2724
+ const siteUrl = process.env.STUDIO_DEV_SITE_URL || "";
2728
2725
  const script = `<script>
2729
2726
  window.__STUDIO_WORKSPACE__ = ${JSON.stringify(workspace)};
2730
2727
  window.__STUDIO_SITE_URL__ = ${JSON.stringify(siteUrl)};