@thewhateverapp/tile-sdk 0.16.4 → 0.16.7
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/bridge/TileBridge.d.ts.map +1 -1
- package/dist/bridge/TileBridge.js +22 -2
- package/dist/templates/slideshow/layout.tsx.template.d.ts +17 -7
- package/dist/templates/slideshow/layout.tsx.template.d.ts.map +1 -1
- package/dist/templates/slideshow/layout.tsx.template.js +403 -185
- package/dist/templates/video/layout.tsx.template.d.ts +17 -7
- package/dist/templates/video/layout.tsx.template.d.ts.map +1 -1
- package/dist/templates/video/layout.tsx.template.js +575 -122
- package/package.json +4 -1
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Video
|
|
2
|
+
* Video Templates - Auto-generated from base-video-worker
|
|
3
|
+
*
|
|
4
|
+
* DO NOT EDIT THIS FILE DIRECTLY!
|
|
5
|
+
* Edit the source files in platform/workers/base-video-worker/ and run:
|
|
6
|
+
* pnpm sync-templates
|
|
7
|
+
*
|
|
8
|
+
* Source files:
|
|
9
|
+
* - TileOverlay: workers/base-video-worker/src/app/tile/TileOverlay.tsx
|
|
10
|
+
* - PageOverlay: workers/base-video-worker/src/app/page/PageOverlay.tsx
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Video Layout Template
|
|
3
14
|
*
|
|
4
15
|
* This layout wraps both /tile and /page routes using a route group.
|
|
5
16
|
* The VideoPlayer is rendered ONCE here and persists across route navigation.
|
|
6
17
|
*
|
|
7
18
|
* File path: src/app/(video)/layout.tsx
|
|
8
|
-
* Routes: src/app/(video)/tile/page.tsx, src/app/(video)/page/page.tsx
|
|
9
19
|
*/
|
|
10
20
|
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
21
|
/**
|
|
12
22
|
* Video Tile Overlay Template
|
|
13
23
|
*
|
|
14
|
-
* Overlay-only component for tile view (
|
|
24
|
+
* Overlay-only component for tile view (256x554px).
|
|
15
25
|
* VideoPlayer is already mounted in the parent layout.
|
|
16
26
|
*
|
|
17
27
|
* File path: src/app/(video)/tile/page.tsx
|
|
18
28
|
*/
|
|
19
|
-
export declare const videoTileOverlayTemplate = "'use client';\n\nimport {
|
|
29
|
+
export declare const videoTileOverlayTemplate = "'use client';\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport { useVideoState, 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\nconst CONTROLS_HIDE_DELAY = 3000;\n\nexport function TileOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n \n const [hasInteracted, setHasInteracted] = useState(false);\n const [controlsVisible, setControlsVisible] = useState(false);\n const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n const showControls = useCallback(() => {\n setHasInteracted(true);\n setControlsVisible(true);\n\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n\n if (state.isPlaying) {\n hideTimeoutRef.current = setTimeout(() => {\n setControlsVisible(false);\n }, CONTROLS_HIDE_DELAY);\n }\n }, [state.isPlaying]);\n\n useEffect(() => {\n if (!state.isPlaying && hasInteracted) {\n setControlsVisible(true);\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n }\n }, [state.isPlaying, hasInteracted]);\n\n useEffect(() => {\n return () => {\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n };\n }, []);\n\n const handleInteraction = useCallback(() => {\n if (!hasInteracted) {\n showControls();\n controls.toggle();\n } else if (controlsVisible) {\n controls.toggle();\n showControls();\n } else {\n showControls();\n }\n }, [hasInteracted, controlsVisible, showControls, controls]);\n\n const showCenterPlayButton = hasInteracted && !state.isPlaying && !state.isLoading && controlsVisible;\n const showBottomControls = hasInteracted && controlsVisible;\n\n return (\n <>\n <div\n className=\"absolute inset-0 z-5 pointer-events-auto cursor-pointer\"\n onClick={handleInteraction}\n onMouseMove={() => hasInteracted && showControls()}\n />\n\n <div className=\"absolute top-3 right-3 z-20 pointer-events-auto\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n tile.navigateToPage();\n }}\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-5 h-5\" 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 </div>\n\n <div\n className={`absolute inset-0 flex items-center justify-center z-10 pointer-events-none transition-opacity duration-300 ${\n showCenterPlayButton ? 'opacity-100' : 'opacity-0'\n }`}\n >\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.play();\n showControls();\n }}\n className=\"bg-black/50 backdrop-blur-sm p-4 rounded-full text-white hover:bg-black/70 transition-colors pointer-events-auto\"\n aria-label=\"Play video\"\n style={{ pointerEvents: showCenterPlayButton ? 'auto' : 'none' }}\n >\n <svg className=\"w-12 h-12\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </div>\n\n <div\n className={`absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-black/80 to-transparent pointer-events-none transition-opacity duration-300 ${\n showBottomControls ? 'opacity-100' : 'opacity-0'\n }`}\n />\n\n <div\n className={`absolute bottom-3 left-3 right-3 z-20 transition-opacity duration-300 ${\n showBottomControls ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'\n }`}\n >\n <div\n className=\"h-1 bg-white/30 rounded-full overflow-hidden mb-2 cursor-pointer\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n showControls();\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-100\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n <div className=\"flex items-center justify-between text-white text-xs\">\n <div className=\"flex items-center gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.toggle();\n showControls();\n }}\n className=\"p-1 hover:bg-white/20 rounded transition-colors\"\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\n >\n {state.isPlaying ? (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n <span className=\"font-mono\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.setMuted(!state.muted);\n showControls();\n }}\n className=\"p-1 hover:bg-white/20 rounded transition-colors\"\n aria-label={state.muted ? 'Unmute' : 'Mute'}\n >\n {state.muted ? (\n <svg className=\"w-4 h-4\" 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-4 h-4\" 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 </div>\n </div>\n\n </>\n );\n}\n";
|
|
20
30
|
/**
|
|
21
31
|
* Video Page Overlay Template
|
|
22
32
|
*
|
|
@@ -25,7 +35,7 @@ export declare const videoTileOverlayTemplate = "'use client';\n\nimport { useVi
|
|
|
25
35
|
*
|
|
26
36
|
* File path: src/app/(video)/page/page.tsx
|
|
27
37
|
*/
|
|
28
|
-
export declare const videoPageOverlayTemplate = "'use client';\n\nimport {
|
|
38
|
+
export declare const videoPageOverlayTemplate = "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useVideoState, 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 function PageOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n const [showControls, setShowControls] = useState(true);\n const [hideTimeout, setHideTimeout] = useState<NodeJS.Timeout | null>(null);\n\n // Auto-hide controls after 3 seconds of inactivity when playing\n const resetHideTimer = useCallback(() => {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n }\n setShowControls(true);\n\n if (state.isPlaying) {\n const timeout = setTimeout(() => {\n setShowControls(false);\n }, 3000);\n setHideTimeout(timeout);\n }\n }, [state.isPlaying, hideTimeout]);\n\n // Show controls when video is paused\n useEffect(() => {\n if (!state.isPlaying) {\n setShowControls(true);\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n setHideTimeout(null);\n }\n } else {\n resetHideTimer();\n }\n }, [state.isPlaying]);\n\n // Cleanup timeout on unmount\n useEffect(() => {\n return () => {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n }\n };\n }, [hideTimeout]);\n\n // Toggle controls on tap/click\n const handleToggleControls = useCallback((e: React.MouseEvent | React.TouchEvent) => {\n // Don't toggle if clicking on a button or control\n const target = e.target as HTMLElement;\n if (target.closest('button, .controls-area')) {\n return;\n }\n\n // Prevent default to avoid double-firing on touch devices\n e.preventDefault();\n\n if (showControls) {\n setShowControls(false);\n } else {\n resetHideTimer();\n }\n }, [showControls, resetHideTimer]);\n\n // Handle mouse movement to show controls\n const handleMouseMove = useCallback(() => {\n if (state.isPlaying) {\n resetHideTimer();\n }\n }, [state.isPlaying, resetHideTimer]);\n\n return (\n <div\n className=\"absolute inset-0 z-20 pointer-events-auto\"\n onClick={handleToggleControls}\n onTouchEnd={handleToggleControls}\n onMouseMove={handleMouseMove}\n >\n {/* Center play button when paused */}\n {!state.isPlaying && !state.isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center pointer-events-auto\">\n <button\n onClick={controls.play}\n className=\"bg-black/50 backdrop-blur-sm p-6 rounded-full text-white hover:bg-black/70 transition-colors\"\n aria-label=\"Play video\"\n >\n <svg className=\"w-16 h-16\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </div>\n )}\n\n {/* Controls - toggleable */}\n <div\n className={`controls-area absolute bottom-0 left-0 right-0 transition-opacity duration-300 ${\n showControls ? 'opacity-100' : 'opacity-0 pointer-events-none'\n }`}\n >\n {/* Gradient background */}\n <div className=\"absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-black/80 to-transparent pointer-events-none\" />\n\n <div className=\"absolute bottom-4 left-4 right-4 z-20 pointer-events-auto\">\n {/* Progress bar / Timeline */}\n <div\n className=\"h-1.5 bg-white/30 rounded-full overflow-hidden mb-3 cursor-pointer group hover:h-2 transition-all\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n resetHideTimer();\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-100 group-hover:bg-blue-400\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n {/* Controls row */}\n <div className=\"flex items-center justify-between text-white\">\n <div className=\"flex items-center gap-3\">\n {/* Play/Pause */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.toggle();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\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 {/* Volume */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.setMuted(!state.muted);\n resetHideTimer();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label={state.muted ? 'Unmute' : 'Mute'}\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 */}\n <span className=\"text-sm font-mono\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n\n {/* Right side - minimize button */}\n <button\n onClick={(e) => {\n e.stopPropagation();\n tile.navigateToTile();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label=\"Minimize to tile\"\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=\"M20 12H4\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n";
|
|
29
39
|
/**
|
|
30
40
|
* Video Preview Entry - Tile
|
|
31
41
|
*
|
|
@@ -34,7 +44,7 @@ export declare const videoPageOverlayTemplate = "'use client';\n\nimport { useVi
|
|
|
34
44
|
*
|
|
35
45
|
* File path: src/app/_preview/tile.tsx
|
|
36
46
|
*/
|
|
37
|
-
export declare const videoPreviewTileTemplate = "'use client';\n\nimport { VideoPlayer, useVideoState,
|
|
47
|
+
export declare const videoPreviewTileTemplate = "'use client';\n\nimport { VideoPlayer, useVideoState, useTile } from '@thewhateverapp/tile-sdk';\nimport { useState, useEffect, useRef, useCallback } from 'react';\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\nconst CONTROLS_HIDE_DELAY = 3000;\n\nfunction TileOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n\n const [hasInteracted, setHasInteracted] = useState(false);\n const [controlsVisible, setControlsVisible] = useState(false);\n const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n const showControls = useCallback(() => {\n setHasInteracted(true);\n setControlsVisible(true);\n\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n\n if (state.isPlaying) {\n hideTimeoutRef.current = setTimeout(() => {\n setControlsVisible(false);\n }, CONTROLS_HIDE_DELAY);\n }\n }, [state.isPlaying]);\n\n useEffect(() => {\n if (!state.isPlaying && hasInteracted) {\n setControlsVisible(true);\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n }\n }, [state.isPlaying, hasInteracted]);\n\n useEffect(() => {\n return () => {\n if (hideTimeoutRef.current) {\n clearTimeout(hideTimeoutRef.current);\n }\n };\n }, []);\n\n const handleInteraction = useCallback(() => {\n if (!hasInteracted) {\n showControls();\n controls.toggle();\n } else if (controlsVisible) {\n controls.toggle();\n showControls();\n } else {\n showControls();\n }\n }, [hasInteracted, controlsVisible, showControls, controls]);\n\n const showCenterPlayButton = hasInteracted && !state.isPlaying && !state.isLoading && controlsVisible;\n const showBottomControls = hasInteracted && controlsVisible;\n\n return (\n <>\n <div\n className=\"absolute inset-0 z-5 pointer-events-auto cursor-pointer\"\n onClick={handleInteraction}\n onMouseMove={() => hasInteracted && showControls()}\n />\n\n <div className=\"absolute top-3 right-3 z-20 pointer-events-auto\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n tile.navigateToPage();\n }}\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-5 h-5\" 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 </div>\n\n <div\n className={`absolute inset-0 flex items-center justify-center z-10 pointer-events-none transition-opacity duration-300 ${\n showCenterPlayButton ? 'opacity-100' : 'opacity-0'\n }`}\n >\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.play();\n showControls();\n }}\n className=\"bg-black/50 backdrop-blur-sm p-4 rounded-full text-white hover:bg-black/70 transition-colors pointer-events-auto\"\n aria-label=\"Play video\"\n style={{ pointerEvents: showCenterPlayButton ? 'auto' : 'none' }}\n >\n <svg className=\"w-12 h-12\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </div>\n\n <div\n className={`absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-black/80 to-transparent pointer-events-none transition-opacity duration-300 ${\n showBottomControls ? 'opacity-100' : 'opacity-0'\n }`}\n />\n\n <div\n className={`absolute bottom-3 left-3 right-3 z-20 transition-opacity duration-300 ${\n showBottomControls ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'\n }`}\n >\n <div\n className=\"h-1 bg-white/30 rounded-full overflow-hidden mb-2 cursor-pointer\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n showControls();\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-100\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n <div className=\"flex items-center justify-between text-white text-xs\">\n <div className=\"flex items-center gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.toggle();\n showControls();\n }}\n className=\"p-1 hover:bg-white/20 rounded transition-colors\"\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\n >\n {state.isPlaying ? (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M6 4h4v16H6V4zm8 0h4v16h-4V4z\" />\n </svg>\n ) : (\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n )}\n </button>\n <span className=\"font-mono\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.setMuted(!state.muted);\n showControls();\n }}\n className=\"p-1 hover:bg-white/20 rounded transition-colors\"\n aria-label={state.muted ? 'Unmute' : 'Mute'}\n >\n {state.muted ? (\n <svg className=\"w-4 h-4\" 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-4 h-4\" 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 </div>\n </div>\n </>\n );\n}\n\nexport default function PreviewTile() {\n return (\n <VideoPlayer className=\"w-full h-full\">\n <TileOverlay />\n </VideoPlayer>\n );\n}\n";
|
|
38
48
|
/**
|
|
39
49
|
* Video Preview Entry - Page
|
|
40
50
|
*
|
|
@@ -43,5 +53,5 @@ export declare const videoPreviewTileTemplate = "'use client';\n\nimport { Video
|
|
|
43
53
|
*
|
|
44
54
|
* File path: src/app/_preview/page.tsx
|
|
45
55
|
*/
|
|
46
|
-
export declare const videoPreviewPageTemplate = "'use client';\n\nimport { VideoPlayer, useVideoState,
|
|
56
|
+
export declare const videoPreviewPageTemplate = "'use client';\n\nimport { VideoPlayer, useVideoState, useTile } from '@thewhateverapp/tile-sdk';\nimport { useState, useEffect, useCallback } from 'react';\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\nfunction PageOverlay() {\n const tile = useTile();\n const { state, controls } = useVideoState();\n const [showControls, setShowControls] = useState(true);\n const [hideTimeout, setHideTimeout] = useState<NodeJS.Timeout | null>(null);\n\n const resetHideTimer = useCallback(() => {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n }\n setShowControls(true);\n\n if (state.isPlaying) {\n const timeout = setTimeout(() => {\n setShowControls(false);\n }, 3000);\n setHideTimeout(timeout);\n }\n }, [state.isPlaying, hideTimeout]);\n\n useEffect(() => {\n if (!state.isPlaying) {\n setShowControls(true);\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n setHideTimeout(null);\n }\n } else {\n resetHideTimer();\n }\n }, [state.isPlaying]);\n\n useEffect(() => {\n return () => {\n if (hideTimeout) {\n clearTimeout(hideTimeout);\n }\n };\n }, [hideTimeout]);\n\n const handleToggleControls = useCallback((e: React.MouseEvent | React.TouchEvent) => {\n const target = e.target as HTMLElement;\n if (target.closest('button, .controls-area')) {\n return;\n }\n e.preventDefault();\n\n if (showControls) {\n setShowControls(false);\n } else {\n resetHideTimer();\n }\n }, [showControls, resetHideTimer]);\n\n const handleMouseMove = useCallback(() => {\n if (state.isPlaying) {\n resetHideTimer();\n }\n }, [state.isPlaying, resetHideTimer]);\n\n return (\n <div\n className=\"absolute inset-0 z-20 pointer-events-auto\"\n onClick={handleToggleControls}\n onTouchEnd={handleToggleControls}\n onMouseMove={handleMouseMove}\n >\n {!state.isPlaying && !state.isLoading && (\n <div className=\"absolute inset-0 flex items-center justify-center pointer-events-auto\">\n <button\n onClick={controls.play}\n className=\"bg-black/50 backdrop-blur-sm p-6 rounded-full text-white hover:bg-black/70 transition-colors\"\n aria-label=\"Play video\"\n >\n <svg className=\"w-16 h-16\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\" />\n </svg>\n </button>\n </div>\n )}\n\n <div\n className={`controls-area absolute bottom-0 left-0 right-0 transition-opacity duration-300 ${\n showControls ? 'opacity-100' : 'opacity-0 pointer-events-none'\n }`}\n >\n <div className=\"absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-black/80 to-transparent pointer-events-none\" />\n\n <div className=\"absolute bottom-4 left-4 right-4 z-20 pointer-events-auto\">\n <div\n className=\"h-1.5 bg-white/30 rounded-full overflow-hidden mb-3 cursor-pointer group hover:h-2 transition-all\"\n onClick={(e) => {\n e.stopPropagation();\n const rect = e.currentTarget.getBoundingClientRect();\n const percent = (e.clientX - rect.left) / rect.width;\n controls.seek(percent * state.duration);\n resetHideTimer();\n }}\n >\n <div\n className=\"h-full bg-white rounded-full transition-all duration-100 group-hover:bg-blue-400\"\n style={{ width: `${(state.currentTime / state.duration) * 100 || 0}%` }}\n />\n </div>\n\n <div className=\"flex items-center justify-between text-white\">\n <div className=\"flex items-center gap-3\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n controls.toggle();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label={state.isPlaying ? 'Pause' : 'Play'}\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 <button\n onClick={(e) => {\n e.stopPropagation();\n controls.setMuted(!state.muted);\n resetHideTimer();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label={state.muted ? 'Unmute' : 'Mute'}\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 <span className=\"text-sm font-mono\">\n {formatTime(state.currentTime)} / {formatTime(state.duration)}\n </span>\n </div>\n\n <button\n onClick={(e) => {\n e.stopPropagation();\n tile.navigateToTile();\n }}\n className=\"p-2 hover:bg-white/20 rounded-full transition-colors\"\n aria-label=\"Minimize to tile\"\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=\"M20 12H4\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n\nexport default function PreviewPage() {\n return (\n <VideoPlayer className=\"w-full h-full\">\n <PageOverlay />\n </VideoPlayer>\n );\n}\n";
|
|
47
57
|
//# 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;;;;;;;;;;GAUG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,uUAY/B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,0kOA+LpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,84OAsMpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,suOAsMpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,q4NA6LpC,CAAC"}
|