2
2
import { computed , nextTick , onMounted , reactive , ref } from ' vue'
3
3
import { useHead } from ' @unhead/vue'
4
4
import type { ClicksContext , SlideRoute } from ' @slidev/types'
5
- import { slidesTitle } from ' ../env'
5
+ import { useVirtualList } from ' @vueuse/core'
6
+ import { slideAspect , slidesTitle } from ' ../env'
6
7
import { getSlidePath } from ' ../logic/slides'
7
8
import { createFixedClicks } from ' ../composables/useClicks'
8
9
import { isColorSchemaConfigured , isDark , toggleDark } from ' ../logic/dark'
@@ -17,6 +18,8 @@ import { useNav } from '../composables/useNav'
17
18
18
19
const cardWidth = 450
19
20
21
+ const cardHeight = computed (() => cardWidth / slideAspect .value )
22
+
20
23
useHead ({ title: ` Overview - ${slidesTitle } ` })
21
24
22
25
const { openInEditor, slides } = useNav ()
@@ -28,6 +31,13 @@ const wordCounts = computed(() => slides.value.map(route => wordCount(route.meta
28
31
const totalWords = computed (() => wordCounts .value .reduce ((a , b ) => a + b , 0 ))
29
32
const totalClicks = computed (() => slides .value .map (route => getSlideClicks (route )).reduce ((a , b ) => a + b , 0 ))
30
33
34
+ const { list : vList, containerProps, wrapperProps, scrollTo : scrollToSlide } = useVirtualList (
35
+ slides ,
36
+ {
37
+ itemHeight: cardHeight .value ,
38
+ },
39
+ )
40
+
31
41
const clicksContextMap = new WeakMap <SlideRoute , ClicksContext >()
32
42
function getClicksContext(route : SlideRoute ) {
33
43
// We create a local clicks context to calculate the total clicks of the slide
@@ -45,6 +55,8 @@ function wordCount(str: string) {
45
55
}
46
56
47
57
function isElementInViewport(el : HTMLElement ) {
58
+ if (! el )
59
+ return false
48
60
const rect = el .getBoundingClientRect ()
49
61
const delta = 20
50
62
return (
@@ -72,12 +84,6 @@ function openSlideInNewTab(path: string) {
72
84
a .click ()
73
85
}
74
86
75
- function scrollToSlide(idx : number ) {
76
- const el = blocks .get (idx )
77
- if (el )
78
- el .scrollIntoView ({ behavior: ' smooth' , block: ' start' })
79
- }
80
-
81
87
function onMarkerClick(e : MouseEvent , clicks : number , route : SlideRoute ) {
82
88
const ctx = getClicksContext (route )
83
89
if (ctx .current === clicks )
@@ -136,85 +142,88 @@ onMounted(() => {
136
142
<main
137
143
class =" flex-1 h-full of-auto"
138
144
:style =" `grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
145
+ v-bind =" containerProps"
139
146
@scroll =" checkActiveBlocks"
140
147
>
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 >
151
175
</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"
177
207
>
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 >
185
210
</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"
188
216
: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)"
191
220
/>
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"
199
224
>
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 >
218
227
</div >
219
228
</div >
220
229
</main >
0 commit comments