Skip to content

Commit 5dc7a96

Browse files
authored
feat(vscode): add overview preview mode (#2601)
1 parent 46bcec2 commit 5dc7a96

14 files changed

Lines changed: 671 additions & 166 deletions

File tree

packages/client/composables/useEmbeddedCtrl.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ export function useEmbeddedControl() {
3131

3232
if (nav.isEmbedded.value) {
3333
throttledWatch(
34-
[nav.currentSlideNo, nav.clicks, nav.hasNext, nav.hasPrev],
35-
([no, clicks, hasNext, hasPrev]) => {
34+
[nav.currentSlideNo, nav.clicks, nav.hasNext, nav.hasPrev, nav.hasPrimarySlide],
35+
([no, clicks, hasNext, hasPrev, hasPrimarySlide]) => {
36+
if (!hasPrimarySlide)
37+
return
3638
window.parent.postMessage(
3739
{
3840
target: 'slidev',

packages/client/internals/ClicksSlider.vue

Lines changed: 99 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,167 @@
11
<script setup lang="ts">
22
import type { ClicksContext } from '@slidev/types'
33
import { clamp, range } from '@antfu/utils'
4-
import { computed } from 'vue'
4+
import { computed, ref } from 'vue'
55
import { CLICKS_MAX } from '../constants'
66
77
const props = withDefaults(defineProps<{
88
clicksContext: ClicksContext
99
readonly?: boolean
1010
active?: boolean
11+
resettable?: boolean
12+
compact?: boolean
13+
attached?: boolean
1114
}>(), {
1215
active: true,
1316
})
1417
18+
const emit = defineEmits<{
19+
(type: 'activate'): void
20+
(type: 'reset'): void
21+
}>()
22+
1523
const total = computed(() => props.clicksContext.total)
1624
const start = computed(() => clamp(0, props.clicksContext.clicksStart, total.value))
1725
const length = computed(() => total.value - start.value + 1)
1826
const current = computed({
1927
get() {
28+
if (props.resettable && !props.active)
29+
return -1
2030
return props.clicksContext.current > total.value ? -1 : props.clicksContext.current
2131
},
2232
set(value: number) {
33+
if (props.resettable && value < 0) {
34+
emit('reset')
35+
// eslint-disable-next-line vue/no-mutating-props
36+
props.clicksContext.current = CLICKS_MAX
37+
return
38+
}
39+
emit('activate')
2340
// eslint-disable-next-line vue/no-mutating-props
2441
props.clicksContext.current = value
2542
},
2643
})
2744
45+
const isReset = computed(() => props.resettable && current.value < 0)
2846
const clicksRange = computed(() => range(start.value, total.value + 1))
47+
const sliderEl = ref<HTMLElement>()
48+
let pointerDown: { id: number, x: number, y: number } | undefined
49+
50+
function getPointerRatio(event: PointerEvent) {
51+
const rect = sliderEl.value!.getBoundingClientRect()
52+
return (event.clientX - rect.left) / Math.max(1, rect.width)
53+
}
54+
55+
// Presses snap to a cell; drags switch only after crossing half a cell.
56+
function setCurrentFromPointer(event: PointerEvent, snap: boolean) {
57+
if (props.readonly || !sliderEl.value || (!snap && !(event.buttons & 1)))
58+
return
59+
const ratio = getPointerRatio(event)
60+
// In resettable mode, dragging left of the rail restores the inactive state.
61+
if (props.resettable && ratio < 0) {
62+
current.value = -1
63+
return
64+
}
65+
// Keep press-at-right-edge inside the last cell.
66+
const position = clamp(0, ratio, snap ? 0.999999 : 1) * length.value
67+
const currentOffset = clamp(0, current.value - start.value, length.value - 1)
68+
let next = snap ? start.value + Math.floor(position) : current.value
69+
if (!snap && position >= currentOffset + 1.5)
70+
next = start.value + Math.floor(position - 0.5)
71+
else if (!snap && position < currentOffset - 0.5)
72+
next = start.value + Math.ceil(position - 0.5)
73+
current.value = clamp(start.value, next, total.value)
74+
}
2975
30-
function onMousedown() {
76+
function onPointerDown(event: PointerEvent) {
3177
if (props.readonly)
3278
return
33-
if (current.value < 0 || current.value > total.value)
34-
current.value = 0
79+
sliderEl.value?.setPointerCapture(event.pointerId)
80+
pointerDown = { id: event.pointerId, x: event.clientX, y: event.clientY }
81+
setCurrentFromPointer(event, true)
82+
}
83+
84+
function onPointerMove(event: PointerEvent) {
85+
if (pointerDown?.id === event.pointerId) {
86+
// Treat tiny movement after pointerdown as part of the click.
87+
if (Math.abs(event.clientX - pointerDown.x) <= 3 && Math.abs(event.clientY - pointerDown.y) <= 3)
88+
return
89+
pointerDown = undefined
90+
}
91+
setCurrentFromPointer(event, false)
3592
}
3693
</script>
3794

3895
<template>
3996
<div
40-
class="flex gap-1 items-center select-none"
97+
class="flex gap-1 select-none"
4198
:title="`Clicks in this slide: ${length}`"
42-
:class="length && props.clicksContext.isMounted ? '' : 'op50'"
99+
:class="[attached ? 'items-end' : 'items-center', length && props.clicksContext.isMounted ? '' : 'op50']"
43100
>
44-
<div class="flex gap-0.2 items-center min-w-16 font-mono mr1">
45-
<div class="i-carbon:cursor-1 text-sm op50" />
101+
<div
102+
class="flex items-center font-mono"
103+
:class="[compact ? 'gap-1 min-w-0 mr0' : 'gap-0.2 min-w-16 mr1', attached ? 'h-[22px]' : '']"
104+
>
105+
<div class="i-carbon:cursor-1 text-sm op50" :class="compact ? 'ml-1' : ''" />
46106
<template v-if="current >= 0 && current !== CLICKS_MAX && active">
47-
<div flex-auto />
48-
<span text-primary>{{ current }}</span>
49-
<span op25 text-sm>/</span>
50-
<span op50 text-sm>{{ total }}</span>
107+
<div v-if="!compact" flex-auto />
108+
<span>
109+
<span text-primary>{{ current }}</span>
110+
<span op25 text-sm>/</span>
111+
<span op50 text-sm>{{ total }}</span>
112+
</span>
51113
</template>
52114
<div
53115
v-else
54-
op50 flex-auto pl1
116+
op50
117+
:class="compact ? '' : 'flex-auto pl1'"
55118
>
56-
{{ total }}
119+
<span
120+
:class="compact ? 'inline-block text-center' : ''"
121+
:style="compact ? { width: `${String(total).length * 2 + 1}ch`, marginLeft: '-0.25ch' } : undefined"
122+
>{{ total }}</span>
57123
</div>
58124
</div>
59125
<div
60-
relative flex-auto h5 font-mono flex="~"
126+
ref="sliderEl"
127+
relative flex-auto font-mono flex="~"
128+
touch-none
129+
:class="[attached ? 'h-[22px]' : 'h5', isReset ? 'op80' : '']"
130+
@pointerdown.capture="onPointerDown"
131+
@pointermove="onPointerMove"
132+
@pointerup="pointerDown = undefined"
133+
@pointercancel="pointerDown = undefined"
61134
>
62135
<div
63136
v-for="i of clicksRange" :key="i"
64137
border="y main" of-hidden relative
65138
:class="[
66-
i === 0 ? 'rounded-l border-l' : '',
67-
i === total ? 'rounded-r border-r' : '',
139+
i === 0 ? 'border-l' : '',
140+
i === 0 ? attached ? 'rounded-tl' : 'rounded-l' : '',
141+
i === total ? 'border-r' : '',
142+
i === total && +i !== +current ? attached ? 'rounded-tr' : 'rounded-r' : '',
143+
attached ? 'border-b-0' : '',
68144
]"
69145
:style="{ width: length > 0 ? `${1 / length * 100}%` : '100%' }"
70146
>
71147
<div
72148
absolute inset-0
73149
:class="(i <= current && active) ? 'bg-primary op15' : ''"
74150
/>
151+
<div
152+
v-if="+i === +current && active"
153+
absolute inset-y-0 right-0 w-0.5 bg-primary z-1
154+
/>
75155
<div
76156
:class="[
77-
(+i === +current && active) ? 'text-primary font-bold op100 border-primary' : 'op30 border-main',
78-
i === 0 ? 'rounded-l' : '',
79-
i === total ? 'rounded-r' : 'border-r-2',
157+
(+i === +current && active) ? 'text-primary font-bold op100' : 'op30',
158+
i !== total ? 'border-r-2 border-main' : '',
80159
]"
81160
w-full h-full text-xs flex items-center justify-center z-1
82161
>
83162
{{ i }}
84163
</div>
85164
</div>
86-
<input
87-
v-model="current"
88-
class="range"
89-
type="range" :min="start" :max="total" :step="1"
90-
absolute inset-0 z-label op0
91-
:class="readonly ? 'pointer-events-none' : ''"
92-
:style="{ '--thumb-width': `${1 / (length + 1) * 100}%` }"
93-
@mousedown="onMousedown"
94-
@focus="event => (event.currentTarget as HTMLElement)?.blur()"
95-
>
96165
</div>
97166
</div>
98167
</template>
99-
100-
<style scoped>
101-
.range {
102-
-webkit-appearance: none;
103-
appearance: none;
104-
background: transparent;
105-
}
106-
.range::-webkit-slider-thumb {
107-
-webkit-appearance: none;
108-
height: 100%;
109-
width: var(--thumb-width, 0.5rem);
110-
}
111-
112-
.range::-moz-range-thumb {
113-
height: 100%;
114-
width: var(--thumb-width, 0.5rem);
115-
}
116-
</style>

packages/client/internals/CodeRunner.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ const props = defineProps<{
2222
2323
const emit = defineEmits(['update:modelValue'])
2424
25-
const { isPrintMode } = useNav()
25+
const { isEmbedded, isPrintMode } = useNav()
2626
2727
const code = useVModel(props, 'modelValue', emit)
2828
2929
const { $renderContext, $clicksContext } = useSlideContext()
30-
const disabled = computed(() => !['slide', 'presenter'].includes($renderContext.value))
30+
const disabled = computed(() => !['slide', 'presenter'].includes($renderContext.value) && !($renderContext.value === 'overview' && isEmbedded.value))
3131
3232
const autorun = isPrintMode.value ? 'once' : props.autorun
3333
const isRunning = ref(autorun)

packages/client/internals/NoteDisplay.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,9 @@ watchEffect(() => {
172172
.slidev-note :first-child {
173173
margin-top: 0;
174174
}
175+
176+
.slidev-note {
177+
overflow-wrap: anywhere;
178+
word-break: break-word;
179+
}
175180
</style>

packages/client/internals/NoteEditable.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,9 @@ watch(
131131
v-else
132132
ref="inputEl"
133133
v-model="note"
134-
class="prose dark:prose-invert resize-none overflow-auto outline-none bg-transparent block border-primary border-2"
135-
style="line-height: 1.75;"
134+
class="prose dark:prose-invert resize-none overflow-auto outline-none bg-transparent block border-primary border-2 slidev-note placeholder:op25"
136135
:style="[props.style, inputHeight != null ? { height: `${inputHeight}px` } : {}]"
137-
:class="props.class"
136+
:class="[props.class, note ? '' : 'italic']"
138137
:placeholder="placeholder"
139138
@keydown.esc="editing = false"
140139
@keydown="onKeyDown"

0 commit comments

Comments
 (0)