@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 +72 -109
- package/dist/server/index.js +4 -7
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,157 +1,120 @@
|
|
|
1
1
|
# @gallop.software/studio
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Standalone media manager for Gallop templates. Upload, process, and sync images to Cloudflare R2 CDN.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **
|
|
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
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
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.
|
|
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
|
-
|
|
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
|
-
#
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
58
|
+
npm run studio
|
|
83
59
|
```
|
|
84
60
|
|
|
85
|
-
|
|
61
|
+
Studio opens in your browser on an available port (default 3001).
|
|
86
62
|
|
|
87
|
-
##
|
|
63
|
+
## Environment Variables
|
|
88
64
|
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
##
|
|
83
|
+
## Setting Up Cache Purge (Custom Domains)
|
|
103
84
|
|
|
104
|
-
|
|
85
|
+
If using a custom domain for your CDN:
|
|
105
86
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
94
|
+
Studio stores image metadata in `_data/_studio.json`:
|
|
121
95
|
|
|
122
96
|
```json
|
|
123
97
|
{
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
package/dist/server/index.js
CHANGED
|
@@ -2675,12 +2675,9 @@ async function startServer(options) {
|
|
|
2675
2675
|
}
|
|
2676
2676
|
const app = express();
|
|
2677
2677
|
process.env.STUDIO_WORKSPACE = workspace;
|
|
2678
|
-
const
|
|
2679
|
-
|
|
2680
|
-
|
|
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.
|
|
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)};
|