powerpointproto slides.waow.tech
slides
at main 238 lines 5.1 kB view raw
1<script lang="ts"> 2 import { 3 editorState, 4 getCurrentSlide, 5 nextSlide, 6 prevSlide, 7 stopPresenting, 8 } from "$lib/state.svelte"; 9 10 let elapsed = $state("00:00"); 11 let interval: ReturnType<typeof setInterval>; 12 let isDragging = $state(false); 13 let dragStartY = 0; 14 let dragStartHeight = 0; 15 16 const MIN_HEIGHT = 60; 17 const MAX_HEIGHT = 400; 18 19 $effect(() => { 20 if (editorState.isPresenting && editorState.presentationStartTime) { 21 interval = setInterval(() => { 22 const diff = Date.now() - editorState.presentationStartTime!; 23 const mins = Math.floor(diff / 60000); 24 const secs = Math.floor((diff % 60000) / 1000); 25 elapsed = `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`; 26 }, 1000); 27 28 return () => clearInterval(interval); 29 } 30 }); 31 32 const handleKeydown = (e: KeyboardEvent) => { 33 if (!editorState.isPresenting) return; 34 35 switch (e.key) { 36 case "ArrowRight": 37 case " ": 38 case "Enter": 39 nextSlide(); 40 break; 41 case "ArrowLeft": 42 prevSlide(); 43 break; 44 case "Escape": 45 stopPresenting(); 46 break; 47 } 48 }; 49 50 const handleDragStart = (e: MouseEvent | TouchEvent) => { 51 isDragging = true; 52 dragStartY = "touches" in e ? e.touches[0].clientY : e.clientY; 53 dragStartHeight = editorState.presenterPanelHeight; 54 e.preventDefault(); 55 }; 56 57 const handleDragMove = (e: MouseEvent | TouchEvent) => { 58 if (!isDragging) return; 59 const clientY = "touches" in e ? e.touches[0].clientY : e.clientY; 60 const delta = dragStartY - clientY; 61 const newHeight = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, dragStartHeight + delta)); 62 editorState.presenterPanelHeight = newHeight; 63 }; 64 65 const handleDragEnd = () => { 66 isDragging = false; 67 }; 68</script> 69 70<svelte:window 71 onkeydown={handleKeydown} 72 onmousemove={handleDragMove} 73 onmouseup={handleDragEnd} 74 ontouchmove={handleDragMove} 75 ontouchend={handleDragEnd} 76/> 77 78{#if editorState.isPresenting} 79 <div 80 class="presenter-overlay" 81 class:dragging={isDragging} 82 style="height: {editorState.presenterPanelHeight}px;" 83 > 84 <!-- svelte-ignore a11y_no_static_element_interactions --> 85 <div 86 class="drag-handle" 87 onmousedown={handleDragStart} 88 ontouchstart={handleDragStart} 89 > 90 <div class="drag-indicator"></div> 91 </div> 92 93 <div class="controls"> 94 <button onclick={prevSlide}>←</button> 95 <span class="slide-counter"> 96 {editorState.currentSlideIndex + 1} / {editorState.deck?.slides.length} 97 </span> 98 <button onclick={nextSlide}>→</button> 99 <span class="timer">{elapsed}</span> 100 <button onclick={stopPresenting}>exit</button> 101 </div> 102 103 {#if editorState.showNotes} 104 <div class="notes"> 105 {#if getCurrentSlide()?.notes} 106 <p>{getCurrentSlide()?.notes}</p> 107 {:else} 108 <p class="no-notes">no notes for this slide</p> 109 {/if} 110 </div> 111 {/if} 112 113 <button 114 class="notes-toggle" 115 onclick={() => (editorState.showNotes = !editorState.showNotes)} 116 > 117 {editorState.showNotes ? "hide notes" : "show notes"} 118 </button> 119 </div> 120{/if} 121 122<style> 123 .presenter-overlay { 124 position: fixed; 125 bottom: 0; 126 left: 0; 127 right: 0; 128 background: rgba(0, 0, 0, 0.95); 129 z-index: 1000; 130 display: flex; 131 flex-direction: column; 132 min-height: 60px; 133 } 134 135 .presenter-overlay.dragging { 136 user-select: none; 137 } 138 139 .drag-handle { 140 position: absolute; 141 top: 0; 142 left: 0; 143 right: 0; 144 height: 12px; 145 cursor: ns-resize; 146 display: flex; 147 align-items: center; 148 justify-content: center; 149 } 150 151 .drag-handle:hover .drag-indicator, 152 .dragging .drag-indicator { 153 background: #666; 154 } 155 156 .drag-indicator { 157 width: 40px; 158 height: 4px; 159 background: #444; 160 border-radius: 2px; 161 transition: background 0.15s ease; 162 } 163 164 .controls { 165 display: flex; 166 align-items: center; 167 justify-content: center; 168 gap: 16px; 169 padding: 16px 20px 8px; 170 } 171 172 .controls button { 173 padding: 8px 16px; 174 background: #333; 175 border: 1px solid #555; 176 color: #fff; 177 cursor: pointer; 178 border-radius: 4px; 179 } 180 181 .controls button:hover { 182 background: #444; 183 } 184 185 .slide-counter { 186 font-size: 18px; 187 font-weight: bold; 188 color: #fff; 189 min-width: 60px; 190 text-align: center; 191 } 192 193 .timer { 194 font-family: monospace; 195 font-size: 18px; 196 color: #3b82f6; 197 } 198 199 .notes { 200 flex: 1; 201 margin: 8px 20px 12px; 202 padding: 12px; 203 background: #1a1a1a; 204 border-radius: 4px; 205 color: #ccc; 206 overflow-y: auto; 207 min-height: 0; 208 } 209 210 .notes p { 211 margin: 0; 212 white-space: pre-wrap; 213 line-height: 1.5; 214 } 215 216 .notes .no-notes { 217 color: #666; 218 font-style: italic; 219 } 220 221 .notes-toggle { 222 position: absolute; 223 right: 20px; 224 top: 16px; 225 padding: 6px 12px; 226 background: transparent; 227 border: 1px solid #555; 228 color: #888; 229 cursor: pointer; 230 font-size: 12px; 231 border-radius: 4px; 232 } 233 234 .notes-toggle:hover { 235 border-color: #777; 236 color: #ccc; 237 } 238</style>