22import { computed , nextTick , onMounted , reactive , ref } from ' vue'
33import { useHead } from ' @unhead/vue'
44import type { ClicksContext , SlideRoute } from ' @slidev/types'
5- import { slidesTitle } from ' ../env'
5+ import { useVirtualList } from ' @vueuse/core'
6+ import { slideAspect , slidesTitle } from ' ../env'
67import { getSlidePath } from ' ../logic/slides'
78import { createFixedClicks } from ' ../composables/useClicks'
89import { isColorSchemaConfigured , isDark , toggleDark } from ' ../logic/dark'
@@ -17,6 +18,8 @@ import { useNav } from '../composables/useNav'
1718
1819const cardWidth = 450
1920
21+ const cardHeight = computed (() => cardWidth / slideAspect .value )
22+
2023useHead ({ title: ` Overview - ${slidesTitle } ` })
2124
2225const { openInEditor, slides } = useNav ()
@@ -28,6 +31,13 @@ const wordCounts = computed(() => slides.value.map(route => wordCount(route.meta
2831const totalWords = computed (() => wordCounts .value .reduce ((a , b ) => a + b , 0 ))
2932const totalClicks = computed (() => slides .value .map (route => getSlideClicks (route )).reduce ((a , b ) => a + b , 0 ))
3033
34+ const { list : vList, containerProps, wrapperProps, scrollTo : scrollToSlide } = useVirtualList (
35+ slides ,
36+ {
37+ itemHeight: cardHeight .value ,
38+ },
39+ )
40+
3141const clicksContextMap = new WeakMap <SlideRoute , ClicksContext >()
3242function getClicksContext(route : SlideRoute ) {
3343 // We create a local clicks context to calculate the total clicks of the slide
@@ -45,6 +55,8 @@ function wordCount(str: string) {
4555}
4656
4757function isElementInViewport(el : HTMLElement ) {
58+ if (! el )
59+ return false
4860 const rect = el .getBoundingClientRect ()
4961 const delta = 20
5062 return (
@@ -72,12 +84,6 @@ function openSlideInNewTab(path: string) {
7284 a .click ()
7385}
7486
75- function scrollToSlide(idx : number ) {
76- const el = blocks .get (idx )
77- if (el )
78- el .scrollIntoView ({ behavior: ' smooth' , block: ' start' })
79- }
80-
8187function onMarkerClick(e : MouseEvent , clicks : number , route : SlideRoute ) {
8288 const ctx = getClicksContext (route )
8389 if (ctx .current === clicks )
@@ -136,85 +142,88 @@ onMounted(() => {
136142 <main
137143 class =" flex-1 h-full of-auto"
138144 :style =" `grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
145+ v-bind =" containerProps"
139146 @scroll =" checkActiveBlocks"
140147 >
141- <div
142- v-for =" (route, idx) of slides"
143- :key =" route.no"
144- :ref =" el => blocks.set(idx, el as any)"
145- class =" relative border-t border-main of-hidden flex gap-4 min-h-50 group"
146- :class =" idx === 0 ? 'pt5' : ''"
147- >
148- <div class =" select-none w-13 text-right my4 flex flex-col gap-1 items-end" >
149- <div class =" text-3xl op20 mb2" >
150- {{ idx + 1 }}
148+ <div v-bind =" wrapperProps" >
149+ <div
150+ v-for =" { data: route, index: idx } of vList"
151+ :key =" route.no"
152+ :ref =" el => blocks.set(idx, el as any)"
153+ class =" relative border-t border-main of-hidden flex gap-4 min-h-50 group"
154+ :class =" idx === 0 ? 'pt5' : ''"
155+ >
156+ <div class =" select-none w-13 text-right my4 flex flex-col gap-1 items-end" >
157+ <div class =" text-3xl op20 mb2" >
158+ {{ idx + 1 }}
159+ </div >
160+ <IconButton
161+ class =" mr--3 op0 group-hover:op80"
162+ title =" Play in new tab"
163+ @click =" openSlideInNewTab(getSlidePath(route, false))"
164+ >
165+ <carbon:presentation-file />
166+ </IconButton >
167+ <IconButton
168+ v-if =" route.meta?.slide"
169+ class =" mr--3 op0 group-hover:op80"
170+ title =" Open in editor"
171+ @click =" openInEditor(`${route.meta.slide.filepath}:${route.meta.slide.start}`)"
172+ >
173+ <carbon:cics-program />
174+ </IconButton >
151175 </div >
152- <IconButton
153- class =" mr--3 op0 group-hover:op80"
154- title =" Play in new tab"
155- @click =" openSlideInNewTab(getSlidePath(route, false))"
156- >
157- <carbon:presentation-file />
158- </IconButton >
159- <IconButton
160- v-if =" route.meta?.slide"
161- class =" mr--3 op0 group-hover:op80"
162- title =" Open in editor"
163- @click =" openInEditor(`${route.meta.slide.filepath}:${route.meta.slide.start}`)"
164- >
165- <carbon:cics-program />
166- </IconButton >
167- </div >
168- <div class =" flex flex-col gap-2 my5" >
169- <div
170- class =" border rounded border-main overflow-hidden bg-main select-none h-max"
171- @dblclick =" openSlideInNewTab(getSlidePath(route, false))"
172- >
173- <SlideContainer
174- :key =" route.no"
175- :width =" cardWidth"
176- class =" pointer-events-none important:[& _*]:select-none"
176+ <div class =" flex flex-col gap-2 my5" >
177+ <div
178+ class =" border rounded border-main overflow-hidden bg-main select-none h-max"
179+ @dblclick =" openSlideInNewTab(getSlidePath(route, false))"
180+ >
181+ <SlideContainer
182+ :key =" route.no"
183+ :width =" cardWidth"
184+ class =" pointer-events-none important:[& _*]:select-none"
185+ >
186+ <SlideWrapper
187+ :clicks-context =" getClicksContext(route)"
188+ :route =" route"
189+ render-context =" overview"
190+ />
191+ <DrawingPreview :page =" route.no" />
192+ </SlideContainer >
193+ </div >
194+ <ClicksSlider
195+ v-if =" getSlideClicks(route)"
196+ :clicks-context =" getClicksContext(route)"
197+ class =" w-full mt-2"
198+ @dblclick =" getClicksContext(route).current = CLICKS_MAX"
199+ />
200+ </div >
201+ <div class =" py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100" >
202+ <IconButton
203+ title =" Edit Note"
204+ class =" rounded-full w-9 h-9 text-sm"
205+ :class =" edittingNote === route.no ? 'important:op0' : ''"
206+ @click =" edittingNote = route.no"
177207 >
178- <SlideWrapper
179- :clicks-context =" getClicksContext(route)"
180- :route =" route"
181- render-context =" overview"
182- />
183- <DrawingPreview :page =" route.no" />
184- </SlideContainer >
208+ <carbon:pen />
209+ </IconButton >
185210 </div >
186- <ClicksSlider
187- v-if =" getSlideClicks(route)"
211+ <NoteEditable
212+ :no =" route.no"
213+ class =" max-w-250 w-250 text-lg rounded p3"
214+ :auto-height =" true"
215+ :editing =" edittingNote === route.no"
188216 :clicks-context =" getClicksContext(route)"
189- class =" w-full mt-2"
190- @dblclick =" getClicksContext(route).current = CLICKS_MAX"
217+ @dblclick =" edittingNote !== route.no ? edittingNote = route.no : null"
218+ @update:editing =" edittingNote = null"
219+ @marker-click =" (e, clicks) => onMarkerClick(e, clicks, route)"
191220 />
192- </div >
193- <div class =" py3 mt-0.5 mr--8 ml--4 op0 transition group-hover:op100" >
194- <IconButton
195- title =" Edit Note"
196- class =" rounded-full w-9 h-9 text-sm"
197- :class =" edittingNote === route.no ? 'important:op0' : ''"
198- @click =" edittingNote = route.no"
221+ <div
222+ v-if =" wordCounts[idx] > 0"
223+ class =" select-none absolute bottom-0 right-0 bg-main rounded-tl p2 op35 text-xs"
199224 >
200- <carbon:pen />
201- </IconButton >
202- </div >
203- <NoteEditable
204- :no =" route.no"
205- class =" max-w-250 w-250 text-lg rounded p3"
206- :auto-height =" true"
207- :editing =" edittingNote === route.no"
208- :clicks-context =" getClicksContext(route)"
209- @dblclick =" edittingNote !== route.no ? edittingNote = route.no : null"
210- @update:editing =" edittingNote = null"
211- @marker-click =" (e, clicks) => onMarkerClick(e, clicks, route)"
212- />
213- <div
214- v-if =" wordCounts[idx] > 0"
215- class =" select-none absolute bottom-0 right-0 bg-main rounded-tl p2 op35 text-xs"
216- >
217- {{ wordCounts[idx] }} words
225+ {{ wordCounts[idx] }} words
226+ </div >
218227 </div >
219228 </div >
220229 </main >
0 commit comments