@thewhateverapp/tile-sdk 0.12.20 → 0.12.21
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/templates/index.d.ts +1 -0
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +1 -1
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.d.ts +8 -0
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.d.ts.map +1 -0
- package/dist/templates/slideshow/PersistentSlideshow.tsx.template.js +212 -0
- package/dist/templates/slideshow/SlideshowManager.ts.template.d.ts +8 -0
- package/dist/templates/slideshow/SlideshowManager.ts.template.d.ts.map +1 -0
- package/dist/templates/slideshow/SlideshowManager.ts.template.js +235 -0
- package/dist/templates/slideshow/index.d.ts +13 -0
- package/dist/templates/slideshow/index.d.ts.map +1 -0
- package/dist/templates/slideshow/index.js +13 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC"}
|
package/dist/templates/index.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slideshow Template - PersistentSlideshow Component
|
|
3
|
+
*
|
|
4
|
+
* React component that attaches to the SlideshowManager singleton.
|
|
5
|
+
* The slideshow state persists across route changes.
|
|
6
|
+
*/
|
|
7
|
+
export declare const persistentSlideshowTemplate = "'use client';\n\nimport { useEffect, useState, createContext, useContext, ReactNode } from 'react';\nimport { SlideshowManager, SlideshowState, SlideImage } from './SlideshowManager';\n\n// Context to share slideshow state with overlays\ninterface SlideshowContextValue {\n state: SlideshowState;\n controls: {\n next: () => void;\n prev: () => void;\n goTo: (index: number) => void;\n pause: () => void;\n resume: () => void;\n toggle: () => void;\n };\n}\n\nconst SlideshowContext = createContext<SlideshowContextValue | null>(null);\n\nexport function useSlideshow(): SlideshowContextValue {\n const context = useContext(SlideshowContext);\n if (!context) {\n throw new Error('useSlideshow must be used within PersistentSlideshow');\n }\n return context;\n}\n\ninterface PersistentSlideshowProps {\n images: SlideImage[];\n /** Auto-advance interval in milliseconds (default: 5000) */\n intervalMs?: number;\n /** Auto-advance slides (default: true) */\n autoAdvance?: boolean;\n /** Transition type (default: 'fade') */\n transition?: 'fade' | 'slide' | 'none';\n /** Transition duration in ms (default: 500) */\n transitionDuration?: number;\n /** Show navigation dots (default: true) */\n showDots?: boolean;\n /** Show navigation arrows (default: true) */\n showArrows?: boolean;\n /** Additional class names */\n className?: string;\n /** Image container class names */\n imageClassName?: string;\n /** Children rendered as overlay */\n children?: ReactNode;\n}\n\nexport function PersistentSlideshow({\n images,\n intervalMs = 5000,\n autoAdvance = true,\n transition = 'fade',\n transitionDuration = 500,\n showDots = true,\n showArrows = true,\n className = '',\n imageClassName = '',\n children\n}: PersistentSlideshowProps) {\n const [state, setState] = useState<SlideshowState>(SlideshowManager.getState());\n\n useEffect(() => {\n // Initialize slideshow with images\n SlideshowManager.initialize(images, {\n intervalMs,\n transitionDuration,\n autoAdvance,\n });\n\n // Subscribe to state changes\n const unsubscribe = SlideshowManager.onStateChange(setState);\n\n return () => {\n unsubscribe();\n // Don't destroy - let slideshow persist for route changes\n };\n }, [images, intervalMs, transitionDuration, autoAdvance]);\n\n const contextValue: SlideshowContextValue = {\n state,\n controls: {\n next: () => SlideshowManager.next(),\n prev: () => SlideshowManager.prev(),\n goTo: (index) => SlideshowManager.goTo(index),\n pause: () => SlideshowManager.pause(),\n resume: () => SlideshowManager.resume(),\n toggle: () => SlideshowManager.toggle(),\n },\n };\n\n // Get transition styles\n const getTransitionStyles = (index: number): React.CSSProperties => {\n const isActive = index === state.currentIndex;\n\n switch (transition) {\n case 'fade':\n return {\n opacity: isActive ? 1 : 0,\n transition: `opacity ${transitionDuration}ms ease-in-out`,\n position: 'absolute',\n inset: 0,\n };\n case 'slide':\n const offset = (index - state.currentIndex) * 100;\n return {\n transform: `translateX(${offset}%)`,\n transition: `transform ${transitionDuration}ms ease-in-out`,\n position: 'absolute',\n inset: 0,\n };\n case 'none':\n default:\n return {\n opacity: isActive ? 1 : 0,\n position: 'absolute',\n inset: 0,\n };\n }\n };\n\n if (images.length === 0) {\n return (\n <div className={`relative w-full h-full bg-black flex items-center justify-center ${className}`}>\n <p className=\"text-white/60\">No images</p>\n </div>\n );\n }\n\n return (\n <SlideshowContext.Provider value={contextValue}>\n <div className={`relative w-full h-full bg-black overflow-hidden ${className}`}>\n {/* Image container */}\n <div className=\"relative w-full h-full\">\n {state.images.map((image, index) => (\n <div\n key={`${image.url}-${index}`}\n style={getTransitionStyles(index)}\n className={imageClassName}\n >\n <img\n src={image.url}\n alt={image.alt || `Slide ${index + 1}`}\n className=\"w-full h-full object-contain\"\n loading={index === 0 ? 'eager' : 'lazy'}\n />\n </div>\n ))}\n </div>\n\n {/* Navigation arrows */}\n {showArrows && state.totalSlides > 1 && (\n <>\n <button\n onClick={() => SlideshowManager.prev()}\n disabled={state.isTransitioning}\n 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\"\n aria-label=\"Previous slide\"\n >\n <svg className=\"w-5 h-5\" 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 <button\n onClick={() => SlideshowManager.next()}\n disabled={state.isTransitioning}\n className=\"absolute right-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\"\n aria-label=\"Next slide\"\n >\n <svg className=\"w-5 h-5\" 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 </>\n )}\n\n {/* Navigation dots */}\n {showDots && state.totalSlides > 1 && (\n <div className=\"absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-1.5\">\n {state.images.map((_, index) => (\n <button\n key={index}\n onClick={() => SlideshowManager.goTo(index)}\n disabled={state.isTransitioning}\n className={`w-2 h-2 rounded-full transition-colors ${\n index === state.currentIndex\n ? 'bg-white'\n : 'bg-white/40 hover:bg-white/60'\n }`}\n aria-label={`Go to slide ${index + 1}`}\n />\n ))}\n </div>\n )}\n\n {/* Overlay children */}\n <div className=\"absolute inset-0 z-10\">\n {children}\n </div>\n </div>\n </SlideshowContext.Provider>\n );\n}\n";
|
|
8
|
+
//# sourceMappingURL=PersistentSlideshow.tsx.template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PersistentSlideshow.tsx.template.d.ts","sourceRoot":"","sources":["../../../src/templates/slideshow/PersistentSlideshow.tsx.template.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,grNA6MvC,CAAC"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slideshow Template - PersistentSlideshow Component
|
|
3
|
+
*
|
|
4
|
+
* React component that attaches to the SlideshowManager singleton.
|
|
5
|
+
* The slideshow state persists across route changes.
|
|
6
|
+
*/
|
|
7
|
+
export const persistentSlideshowTemplate = `'use client';
|
|
8
|
+
|
|
9
|
+
import { useEffect, useState, createContext, useContext, ReactNode } from 'react';
|
|
10
|
+
import { SlideshowManager, SlideshowState, SlideImage } from './SlideshowManager';
|
|
11
|
+
|
|
12
|
+
// Context to share slideshow state with overlays
|
|
13
|
+
interface SlideshowContextValue {
|
|
14
|
+
state: SlideshowState;
|
|
15
|
+
controls: {
|
|
16
|
+
next: () => void;
|
|
17
|
+
prev: () => void;
|
|
18
|
+
goTo: (index: number) => void;
|
|
19
|
+
pause: () => void;
|
|
20
|
+
resume: () => void;
|
|
21
|
+
toggle: () => void;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const SlideshowContext = createContext<SlideshowContextValue | null>(null);
|
|
26
|
+
|
|
27
|
+
export function useSlideshow(): SlideshowContextValue {
|
|
28
|
+
const context = useContext(SlideshowContext);
|
|
29
|
+
if (!context) {
|
|
30
|
+
throw new Error('useSlideshow must be used within PersistentSlideshow');
|
|
31
|
+
}
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface PersistentSlideshowProps {
|
|
36
|
+
images: SlideImage[];
|
|
37
|
+
/** Auto-advance interval in milliseconds (default: 5000) */
|
|
38
|
+
intervalMs?: number;
|
|
39
|
+
/** Auto-advance slides (default: true) */
|
|
40
|
+
autoAdvance?: boolean;
|
|
41
|
+
/** Transition type (default: 'fade') */
|
|
42
|
+
transition?: 'fade' | 'slide' | 'none';
|
|
43
|
+
/** Transition duration in ms (default: 500) */
|
|
44
|
+
transitionDuration?: number;
|
|
45
|
+
/** Show navigation dots (default: true) */
|
|
46
|
+
showDots?: boolean;
|
|
47
|
+
/** Show navigation arrows (default: true) */
|
|
48
|
+
showArrows?: boolean;
|
|
49
|
+
/** Additional class names */
|
|
50
|
+
className?: string;
|
|
51
|
+
/** Image container class names */
|
|
52
|
+
imageClassName?: string;
|
|
53
|
+
/** Children rendered as overlay */
|
|
54
|
+
children?: ReactNode;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function PersistentSlideshow({
|
|
58
|
+
images,
|
|
59
|
+
intervalMs = 5000,
|
|
60
|
+
autoAdvance = true,
|
|
61
|
+
transition = 'fade',
|
|
62
|
+
transitionDuration = 500,
|
|
63
|
+
showDots = true,
|
|
64
|
+
showArrows = true,
|
|
65
|
+
className = '',
|
|
66
|
+
imageClassName = '',
|
|
67
|
+
children
|
|
68
|
+
}: PersistentSlideshowProps) {
|
|
69
|
+
const [state, setState] = useState<SlideshowState>(SlideshowManager.getState());
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
// Initialize slideshow with images
|
|
73
|
+
SlideshowManager.initialize(images, {
|
|
74
|
+
intervalMs,
|
|
75
|
+
transitionDuration,
|
|
76
|
+
autoAdvance,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Subscribe to state changes
|
|
80
|
+
const unsubscribe = SlideshowManager.onStateChange(setState);
|
|
81
|
+
|
|
82
|
+
return () => {
|
|
83
|
+
unsubscribe();
|
|
84
|
+
// Don't destroy - let slideshow persist for route changes
|
|
85
|
+
};
|
|
86
|
+
}, [images, intervalMs, transitionDuration, autoAdvance]);
|
|
87
|
+
|
|
88
|
+
const contextValue: SlideshowContextValue = {
|
|
89
|
+
state,
|
|
90
|
+
controls: {
|
|
91
|
+
next: () => SlideshowManager.next(),
|
|
92
|
+
prev: () => SlideshowManager.prev(),
|
|
93
|
+
goTo: (index) => SlideshowManager.goTo(index),
|
|
94
|
+
pause: () => SlideshowManager.pause(),
|
|
95
|
+
resume: () => SlideshowManager.resume(),
|
|
96
|
+
toggle: () => SlideshowManager.toggle(),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Get transition styles
|
|
101
|
+
const getTransitionStyles = (index: number): React.CSSProperties => {
|
|
102
|
+
const isActive = index === state.currentIndex;
|
|
103
|
+
|
|
104
|
+
switch (transition) {
|
|
105
|
+
case 'fade':
|
|
106
|
+
return {
|
|
107
|
+
opacity: isActive ? 1 : 0,
|
|
108
|
+
transition: \`opacity \${transitionDuration}ms ease-in-out\`,
|
|
109
|
+
position: 'absolute',
|
|
110
|
+
inset: 0,
|
|
111
|
+
};
|
|
112
|
+
case 'slide':
|
|
113
|
+
const offset = (index - state.currentIndex) * 100;
|
|
114
|
+
return {
|
|
115
|
+
transform: \`translateX(\${offset}%)\`,
|
|
116
|
+
transition: \`transform \${transitionDuration}ms ease-in-out\`,
|
|
117
|
+
position: 'absolute',
|
|
118
|
+
inset: 0,
|
|
119
|
+
};
|
|
120
|
+
case 'none':
|
|
121
|
+
default:
|
|
122
|
+
return {
|
|
123
|
+
opacity: isActive ? 1 : 0,
|
|
124
|
+
position: 'absolute',
|
|
125
|
+
inset: 0,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (images.length === 0) {
|
|
131
|
+
return (
|
|
132
|
+
<div className={\`relative w-full h-full bg-black flex items-center justify-center \${className}\`}>
|
|
133
|
+
<p className="text-white/60">No images</p>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<SlideshowContext.Provider value={contextValue}>
|
|
140
|
+
<div className={\`relative w-full h-full bg-black overflow-hidden \${className}\`}>
|
|
141
|
+
{/* Image container */}
|
|
142
|
+
<div className="relative w-full h-full">
|
|
143
|
+
{state.images.map((image, index) => (
|
|
144
|
+
<div
|
|
145
|
+
key={\`\${image.url}-\${index}\`}
|
|
146
|
+
style={getTransitionStyles(index)}
|
|
147
|
+
className={imageClassName}
|
|
148
|
+
>
|
|
149
|
+
<img
|
|
150
|
+
src={image.url}
|
|
151
|
+
alt={image.alt || \`Slide \${index + 1}\`}
|
|
152
|
+
className="w-full h-full object-contain"
|
|
153
|
+
loading={index === 0 ? 'eager' : 'lazy'}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
))}
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Navigation arrows */}
|
|
160
|
+
{showArrows && state.totalSlides > 1 && (
|
|
161
|
+
<>
|
|
162
|
+
<button
|
|
163
|
+
onClick={() => SlideshowManager.prev()}
|
|
164
|
+
disabled={state.isTransitioning}
|
|
165
|
+
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"
|
|
166
|
+
aria-label="Previous slide"
|
|
167
|
+
>
|
|
168
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
169
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
170
|
+
</svg>
|
|
171
|
+
</button>
|
|
172
|
+
<button
|
|
173
|
+
onClick={() => SlideshowManager.next()}
|
|
174
|
+
disabled={state.isTransitioning}
|
|
175
|
+
className="absolute right-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"
|
|
176
|
+
aria-label="Next slide"
|
|
177
|
+
>
|
|
178
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
179
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
180
|
+
</svg>
|
|
181
|
+
</button>
|
|
182
|
+
</>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
{/* Navigation dots */}
|
|
186
|
+
{showDots && state.totalSlides > 1 && (
|
|
187
|
+
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 flex gap-1.5">
|
|
188
|
+
{state.images.map((_, index) => (
|
|
189
|
+
<button
|
|
190
|
+
key={index}
|
|
191
|
+
onClick={() => SlideshowManager.goTo(index)}
|
|
192
|
+
disabled={state.isTransitioning}
|
|
193
|
+
className={\`w-2 h-2 rounded-full transition-colors \${
|
|
194
|
+
index === state.currentIndex
|
|
195
|
+
? 'bg-white'
|
|
196
|
+
: 'bg-white/40 hover:bg-white/60'
|
|
197
|
+
}\`}
|
|
198
|
+
aria-label={\`Go to slide \${index + 1}\`}
|
|
199
|
+
/>
|
|
200
|
+
))}
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{/* Overlay children */}
|
|
205
|
+
<div className="absolute inset-0 z-10">
|
|
206
|
+
{children}
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</SlideshowContext.Provider>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slideshow Template - SlideshowManager
|
|
3
|
+
*
|
|
4
|
+
* Singleton to persist slideshow state across route changes.
|
|
5
|
+
* Similar pattern to VideoManager - holds state globally.
|
|
6
|
+
*/
|
|
7
|
+
export declare const slideshowManagerTemplate = "/**\n * SlideshowManager - Singleton to persist slideshow state across route changes\n *\n * Maintains currentIndex, isPaused, and images across Next.js soft navigations.\n * When navigating from /tile to /page, the slideshow stays on the same slide.\n */\n\ndeclare global {\n interface Window {\n __slideshowState?: SlideshowState;\n __slideshowInitialized?: boolean;\n }\n}\n\nexport interface SlideImage {\n url: string;\n alt?: string;\n caption?: string;\n}\n\nexport interface SlideshowState {\n currentIndex: number;\n totalSlides: number;\n isTransitioning: boolean;\n isPaused: boolean;\n images: SlideImage[];\n}\n\ntype StateCallback = (state: SlideshowState) => void;\n\nclass SlideshowManagerClass {\n private stateCallbacks: Set<StateCallback> = new Set();\n private currentState: SlideshowState = {\n currentIndex: 0,\n totalSlides: 0,\n isTransitioning: false,\n isPaused: false,\n images: [],\n };\n private autoAdvanceInterval: NodeJS.Timeout | null = null;\n private transitionDuration = 500;\n private intervalMs = 5000;\n\n constructor() {\n // Restore state from window if available\n if (typeof window !== 'undefined' && window.__slideshowState) {\n this.currentState = window.__slideshowState;\n }\n }\n\n /**\n * Initialize slideshow with images\n */\n initialize(images: SlideImage[], options?: {\n intervalMs?: number;\n transitionDuration?: number;\n autoAdvance?: boolean;\n }): void {\n if (typeof window === 'undefined') return;\n\n // Check if already initialized with same images\n const imagesMatch = this.currentState.images.length === images.length &&\n this.currentState.images.every((img, i) => img.url === images[i]?.url);\n\n if (window.__slideshowInitialized && imagesMatch) {\n // Already initialized with same images, just restore callbacks\n return;\n }\n\n this.intervalMs = options?.intervalMs ?? 5000;\n this.transitionDuration = options?.transitionDuration ?? 500;\n\n this.currentState = {\n ...this.currentState,\n images,\n totalSlides: images.length,\n // Keep current index if valid, otherwise reset\n currentIndex: this.currentState.currentIndex < images.length\n ? this.currentState.currentIndex\n : 0,\n isPaused: options?.autoAdvance === false,\n };\n\n window.__slideshowState = this.currentState;\n window.__slideshowInitialized = true;\n\n this.notifyCallbacks();\n\n // Start auto-advance if enabled\n if (!this.currentState.isPaused && images.length > 1) {\n this.startAutoAdvance();\n }\n }\n\n /**\n * Get current state\n */\n getState(): SlideshowState {\n return { ...this.currentState };\n }\n\n /**\n * Subscribe to state changes\n */\n onStateChange(callback: StateCallback): () => void {\n this.stateCallbacks.add(callback);\n callback(this.currentState);\n return () => this.stateCallbacks.delete(callback);\n }\n\n /**\n * Go to next slide\n */\n next(): void {\n if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1) return;\n\n this.updateState({ isTransitioning: true });\n const nextIndex = (this.currentState.currentIndex + 1) % this.currentState.totalSlides;\n this.updateState({ currentIndex: nextIndex });\n\n setTimeout(() => {\n this.updateState({ isTransitioning: false });\n }, this.transitionDuration);\n }\n\n /**\n * Go to previous slide\n */\n prev(): void {\n if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1) return;\n\n this.updateState({ isTransitioning: true });\n const prevIndex = (this.currentState.currentIndex - 1 + this.currentState.totalSlides) % this.currentState.totalSlides;\n this.updateState({ currentIndex: prevIndex });\n\n setTimeout(() => {\n this.updateState({ isTransitioning: false });\n }, this.transitionDuration);\n }\n\n /**\n * Go to specific slide\n */\n goTo(index: number): void {\n if (\n this.currentState.isTransitioning ||\n index === this.currentState.currentIndex ||\n index < 0 ||\n index >= this.currentState.totalSlides\n ) return;\n\n this.updateState({ isTransitioning: true, currentIndex: index });\n\n setTimeout(() => {\n this.updateState({ isTransitioning: false });\n }, this.transitionDuration);\n }\n\n /**\n * Pause auto-advance\n */\n pause(): void {\n this.updateState({ isPaused: true });\n this.stopAutoAdvance();\n }\n\n /**\n * Resume auto-advance\n */\n resume(): void {\n this.updateState({ isPaused: false });\n if (this.currentState.totalSlides > 1) {\n this.startAutoAdvance();\n }\n }\n\n /**\n * Toggle pause/resume\n */\n toggle(): void {\n if (this.currentState.isPaused) {\n this.resume();\n } else {\n this.pause();\n }\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n this.stopAutoAdvance();\n this.stateCallbacks.clear();\n if (typeof window !== 'undefined') {\n window.__slideshowInitialized = false;\n }\n }\n\n private startAutoAdvance(): void {\n this.stopAutoAdvance();\n this.autoAdvanceInterval = setInterval(() => {\n if (!this.currentState.isPaused) {\n this.next();\n }\n }, this.intervalMs);\n }\n\n private stopAutoAdvance(): void {\n if (this.autoAdvanceInterval) {\n clearInterval(this.autoAdvanceInterval);\n this.autoAdvanceInterval = null;\n }\n }\n\n private updateState(partial: Partial<SlideshowState>): void {\n this.currentState = { ...this.currentState, ...partial };\n if (typeof window !== 'undefined') {\n window.__slideshowState = this.currentState;\n }\n this.notifyCallbacks();\n }\n\n private notifyCallbacks(): void {\n this.stateCallbacks.forEach(cb => cb(this.currentState));\n }\n}\n\nexport const SlideshowManager = new SlideshowManagerClass();\n";
|
|
8
|
+
//# sourceMappingURL=SlideshowManager.ts.template.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SlideshowManager.ts.template.d.ts","sourceRoot":"","sources":["../../../src/templates/slideshow/SlideshowManager.ts.template.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,s1LAoOpC,CAAC"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slideshow Template - SlideshowManager
|
|
3
|
+
*
|
|
4
|
+
* Singleton to persist slideshow state across route changes.
|
|
5
|
+
* Similar pattern to VideoManager - holds state globally.
|
|
6
|
+
*/
|
|
7
|
+
export const slideshowManagerTemplate = `/**
|
|
8
|
+
* SlideshowManager - Singleton to persist slideshow state across route changes
|
|
9
|
+
*
|
|
10
|
+
* Maintains currentIndex, isPaused, and images across Next.js soft navigations.
|
|
11
|
+
* When navigating from /tile to /page, the slideshow stays on the same slide.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
interface Window {
|
|
16
|
+
__slideshowState?: SlideshowState;
|
|
17
|
+
__slideshowInitialized?: boolean;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SlideImage {
|
|
22
|
+
url: string;
|
|
23
|
+
alt?: string;
|
|
24
|
+
caption?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SlideshowState {
|
|
28
|
+
currentIndex: number;
|
|
29
|
+
totalSlides: number;
|
|
30
|
+
isTransitioning: boolean;
|
|
31
|
+
isPaused: boolean;
|
|
32
|
+
images: SlideImage[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type StateCallback = (state: SlideshowState) => void;
|
|
36
|
+
|
|
37
|
+
class SlideshowManagerClass {
|
|
38
|
+
private stateCallbacks: Set<StateCallback> = new Set();
|
|
39
|
+
private currentState: SlideshowState = {
|
|
40
|
+
currentIndex: 0,
|
|
41
|
+
totalSlides: 0,
|
|
42
|
+
isTransitioning: false,
|
|
43
|
+
isPaused: false,
|
|
44
|
+
images: [],
|
|
45
|
+
};
|
|
46
|
+
private autoAdvanceInterval: NodeJS.Timeout | null = null;
|
|
47
|
+
private transitionDuration = 500;
|
|
48
|
+
private intervalMs = 5000;
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
// Restore state from window if available
|
|
52
|
+
if (typeof window !== 'undefined' && window.__slideshowState) {
|
|
53
|
+
this.currentState = window.__slideshowState;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize slideshow with images
|
|
59
|
+
*/
|
|
60
|
+
initialize(images: SlideImage[], options?: {
|
|
61
|
+
intervalMs?: number;
|
|
62
|
+
transitionDuration?: number;
|
|
63
|
+
autoAdvance?: boolean;
|
|
64
|
+
}): void {
|
|
65
|
+
if (typeof window === 'undefined') return;
|
|
66
|
+
|
|
67
|
+
// Check if already initialized with same images
|
|
68
|
+
const imagesMatch = this.currentState.images.length === images.length &&
|
|
69
|
+
this.currentState.images.every((img, i) => img.url === images[i]?.url);
|
|
70
|
+
|
|
71
|
+
if (window.__slideshowInitialized && imagesMatch) {
|
|
72
|
+
// Already initialized with same images, just restore callbacks
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.intervalMs = options?.intervalMs ?? 5000;
|
|
77
|
+
this.transitionDuration = options?.transitionDuration ?? 500;
|
|
78
|
+
|
|
79
|
+
this.currentState = {
|
|
80
|
+
...this.currentState,
|
|
81
|
+
images,
|
|
82
|
+
totalSlides: images.length,
|
|
83
|
+
// Keep current index if valid, otherwise reset
|
|
84
|
+
currentIndex: this.currentState.currentIndex < images.length
|
|
85
|
+
? this.currentState.currentIndex
|
|
86
|
+
: 0,
|
|
87
|
+
isPaused: options?.autoAdvance === false,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
window.__slideshowState = this.currentState;
|
|
91
|
+
window.__slideshowInitialized = true;
|
|
92
|
+
|
|
93
|
+
this.notifyCallbacks();
|
|
94
|
+
|
|
95
|
+
// Start auto-advance if enabled
|
|
96
|
+
if (!this.currentState.isPaused && images.length > 1) {
|
|
97
|
+
this.startAutoAdvance();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get current state
|
|
103
|
+
*/
|
|
104
|
+
getState(): SlideshowState {
|
|
105
|
+
return { ...this.currentState };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Subscribe to state changes
|
|
110
|
+
*/
|
|
111
|
+
onStateChange(callback: StateCallback): () => void {
|
|
112
|
+
this.stateCallbacks.add(callback);
|
|
113
|
+
callback(this.currentState);
|
|
114
|
+
return () => this.stateCallbacks.delete(callback);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Go to next slide
|
|
119
|
+
*/
|
|
120
|
+
next(): void {
|
|
121
|
+
if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1) return;
|
|
122
|
+
|
|
123
|
+
this.updateState({ isTransitioning: true });
|
|
124
|
+
const nextIndex = (this.currentState.currentIndex + 1) % this.currentState.totalSlides;
|
|
125
|
+
this.updateState({ currentIndex: nextIndex });
|
|
126
|
+
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
this.updateState({ isTransitioning: false });
|
|
129
|
+
}, this.transitionDuration);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Go to previous slide
|
|
134
|
+
*/
|
|
135
|
+
prev(): void {
|
|
136
|
+
if (this.currentState.isTransitioning || this.currentState.totalSlides <= 1) return;
|
|
137
|
+
|
|
138
|
+
this.updateState({ isTransitioning: true });
|
|
139
|
+
const prevIndex = (this.currentState.currentIndex - 1 + this.currentState.totalSlides) % this.currentState.totalSlides;
|
|
140
|
+
this.updateState({ currentIndex: prevIndex });
|
|
141
|
+
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
this.updateState({ isTransitioning: false });
|
|
144
|
+
}, this.transitionDuration);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Go to specific slide
|
|
149
|
+
*/
|
|
150
|
+
goTo(index: number): void {
|
|
151
|
+
if (
|
|
152
|
+
this.currentState.isTransitioning ||
|
|
153
|
+
index === this.currentState.currentIndex ||
|
|
154
|
+
index < 0 ||
|
|
155
|
+
index >= this.currentState.totalSlides
|
|
156
|
+
) return;
|
|
157
|
+
|
|
158
|
+
this.updateState({ isTransitioning: true, currentIndex: index });
|
|
159
|
+
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
this.updateState({ isTransitioning: false });
|
|
162
|
+
}, this.transitionDuration);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Pause auto-advance
|
|
167
|
+
*/
|
|
168
|
+
pause(): void {
|
|
169
|
+
this.updateState({ isPaused: true });
|
|
170
|
+
this.stopAutoAdvance();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Resume auto-advance
|
|
175
|
+
*/
|
|
176
|
+
resume(): void {
|
|
177
|
+
this.updateState({ isPaused: false });
|
|
178
|
+
if (this.currentState.totalSlides > 1) {
|
|
179
|
+
this.startAutoAdvance();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Toggle pause/resume
|
|
185
|
+
*/
|
|
186
|
+
toggle(): void {
|
|
187
|
+
if (this.currentState.isPaused) {
|
|
188
|
+
this.resume();
|
|
189
|
+
} else {
|
|
190
|
+
this.pause();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Clean up resources
|
|
196
|
+
*/
|
|
197
|
+
destroy(): void {
|
|
198
|
+
this.stopAutoAdvance();
|
|
199
|
+
this.stateCallbacks.clear();
|
|
200
|
+
if (typeof window !== 'undefined') {
|
|
201
|
+
window.__slideshowInitialized = false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private startAutoAdvance(): void {
|
|
206
|
+
this.stopAutoAdvance();
|
|
207
|
+
this.autoAdvanceInterval = setInterval(() => {
|
|
208
|
+
if (!this.currentState.isPaused) {
|
|
209
|
+
this.next();
|
|
210
|
+
}
|
|
211
|
+
}, this.intervalMs);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private stopAutoAdvance(): void {
|
|
215
|
+
if (this.autoAdvanceInterval) {
|
|
216
|
+
clearInterval(this.autoAdvanceInterval);
|
|
217
|
+
this.autoAdvanceInterval = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private updateState(partial: Partial<SlideshowState>): void {
|
|
222
|
+
this.currentState = { ...this.currentState, ...partial };
|
|
223
|
+
if (typeof window !== 'undefined') {
|
|
224
|
+
window.__slideshowState = this.currentState;
|
|
225
|
+
}
|
|
226
|
+
this.notifyCallbacks();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private notifyCallbacks(): void {
|
|
230
|
+
this.stateCallbacks.forEach(cb => cb(this.currentState));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const SlideshowManager = new SlideshowManagerClass();
|
|
235
|
+
`;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slideshow Template Exports
|
|
3
|
+
*
|
|
4
|
+
* These templates are used by tile-deploy/agent-service to generate
|
|
5
|
+
* slideshow tiles with persistent state across route navigation.
|
|
6
|
+
*/
|
|
7
|
+
export { slideshowManagerTemplate } from './SlideshowManager.ts.template';
|
|
8
|
+
export { persistentSlideshowTemplate } from './PersistentSlideshow.tsx.template';
|
|
9
|
+
export declare const slideshowTemplateFiles: {
|
|
10
|
+
'src/shared/lib/SlideshowManager.ts': string;
|
|
11
|
+
'src/shared/components/PersistentSlideshow.tsx': string;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/slideshow/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AAGjF,eAAO,MAAM,sBAAsB;;;CAGlC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slideshow Template Exports
|
|
3
|
+
*
|
|
4
|
+
* These templates are used by tile-deploy/agent-service to generate
|
|
5
|
+
* slideshow tiles with persistent state across route navigation.
|
|
6
|
+
*/
|
|
7
|
+
export { slideshowManagerTemplate } from './SlideshowManager.ts.template';
|
|
8
|
+
export { persistentSlideshowTemplate } from './PersistentSlideshow.tsx.template';
|
|
9
|
+
// Template file structure for slideshow tiles
|
|
10
|
+
export const slideshowTemplateFiles = {
|
|
11
|
+
'src/shared/lib/SlideshowManager.ts': 'slideshowManagerTemplate',
|
|
12
|
+
'src/shared/components/PersistentSlideshow.tsx': 'persistentSlideshowTemplate',
|
|
13
|
+
};
|