@gallop.software/studio 0.1.68 → 0.1.69
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/handlers.d.mts +1 -1
- package/dist/handlers.d.ts +1 -1
- package/dist/index.d.mts +17 -12
- package/dist/index.d.ts +17 -12
- package/dist/index.js +90 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +90 -18
- package/dist/index.mjs.map +1 -1
- package/dist/types-CNVLjvIw.d.mts +119 -0
- package/dist/types-CNVLjvIw.d.ts +119 -0
- package/package.json +1 -1
- package/dist/types-1m_7EjJU.d.mts +0 -79
- package/dist/types-1m_7EjJU.d.ts +0 -79
package/dist/handlers.d.mts
CHANGED
package/dist/handlers.d.ts
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _emotion_react_jsx_runtime from '@emotion/react/jsx-runtime';
|
|
2
|
-
import { I as
|
|
3
|
-
export { C as CdnStatus, F as FileItem, c as StudioConfig } from './types-
|
|
2
|
+
import { I as ImageEntry, S as StudioMeta, L as LeanMeta } from './types-CNVLjvIw.mjs';
|
|
3
|
+
export { C as CdnStatus, F as FileItem, a as ImageSize, b as LeanImageEntry, c as LeanImageSize, d as SizeEntry, e as StudioConfig, g as getThumbnailPath, i as isLeanMeta, t as toLeanMeta } from './types-CNVLjvIw.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Floating button that opens the Studio modal.
|
|
@@ -9,26 +9,31 @@ export { C as CdnStatus, F as FileItem, c as StudioConfig } from './types-1m_7Ej
|
|
|
9
9
|
*/
|
|
10
10
|
declare function StudioButton(): _emotion_react_jsx_runtime.JSX.Element | null;
|
|
11
11
|
|
|
12
|
+
type UnifiedMeta = StudioMeta | LeanMeta;
|
|
12
13
|
/**
|
|
13
|
-
* The meta object
|
|
14
|
-
* This is read from _data/_meta.json in the consuming project.
|
|
14
|
+
* The meta object in verbose format (for backward compatibility)
|
|
15
15
|
*/
|
|
16
16
|
declare const meta: StudioMeta;
|
|
17
17
|
/**
|
|
18
|
-
* Initialize meta from a JSON object (
|
|
18
|
+
* Initialize meta from a JSON object (handles both formats)
|
|
19
19
|
*/
|
|
20
|
-
declare function initializeMeta(data:
|
|
20
|
+
declare function initializeMeta(data: UnifiedMeta): void;
|
|
21
21
|
/**
|
|
22
|
-
* Get the resolved URL for an image
|
|
22
|
+
* Get the resolved URL for an image
|
|
23
|
+
* Works with both verbose and lean formats
|
|
23
24
|
*/
|
|
24
|
-
declare function getImageUrl(imageKey: string, size?:
|
|
25
|
+
declare function getImageUrl(imageKey: string, size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large' | 'full'): string | undefined;
|
|
25
26
|
/**
|
|
26
|
-
* Get
|
|
27
|
+
* Get image entry (verbose format for backward compat)
|
|
27
28
|
*/
|
|
28
29
|
declare function getStudioMeta(imageKey: string): ImageEntry | undefined;
|
|
29
30
|
/**
|
|
30
|
-
* Get
|
|
31
|
+
* Get dimensions for an image
|
|
31
32
|
*/
|
|
32
|
-
declare function getImageSize(imageKey: string, size?:
|
|
33
|
+
declare function getImageSize(imageKey: string, size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large' | 'full'): {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
path?: string;
|
|
37
|
+
} | undefined;
|
|
33
38
|
|
|
34
|
-
export { ImageEntry,
|
|
39
|
+
export { ImageEntry, LeanMeta, StudioButton, StudioMeta, getImageSize, getImageUrl, getStudioMeta, initializeMeta, meta };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _emotion_react_jsx_runtime from '@emotion/react/jsx-runtime';
|
|
2
|
-
import { I as
|
|
3
|
-
export { C as CdnStatus, F as FileItem, c as StudioConfig } from './types-
|
|
2
|
+
import { I as ImageEntry, S as StudioMeta, L as LeanMeta } from './types-CNVLjvIw.js';
|
|
3
|
+
export { C as CdnStatus, F as FileItem, a as ImageSize, b as LeanImageEntry, c as LeanImageSize, d as SizeEntry, e as StudioConfig, g as getThumbnailPath, i as isLeanMeta, t as toLeanMeta } from './types-CNVLjvIw.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Floating button that opens the Studio modal.
|
|
@@ -9,26 +9,31 @@ export { C as CdnStatus, F as FileItem, c as StudioConfig } from './types-1m_7Ej
|
|
|
9
9
|
*/
|
|
10
10
|
declare function StudioButton(): _emotion_react_jsx_runtime.JSX.Element | null;
|
|
11
11
|
|
|
12
|
+
type UnifiedMeta = StudioMeta | LeanMeta;
|
|
12
13
|
/**
|
|
13
|
-
* The meta object
|
|
14
|
-
* This is read from _data/_meta.json in the consuming project.
|
|
14
|
+
* The meta object in verbose format (for backward compatibility)
|
|
15
15
|
*/
|
|
16
16
|
declare const meta: StudioMeta;
|
|
17
17
|
/**
|
|
18
|
-
* Initialize meta from a JSON object (
|
|
18
|
+
* Initialize meta from a JSON object (handles both formats)
|
|
19
19
|
*/
|
|
20
|
-
declare function initializeMeta(data:
|
|
20
|
+
declare function initializeMeta(data: UnifiedMeta): void;
|
|
21
21
|
/**
|
|
22
|
-
* Get the resolved URL for an image
|
|
22
|
+
* Get the resolved URL for an image
|
|
23
|
+
* Works with both verbose and lean formats
|
|
23
24
|
*/
|
|
24
|
-
declare function getImageUrl(imageKey: string, size?:
|
|
25
|
+
declare function getImageUrl(imageKey: string, size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large' | 'full'): string | undefined;
|
|
25
26
|
/**
|
|
26
|
-
* Get
|
|
27
|
+
* Get image entry (verbose format for backward compat)
|
|
27
28
|
*/
|
|
28
29
|
declare function getStudioMeta(imageKey: string): ImageEntry | undefined;
|
|
29
30
|
/**
|
|
30
|
-
* Get
|
|
31
|
+
* Get dimensions for an image
|
|
31
32
|
*/
|
|
32
|
-
declare function getImageSize(imageKey: string, size?:
|
|
33
|
+
declare function getImageSize(imageKey: string, size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large' | 'full'): {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
path?: string;
|
|
37
|
+
} | undefined;
|
|
33
38
|
|
|
34
|
-
export { ImageEntry,
|
|
39
|
+
export { ImageEntry, LeanMeta, StudioButton, StudioMeta, getImageSize, getImageUrl, getStudioMeta, initializeMeta, meta };
|
package/dist/index.js
CHANGED
|
@@ -175,35 +175,104 @@ function LoadingState() {
|
|
|
175
175
|
] }) });
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// src/types.ts
|
|
179
|
+
function getThumbnailPath(originalPath, size) {
|
|
180
|
+
const ext = _optionalChain([originalPath, 'access', _ => _.match, 'call', _2 => _2(/\.\w+$/), 'optionalAccess', _3 => _3[0]]) || ".jpg";
|
|
181
|
+
const base = originalPath.replace(/\.\w+$/, "");
|
|
182
|
+
const outputExt = ext.toLowerCase() === ".png" ? ".png" : ".jpg";
|
|
183
|
+
return `/images${base}-${size}${outputExt}`;
|
|
184
|
+
}
|
|
185
|
+
function toLeanMeta(verbose) {
|
|
186
|
+
const lean = {};
|
|
187
|
+
for (const [key, entry] of Object.entries(verbose.images)) {
|
|
188
|
+
const pathKey = _optionalChain([entry, 'access', _4 => _4.original, 'optionalAccess', _5 => _5.path]) || `/${key}`;
|
|
189
|
+
lean[pathKey] = {
|
|
190
|
+
w: _optionalChain([entry, 'access', _6 => _6.original, 'optionalAccess', _7 => _7.width]) || 0,
|
|
191
|
+
h: _optionalChain([entry, 'access', _8 => _8.original, 'optionalAccess', _9 => _9.height]) || 0,
|
|
192
|
+
blur: entry.blurhash || ""
|
|
193
|
+
};
|
|
194
|
+
if (_optionalChain([entry, 'access', _10 => _10.cdn, 'optionalAccess', _11 => _11.synced])) {
|
|
195
|
+
lean[pathKey].s = 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return lean;
|
|
199
|
+
}
|
|
200
|
+
function isLeanMeta(meta2) {
|
|
201
|
+
if (!meta2 || typeof meta2 !== "object") return false;
|
|
202
|
+
return !("images" in meta2);
|
|
203
|
+
}
|
|
204
|
+
|
|
178
205
|
// src/lib/meta.ts
|
|
179
|
-
var
|
|
206
|
+
var _verboseMeta = null;
|
|
207
|
+
var _leanMeta = {};
|
|
208
|
+
var meta = {
|
|
180
209
|
$schema: "https://gallop.software/schemas/studio-meta.json",
|
|
181
210
|
version: 1,
|
|
182
211
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
183
212
|
images: {}
|
|
184
213
|
};
|
|
185
|
-
var meta = _meta;
|
|
186
214
|
function initializeMeta(data) {
|
|
187
|
-
|
|
188
|
-
|
|
215
|
+
if (isLeanMeta(data)) {
|
|
216
|
+
_leanMeta = data;
|
|
217
|
+
_verboseMeta = null;
|
|
218
|
+
} else {
|
|
219
|
+
_verboseMeta = data;
|
|
220
|
+
_leanMeta = toLeanMeta(data);
|
|
221
|
+
Object.assign(meta, data);
|
|
222
|
+
}
|
|
189
223
|
}
|
|
190
|
-
function getImageUrl(imageKey, size
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
224
|
+
function getImageUrl(imageKey, size) {
|
|
225
|
+
const cdnUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL || process.env.NEXT_PUBLIC_CDN_URL;
|
|
226
|
+
const leanEntry = _leanMeta[imageKey];
|
|
227
|
+
if (leanEntry) {
|
|
228
|
+
const sizeMap = {
|
|
229
|
+
small: "sm",
|
|
230
|
+
medium: "md",
|
|
231
|
+
large: "lg"
|
|
232
|
+
};
|
|
233
|
+
if (!size || size === "full") {
|
|
234
|
+
if (leanEntry.s && cdnUrl) {
|
|
235
|
+
return `${cdnUrl}${imageKey}`;
|
|
236
|
+
}
|
|
237
|
+
return imageKey;
|
|
238
|
+
}
|
|
239
|
+
const normalizedSize = sizeMap[size] || size;
|
|
240
|
+
const thumbPath = getThumbnailPath(imageKey, normalizedSize);
|
|
241
|
+
if (leanEntry.s && cdnUrl) {
|
|
242
|
+
return `${cdnUrl}${thumbPath}`;
|
|
243
|
+
}
|
|
244
|
+
return thumbPath;
|
|
245
|
+
}
|
|
246
|
+
if (_verboseMeta) {
|
|
247
|
+
const entry = _verboseMeta.images[imageKey];
|
|
248
|
+
if (!entry) return void 0;
|
|
249
|
+
const sizeData = entry.sizes[size || "medium"] || entry.sizes.full;
|
|
250
|
+
if (!sizeData) return void 0;
|
|
251
|
+
if (_optionalChain([entry, 'access', _12 => _12.cdn, 'optionalAccess', _13 => _13.synced]) && entry.cdn.baseUrl) {
|
|
252
|
+
return `${entry.cdn.baseUrl}${sizeData.path}`;
|
|
253
|
+
}
|
|
254
|
+
return sizeData.path;
|
|
197
255
|
}
|
|
198
|
-
return
|
|
256
|
+
return void 0;
|
|
199
257
|
}
|
|
200
258
|
function getStudioMeta(imageKey) {
|
|
201
|
-
|
|
259
|
+
if (_verboseMeta) {
|
|
260
|
+
return _verboseMeta.images[imageKey];
|
|
261
|
+
}
|
|
262
|
+
return void 0;
|
|
202
263
|
}
|
|
203
|
-
function getImageSize(imageKey, size
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
-
|
|
264
|
+
function getImageSize(imageKey, size) {
|
|
265
|
+
const leanEntry = _leanMeta[imageKey];
|
|
266
|
+
if (leanEntry) {
|
|
267
|
+
return { width: leanEntry.w, height: leanEntry.h };
|
|
268
|
+
}
|
|
269
|
+
if (_verboseMeta) {
|
|
270
|
+
const entry = _verboseMeta.images[imageKey];
|
|
271
|
+
if (!entry) return void 0;
|
|
272
|
+
const sizeData = entry.sizes[size || "medium"] || entry.sizes.full;
|
|
273
|
+
return sizeData;
|
|
274
|
+
}
|
|
275
|
+
return void 0;
|
|
207
276
|
}
|
|
208
277
|
|
|
209
278
|
|
|
@@ -212,5 +281,8 @@ function getImageSize(imageKey, size = "medium") {
|
|
|
212
281
|
|
|
213
282
|
|
|
214
283
|
|
|
215
|
-
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
exports.StudioButton = StudioButton; exports.getImageSize = getImageSize; exports.getImageUrl = getImageUrl; exports.getStudioMeta = getStudioMeta; exports.getThumbnailPath = getThumbnailPath; exports.initializeMeta = initializeMeta; exports.isLeanMeta = isLeanMeta; exports.meta = meta; exports.toLeanMeta = toLeanMeta;
|
|
216
288
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/index.js","../src/components/StudioButton.tsx","../src/lib/meta.ts"],"names":[],"mappings":"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","file":"/Users/chrisb/Sites/studio/dist/index.js","sourcesContent":[null,"/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState, useEffect, lazy, Suspense } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { colors, fontStack, fontSize, baseReset } from './tokens'\n\n// Lazy load the full Studio UI to avoid bundling in production\nconst StudioUI = lazy(() => import('./StudioUI'))\n\nconst spin = keyframes`\n to {\n transform: rotate(360deg);\n }\n`\n\nconst styles = {\n button: css`\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9998;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: ${colors.primary};\n color: white;\n box-shadow: 0 4px 12px ${colors.shadowDark}, 0 1px 3px ${colors.shadow};\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n font-family: ${fontStack};\n \n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 20px ${colors.shadowDark}, 0 2px 6px ${colors.shadow};\n background: ${colors.primaryHover};\n }\n \n &:active {\n transform: translateY(0);\n }\n `,\n buttonIcon: css`\n width: 24px;\n height: 24px;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 9999;\n transition: opacity 0.2s ease, visibility 0.2s ease;\n `,\n overlayHidden: css`\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(26, 31, 54, 0.4);\n backdrop-filter: blur(4px);\n `,\n modal: css`\n ${baseReset}\n position: absolute;\n top: 24px;\n right: 24px;\n bottom: 24px;\n left: 24px;\n background-color: ${colors.surface};\n border-radius: 12px;\n box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n `,\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: ${colors.background};\n font-family: ${fontStack};\n `,\n loadingContent: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n `,\n spinner: css`\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: 3px solid ${colors.border};\n border-top-color: ${colors.primary};\n animation: ${spin} 0.8s linear infinite;\n `,\n loadingText: css`\n color: ${colors.textSecondary};\n font-size: ${fontSize.base};\n font-weight: 500;\n margin: 0;\n letter-spacing: -0.01em;\n `,\n}\n\n/**\n * Floating button that opens the Studio modal.\n * Fixed position in bottom-right corner.\n * Only renders in development mode.\n */\nexport function StudioButton() {\n const [mounted, setMounted] = useState(false)\n const [isOpen, setIsOpen] = useState(false)\n const [hasBeenOpened, setHasBeenOpened] = useState(false)\n\n // Only render on client to avoid hydration mismatch\n useEffect(() => {\n setMounted(true)\n }, [])\n\n const handleOpen = () => {\n setIsOpen(true)\n setHasBeenOpened(true)\n }\n\n // Only render in development and on client\n if (!mounted || process.env.NODE_ENV !== 'development') {\n return null\n }\n\n return (\n <>\n {!isOpen && (\n <button\n css={styles.button}\n onClick={handleOpen}\n title=\"Open Studio\"\n aria-label=\"Open Studio media manager\"\n >\n <ImageIcon />\n </button>\n )}\n\n {/* Keep mounted once opened to preserve state */}\n {hasBeenOpened && (\n <div css={[styles.overlay, !isOpen && styles.overlayHidden]}>\n <div css={styles.backdrop} onClick={() => setIsOpen(false)} />\n <div css={styles.modal}>\n <Suspense fallback={<LoadingState />}>\n <StudioUI onClose={() => setIsOpen(false)} isVisible={isOpen} />\n </Suspense>\n </div>\n </div>\n )}\n </>\n )\n}\n\nfunction ImageIcon() {\n return (\n <svg\n css={styles.buttonIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n )\n}\n\nfunction LoadingState() {\n return (\n <div css={styles.loading}>\n <div css={styles.loadingContent}>\n <div css={styles.spinner} />\n <p css={styles.loadingText}>Loading Studio...</p>\n </div>\n </div>\n )\n}\n","import type { StudioMeta, ImageEntry, ImageSize, SizeEntry } from '../types'\n\n// Default empty meta - will be populated when reading from project\nlet _meta: StudioMeta = {\n $schema: 'https://gallop.software/schemas/studio-meta.json',\n version: 1,\n generatedAt: new Date().toISOString(),\n images: {},\n}\n\n/**\n * The meta object containing all image metadata.\n * This is read from _data/_meta.json in the consuming project.\n */\nexport const meta: StudioMeta = _meta\n\n/**\n * Initialize meta from a JSON object (called during build/runtime)\n */\nexport function initializeMeta(data: StudioMeta): void {\n _meta = data\n Object.assign(meta, data)\n}\n\n/**\n * Get the resolved URL for an image, handling CDN vs local paths\n */\nexport function getImageUrl(\n imageKey: string,\n size: ImageSize = 'medium'\n): string | undefined {\n const image = meta.images[imageKey]\n if (!image) return undefined\n\n const sizeData = image.sizes[size] || image.sizes.full\n if (!sizeData) return undefined\n\n // If synced to CDN, use CDN URL\n if (image.cdn?.synced && image.cdn.baseUrl) {\n return `${image.cdn.baseUrl}${sizeData.path}`\n }\n\n // Otherwise use local path\n return sizeData.path\n}\n\n/**\n * Get the full image entry for a key\n */\nexport function getStudioMeta(imageKey: string): ImageEntry | undefined {\n return meta.images[imageKey]\n}\n\n/**\n * Get size data for an image\n */\nexport function getImageSize(\n imageKey: string,\n size: ImageSize = 'medium'\n): SizeEntry | undefined {\n const image = meta.images[imageKey]\n if (!image) return undefined\n return image.sizes[size] || image.sizes.full\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/Users/chrisb/Sites/studio/dist/index.js","../src/components/StudioButton.tsx","../src/types.ts","../src/lib/meta.ts"],"names":["meta"],"mappings":"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","file":"/Users/chrisb/Sites/studio/dist/index.js","sourcesContent":[null,"/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState, useEffect, lazy, Suspense } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { colors, fontStack, fontSize, baseReset } from './tokens'\n\n// Lazy load the full Studio UI to avoid bundling in production\nconst StudioUI = lazy(() => import('./StudioUI'))\n\nconst spin = keyframes`\n to {\n transform: rotate(360deg);\n }\n`\n\nconst styles = {\n button: css`\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9998;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: ${colors.primary};\n color: white;\n box-shadow: 0 4px 12px ${colors.shadowDark}, 0 1px 3px ${colors.shadow};\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n font-family: ${fontStack};\n \n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 20px ${colors.shadowDark}, 0 2px 6px ${colors.shadow};\n background: ${colors.primaryHover};\n }\n \n &:active {\n transform: translateY(0);\n }\n `,\n buttonIcon: css`\n width: 24px;\n height: 24px;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 9999;\n transition: opacity 0.2s ease, visibility 0.2s ease;\n `,\n overlayHidden: css`\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(26, 31, 54, 0.4);\n backdrop-filter: blur(4px);\n `,\n modal: css`\n ${baseReset}\n position: absolute;\n top: 24px;\n right: 24px;\n bottom: 24px;\n left: 24px;\n background-color: ${colors.surface};\n border-radius: 12px;\n box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n `,\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: ${colors.background};\n font-family: ${fontStack};\n `,\n loadingContent: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n `,\n spinner: css`\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: 3px solid ${colors.border};\n border-top-color: ${colors.primary};\n animation: ${spin} 0.8s linear infinite;\n `,\n loadingText: css`\n color: ${colors.textSecondary};\n font-size: ${fontSize.base};\n font-weight: 500;\n margin: 0;\n letter-spacing: -0.01em;\n `,\n}\n\n/**\n * Floating button that opens the Studio modal.\n * Fixed position in bottom-right corner.\n * Only renders in development mode.\n */\nexport function StudioButton() {\n const [mounted, setMounted] = useState(false)\n const [isOpen, setIsOpen] = useState(false)\n const [hasBeenOpened, setHasBeenOpened] = useState(false)\n\n // Only render on client to avoid hydration mismatch\n useEffect(() => {\n setMounted(true)\n }, [])\n\n const handleOpen = () => {\n setIsOpen(true)\n setHasBeenOpened(true)\n }\n\n // Only render in development and on client\n if (!mounted || process.env.NODE_ENV !== 'development') {\n return null\n }\n\n return (\n <>\n {!isOpen && (\n <button\n css={styles.button}\n onClick={handleOpen}\n title=\"Open Studio\"\n aria-label=\"Open Studio media manager\"\n >\n <ImageIcon />\n </button>\n )}\n\n {/* Keep mounted once opened to preserve state */}\n {hasBeenOpened && (\n <div css={[styles.overlay, !isOpen && styles.overlayHidden]}>\n <div css={styles.backdrop} onClick={() => setIsOpen(false)} />\n <div css={styles.modal}>\n <Suspense fallback={<LoadingState />}>\n <StudioUI onClose={() => setIsOpen(false)} isVisible={isOpen} />\n </Suspense>\n </div>\n </div>\n )}\n </>\n )\n}\n\nfunction ImageIcon() {\n return (\n <svg\n css={styles.buttonIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n )\n}\n\nfunction LoadingState() {\n return (\n <div css={styles.loading}>\n <div css={styles.loadingContent}>\n <div css={styles.spinner} />\n <p css={styles.loadingText}>Loading Studio...</p>\n </div>\n </div>\n )\n}\n","/**\n * Image size variants (verbose format used by handlers)\n */\nexport type ImageSize = 'small' | 'medium' | 'large' | 'full'\n\n/**\n * Image size variants (lean format for consumers)\n */\nexport type LeanImageSize = 'sm' | 'md' | 'lg'\n\n/**\n * Size entry with path and dimensions\n */\nexport interface SizeEntry {\n path: string\n width: number\n height: number\n}\n\n/**\n * CDN sync status\n */\nexport interface CdnStatus {\n synced: boolean\n baseUrl: string\n syncedAt: string\n}\n\n/**\n * Image entry in meta (verbose format - used by Studio handlers)\n */\nexport interface ImageEntry {\n original: {\n path: string\n width: number\n height: number\n fileSize: number\n }\n sizes: {\n full: SizeEntry\n large: SizeEntry\n medium: SizeEntry\n small: SizeEntry\n [key: string]: SizeEntry\n }\n blurhash: string\n dominantColor: string\n cdn: CdnStatus | null\n}\n\n/**\n * Studio meta schema (verbose format - used by Studio handlers)\n */\nexport interface StudioMeta {\n $schema: string\n version: number\n generatedAt: string\n images: Record<string, ImageEntry>\n}\n\n/**\n * Lean image entry - minimal metadata for consumers\n * ~80 bytes per image vs ~500 bytes in verbose format\n */\nexport interface LeanImageEntry {\n /** Original width */\n w: number\n /** Original height */\n h: number\n /** Blurhash for placeholder */\n blur: string\n /** Synced to CDN (present and 1 if synced, omit if not) */\n s?: 1\n}\n\n/**\n * Lean meta schema - flat structure with path as key\n */\nexport type LeanMeta = Record<string, LeanImageEntry>\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n cdnSynced?: boolean\n fileCount?: number\n totalSize?: number\n thumbnail?: string\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Helper to derive thumbnail path from original path\n */\nexport function getThumbnailPath(originalPath: string, size: LeanImageSize): string {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Convert verbose StudioMeta to LeanMeta\n */\nexport function toLeanMeta(verbose: StudioMeta): LeanMeta {\n const lean: LeanMeta = {}\n \n for (const [key, entry] of Object.entries(verbose.images)) {\n const pathKey = entry.original?.path || `/${key}`\n lean[pathKey] = {\n w: entry.original?.width || 0,\n h: entry.original?.height || 0,\n blur: entry.blurhash || '',\n }\n if (entry.cdn?.synced) {\n lean[pathKey].s = 1\n }\n }\n \n return lean\n}\n\n/**\n * Check if meta is in lean format (no 'images' wrapper)\n */\nexport function isLeanMeta(meta: unknown): meta is LeanMeta {\n if (!meta || typeof meta !== 'object') return false\n // Lean format doesn't have 'images' property\n return !('images' in meta)\n}\n","import type { StudioMeta, ImageEntry, LeanMeta, LeanImageEntry } from '../types'\nimport { getThumbnailPath, toLeanMeta, isLeanMeta } from '../types'\n\n// Unified meta - can be either format\ntype UnifiedMeta = StudioMeta | LeanMeta\n\n// Store both formats\nlet _verboseMeta: StudioMeta | null = null\nlet _leanMeta: LeanMeta = {}\n\n/**\n * The meta object in verbose format (for backward compatibility)\n */\nexport const meta: StudioMeta = {\n $schema: 'https://gallop.software/schemas/studio-meta.json',\n version: 1,\n generatedAt: new Date().toISOString(),\n images: {},\n}\n\n/**\n * Get lean meta for efficient access\n */\nexport function getLeanMeta(): LeanMeta {\n return _leanMeta\n}\n\n/**\n * Initialize meta from a JSON object (handles both formats)\n */\nexport function initializeMeta(data: UnifiedMeta): void {\n if (isLeanMeta(data)) {\n // Already lean format\n _leanMeta = data\n _verboseMeta = null\n } else {\n // Verbose format - convert to lean\n _verboseMeta = data\n _leanMeta = toLeanMeta(data)\n // Also update the exported meta object for backward compat\n Object.assign(meta, data)\n }\n}\n\n/**\n * Get the resolved URL for an image\n * Works with both verbose and lean formats\n */\nexport function getImageUrl(\n imageKey: string,\n size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large' | 'full'\n): string | undefined {\n const cdnUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL || process.env.NEXT_PUBLIC_CDN_URL\n \n // Try lean meta first\n const leanEntry = _leanMeta[imageKey]\n if (leanEntry) {\n // Map old size names to new\n const sizeMap: Record<string, 'sm' | 'md' | 'lg'> = {\n small: 'sm',\n medium: 'md', \n large: 'lg',\n }\n \n if (!size || size === 'full') {\n // Return original path\n if (leanEntry.s && cdnUrl) {\n return `${cdnUrl}${imageKey}`\n }\n return imageKey\n }\n \n const normalizedSize = sizeMap[size] || size as 'sm' | 'md' | 'lg'\n const thumbPath = getThumbnailPath(imageKey, normalizedSize)\n \n if (leanEntry.s && cdnUrl) {\n return `${cdnUrl}${thumbPath}`\n }\n return thumbPath\n }\n \n // Fall back to verbose meta\n if (_verboseMeta) {\n const entry = _verboseMeta.images[imageKey]\n if (!entry) return undefined\n \n const sizeData = entry.sizes[size || 'medium'] || entry.sizes.full\n if (!sizeData) return undefined\n \n if (entry.cdn?.synced && entry.cdn.baseUrl) {\n return `${entry.cdn.baseUrl}${sizeData.path}`\n }\n return sizeData.path\n }\n \n return undefined\n}\n\n/**\n * Get image entry (verbose format for backward compat)\n */\nexport function getStudioMeta(imageKey: string): ImageEntry | undefined {\n if (_verboseMeta) {\n return _verboseMeta.images[imageKey]\n }\n return undefined\n}\n\n/**\n * Get lean entry\n */\nexport function getLeanEntry(imageKey: string): LeanImageEntry | undefined {\n return _leanMeta[imageKey]\n}\n\n/**\n * Get dimensions for an image\n */\nexport function getImageSize(\n imageKey: string,\n size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large' | 'full'\n): { width: number; height: number; path?: string } | undefined {\n // Try lean meta first\n const leanEntry = _leanMeta[imageKey]\n if (leanEntry) {\n // For lean, we only have original dimensions\n return { width: leanEntry.w, height: leanEntry.h }\n }\n \n // Fall back to verbose meta\n if (_verboseMeta) {\n const entry = _verboseMeta.images[imageKey]\n if (!entry) return undefined\n const sizeData = entry.sizes[size || 'medium'] || entry.sizes.full\n return sizeData\n }\n \n return undefined\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -175,42 +175,114 @@ function LoadingState() {
|
|
|
175
175
|
] }) });
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// src/types.ts
|
|
179
|
+
function getThumbnailPath(originalPath, size) {
|
|
180
|
+
const ext = originalPath.match(/\.\w+$/)?.[0] || ".jpg";
|
|
181
|
+
const base = originalPath.replace(/\.\w+$/, "");
|
|
182
|
+
const outputExt = ext.toLowerCase() === ".png" ? ".png" : ".jpg";
|
|
183
|
+
return `/images${base}-${size}${outputExt}`;
|
|
184
|
+
}
|
|
185
|
+
function toLeanMeta(verbose) {
|
|
186
|
+
const lean = {};
|
|
187
|
+
for (const [key, entry] of Object.entries(verbose.images)) {
|
|
188
|
+
const pathKey = entry.original?.path || `/${key}`;
|
|
189
|
+
lean[pathKey] = {
|
|
190
|
+
w: entry.original?.width || 0,
|
|
191
|
+
h: entry.original?.height || 0,
|
|
192
|
+
blur: entry.blurhash || ""
|
|
193
|
+
};
|
|
194
|
+
if (entry.cdn?.synced) {
|
|
195
|
+
lean[pathKey].s = 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return lean;
|
|
199
|
+
}
|
|
200
|
+
function isLeanMeta(meta2) {
|
|
201
|
+
if (!meta2 || typeof meta2 !== "object") return false;
|
|
202
|
+
return !("images" in meta2);
|
|
203
|
+
}
|
|
204
|
+
|
|
178
205
|
// src/lib/meta.ts
|
|
179
|
-
var
|
|
206
|
+
var _verboseMeta = null;
|
|
207
|
+
var _leanMeta = {};
|
|
208
|
+
var meta = {
|
|
180
209
|
$schema: "https://gallop.software/schemas/studio-meta.json",
|
|
181
210
|
version: 1,
|
|
182
211
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
183
212
|
images: {}
|
|
184
213
|
};
|
|
185
|
-
var meta = _meta;
|
|
186
214
|
function initializeMeta(data) {
|
|
187
|
-
|
|
188
|
-
|
|
215
|
+
if (isLeanMeta(data)) {
|
|
216
|
+
_leanMeta = data;
|
|
217
|
+
_verboseMeta = null;
|
|
218
|
+
} else {
|
|
219
|
+
_verboseMeta = data;
|
|
220
|
+
_leanMeta = toLeanMeta(data);
|
|
221
|
+
Object.assign(meta, data);
|
|
222
|
+
}
|
|
189
223
|
}
|
|
190
|
-
function getImageUrl(imageKey, size
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
224
|
+
function getImageUrl(imageKey, size) {
|
|
225
|
+
const cdnUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL || process.env.NEXT_PUBLIC_CDN_URL;
|
|
226
|
+
const leanEntry = _leanMeta[imageKey];
|
|
227
|
+
if (leanEntry) {
|
|
228
|
+
const sizeMap = {
|
|
229
|
+
small: "sm",
|
|
230
|
+
medium: "md",
|
|
231
|
+
large: "lg"
|
|
232
|
+
};
|
|
233
|
+
if (!size || size === "full") {
|
|
234
|
+
if (leanEntry.s && cdnUrl) {
|
|
235
|
+
return `${cdnUrl}${imageKey}`;
|
|
236
|
+
}
|
|
237
|
+
return imageKey;
|
|
238
|
+
}
|
|
239
|
+
const normalizedSize = sizeMap[size] || size;
|
|
240
|
+
const thumbPath = getThumbnailPath(imageKey, normalizedSize);
|
|
241
|
+
if (leanEntry.s && cdnUrl) {
|
|
242
|
+
return `${cdnUrl}${thumbPath}`;
|
|
243
|
+
}
|
|
244
|
+
return thumbPath;
|
|
245
|
+
}
|
|
246
|
+
if (_verboseMeta) {
|
|
247
|
+
const entry = _verboseMeta.images[imageKey];
|
|
248
|
+
if (!entry) return void 0;
|
|
249
|
+
const sizeData = entry.sizes[size || "medium"] || entry.sizes.full;
|
|
250
|
+
if (!sizeData) return void 0;
|
|
251
|
+
if (entry.cdn?.synced && entry.cdn.baseUrl) {
|
|
252
|
+
return `${entry.cdn.baseUrl}${sizeData.path}`;
|
|
253
|
+
}
|
|
254
|
+
return sizeData.path;
|
|
197
255
|
}
|
|
198
|
-
return
|
|
256
|
+
return void 0;
|
|
199
257
|
}
|
|
200
258
|
function getStudioMeta(imageKey) {
|
|
201
|
-
|
|
259
|
+
if (_verboseMeta) {
|
|
260
|
+
return _verboseMeta.images[imageKey];
|
|
261
|
+
}
|
|
262
|
+
return void 0;
|
|
202
263
|
}
|
|
203
|
-
function getImageSize(imageKey, size
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
-
|
|
264
|
+
function getImageSize(imageKey, size) {
|
|
265
|
+
const leanEntry = _leanMeta[imageKey];
|
|
266
|
+
if (leanEntry) {
|
|
267
|
+
return { width: leanEntry.w, height: leanEntry.h };
|
|
268
|
+
}
|
|
269
|
+
if (_verboseMeta) {
|
|
270
|
+
const entry = _verboseMeta.images[imageKey];
|
|
271
|
+
if (!entry) return void 0;
|
|
272
|
+
const sizeData = entry.sizes[size || "medium"] || entry.sizes.full;
|
|
273
|
+
return sizeData;
|
|
274
|
+
}
|
|
275
|
+
return void 0;
|
|
207
276
|
}
|
|
208
277
|
export {
|
|
209
278
|
StudioButton,
|
|
210
279
|
getImageSize,
|
|
211
280
|
getImageUrl,
|
|
212
281
|
getStudioMeta,
|
|
282
|
+
getThumbnailPath,
|
|
213
283
|
initializeMeta,
|
|
214
|
-
|
|
284
|
+
isLeanMeta,
|
|
285
|
+
meta,
|
|
286
|
+
toLeanMeta
|
|
215
287
|
};
|
|
216
288
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/StudioButton.tsx","../src/lib/meta.ts"],"sourcesContent":["/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState, useEffect, lazy, Suspense } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { colors, fontStack, fontSize, baseReset } from './tokens'\n\n// Lazy load the full Studio UI to avoid bundling in production\nconst StudioUI = lazy(() => import('./StudioUI'))\n\nconst spin = keyframes`\n to {\n transform: rotate(360deg);\n }\n`\n\nconst styles = {\n button: css`\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9998;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: ${colors.primary};\n color: white;\n box-shadow: 0 4px 12px ${colors.shadowDark}, 0 1px 3px ${colors.shadow};\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n font-family: ${fontStack};\n \n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 20px ${colors.shadowDark}, 0 2px 6px ${colors.shadow};\n background: ${colors.primaryHover};\n }\n \n &:active {\n transform: translateY(0);\n }\n `,\n buttonIcon: css`\n width: 24px;\n height: 24px;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 9999;\n transition: opacity 0.2s ease, visibility 0.2s ease;\n `,\n overlayHidden: css`\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(26, 31, 54, 0.4);\n backdrop-filter: blur(4px);\n `,\n modal: css`\n ${baseReset}\n position: absolute;\n top: 24px;\n right: 24px;\n bottom: 24px;\n left: 24px;\n background-color: ${colors.surface};\n border-radius: 12px;\n box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n `,\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: ${colors.background};\n font-family: ${fontStack};\n `,\n loadingContent: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n `,\n spinner: css`\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: 3px solid ${colors.border};\n border-top-color: ${colors.primary};\n animation: ${spin} 0.8s linear infinite;\n `,\n loadingText: css`\n color: ${colors.textSecondary};\n font-size: ${fontSize.base};\n font-weight: 500;\n margin: 0;\n letter-spacing: -0.01em;\n `,\n}\n\n/**\n * Floating button that opens the Studio modal.\n * Fixed position in bottom-right corner.\n * Only renders in development mode.\n */\nexport function StudioButton() {\n const [mounted, setMounted] = useState(false)\n const [isOpen, setIsOpen] = useState(false)\n const [hasBeenOpened, setHasBeenOpened] = useState(false)\n\n // Only render on client to avoid hydration mismatch\n useEffect(() => {\n setMounted(true)\n }, [])\n\n const handleOpen = () => {\n setIsOpen(true)\n setHasBeenOpened(true)\n }\n\n // Only render in development and on client\n if (!mounted || process.env.NODE_ENV !== 'development') {\n return null\n }\n\n return (\n <>\n {!isOpen && (\n <button\n css={styles.button}\n onClick={handleOpen}\n title=\"Open Studio\"\n aria-label=\"Open Studio media manager\"\n >\n <ImageIcon />\n </button>\n )}\n\n {/* Keep mounted once opened to preserve state */}\n {hasBeenOpened && (\n <div css={[styles.overlay, !isOpen && styles.overlayHidden]}>\n <div css={styles.backdrop} onClick={() => setIsOpen(false)} />\n <div css={styles.modal}>\n <Suspense fallback={<LoadingState />}>\n <StudioUI onClose={() => setIsOpen(false)} isVisible={isOpen} />\n </Suspense>\n </div>\n </div>\n )}\n </>\n )\n}\n\nfunction ImageIcon() {\n return (\n <svg\n css={styles.buttonIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n )\n}\n\nfunction LoadingState() {\n return (\n <div css={styles.loading}>\n <div css={styles.loadingContent}>\n <div css={styles.spinner} />\n <p css={styles.loadingText}>Loading Studio...</p>\n </div>\n </div>\n )\n}\n","import type { StudioMeta, ImageEntry, ImageSize, SizeEntry } from '../types'\n\n// Default empty meta - will be populated when reading from project\nlet _meta: StudioMeta = {\n $schema: 'https://gallop.software/schemas/studio-meta.json',\n version: 1,\n generatedAt: new Date().toISOString(),\n images: {},\n}\n\n/**\n * The meta object containing all image metadata.\n * This is read from _data/_meta.json in the consuming project.\n */\nexport const meta: StudioMeta = _meta\n\n/**\n * Initialize meta from a JSON object (called during build/runtime)\n */\nexport function initializeMeta(data: StudioMeta): void {\n _meta = data\n Object.assign(meta, data)\n}\n\n/**\n * Get the resolved URL for an image, handling CDN vs local paths\n */\nexport function getImageUrl(\n imageKey: string,\n size: ImageSize = 'medium'\n): string | undefined {\n const image = meta.images[imageKey]\n if (!image) return undefined\n\n const sizeData = image.sizes[size] || image.sizes.full\n if (!sizeData) return undefined\n\n // If synced to CDN, use CDN URL\n if (image.cdn?.synced && image.cdn.baseUrl) {\n return `${image.cdn.baseUrl}${sizeData.path}`\n }\n\n // Otherwise use local path\n return sizeData.path\n}\n\n/**\n * Get the full image entry for a key\n */\nexport function getStudioMeta(imageKey: string): ImageEntry | undefined {\n return meta.images[imageKey]\n}\n\n/**\n * Get size data for an image\n */\nexport function getImageSize(\n imageKey: string,\n size: ImageSize = 'medium'\n): SizeEntry | undefined {\n const image = meta.images[imageKey]\n if (!image) return undefined\n return image.sizes[size] || image.sizes.full\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/components/StudioButton.tsx","../src/types.ts","../src/lib/meta.ts"],"sourcesContent":["/** @jsxImportSource @emotion/react */\n'use client'\n\nimport { useState, useEffect, lazy, Suspense } from 'react'\nimport { css, keyframes } from '@emotion/react'\nimport { colors, fontStack, fontSize, baseReset } from './tokens'\n\n// Lazy load the full Studio UI to avoid bundling in production\nconst StudioUI = lazy(() => import('./StudioUI'))\n\nconst spin = keyframes`\n to {\n transform: rotate(360deg);\n }\n`\n\nconst styles = {\n button: css`\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 9998;\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: ${colors.primary};\n color: white;\n box-shadow: 0 4px 12px ${colors.shadowDark}, 0 1px 3px ${colors.shadow};\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n cursor: pointer;\n transition: all 0.15s ease;\n font-family: ${fontStack};\n \n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 20px ${colors.shadowDark}, 0 2px 6px ${colors.shadow};\n background: ${colors.primaryHover};\n }\n \n &:active {\n transform: translateY(0);\n }\n `,\n buttonIcon: css`\n width: 24px;\n height: 24px;\n `,\n overlay: css`\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 9999;\n transition: opacity 0.2s ease, visibility 0.2s ease;\n `,\n overlayHidden: css`\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n `,\n backdrop: css`\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-color: rgba(26, 31, 54, 0.4);\n backdrop-filter: blur(4px);\n `,\n modal: css`\n ${baseReset}\n position: absolute;\n top: 24px;\n right: 24px;\n bottom: 24px;\n left: 24px;\n background-color: ${colors.surface};\n border-radius: 12px;\n box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n `,\n loading: css`\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n background: ${colors.background};\n font-family: ${fontStack};\n `,\n loadingContent: css`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n `,\n spinner: css`\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: 3px solid ${colors.border};\n border-top-color: ${colors.primary};\n animation: ${spin} 0.8s linear infinite;\n `,\n loadingText: css`\n color: ${colors.textSecondary};\n font-size: ${fontSize.base};\n font-weight: 500;\n margin: 0;\n letter-spacing: -0.01em;\n `,\n}\n\n/**\n * Floating button that opens the Studio modal.\n * Fixed position in bottom-right corner.\n * Only renders in development mode.\n */\nexport function StudioButton() {\n const [mounted, setMounted] = useState(false)\n const [isOpen, setIsOpen] = useState(false)\n const [hasBeenOpened, setHasBeenOpened] = useState(false)\n\n // Only render on client to avoid hydration mismatch\n useEffect(() => {\n setMounted(true)\n }, [])\n\n const handleOpen = () => {\n setIsOpen(true)\n setHasBeenOpened(true)\n }\n\n // Only render in development and on client\n if (!mounted || process.env.NODE_ENV !== 'development') {\n return null\n }\n\n return (\n <>\n {!isOpen && (\n <button\n css={styles.button}\n onClick={handleOpen}\n title=\"Open Studio\"\n aria-label=\"Open Studio media manager\"\n >\n <ImageIcon />\n </button>\n )}\n\n {/* Keep mounted once opened to preserve state */}\n {hasBeenOpened && (\n <div css={[styles.overlay, !isOpen && styles.overlayHidden]}>\n <div css={styles.backdrop} onClick={() => setIsOpen(false)} />\n <div css={styles.modal}>\n <Suspense fallback={<LoadingState />}>\n <StudioUI onClose={() => setIsOpen(false)} isVisible={isOpen} />\n </Suspense>\n </div>\n </div>\n )}\n </>\n )\n}\n\nfunction ImageIcon() {\n return (\n <svg\n css={styles.buttonIcon}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <polyline points=\"21 15 16 10 5 21\" />\n </svg>\n )\n}\n\nfunction LoadingState() {\n return (\n <div css={styles.loading}>\n <div css={styles.loadingContent}>\n <div css={styles.spinner} />\n <p css={styles.loadingText}>Loading Studio...</p>\n </div>\n </div>\n )\n}\n","/**\n * Image size variants (verbose format used by handlers)\n */\nexport type ImageSize = 'small' | 'medium' | 'large' | 'full'\n\n/**\n * Image size variants (lean format for consumers)\n */\nexport type LeanImageSize = 'sm' | 'md' | 'lg'\n\n/**\n * Size entry with path and dimensions\n */\nexport interface SizeEntry {\n path: string\n width: number\n height: number\n}\n\n/**\n * CDN sync status\n */\nexport interface CdnStatus {\n synced: boolean\n baseUrl: string\n syncedAt: string\n}\n\n/**\n * Image entry in meta (verbose format - used by Studio handlers)\n */\nexport interface ImageEntry {\n original: {\n path: string\n width: number\n height: number\n fileSize: number\n }\n sizes: {\n full: SizeEntry\n large: SizeEntry\n medium: SizeEntry\n small: SizeEntry\n [key: string]: SizeEntry\n }\n blurhash: string\n dominantColor: string\n cdn: CdnStatus | null\n}\n\n/**\n * Studio meta schema (verbose format - used by Studio handlers)\n */\nexport interface StudioMeta {\n $schema: string\n version: number\n generatedAt: string\n images: Record<string, ImageEntry>\n}\n\n/**\n * Lean image entry - minimal metadata for consumers\n * ~80 bytes per image vs ~500 bytes in verbose format\n */\nexport interface LeanImageEntry {\n /** Original width */\n w: number\n /** Original height */\n h: number\n /** Blurhash for placeholder */\n blur: string\n /** Synced to CDN (present and 1 if synced, omit if not) */\n s?: 1\n}\n\n/**\n * Lean meta schema - flat structure with path as key\n */\nexport type LeanMeta = Record<string, LeanImageEntry>\n\n/**\n * File/folder item for browser\n */\nexport interface FileItem {\n name: string\n path: string\n type: 'file' | 'folder'\n size?: number\n dimensions?: { width: number; height: number }\n cdnSynced?: boolean\n fileCount?: number\n totalSize?: number\n thumbnail?: string\n hasThumbnail?: boolean\n}\n\n/**\n * Studio configuration\n */\nexport interface StudioConfig {\n r2AccountId?: string\n r2AccessKeyId?: string\n r2SecretAccessKey?: string\n r2BucketName?: string\n r2PublicUrl?: string\n thumbnailSizes?: {\n small: number\n medium: number\n large: number\n }\n}\n\n/**\n * Helper to derive thumbnail path from original path\n */\nexport function getThumbnailPath(originalPath: string, size: LeanImageSize): string {\n const ext = originalPath.match(/\\.\\w+$/)?.[0] || '.jpg'\n const base = originalPath.replace(/\\.\\w+$/, '')\n const outputExt = ext.toLowerCase() === '.png' ? '.png' : '.jpg'\n return `/images${base}-${size}${outputExt}`\n}\n\n/**\n * Convert verbose StudioMeta to LeanMeta\n */\nexport function toLeanMeta(verbose: StudioMeta): LeanMeta {\n const lean: LeanMeta = {}\n \n for (const [key, entry] of Object.entries(verbose.images)) {\n const pathKey = entry.original?.path || `/${key}`\n lean[pathKey] = {\n w: entry.original?.width || 0,\n h: entry.original?.height || 0,\n blur: entry.blurhash || '',\n }\n if (entry.cdn?.synced) {\n lean[pathKey].s = 1\n }\n }\n \n return lean\n}\n\n/**\n * Check if meta is in lean format (no 'images' wrapper)\n */\nexport function isLeanMeta(meta: unknown): meta is LeanMeta {\n if (!meta || typeof meta !== 'object') return false\n // Lean format doesn't have 'images' property\n return !('images' in meta)\n}\n","import type { StudioMeta, ImageEntry, LeanMeta, LeanImageEntry } from '../types'\nimport { getThumbnailPath, toLeanMeta, isLeanMeta } from '../types'\n\n// Unified meta - can be either format\ntype UnifiedMeta = StudioMeta | LeanMeta\n\n// Store both formats\nlet _verboseMeta: StudioMeta | null = null\nlet _leanMeta: LeanMeta = {}\n\n/**\n * The meta object in verbose format (for backward compatibility)\n */\nexport const meta: StudioMeta = {\n $schema: 'https://gallop.software/schemas/studio-meta.json',\n version: 1,\n generatedAt: new Date().toISOString(),\n images: {},\n}\n\n/**\n * Get lean meta for efficient access\n */\nexport function getLeanMeta(): LeanMeta {\n return _leanMeta\n}\n\n/**\n * Initialize meta from a JSON object (handles both formats)\n */\nexport function initializeMeta(data: UnifiedMeta): void {\n if (isLeanMeta(data)) {\n // Already lean format\n _leanMeta = data\n _verboseMeta = null\n } else {\n // Verbose format - convert to lean\n _verboseMeta = data\n _leanMeta = toLeanMeta(data)\n // Also update the exported meta object for backward compat\n Object.assign(meta, data)\n }\n}\n\n/**\n * Get the resolved URL for an image\n * Works with both verbose and lean formats\n */\nexport function getImageUrl(\n imageKey: string,\n size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large' | 'full'\n): string | undefined {\n const cdnUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL || process.env.NEXT_PUBLIC_CDN_URL\n \n // Try lean meta first\n const leanEntry = _leanMeta[imageKey]\n if (leanEntry) {\n // Map old size names to new\n const sizeMap: Record<string, 'sm' | 'md' | 'lg'> = {\n small: 'sm',\n medium: 'md', \n large: 'lg',\n }\n \n if (!size || size === 'full') {\n // Return original path\n if (leanEntry.s && cdnUrl) {\n return `${cdnUrl}${imageKey}`\n }\n return imageKey\n }\n \n const normalizedSize = sizeMap[size] || size as 'sm' | 'md' | 'lg'\n const thumbPath = getThumbnailPath(imageKey, normalizedSize)\n \n if (leanEntry.s && cdnUrl) {\n return `${cdnUrl}${thumbPath}`\n }\n return thumbPath\n }\n \n // Fall back to verbose meta\n if (_verboseMeta) {\n const entry = _verboseMeta.images[imageKey]\n if (!entry) return undefined\n \n const sizeData = entry.sizes[size || 'medium'] || entry.sizes.full\n if (!sizeData) return undefined\n \n if (entry.cdn?.synced && entry.cdn.baseUrl) {\n return `${entry.cdn.baseUrl}${sizeData.path}`\n }\n return sizeData.path\n }\n \n return undefined\n}\n\n/**\n * Get image entry (verbose format for backward compat)\n */\nexport function getStudioMeta(imageKey: string): ImageEntry | undefined {\n if (_verboseMeta) {\n return _verboseMeta.images[imageKey]\n }\n return undefined\n}\n\n/**\n * Get lean entry\n */\nexport function getLeanEntry(imageKey: string): LeanImageEntry | undefined {\n return _leanMeta[imageKey]\n}\n\n/**\n * Get dimensions for an image\n */\nexport function getImageSize(\n imageKey: string,\n size?: 'sm' | 'md' | 'lg' | 'small' | 'medium' | 'large' | 'full'\n): { width: number; height: number; path?: string } | undefined {\n // Try lean meta first\n const leanEntry = _leanMeta[imageKey]\n if (leanEntry) {\n // For lean, we only have original dimensions\n return { width: leanEntry.w, height: leanEntry.h }\n }\n \n // Fall back to verbose meta\n if (_verboseMeta) {\n const entry = _verboseMeta.images[imageKey]\n if (!entry) return undefined\n const sizeData = entry.sizes[size || 'medium'] || entry.sizes.full\n return sizeData\n }\n \n return undefined\n}\n"],"mappings":"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","names":["meta"]}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image size variants (verbose format used by handlers)
|
|
3
|
+
*/
|
|
4
|
+
type ImageSize = 'small' | 'medium' | 'large' | 'full';
|
|
5
|
+
/**
|
|
6
|
+
* Image size variants (lean format for consumers)
|
|
7
|
+
*/
|
|
8
|
+
type LeanImageSize = 'sm' | 'md' | 'lg';
|
|
9
|
+
/**
|
|
10
|
+
* Size entry with path and dimensions
|
|
11
|
+
*/
|
|
12
|
+
interface SizeEntry {
|
|
13
|
+
path: string;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* CDN sync status
|
|
19
|
+
*/
|
|
20
|
+
interface CdnStatus {
|
|
21
|
+
synced: boolean;
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
syncedAt: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Image entry in meta (verbose format - used by Studio handlers)
|
|
27
|
+
*/
|
|
28
|
+
interface ImageEntry {
|
|
29
|
+
original: {
|
|
30
|
+
path: string;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
fileSize: number;
|
|
34
|
+
};
|
|
35
|
+
sizes: {
|
|
36
|
+
full: SizeEntry;
|
|
37
|
+
large: SizeEntry;
|
|
38
|
+
medium: SizeEntry;
|
|
39
|
+
small: SizeEntry;
|
|
40
|
+
[key: string]: SizeEntry;
|
|
41
|
+
};
|
|
42
|
+
blurhash: string;
|
|
43
|
+
dominantColor: string;
|
|
44
|
+
cdn: CdnStatus | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Studio meta schema (verbose format - used by Studio handlers)
|
|
48
|
+
*/
|
|
49
|
+
interface StudioMeta {
|
|
50
|
+
$schema: string;
|
|
51
|
+
version: number;
|
|
52
|
+
generatedAt: string;
|
|
53
|
+
images: Record<string, ImageEntry>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Lean image entry - minimal metadata for consumers
|
|
57
|
+
* ~80 bytes per image vs ~500 bytes in verbose format
|
|
58
|
+
*/
|
|
59
|
+
interface LeanImageEntry {
|
|
60
|
+
/** Original width */
|
|
61
|
+
w: number;
|
|
62
|
+
/** Original height */
|
|
63
|
+
h: number;
|
|
64
|
+
/** Blurhash for placeholder */
|
|
65
|
+
blur: string;
|
|
66
|
+
/** Synced to CDN (present and 1 if synced, omit if not) */
|
|
67
|
+
s?: 1;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Lean meta schema - flat structure with path as key
|
|
71
|
+
*/
|
|
72
|
+
type LeanMeta = Record<string, LeanImageEntry>;
|
|
73
|
+
/**
|
|
74
|
+
* File/folder item for browser
|
|
75
|
+
*/
|
|
76
|
+
interface FileItem {
|
|
77
|
+
name: string;
|
|
78
|
+
path: string;
|
|
79
|
+
type: 'file' | 'folder';
|
|
80
|
+
size?: number;
|
|
81
|
+
dimensions?: {
|
|
82
|
+
width: number;
|
|
83
|
+
height: number;
|
|
84
|
+
};
|
|
85
|
+
cdnSynced?: boolean;
|
|
86
|
+
fileCount?: number;
|
|
87
|
+
totalSize?: number;
|
|
88
|
+
thumbnail?: string;
|
|
89
|
+
hasThumbnail?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Studio configuration
|
|
93
|
+
*/
|
|
94
|
+
interface StudioConfig {
|
|
95
|
+
r2AccountId?: string;
|
|
96
|
+
r2AccessKeyId?: string;
|
|
97
|
+
r2SecretAccessKey?: string;
|
|
98
|
+
r2BucketName?: string;
|
|
99
|
+
r2PublicUrl?: string;
|
|
100
|
+
thumbnailSizes?: {
|
|
101
|
+
small: number;
|
|
102
|
+
medium: number;
|
|
103
|
+
large: number;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Helper to derive thumbnail path from original path
|
|
108
|
+
*/
|
|
109
|
+
declare function getThumbnailPath(originalPath: string, size: LeanImageSize): string;
|
|
110
|
+
/**
|
|
111
|
+
* Convert verbose StudioMeta to LeanMeta
|
|
112
|
+
*/
|
|
113
|
+
declare function toLeanMeta(verbose: StudioMeta): LeanMeta;
|
|
114
|
+
/**
|
|
115
|
+
* Check if meta is in lean format (no 'images' wrapper)
|
|
116
|
+
*/
|
|
117
|
+
declare function isLeanMeta(meta: unknown): meta is LeanMeta;
|
|
118
|
+
|
|
119
|
+
export { type CdnStatus as C, type FileItem as F, type ImageEntry as I, type LeanMeta as L, type StudioMeta as S, type ImageSize as a, type LeanImageEntry as b, type LeanImageSize as c, type SizeEntry as d, type StudioConfig as e, getThumbnailPath as g, isLeanMeta as i, toLeanMeta as t };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image size variants (verbose format used by handlers)
|
|
3
|
+
*/
|
|
4
|
+
type ImageSize = 'small' | 'medium' | 'large' | 'full';
|
|
5
|
+
/**
|
|
6
|
+
* Image size variants (lean format for consumers)
|
|
7
|
+
*/
|
|
8
|
+
type LeanImageSize = 'sm' | 'md' | 'lg';
|
|
9
|
+
/**
|
|
10
|
+
* Size entry with path and dimensions
|
|
11
|
+
*/
|
|
12
|
+
interface SizeEntry {
|
|
13
|
+
path: string;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* CDN sync status
|
|
19
|
+
*/
|
|
20
|
+
interface CdnStatus {
|
|
21
|
+
synced: boolean;
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
syncedAt: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Image entry in meta (verbose format - used by Studio handlers)
|
|
27
|
+
*/
|
|
28
|
+
interface ImageEntry {
|
|
29
|
+
original: {
|
|
30
|
+
path: string;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
fileSize: number;
|
|
34
|
+
};
|
|
35
|
+
sizes: {
|
|
36
|
+
full: SizeEntry;
|
|
37
|
+
large: SizeEntry;
|
|
38
|
+
medium: SizeEntry;
|
|
39
|
+
small: SizeEntry;
|
|
40
|
+
[key: string]: SizeEntry;
|
|
41
|
+
};
|
|
42
|
+
blurhash: string;
|
|
43
|
+
dominantColor: string;
|
|
44
|
+
cdn: CdnStatus | null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Studio meta schema (verbose format - used by Studio handlers)
|
|
48
|
+
*/
|
|
49
|
+
interface StudioMeta {
|
|
50
|
+
$schema: string;
|
|
51
|
+
version: number;
|
|
52
|
+
generatedAt: string;
|
|
53
|
+
images: Record<string, ImageEntry>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Lean image entry - minimal metadata for consumers
|
|
57
|
+
* ~80 bytes per image vs ~500 bytes in verbose format
|
|
58
|
+
*/
|
|
59
|
+
interface LeanImageEntry {
|
|
60
|
+
/** Original width */
|
|
61
|
+
w: number;
|
|
62
|
+
/** Original height */
|
|
63
|
+
h: number;
|
|
64
|
+
/** Blurhash for placeholder */
|
|
65
|
+
blur: string;
|
|
66
|
+
/** Synced to CDN (present and 1 if synced, omit if not) */
|
|
67
|
+
s?: 1;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Lean meta schema - flat structure with path as key
|
|
71
|
+
*/
|
|
72
|
+
type LeanMeta = Record<string, LeanImageEntry>;
|
|
73
|
+
/**
|
|
74
|
+
* File/folder item for browser
|
|
75
|
+
*/
|
|
76
|
+
interface FileItem {
|
|
77
|
+
name: string;
|
|
78
|
+
path: string;
|
|
79
|
+
type: 'file' | 'folder';
|
|
80
|
+
size?: number;
|
|
81
|
+
dimensions?: {
|
|
82
|
+
width: number;
|
|
83
|
+
height: number;
|
|
84
|
+
};
|
|
85
|
+
cdnSynced?: boolean;
|
|
86
|
+
fileCount?: number;
|
|
87
|
+
totalSize?: number;
|
|
88
|
+
thumbnail?: string;
|
|
89
|
+
hasThumbnail?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Studio configuration
|
|
93
|
+
*/
|
|
94
|
+
interface StudioConfig {
|
|
95
|
+
r2AccountId?: string;
|
|
96
|
+
r2AccessKeyId?: string;
|
|
97
|
+
r2SecretAccessKey?: string;
|
|
98
|
+
r2BucketName?: string;
|
|
99
|
+
r2PublicUrl?: string;
|
|
100
|
+
thumbnailSizes?: {
|
|
101
|
+
small: number;
|
|
102
|
+
medium: number;
|
|
103
|
+
large: number;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Helper to derive thumbnail path from original path
|
|
108
|
+
*/
|
|
109
|
+
declare function getThumbnailPath(originalPath: string, size: LeanImageSize): string;
|
|
110
|
+
/**
|
|
111
|
+
* Convert verbose StudioMeta to LeanMeta
|
|
112
|
+
*/
|
|
113
|
+
declare function toLeanMeta(verbose: StudioMeta): LeanMeta;
|
|
114
|
+
/**
|
|
115
|
+
* Check if meta is in lean format (no 'images' wrapper)
|
|
116
|
+
*/
|
|
117
|
+
declare function isLeanMeta(meta: unknown): meta is LeanMeta;
|
|
118
|
+
|
|
119
|
+
export { type CdnStatus as C, type FileItem as F, type ImageEntry as I, type LeanMeta as L, type StudioMeta as S, type ImageSize as a, type LeanImageEntry as b, type LeanImageSize as c, type SizeEntry as d, type StudioConfig as e, getThumbnailPath as g, isLeanMeta as i, toLeanMeta as t };
|
package/package.json
CHANGED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image size variants
|
|
3
|
-
*/
|
|
4
|
-
type ImageSize = 'small' | 'medium' | 'large' | 'full';
|
|
5
|
-
/**
|
|
6
|
-
* Size entry with path and dimensions
|
|
7
|
-
*/
|
|
8
|
-
interface SizeEntry {
|
|
9
|
-
path: string;
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* CDN sync status
|
|
15
|
-
*/
|
|
16
|
-
interface CdnStatus {
|
|
17
|
-
synced: boolean;
|
|
18
|
-
baseUrl: string;
|
|
19
|
-
syncedAt: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Image entry in meta
|
|
23
|
-
*/
|
|
24
|
-
interface ImageEntry {
|
|
25
|
-
original: {
|
|
26
|
-
path: string;
|
|
27
|
-
width: number;
|
|
28
|
-
height: number;
|
|
29
|
-
fileSize: number;
|
|
30
|
-
};
|
|
31
|
-
sizes: Record<ImageSize, SizeEntry>;
|
|
32
|
-
blurhash: string;
|
|
33
|
-
dominantColor: string;
|
|
34
|
-
cdn: CdnStatus | null;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Studio meta schema
|
|
38
|
-
*/
|
|
39
|
-
interface StudioMeta {
|
|
40
|
-
$schema: string;
|
|
41
|
-
version: number;
|
|
42
|
-
generatedAt: string;
|
|
43
|
-
images: Record<string, ImageEntry>;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* File/folder item for browser
|
|
47
|
-
*/
|
|
48
|
-
interface FileItem {
|
|
49
|
-
name: string;
|
|
50
|
-
path: string;
|
|
51
|
-
type: 'file' | 'folder';
|
|
52
|
-
size?: number;
|
|
53
|
-
dimensions?: {
|
|
54
|
-
width: number;
|
|
55
|
-
height: number;
|
|
56
|
-
};
|
|
57
|
-
cdnSynced?: boolean;
|
|
58
|
-
fileCount?: number;
|
|
59
|
-
totalSize?: number;
|
|
60
|
-
thumbnail?: string;
|
|
61
|
-
hasThumbnail?: boolean;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Studio configuration
|
|
65
|
-
*/
|
|
66
|
-
interface StudioConfig {
|
|
67
|
-
r2AccountId?: string;
|
|
68
|
-
r2AccessKeyId?: string;
|
|
69
|
-
r2SecretAccessKey?: string;
|
|
70
|
-
r2BucketName?: string;
|
|
71
|
-
r2PublicUrl?: string;
|
|
72
|
-
thumbnailSizes?: {
|
|
73
|
-
small: number;
|
|
74
|
-
medium: number;
|
|
75
|
-
large: number;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export type { CdnStatus as C, FileItem as F, ImageSize as I, SizeEntry as S, ImageEntry as a, StudioMeta as b, StudioConfig as c };
|
package/dist/types-1m_7EjJU.d.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image size variants
|
|
3
|
-
*/
|
|
4
|
-
type ImageSize = 'small' | 'medium' | 'large' | 'full';
|
|
5
|
-
/**
|
|
6
|
-
* Size entry with path and dimensions
|
|
7
|
-
*/
|
|
8
|
-
interface SizeEntry {
|
|
9
|
-
path: string;
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* CDN sync status
|
|
15
|
-
*/
|
|
16
|
-
interface CdnStatus {
|
|
17
|
-
synced: boolean;
|
|
18
|
-
baseUrl: string;
|
|
19
|
-
syncedAt: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Image entry in meta
|
|
23
|
-
*/
|
|
24
|
-
interface ImageEntry {
|
|
25
|
-
original: {
|
|
26
|
-
path: string;
|
|
27
|
-
width: number;
|
|
28
|
-
height: number;
|
|
29
|
-
fileSize: number;
|
|
30
|
-
};
|
|
31
|
-
sizes: Record<ImageSize, SizeEntry>;
|
|
32
|
-
blurhash: string;
|
|
33
|
-
dominantColor: string;
|
|
34
|
-
cdn: CdnStatus | null;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Studio meta schema
|
|
38
|
-
*/
|
|
39
|
-
interface StudioMeta {
|
|
40
|
-
$schema: string;
|
|
41
|
-
version: number;
|
|
42
|
-
generatedAt: string;
|
|
43
|
-
images: Record<string, ImageEntry>;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* File/folder item for browser
|
|
47
|
-
*/
|
|
48
|
-
interface FileItem {
|
|
49
|
-
name: string;
|
|
50
|
-
path: string;
|
|
51
|
-
type: 'file' | 'folder';
|
|
52
|
-
size?: number;
|
|
53
|
-
dimensions?: {
|
|
54
|
-
width: number;
|
|
55
|
-
height: number;
|
|
56
|
-
};
|
|
57
|
-
cdnSynced?: boolean;
|
|
58
|
-
fileCount?: number;
|
|
59
|
-
totalSize?: number;
|
|
60
|
-
thumbnail?: string;
|
|
61
|
-
hasThumbnail?: boolean;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Studio configuration
|
|
65
|
-
*/
|
|
66
|
-
interface StudioConfig {
|
|
67
|
-
r2AccountId?: string;
|
|
68
|
-
r2AccessKeyId?: string;
|
|
69
|
-
r2SecretAccessKey?: string;
|
|
70
|
-
r2BucketName?: string;
|
|
71
|
-
r2PublicUrl?: string;
|
|
72
|
-
thumbnailSizes?: {
|
|
73
|
-
small: number;
|
|
74
|
-
medium: number;
|
|
75
|
-
large: number;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export type { CdnStatus as C, FileItem as F, ImageSize as I, SizeEntry as S, ImageEntry as a, StudioMeta as b, StudioConfig as c };
|