@thewhateverapp/tile-sdk 0.13.1 → 0.13.3
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/react/overlay/Slideshow.d.ts +5 -1
- package/dist/react/overlay/Slideshow.d.ts.map +1 -1
- package/dist/react/overlay/Slideshow.js +50 -15
- package/dist/templates/slideshow/index.d.ts +15 -0
- package/dist/templates/slideshow/index.d.ts.map +1 -1
- package/dist/templates/slideshow/index.js +19 -1
- package/dist/templates/slideshow/layout.tsx.template.d.ts +29 -0
- package/dist/templates/slideshow/layout.tsx.template.d.ts.map +1 -0
- package/dist/templates/slideshow/layout.tsx.template.js +183 -0
- package/dist/templates/video/index.d.ts +15 -1
- package/dist/templates/video/index.d.ts.map +1 -1
- package/dist/templates/video/index.js +19 -2
- package/dist/templates/video/layout.tsx.template.d.ts +24 -3
- package/dist/templates/video/layout.tsx.template.d.ts.map +1 -1
- package/dist/templates/video/layout.tsx.template.js +186 -13
- package/package.json +1 -1
|
@@ -86,6 +86,10 @@ export interface SlideshowProps {
|
|
|
86
86
|
showDots?: boolean;
|
|
87
87
|
/** Show navigation arrows (default: true) */
|
|
88
88
|
showArrows?: boolean;
|
|
89
|
+
/** Enable swipe gestures on touch devices (default: true) */
|
|
90
|
+
swipeable?: boolean;
|
|
91
|
+
/** Image fit mode: 'cover' fills container (may crop), 'contain' shows full image (may letterbox) */
|
|
92
|
+
objectFit?: 'cover' | 'contain';
|
|
89
93
|
/** Children rendered as overlay */
|
|
90
94
|
children?: ReactNode;
|
|
91
95
|
/** Additional class names */
|
|
@@ -106,7 +110,7 @@ export interface SlideshowProps {
|
|
|
106
110
|
* </Slideshow>
|
|
107
111
|
* ```
|
|
108
112
|
*/
|
|
109
|
-
export declare function Slideshow({ images, intervalMs, autoAdvance, transition, transitionDuration, showDots, showArrows, children, className, imageClassName, }: SlideshowProps): React.JSX.Element;
|
|
113
|
+
export declare function Slideshow({ images, intervalMs, autoAdvance, transition, transitionDuration, showDots, showArrows, swipeable, objectFit, children, className, imageClassName, }: SlideshowProps): React.JSX.Element;
|
|
110
114
|
/**
|
|
111
115
|
* Hook to access slideshow state and controls from within Slideshow children.
|
|
112
116
|
* Also aliased as useSlideshow for consistency with VideoPlayer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Slideshow.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/Slideshow.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Slideshow.d.ts","sourceRoot":"","sources":["../../../src/react/overlay/Slideshow.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAMf,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,oBAAoB,CAAC,EAAE,uBAAuB,CAAC;KAChD;CACF;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,cAAc,CAAC;IACtB,QAAQ,EAAE,iBAAiB,CAAC;CAC7B;AAED,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAErD;;;;;GAKG;AACH,cAAM,uBAAuB;IAC3B,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,YAAY,CAMlB;IACF,OAAO,CAAC,WAAW,CAA+B;IAClD,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,kBAAkB,CAAe;IAEzC;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE;QACzC,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;KAC7B,GAAG,IAAI;IAgCR;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB,IAAI,IAAI,IAAI;IAgBZ,IAAI,IAAI,IAAI;IAgBZ,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAqBzB,KAAK,IAAI,IAAI;IAMb,MAAM,IAAI,IAAI;IAMd,MAAM,IAAI,IAAI;IASd,QAAQ,IAAI,cAAc;IAI1B,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,IAAI;IAMlD,OAAO,CAAC,eAAe;IAIvB;;OAEG;IACH,OAAO,IAAI,IAAI;CAIhB;AAqBD,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACvC,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qGAAqG;IACrG,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,mCAAmC;IACnC,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,UAAiB,EACjB,WAAkB,EAClB,UAAmB,EACnB,kBAAwB,EACxB,QAAe,EACf,UAAiB,EACjB,SAAgB,EAChB,SAAmB,EACnB,QAAQ,EACR,SAAc,EACd,cAAmB,GACpB,EAAE,cAAc,qBAkMhB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,qBAAqB,CAMzD;AAGD,eAAO,MAAM,YAAY,0BAAoB,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import React, { createContext, useContext, useEffect, useState, useCallback, } from 'react';
|
|
2
|
+
import React, { createContext, useContext, useEffect, useRef, useState, useCallback, } from 'react';
|
|
3
3
|
/**
|
|
4
4
|
* SlideshowSingleton - Manages slideshow state across route changes.
|
|
5
5
|
*
|
|
@@ -186,8 +186,10 @@ const SlideshowContext = createContext(null);
|
|
|
186
186
|
* </Slideshow>
|
|
187
187
|
* ```
|
|
188
188
|
*/
|
|
189
|
-
export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, transition = 'fade', transitionDuration = 500, showDots = true, showArrows = true, children, className = '', imageClassName = '', }) {
|
|
189
|
+
export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, transition = 'fade', transitionDuration = 500, showDots = true, showArrows = true, swipeable = true, objectFit = 'cover', children, className = '', imageClassName = '', }) {
|
|
190
190
|
const [state, setState] = useState(() => getSlideshowSingleton().getState());
|
|
191
|
+
const containerRef = useRef(null);
|
|
192
|
+
const touchStartRef = useRef(null);
|
|
191
193
|
// Initialize and subscribe to singleton
|
|
192
194
|
useEffect(() => {
|
|
193
195
|
const singleton = getSlideshowSingleton();
|
|
@@ -201,6 +203,49 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
|
|
|
201
203
|
const unsubscribe = singleton.onStateChange(setState);
|
|
202
204
|
return unsubscribe;
|
|
203
205
|
}, [images, intervalMs, autoAdvance, transitionDuration]);
|
|
206
|
+
// Control functions bound to singleton
|
|
207
|
+
const singleton = getSlideshowSingleton();
|
|
208
|
+
const controls = {
|
|
209
|
+
next: useCallback(() => singleton.next(), []),
|
|
210
|
+
prev: useCallback(() => singleton.prev(), []),
|
|
211
|
+
goTo: useCallback((index) => singleton.goTo(index), []),
|
|
212
|
+
pause: useCallback(() => singleton.pause(), []),
|
|
213
|
+
resume: useCallback(() => singleton.resume(), []),
|
|
214
|
+
toggle: useCallback(() => singleton.toggle(), []),
|
|
215
|
+
};
|
|
216
|
+
// Touch/swipe handlers
|
|
217
|
+
const handleTouchStart = useCallback((e) => {
|
|
218
|
+
if (!swipeable || state.totalSlides <= 1)
|
|
219
|
+
return;
|
|
220
|
+
const touch = e.touches[0];
|
|
221
|
+
touchStartRef.current = {
|
|
222
|
+
x: touch.clientX,
|
|
223
|
+
y: touch.clientY,
|
|
224
|
+
time: Date.now(),
|
|
225
|
+
};
|
|
226
|
+
}, [swipeable, state.totalSlides]);
|
|
227
|
+
const handleTouchEnd = useCallback((e) => {
|
|
228
|
+
if (!swipeable || !touchStartRef.current || state.isTransitioning)
|
|
229
|
+
return;
|
|
230
|
+
const touch = e.changedTouches[0];
|
|
231
|
+
const deltaX = touch.clientX - touchStartRef.current.x;
|
|
232
|
+
const deltaY = touch.clientY - touchStartRef.current.y;
|
|
233
|
+
const deltaTime = Date.now() - touchStartRef.current.time;
|
|
234
|
+
// Minimum swipe distance (50px) and max time (300ms for quick swipe, or slower with more distance)
|
|
235
|
+
const minSwipeDistance = 50;
|
|
236
|
+
const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
|
|
237
|
+
const isValidSwipe = Math.abs(deltaX) > minSwipeDistance && isHorizontalSwipe;
|
|
238
|
+
const isQuickSwipe = deltaTime < 300 || Math.abs(deltaX) > 100;
|
|
239
|
+
if (isValidSwipe && isQuickSwipe) {
|
|
240
|
+
if (deltaX > 0) {
|
|
241
|
+
controls.prev();
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
controls.next();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
touchStartRef.current = null;
|
|
248
|
+
}, [swipeable, state.isTransitioning, controls]);
|
|
204
249
|
// Get transition styles
|
|
205
250
|
const getTransitionStyles = (index) => {
|
|
206
251
|
const isActive = index === state.currentIndex;
|
|
@@ -229,16 +274,6 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
|
|
|
229
274
|
};
|
|
230
275
|
}
|
|
231
276
|
};
|
|
232
|
-
// Control functions bound to singleton
|
|
233
|
-
const singleton = getSlideshowSingleton();
|
|
234
|
-
const controls = {
|
|
235
|
-
next: useCallback(() => singleton.next(), []),
|
|
236
|
-
prev: useCallback(() => singleton.prev(), []),
|
|
237
|
-
goTo: useCallback((index) => singleton.goTo(index), []),
|
|
238
|
-
pause: useCallback(() => singleton.pause(), []),
|
|
239
|
-
resume: useCallback(() => singleton.resume(), []),
|
|
240
|
-
toggle: useCallback(() => singleton.toggle(), []),
|
|
241
|
-
};
|
|
242
277
|
const contextValue = {
|
|
243
278
|
state,
|
|
244
279
|
controls,
|
|
@@ -248,9 +283,9 @@ export function Slideshow({ images, intervalMs = 5000, autoAdvance = true, trans
|
|
|
248
283
|
React.createElement("p", { className: "text-white/60" }, "No images")));
|
|
249
284
|
}
|
|
250
285
|
return (React.createElement(SlideshowContext.Provider, { value: contextValue },
|
|
251
|
-
React.createElement("div", { className: `relative w-full h-full bg-black overflow-hidden ${className}
|
|
252
|
-
React.createElement("div", { className: "relative w-full h-full" }, images.map((image, index) => (React.createElement("div", { key: `${image.url}-${index}`, style: getTransitionStyles(index), className: imageClassName },
|
|
253
|
-
React.createElement("img", { src: image.url, alt: image.alt || `Slide ${index + 1}`, className:
|
|
286
|
+
React.createElement("div", { ref: containerRef, className: `relative w-full h-full bg-black overflow-hidden ${className}`, onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd },
|
|
287
|
+
React.createElement("div", { className: "relative w-full h-full" }, images.map((image, index) => (React.createElement("div", { key: `${image.url}-${index}`, style: getTransitionStyles(index), className: `flex items-center justify-center ${imageClassName}` },
|
|
288
|
+
React.createElement("img", { src: image.url, alt: image.alt || `Slide ${index + 1}`, className: `w-full h-full ${objectFit === 'cover' ? 'object-cover' : 'object-contain'}`, style: { objectPosition: 'center' }, loading: index === 0 ? 'eager' : 'lazy', draggable: false }))))),
|
|
254
289
|
showArrows && state.totalSlides > 1 && (React.createElement(React.Fragment, null,
|
|
255
290
|
React.createElement("button", { onClick: controls.prev, disabled: state.isTransitioning, className: "absolute left-2 top-1/2 -translate-y-1/2 p-2 bg-black/40 hover:bg-black/60 rounded-full text-white transition-colors disabled:opacity-50", "aria-label": "Previous slide" },
|
|
256
291
|
React.createElement("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
|
|
@@ -3,10 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These templates are used by tile-deploy/agent-service to generate
|
|
5
5
|
* slideshow tiles with persistent state across route navigation.
|
|
6
|
+
*
|
|
7
|
+
* Layout-based approach:
|
|
8
|
+
* - Slideshow is mounted ONCE in the layout (route group)
|
|
9
|
+
* - Tile and page routes only render overlay content
|
|
10
|
+
* - This ensures seamless state persistence across navigation
|
|
6
11
|
*/
|
|
12
|
+
export { slideshowLayoutTemplate, slideshowTileOverlayTemplate, slideshowPageOverlayTemplate, } from './layout.tsx.template.js';
|
|
7
13
|
export { slideshowManagerTemplate } from './SlideshowManager.ts.template.js';
|
|
8
14
|
export { persistentSlideshowTemplate } from './PersistentSlideshow.tsx.template.js';
|
|
15
|
+
/**
|
|
16
|
+
* Template file structure for slideshow tiles using layout-based approach.
|
|
17
|
+
* The (slideshow) route group ensures state persists across tile/page navigation.
|
|
18
|
+
*/
|
|
9
19
|
export declare const slideshowTemplateFiles: {
|
|
20
|
+
'src/app/(slideshow)/layout.tsx': string;
|
|
21
|
+
'src/app/(slideshow)/tile/page.tsx': string;
|
|
22
|
+
'src/app/(slideshow)/page/page.tsx': string;
|
|
23
|
+
};
|
|
24
|
+
export declare const slideshowLegacyTemplateFiles: {
|
|
10
25
|
'src/shared/lib/SlideshowManager.ts': string;
|
|
11
26
|
'src/shared/components/PersistentSlideshow.tsx': string;
|
|
12
27
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/slideshow/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/slideshow/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AAEpF;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;CAMlC,CAAC;AAGF,eAAO,MAAM,4BAA4B;;;CAGxC,CAAC"}
|
|
@@ -3,12 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These templates are used by tile-deploy/agent-service to generate
|
|
5
5
|
* slideshow tiles with persistent state across route navigation.
|
|
6
|
+
*
|
|
7
|
+
* Layout-based approach:
|
|
8
|
+
* - Slideshow is mounted ONCE in the layout (route group)
|
|
9
|
+
* - Tile and page routes only render overlay content
|
|
10
|
+
* - This ensures seamless state persistence across navigation
|
|
6
11
|
*/
|
|
7
12
|
// ESM requires explicit .js extension for imports
|
|
13
|
+
export { slideshowLayoutTemplate, slideshowTileOverlayTemplate, slideshowPageOverlayTemplate, } from './layout.tsx.template.js';
|
|
14
|
+
// Legacy templates (kept for backwards compatibility)
|
|
8
15
|
export { slideshowManagerTemplate } from './SlideshowManager.ts.template.js';
|
|
9
16
|
export { persistentSlideshowTemplate } from './PersistentSlideshow.tsx.template.js';
|
|
10
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Template file structure for slideshow tiles using layout-based approach.
|
|
19
|
+
* The (slideshow) route group ensures state persists across tile/page navigation.
|
|
20
|
+
*/
|
|
11
21
|
export const slideshowTemplateFiles = {
|
|
22
|
+
// Layout mounts Slideshow once
|
|
23
|
+
'src/app/(slideshow)/layout.tsx': 'slideshowLayoutTemplate',
|
|
24
|
+
// Overlay-only pages (no Slideshow wrapper needed)
|
|
25
|
+
'src/app/(slideshow)/tile/page.tsx': 'slideshowTileOverlayTemplate',
|
|
26
|
+
'src/app/(slideshow)/page/page.tsx': 'slideshowPageOverlayTemplate',
|
|
27
|
+
};
|
|
28
|
+
// Legacy file structure (deprecated)
|
|
29
|
+
export const slideshowLegacyTemplateFiles = {
|
|
12
30
|
'src/shared/lib/SlideshowManager.ts': 'slideshowManagerTemplate',
|
|
13
31
|
'src/shared/components/PersistentSlideshow.tsx': 'persistentSlideshowTemplate',
|
|
14
32
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slideshow Template - Layout
|
|
3
|
+
*
|
|
4
|
+
* This layout wraps both /tile and /page routes using a route group.
|
|
5
|
+
* The Slideshow is rendered ONCE here and persists across route navigation.
|
|
6
|
+
*
|
|
7
|
+
* File path: src/app/(slideshow)/layout.tsx
|
|
8
|
+
* Routes: src/app/(slideshow)/tile/page.tsx, src/app/(slideshow)/page/page.tsx
|
|
9
|
+
*/
|
|
10
|
+
export declare const slideshowLayoutTemplate = "'use client';\n\nimport { Slideshow } from '@thewhateverapp/tile-sdk';\n\n// Slideshow configuration injected at generation time\nconst SLIDESHOW_CONFIG = __SLIDESHOW_CONFIG__;\n\nexport default function SlideshowLayout({ children }: { children: React.ReactNode }) {\n return (\n <Slideshow\n images={SLIDESHOW_CONFIG.images}\n autoAdvance={SLIDESHOW_CONFIG.autoAdvance}\n intervalMs={SLIDESHOW_CONFIG.intervalMs}\n transition={SLIDESHOW_CONFIG.transition}\n showDots={true}\n showArrows={true}\n className=\"w-full h-full\"\n >\n {/* Children are the route-specific overlays */}\n {children}\n </Slideshow>\n );\n}\n";
|
|
11
|
+
/**
|
|
12
|
+
* Slideshow Tile Overlay Template
|
|
13
|
+
*
|
|
14
|
+
* Overlay-only component for tile view (256×554px).
|
|
15
|
+
* Slideshow is already mounted in the parent layout.
|
|
16
|
+
*
|
|
17
|
+
* File path: src/app/(slideshow)/tile/page.tsx
|
|
18
|
+
*/
|
|
19
|
+
export declare const slideshowTileOverlayTemplate = "'use client';\n\nimport { useSlideshowState, OverlaySlot, GradientOverlay } from '@thewhateverapp/tile-sdk';\nimport { useTile } from '@thewhateverapp/tile-sdk';\n\nexport default function TileOverlay() {\n const tile = useTile();\n const { state } = useSlideshowState();\n\n return (\n <>\n {/* Expand button - opens full page view */}\n <OverlaySlot position=\"top-right\">\n <button\n onClick={() => tile.navigateToPage()}\n className=\"bg-black/40 backdrop-blur-sm p-2 rounded-full text-white hover:bg-black/60 transition-colors\"\n aria-label=\"Expand to full view\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4\" />\n </svg>\n </button>\n </OverlaySlot>\n\n {/* Slide counter */}\n <OverlaySlot position=\"top-left\">\n <div className=\"bg-black/50 backdrop-blur-sm px-2 py-1 rounded text-xs text-white\">\n {state.currentIndex + 1} / {state.totalSlides}\n </div>\n </OverlaySlot>\n\n {/* Caption at bottom */}\n {state.images[state.currentIndex]?.caption && (\n <GradientOverlay position=\"bottom\" className=\"p-4\">\n <p className=\"text-white text-sm\">{state.images[state.currentIndex].caption}</p>\n </GradientOverlay>\n )}\n </>\n );\n}\n";
|
|
20
|
+
/**
|
|
21
|
+
* Slideshow Page Overlay Template
|
|
22
|
+
*
|
|
23
|
+
* Overlay-only component for full page view.
|
|
24
|
+
* Slideshow is already mounted in the parent layout.
|
|
25
|
+
*
|
|
26
|
+
* File path: src/app/(slideshow)/page/page.tsx
|
|
27
|
+
*/
|
|
28
|
+
export declare const slideshowPageOverlayTemplate = "'use client';\n\nimport { useSlideshowState, OverlaySlot, GradientOverlay } from '@thewhateverapp/tile-sdk';\n\nexport default function PageOverlay() {\n const { state, controls } = useSlideshowState();\n\n return (\n <>\n {/* Slide counter */}\n <OverlaySlot position=\"top-right\">\n <div className=\"bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-full text-sm text-white\">\n {state.currentIndex + 1} / {state.totalSlides}\n </div>\n </OverlaySlot>\n\n {/* Navigation arrows (larger for full page) */}\n <OverlaySlot position=\"center-left\">\n <button\n onClick={controls.prev}\n className=\"bg-black/40 backdrop-blur-sm p-3 rounded-full text-white hover:bg-black/60 transition-colors\"\n aria-label=\"Previous slide\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n </OverlaySlot>\n\n <OverlaySlot position=\"center-right\">\n <button\n onClick={controls.next}\n className=\"bg-black/40 backdrop-blur-sm p-3 rounded-full text-white hover:bg-black/60 transition-colors\"\n aria-label=\"Next slide\"\n >\n <svg className=\"w-6 h-6\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </OverlaySlot>\n\n {/* Caption and controls at bottom */}\n <GradientOverlay position=\"bottom\" className=\"p-6\">\n <div className=\"space-y-3\">\n {/* Caption */}\n {state.images[state.currentIndex]?.caption && (\n <p className=\"text-white text-base\">{state.images[state.currentIndex].caption}</p>\n )}\n\n {/* Controls row */}\n <div className=\"flex items-center justify-between\">\n {/* Play/Pause autoplay */}\n <button\n onClick={controls.toggle}\n className=\"text-white hover:text-white/80 transition-colors flex items-center gap-2 text-sm\"\n >\n {state.isPaused ? (\n <>\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n <span>Play slideshow</span>\n </>\n ) : (\n <>\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" />\n </svg>\n <span>Pause slideshow</span>\n </>\n )}\n </button>\n\n {/* Dot indicators */}\n <div className=\"flex gap-2\">\n {state.images.map((_, idx) => (\n <button\n key={idx}\n onClick={() => controls.goTo(idx)}\n className={`w-2 h-2 rounded-full transition-colors ${\n idx === state.currentIndex ? 'bg-white' : 'bg-white/40 hover:bg-white/60'\n }`}\n aria-label={`Go to slide ${idx + 1}`}\n />\n ))}\n </div>\n </div>\n </div>\n </GradientOverlay>\n </>\n );\n}\n";
|
|
29
|
+
//# sourceMappingURL=layout.tsx.template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.tsx.template.d.ts","sourceRoot":"","sources":["../../../src/templates/slideshow/layout.tsx.template.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,sqBAuBnC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,8gDAwCxC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,49GA4FxC,CAAC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slideshow Template - Layout
|
|
3
|
+
*
|
|
4
|
+
* This layout wraps both /tile and /page routes using a route group.
|
|
5
|
+
* The Slideshow is rendered ONCE here and persists across route navigation.
|
|
6
|
+
*
|
|
7
|
+
* File path: src/app/(slideshow)/layout.tsx
|
|
8
|
+
* Routes: src/app/(slideshow)/tile/page.tsx, src/app/(slideshow)/page/page.tsx
|
|
9
|
+
*/
|
|
10
|
+
export const slideshowLayoutTemplate = `'use client';
|
|
11
|
+
|
|
12
|
+
import { Slideshow } from '@thewhateverapp/tile-sdk';
|
|
13
|
+
|
|
14
|
+
// Slideshow configuration injected at generation time
|
|
15
|
+
const SLIDESHOW_CONFIG = __SLIDESHOW_CONFIG__;
|
|
16
|
+
|
|
17
|
+
export default function SlideshowLayout({ children }: { children: React.ReactNode }) {
|
|
18
|
+
return (
|
|
19
|
+
<Slideshow
|
|
20
|
+
images={SLIDESHOW_CONFIG.images}
|
|
21
|
+
autoAdvance={SLIDESHOW_CONFIG.autoAdvance}
|
|
22
|
+
intervalMs={SLIDESHOW_CONFIG.intervalMs}
|
|
23
|
+
transition={SLIDESHOW_CONFIG.transition}
|
|
24
|
+
showDots={true}
|
|
25
|
+
showArrows={true}
|
|
26
|
+
className="w-full h-full"
|
|
27
|
+
>
|
|
28
|
+
{/* Children are the route-specific overlays */}
|
|
29
|
+
{children}
|
|
30
|
+
</Slideshow>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
/**
|
|
35
|
+
* Slideshow Tile Overlay Template
|
|
36
|
+
*
|
|
37
|
+
* Overlay-only component for tile view (256×554px).
|
|
38
|
+
* Slideshow is already mounted in the parent layout.
|
|
39
|
+
*
|
|
40
|
+
* File path: src/app/(slideshow)/tile/page.tsx
|
|
41
|
+
*/
|
|
42
|
+
export const slideshowTileOverlayTemplate = `'use client';
|
|
43
|
+
|
|
44
|
+
import { useSlideshowState, OverlaySlot, GradientOverlay } from '@thewhateverapp/tile-sdk';
|
|
45
|
+
import { useTile } from '@thewhateverapp/tile-sdk';
|
|
46
|
+
|
|
47
|
+
export default function TileOverlay() {
|
|
48
|
+
const tile = useTile();
|
|
49
|
+
const { state } = useSlideshowState();
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
{/* Expand button - opens full page view */}
|
|
54
|
+
<OverlaySlot position="top-right">
|
|
55
|
+
<button
|
|
56
|
+
onClick={() => tile.navigateToPage()}
|
|
57
|
+
className="bg-black/40 backdrop-blur-sm p-2 rounded-full text-white hover:bg-black/60 transition-colors"
|
|
58
|
+
aria-label="Expand to full view"
|
|
59
|
+
>
|
|
60
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
61
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
|
62
|
+
</svg>
|
|
63
|
+
</button>
|
|
64
|
+
</OverlaySlot>
|
|
65
|
+
|
|
66
|
+
{/* Slide counter */}
|
|
67
|
+
<OverlaySlot position="top-left">
|
|
68
|
+
<div className="bg-black/50 backdrop-blur-sm px-2 py-1 rounded text-xs text-white">
|
|
69
|
+
{state.currentIndex + 1} / {state.totalSlides}
|
|
70
|
+
</div>
|
|
71
|
+
</OverlaySlot>
|
|
72
|
+
|
|
73
|
+
{/* Caption at bottom */}
|
|
74
|
+
{state.images[state.currentIndex]?.caption && (
|
|
75
|
+
<GradientOverlay position="bottom" className="p-4">
|
|
76
|
+
<p className="text-white text-sm">{state.images[state.currentIndex].caption}</p>
|
|
77
|
+
</GradientOverlay>
|
|
78
|
+
)}
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
/**
|
|
84
|
+
* Slideshow Page Overlay Template
|
|
85
|
+
*
|
|
86
|
+
* Overlay-only component for full page view.
|
|
87
|
+
* Slideshow is already mounted in the parent layout.
|
|
88
|
+
*
|
|
89
|
+
* File path: src/app/(slideshow)/page/page.tsx
|
|
90
|
+
*/
|
|
91
|
+
export const slideshowPageOverlayTemplate = `'use client';
|
|
92
|
+
|
|
93
|
+
import { useSlideshowState, OverlaySlot, GradientOverlay } from '@thewhateverapp/tile-sdk';
|
|
94
|
+
|
|
95
|
+
export default function PageOverlay() {
|
|
96
|
+
const { state, controls } = useSlideshowState();
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<>
|
|
100
|
+
{/* Slide counter */}
|
|
101
|
+
<OverlaySlot position="top-right">
|
|
102
|
+
<div className="bg-black/50 backdrop-blur-sm px-3 py-1.5 rounded-full text-sm text-white">
|
|
103
|
+
{state.currentIndex + 1} / {state.totalSlides}
|
|
104
|
+
</div>
|
|
105
|
+
</OverlaySlot>
|
|
106
|
+
|
|
107
|
+
{/* Navigation arrows (larger for full page) */}
|
|
108
|
+
<OverlaySlot position="center-left">
|
|
109
|
+
<button
|
|
110
|
+
onClick={controls.prev}
|
|
111
|
+
className="bg-black/40 backdrop-blur-sm p-3 rounded-full text-white hover:bg-black/60 transition-colors"
|
|
112
|
+
aria-label="Previous slide"
|
|
113
|
+
>
|
|
114
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
115
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
116
|
+
</svg>
|
|
117
|
+
</button>
|
|
118
|
+
</OverlaySlot>
|
|
119
|
+
|
|
120
|
+
<OverlaySlot position="center-right">
|
|
121
|
+
<button
|
|
122
|
+
onClick={controls.next}
|
|
123
|
+
className="bg-black/40 backdrop-blur-sm p-3 rounded-full text-white hover:bg-black/60 transition-colors"
|
|
124
|
+
aria-label="Next slide"
|
|
125
|
+
>
|
|
126
|
+
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
127
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
128
|
+
</svg>
|
|
129
|
+
</button>
|
|
130
|
+
</OverlaySlot>
|
|
131
|
+
|
|
132
|
+
{/* Caption and controls at bottom */}
|
|
133
|
+
<GradientOverlay position="bottom" className="p-6">
|
|
134
|
+
<div className="space-y-3">
|
|
135
|
+
{/* Caption */}
|
|
136
|
+
{state.images[state.currentIndex]?.caption && (
|
|
137
|
+
<p className="text-white text-base">{state.images[state.currentIndex].caption}</p>
|
|
138
|
+
)}
|
|
139
|
+
|
|
140
|
+
{/* Controls row */}
|
|
141
|
+
<div className="flex items-center justify-between">
|
|
142
|
+
{/* Play/Pause autoplay */}
|
|
143
|
+
<button
|
|
144
|
+
onClick={controls.toggle}
|
|
145
|
+
className="text-white hover:text-white/80 transition-colors flex items-center gap-2 text-sm"
|
|
146
|
+
>
|
|
147
|
+
{state.isPaused ? (
|
|
148
|
+
<>
|
|
149
|
+
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
150
|
+
<path d="M8 5v14l11-7z" />
|
|
151
|
+
</svg>
|
|
152
|
+
<span>Play slideshow</span>
|
|
153
|
+
</>
|
|
154
|
+
) : (
|
|
155
|
+
<>
|
|
156
|
+
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
157
|
+
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
|
|
158
|
+
</svg>
|
|
159
|
+
<span>Pause slideshow</span>
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
162
|
+
</button>
|
|
163
|
+
|
|
164
|
+
{/* Dot indicators */}
|
|
165
|
+
<div className="flex gap-2">
|
|
166
|
+
{state.images.map((_, idx) => (
|
|
167
|
+
<button
|
|
168
|
+
key={idx}
|
|
169
|
+
onClick={() => controls.goTo(idx)}
|
|
170
|
+
className={\`w-2 h-2 rounded-full transition-colors \${
|
|
171
|
+
idx === state.currentIndex ? 'bg-white' : 'bg-white/40 hover:bg-white/60'
|
|
172
|
+
}\`}
|
|
173
|
+
aria-label={\`Go to slide \${idx + 1}\`}
|
|
174
|
+
/>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</GradientOverlay>
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
`;
|
|
@@ -3,11 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These templates are used by tile-deploy/agent-service to generate
|
|
5
5
|
* video tiles with persistent playback across route navigation.
|
|
6
|
+
*
|
|
7
|
+
* Layout-based approach:
|
|
8
|
+
* - VideoPlayer is mounted ONCE in the layout (route group)
|
|
9
|
+
* - Tile and page routes only render overlay content
|
|
10
|
+
* - This ensures seamless video persistence across navigation
|
|
6
11
|
*/
|
|
7
|
-
export { videoLayoutTemplate } from './layout.tsx.template.js';
|
|
12
|
+
export { videoLayoutTemplate, videoTileOverlayTemplate, videoPageOverlayTemplate, } from './layout.tsx.template.js';
|
|
8
13
|
export { videoManagerTemplate } from './VideoManager.ts.template.js';
|
|
9
14
|
export { persistentVideoTemplate } from './PersistentVideo.tsx.template.js';
|
|
15
|
+
/**
|
|
16
|
+
* Template file structure for video tiles using layout-based approach.
|
|
17
|
+
* The (video) route group ensures VideoPlayer persists across tile/page navigation.
|
|
18
|
+
*/
|
|
10
19
|
export declare const videoTemplateFiles: {
|
|
20
|
+
'src/app/(video)/layout.tsx': string;
|
|
21
|
+
'src/app/(video)/tile/page.tsx': string;
|
|
22
|
+
'src/app/(video)/page/page.tsx': string;
|
|
23
|
+
};
|
|
24
|
+
export declare const videoLegacyTemplateFiles: {
|
|
11
25
|
'src/shared/lib/VideoManager.ts': string;
|
|
12
26
|
'src/shared/components/PersistentVideo.tsx': string;
|
|
13
27
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/video/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/video/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;CAM9B,CAAC;AAGF,eAAO,MAAM,wBAAwB;;;CAGpC,CAAC"}
|
|
@@ -3,13 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These templates are used by tile-deploy/agent-service to generate
|
|
5
5
|
* video tiles with persistent playback across route navigation.
|
|
6
|
+
*
|
|
7
|
+
* Layout-based approach:
|
|
8
|
+
* - VideoPlayer is mounted ONCE in the layout (route group)
|
|
9
|
+
* - Tile and page routes only render overlay content
|
|
10
|
+
* - This ensures seamless video persistence across navigation
|
|
6
11
|
*/
|
|
7
12
|
// ESM requires explicit .js extension for imports
|
|
8
|
-
export { videoLayoutTemplate } from './layout.tsx.template.js';
|
|
13
|
+
export { videoLayoutTemplate, videoTileOverlayTemplate, videoPageOverlayTemplate, } from './layout.tsx.template.js';
|
|
14
|
+
// Legacy templates (kept for backwards compatibility)
|
|
9
15
|
export { videoManagerTemplate } from './VideoManager.ts.template.js';
|
|
10
16
|
export { persistentVideoTemplate } from './PersistentVideo.tsx.template.js';
|
|
11
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Template file structure for video tiles using layout-based approach.
|
|
19
|
+
* The (video) route group ensures VideoPlayer persists across tile/page navigation.
|
|
20
|
+
*/
|
|
12
21
|
export const videoTemplateFiles = {
|
|
22
|
+
// Layout mounts VideoPlayer once
|
|
23
|
+
'src/app/(video)/layout.tsx': 'videoLayoutTemplate',
|
|
24
|
+
// Overlay-only pages (no VideoPlayer wrapper needed)
|
|
25
|
+
'src/app/(video)/tile/page.tsx': 'videoTileOverlayTemplate',
|
|
26
|
+
'src/app/(video)/page/page.tsx': 'videoPageOverlayTemplate',
|
|
27
|
+
};
|
|
28
|
+
// Legacy file structure (deprecated)
|
|
29
|
+
export const videoLegacyTemplateFiles = {
|
|
13
30
|
'src/shared/lib/VideoManager.ts': 'videoManagerTemplate',
|
|
14
31
|
'src/shared/components/PersistentVideo.tsx': 'persistentVideoTemplate',
|
|
15
32
|
};
|
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Video Template - Layout
|
|
3
3
|
*
|
|
4
|
-
* This layout wraps both /tile and /page routes.
|
|
5
|
-
* The VideoPlayer is rendered here
|
|
4
|
+
* This layout wraps both /tile and /page routes using a route group.
|
|
5
|
+
* The VideoPlayer is rendered ONCE here and persists across route navigation.
|
|
6
|
+
*
|
|
7
|
+
* File path: src/app/(video)/layout.tsx
|
|
8
|
+
* Routes: src/app/(video)/tile/page.tsx, src/app/(video)/page/page.tsx
|
|
9
|
+
*/
|
|
10
|
+
export declare const videoLayoutTemplate = "'use client';\n\nimport { VideoPlayer } from '@thewhateverapp/tile-sdk';\n\nexport default function VideoLayout({ children }: { children: React.ReactNode }) {\n return (\n <VideoPlayer className=\"w-full h-full\">\n {/* Children are the route-specific overlays */}\n {children}\n </VideoPlayer>\n );\n}\n";
|
|
11
|
+
/**
|
|
12
|
+
* Video Tile Overlay Template
|
|
13
|
+
*
|
|
14
|
+
* Overlay-only component for tile view (256×554px).
|
|
15
|
+
* VideoPlayer is already mounted in the parent layout.
|
|
16
|
+
*
|
|
17
|
+
* File path: src/app/(video)/tile/page.tsx
|
|
18
|
+
*/
|
|
19
|
+
export declare const videoTileOverlayTemplate = "'use client';\n\nimport { useVideoState, OverlaySlot, GradientOverlay } from '@thewhateverapp/tile-sdk';\nimport { useTile } from '@thewhateverapp/tile-sdk';\n\nfunction formatTime(seconds: number): string {\n if (!seconds || !isFinite(seconds)) return '0:00';\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\nexport default function TileOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n\n return (\n <>\n {/* Expand button - opens full page view */}\n <OverlaySlot position=\"top-right\">\n <button\n onClick={() => tile.navigateToPage()}\n className=\"bg-black/40 backdrop-blur-sm p-2 rounded-full text-white hover:bg-black/60 transition-colors\"\n aria-label=\"Expand to full view\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4\" />\n </svg>\n </button>\n </OverlaySlot>\n\n {/* Play/Pause button in center when paused */}\n {!state.isPlaying && !state.isLoading && (\n <OverlaySlot position=\"center\">\n <button\n onClick={controls.play}\n className=\"w-16 h-16 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-colors\"\n >\n <svg className=\"w-8 h-8 text-white ml-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </OverlaySlot>\n )}\n\n {/* Bottom gradient with progress */}\n <GradientOverlay position=\"bottom\" className=\"p-4\">\n <div className=\"space-y-2\">\n {/* Progress bar */}\n <div className=\"h-1 bg-white/30 rounded-full overflow-hidden\">\n <div\n className=\"h-full bg-white rounded-full transition-all duration-200\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n {/* Time display */}\n <div className=\"flex justify-between text-xs text-white/80\">\n <span>{formatTime(state.currentTime)}</span>\n <span>{formatTime(state.duration)}</span>\n </div>\n </div>\n </GradientOverlay>\n </>\n );\n}\n";
|
|
20
|
+
/**
|
|
21
|
+
* Video Page Overlay Template
|
|
22
|
+
*
|
|
23
|
+
* Overlay-only component for full page view.
|
|
24
|
+
* VideoPlayer is already mounted in the parent layout.
|
|
25
|
+
*
|
|
26
|
+
* File path: src/app/(video)/page/page.tsx
|
|
6
27
|
*/
|
|
7
|
-
export declare const
|
|
28
|
+
export declare const videoPageOverlayTemplate = "'use client';\n\nimport { useVideoState, OverlaySlot, GradientOverlay } from '@thewhateverapp/tile-sdk';\n\nfunction formatTime(seconds: number): string {\n if (!seconds || !isFinite(seconds)) return '0:00';\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins}:${secs.toString().padStart(2, '0')}`;\n}\n\nexport default function PageOverlay() {\n const { state, controls } = useVideoState();\n\n return (\n <>\n {/* Play/Pause button in center when paused */}\n {!state.isPlaying && !state.isLoading && (\n <OverlaySlot position=\"center\">\n <button\n onClick={controls.play}\n className=\"w-20 h-20 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-colors\"\n >\n <svg className=\"w-10 h-10 text-white ml-1\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </OverlaySlot>\n )}\n\n {/* Bottom gradient with controls */}\n <GradientOverlay position=\"bottom\" className=\"p-6\">\n <div className=\"space-y-3\">\n {/* Progress bar */}\n <div\n className=\"h-1.5 bg-white/30 rounded-full overflow-hidden cursor-pointer\"\n onClick={(e) => {\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-200\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n {/* Controls row */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n {/* Play/Pause */}\n <button\n onClick={controls.toggle}\n className=\"text-white hover:text-white/80 transition-colors\"\n >\n {state.isPlaying ? (\n <svg className=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" />\n </svg>\n ) : (\n <svg className=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n\n {/* Mute toggle */}\n <button\n onClick={() => controls.setMuted(!state.muted)}\n className=\"text-white hover:text-white/80 transition-colors\"\n >\n {state.muted ? (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z\" />\n </svg>\n ) : (\n <svg className=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\" />\n </svg>\n )}\n </button>\n\n {/* Time display */}\n <span className=\"text-sm text-white/80\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n </div>\n </div>\n </GradientOverlay>\n </>\n );\n}\n";
|
|
8
29
|
//# sourceMappingURL=layout.tsx.template.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.tsx.template.d.ts","sourceRoot":"","sources":["../../../src/templates/video/layout.tsx.template.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"layout.tsx.template.d.ts","sourceRoot":"","sources":["../../../src/templates/video/layout.tsx.template.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,uUAY/B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,qiFAiEpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,qgIA8FpC,CAAC"}
|
|
@@ -1,26 +1,199 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Video Template - Layout
|
|
3
3
|
*
|
|
4
|
-
* This layout wraps both /tile and /page routes.
|
|
5
|
-
* The VideoPlayer is rendered here
|
|
4
|
+
* This layout wraps both /tile and /page routes using a route group.
|
|
5
|
+
* The VideoPlayer is rendered ONCE here and persists across route navigation.
|
|
6
|
+
*
|
|
7
|
+
* File path: src/app/(video)/layout.tsx
|
|
8
|
+
* Routes: src/app/(video)/tile/page.tsx, src/app/(video)/page/page.tsx
|
|
6
9
|
*/
|
|
7
10
|
export const videoLayoutTemplate = `'use client';
|
|
8
11
|
|
|
9
|
-
import { VideoPlayer } from '@thewhateverapp/tile-sdk
|
|
10
|
-
import { usePathname } from 'next/navigation';
|
|
12
|
+
import { VideoPlayer } from '@thewhateverapp/tile-sdk';
|
|
11
13
|
|
|
12
14
|
export default function VideoLayout({ children }: { children: React.ReactNode }) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
return (
|
|
16
|
+
<VideoPlayer className="w-full h-full">
|
|
17
|
+
{/* Children are the route-specific overlays */}
|
|
18
|
+
{children}
|
|
19
|
+
</VideoPlayer>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
/**
|
|
24
|
+
* Video Tile Overlay Template
|
|
25
|
+
*
|
|
26
|
+
* Overlay-only component for tile view (256×554px).
|
|
27
|
+
* VideoPlayer is already mounted in the parent layout.
|
|
28
|
+
*
|
|
29
|
+
* File path: src/app/(video)/tile/page.tsx
|
|
30
|
+
*/
|
|
31
|
+
export const videoTileOverlayTemplate = `'use client';
|
|
32
|
+
|
|
33
|
+
import { useVideoState, OverlaySlot, GradientOverlay } from '@thewhateverapp/tile-sdk';
|
|
34
|
+
import { useTile } from '@thewhateverapp/tile-sdk';
|
|
35
|
+
|
|
36
|
+
function formatTime(seconds: number): string {
|
|
37
|
+
if (!seconds || !isFinite(seconds)) return '0:00';
|
|
38
|
+
const mins = Math.floor(seconds / 60);
|
|
39
|
+
const secs = Math.floor(seconds % 60);
|
|
40
|
+
return \`\${mins}:\${secs.toString().padStart(2, '0')}\`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default function TileOverlay() {
|
|
44
|
+
const tile = useTile();
|
|
45
|
+
const { state, controls } = useVideoState();
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
{/* Expand button - opens full page view */}
|
|
50
|
+
<OverlaySlot position="top-right">
|
|
51
|
+
<button
|
|
52
|
+
onClick={() => tile.navigateToPage()}
|
|
53
|
+
className="bg-black/40 backdrop-blur-sm p-2 rounded-full text-white hover:bg-black/60 transition-colors"
|
|
54
|
+
aria-label="Expand to full view"
|
|
55
|
+
>
|
|
56
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
57
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
</OverlaySlot>
|
|
61
|
+
|
|
62
|
+
{/* Play/Pause button in center when paused */}
|
|
63
|
+
{!state.isPlaying && !state.isLoading && (
|
|
64
|
+
<OverlaySlot position="center">
|
|
65
|
+
<button
|
|
66
|
+
onClick={controls.play}
|
|
67
|
+
className="w-16 h-16 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-colors"
|
|
68
|
+
>
|
|
69
|
+
<svg className="w-8 h-8 text-white ml-1" fill="currentColor" viewBox="0 0 24 24">
|
|
70
|
+
<path d="M8 5v14l11-7z" />
|
|
71
|
+
</svg>
|
|
72
|
+
</button>
|
|
73
|
+
</OverlaySlot>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{/* Bottom gradient with progress */}
|
|
77
|
+
<GradientOverlay position="bottom" className="p-4">
|
|
78
|
+
<div className="space-y-2">
|
|
79
|
+
{/* Progress bar */}
|
|
80
|
+
<div className="h-1 bg-white/30 rounded-full overflow-hidden">
|
|
81
|
+
<div
|
|
82
|
+
className="h-full bg-white rounded-full transition-all duration-200"
|
|
83
|
+
style={{ width: \`\${(state.currentTime / state.duration) * 100 || 0}%\` }}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
{/* Time display */}
|
|
87
|
+
<div className="flex justify-between text-xs text-white/80">
|
|
88
|
+
<span>{formatTime(state.currentTime)}</span>
|
|
89
|
+
<span>{formatTime(state.duration)}</span>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</GradientOverlay>
|
|
93
|
+
</>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
/**
|
|
98
|
+
* Video Page Overlay Template
|
|
99
|
+
*
|
|
100
|
+
* Overlay-only component for full page view.
|
|
101
|
+
* VideoPlayer is already mounted in the parent layout.
|
|
102
|
+
*
|
|
103
|
+
* File path: src/app/(video)/page/page.tsx
|
|
104
|
+
*/
|
|
105
|
+
export const videoPageOverlayTemplate = `'use client';
|
|
106
|
+
|
|
107
|
+
import { useVideoState, OverlaySlot, GradientOverlay } from '@thewhateverapp/tile-sdk';
|
|
108
|
+
|
|
109
|
+
function formatTime(seconds: number): string {
|
|
110
|
+
if (!seconds || !isFinite(seconds)) return '0:00';
|
|
111
|
+
const mins = Math.floor(seconds / 60);
|
|
112
|
+
const secs = Math.floor(seconds % 60);
|
|
113
|
+
return \`\${mins}:\${secs.toString().padStart(2, '0')}\`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default function PageOverlay() {
|
|
117
|
+
const { state, controls } = useVideoState();
|
|
15
118
|
|
|
16
119
|
return (
|
|
17
|
-
|
|
18
|
-
{/*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
120
|
+
<>
|
|
121
|
+
{/* Play/Pause button in center when paused */}
|
|
122
|
+
{!state.isPlaying && !state.isLoading && (
|
|
123
|
+
<OverlaySlot position="center">
|
|
124
|
+
<button
|
|
125
|
+
onClick={controls.play}
|
|
126
|
+
className="w-20 h-20 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-colors"
|
|
127
|
+
>
|
|
128
|
+
<svg className="w-10 h-10 text-white ml-1" fill="currentColor" viewBox="0 0 24 24">
|
|
129
|
+
<path d="M8 5v14l11-7z" />
|
|
130
|
+
</svg>
|
|
131
|
+
</button>
|
|
132
|
+
</OverlaySlot>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{/* Bottom gradient with controls */}
|
|
136
|
+
<GradientOverlay position="bottom" className="p-6">
|
|
137
|
+
<div className="space-y-3">
|
|
138
|
+
{/* Progress bar */}
|
|
139
|
+
<div
|
|
140
|
+
className="h-1.5 bg-white/30 rounded-full overflow-hidden cursor-pointer"
|
|
141
|
+
onClick={(e) => {
|
|
142
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
143
|
+
const percent = (e.clientX - rect.left) / rect.width;
|
|
144
|
+
controls.seek(percent * state.duration);
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
<div
|
|
148
|
+
className="h-full bg-white rounded-full transition-all duration-200"
|
|
149
|
+
style={{ width: \`\${(state.currentTime / state.duration) * 100 || 0}%\` }}
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{/* Controls row */}
|
|
154
|
+
<div className="flex items-center justify-between">
|
|
155
|
+
<div className="flex items-center gap-4">
|
|
156
|
+
{/* Play/Pause */}
|
|
157
|
+
<button
|
|
158
|
+
onClick={controls.toggle}
|
|
159
|
+
className="text-white hover:text-white/80 transition-colors"
|
|
160
|
+
>
|
|
161
|
+
{state.isPlaying ? (
|
|
162
|
+
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
|
163
|
+
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
|
|
164
|
+
</svg>
|
|
165
|
+
) : (
|
|
166
|
+
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
|
167
|
+
<path d="M8 5v14l11-7z" />
|
|
168
|
+
</svg>
|
|
169
|
+
)}
|
|
170
|
+
</button>
|
|
171
|
+
|
|
172
|
+
{/* Mute toggle */}
|
|
173
|
+
<button
|
|
174
|
+
onClick={() => controls.setMuted(!state.muted)}
|
|
175
|
+
className="text-white hover:text-white/80 transition-colors"
|
|
176
|
+
>
|
|
177
|
+
{state.muted ? (
|
|
178
|
+
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
179
|
+
<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z" />
|
|
180
|
+
</svg>
|
|
181
|
+
) : (
|
|
182
|
+
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
|
183
|
+
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z" />
|
|
184
|
+
</svg>
|
|
185
|
+
)}
|
|
186
|
+
</button>
|
|
187
|
+
|
|
188
|
+
{/* Time display */}
|
|
189
|
+
<span className="text-sm text-white/80">
|
|
190
|
+
{formatTime(state.currentTime)} / {formatTime(state.duration)}
|
|
191
|
+
</span>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</GradientOverlay>
|
|
196
|
+
</>
|
|
24
197
|
);
|
|
25
198
|
}
|
|
26
199
|
`;
|