Skip to content

Commit

Permalink
feat: add clicksStart option in page frontmatter (#1456)
Browse files Browse the repository at this point in the history
Co-authored-by: _Kerman <kermanx@qq.com>
Co-authored-by: Anthony Fu <github@antfu.me>
  • Loading branch information
3 people committed Mar 29, 2024
1 parent b791336 commit f7cf8fa
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 22 deletions.
1 change: 1 addition & 0 deletions docs/custom/index.md
Expand Up @@ -106,6 +106,7 @@ Check out the [type definitions](https://github.com/slidevjs/slidev/blob/main/pa
In addition, every slide accepts the following configuration in the Frontmatter block:

- `clicks` (`number`): Custom clicks count (learn more [here](/guide/animations.html#custom-total-clicks-count)).
- `clicksStart` (`number`): Custom start clicks count.
- `disabled` (`boolean`): Completely disable and hide the slide.
- `hide` (`boolean`): The same as `disabled`.
- `hideInToc` (`boolean`): Hide the slide for the `<Toc>` components (learn more [here](/builtin/components.html#toc)).
Expand Down
20 changes: 14 additions & 6 deletions packages/client/composables/useClicks.ts
@@ -1,4 +1,4 @@
import { sum } from '@antfu/utils'
import { clamp, sum } from '@antfu/utils'
import type { ClicksContext, SlideRoute } from '@slidev/types'
import type { Ref } from 'vue'
import { ref, shallowReactive } from 'vue'
Expand All @@ -7,18 +7,21 @@ import { routeForceRefresh } from '../logic/route'

export function createClicksContextBase(
current: Ref<number>,
clicksOverrides?: number,
clicksStart = 0,
clicksTotalOverrides?: number,
): ClicksContext {
const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
const map: ClicksContext['map'] = shallowReactive(new Map())

return {
get current() {
return +current.value
// Here we haven't know clicksTotal yet.
return clamp(+current.value, clicksStart, this.total)
},
set current(value) {
current.value = +value
current.value = clamp(+value, clicksStart, this.total)
},
clicksStart,
relativeOffsets,
map,
onMounted() { },
Expand Down Expand Up @@ -56,7 +59,7 @@ export function createClicksContextBase(
get total() {
// eslint-disable-next-line no-unused-expressions
routeForceRefresh.value
return clicksOverrides ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
return clicksTotalOverrides ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
},
}
}
Expand All @@ -65,5 +68,10 @@ export function createFixedClicks(
route?: SlideRoute | undefined,
currentInit = 0,
): ClicksContext {
return createClicksContextBase(ref(currentInit), route?.meta?.clicks)
const clicksStart = route?.meta.slide?.frontmatter.clicksStart ?? 0
return createClicksContextBase(
ref(Math.max(currentInit, clicksStart)),
clicksStart,
route?.meta?.clicks,
)
}
22 changes: 15 additions & 7 deletions packages/client/composables/useNav.ts
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from 'vue-router'
import type { RouteLocationNormalized, Router } from 'vue-router'
import { createSharedComposable } from '@vueuse/core'
import { logicOr } from '@vueuse/math'
import { clamp } from '@antfu/utils'
import { getCurrentTransition } from '../logic/transition'
import { getSlide, getSlidePath } from '../logic/slides'
import { CLICKS_MAX } from '../constants'
Expand Down Expand Up @@ -33,6 +34,7 @@ export interface SlidevContextNav {

clicksContext: ComputedRef<ClicksContext>
clicks: ComputedRef<number>
clicksStart: ComputedRef<number>
clicksTotal: ComputedRef<number>

/** The table of content tree */
Expand Down Expand Up @@ -99,6 +101,7 @@ export function useNavBase(
const currentLayout = computed(() => currentSlideRoute.value.meta?.layout || (currentSlideNo.value === 1 ? 'cover' : 'default'))

const clicks = computed(() => clicksContext.value.current)
const clicksStart = computed(() => clicksContext.value.clicksStart)
const clicksTotal = computed(() => clicksContext.value.total)
const nextRoute = computed(() => slides.value[Math.min(slides.value.length, currentSlideNo.value + 1) - 1])
const prevRoute = computed(() => slides.value[Math.max(1, currentSlideNo.value - 1) - 1])
Expand Down Expand Up @@ -140,7 +143,7 @@ export function useNavBase(

async function prev() {
clicksDirection.value = -1
if (queryClicks.value <= 0)
if (queryClicks.value <= clicksStart.value)
await prevSlide()
else
queryClicks.value -= 1
Expand Down Expand Up @@ -177,6 +180,9 @@ export function useNavBase(
skipTransition.value = false
const pageChanged = currentSlideNo.value !== page
const clicksChanged = clicks !== queryClicks.value
const meta = getSlide(page)?.meta
const clicksStart = meta?.slide?.frontmatter.clicksStart ?? 0
clicks = clamp(clicks, clicksStart, meta?.__clicksContext?.total ?? CLICKS_MAX)
if (pageChanged || clicksChanged) {
await router?.push({
path: getSlidePath(page, isPresenter.value),
Expand All @@ -202,6 +208,7 @@ export function useNavBase(
prevRoute,
clicksContext,
clicks,
clicksStart,
clicksTotal,
hasNext,
hasPrev,
Expand Down Expand Up @@ -289,24 +296,25 @@ const useNavState = createSharedComposable((): SlidevContextNavState => {
computed({
get() {
if (currentSlideNo.value === thisNo)
return +(queryClicksRaw.value || 0) || 0
return Math.max(+(queryClicksRaw.value ?? 0), context.clicksStart)
else if (currentSlideNo.value > thisNo)
return CLICKS_MAX
else
return 0
return context.clicksStart
},
set(v) {
if (currentSlideNo.value === thisNo)
queryClicksRaw.value = Math.min(v, context.total).toString()
queryClicksRaw.value = clamp(v, context.clicksStart, context.total).toString()
},
}),
route?.meta?.clicks,
route?.meta.slide?.frontmatter.clicksStart ?? 0,
route?.meta.clicks,
)

// On slide mounted, make sure the query is not greater than the total
context.onMounted = () => {
if (queryClicksRaw.value)
queryClicksRaw.value = Math.min(+queryClicksRaw.value, context.total).toString()
if (currentSlideNo.value === thisNo)
queryClicksRaw.value = clamp(+queryClicksRaw.value, context.clicksStart, context.total).toString()
}

if (route?.meta)
Expand Down
1 change: 1 addition & 0 deletions packages/client/constants.ts
Expand Up @@ -31,6 +31,7 @@ export const TRUST_ORIGINS = [

export const FRONTMATTER_FIELDS = [
'clicks',
'clicksStart',
'disabled',
'hide',
'hideInToc',
Expand Down
17 changes: 10 additions & 7 deletions packages/client/internals/ClicksSlider.vue
@@ -1,5 +1,6 @@
<script setup lang="ts">
import type { ClicksContext } from '@slidev/types'
import { clamp, range } from '@antfu/utils'
import { computed } from 'vue'
import { CLICKS_MAX } from '../constants'
Expand All @@ -8,6 +9,8 @@ const props = defineProps<{
}>()
const total = computed(() => props.clicksContext.total)
const start = computed(() => clamp(0, props.clicksContext.clicksStart, total.value))
const length = computed(() => total.value - start.value + 1)
const current = computed({
get() {
return props.clicksContext.current > total.value ? -1 : props.clicksContext.current
Expand All @@ -18,7 +21,7 @@ const current = computed({
},
})
const range = computed(() => Array.from({ length: total.value + 1 }, (_, i) => i))
const clicksRange = computed(() => range(start.value, total.value + 1))
function onMousedown() {
if (current.value < 0 || current.value > total.value)
Expand All @@ -29,8 +32,8 @@ function onMousedown() {
<template>
<div
class="flex gap-1 items-center select-none"
:title="`Clicks in this slide: ${total}`"
:class="total ? '' : 'op50'"
:title="`Clicks in this slide: ${length}`"
:class="length ? '' : 'op50'"
>
<div class="flex gap-0.5 items-center min-w-16 font-mono mr1">
<carbon:cursor-1 text-sm op50 />
Expand All @@ -46,13 +49,13 @@ function onMousedown() {
@dblclick="current = clicksContext.total"
>
<div
v-for="i of range" :key="i"
v-for="i of clicksRange" :key="i"
border="y main" of-hidden relative
:class="[
i === 0 ? 'rounded-l border-l' : '',
i === total ? 'rounded-r border-r' : '',
]"
:style="{ width: total > 0 ? `${1 / total * 100}%` : '100%' }"
:style="{ width: length > 0 ? `${1 / length * 100}%` : '100%' }"
>
<div absolute inset-0 :class="i <= current ? 'bg-primary op15' : ''" />
<div
Expand All @@ -69,8 +72,8 @@ function onMousedown() {
<input
v-model="current"
class="range" absolute inset-0
type="range" :min="0" :max="total" :step="1" z-10 op0
:style="{ '--thumb-width': `${1 / (total + 1) * 100}%` }"
type="range" :min="start" :max="total" :step="1" z-10 op0
:style="{ '--thumb-width': `${1 / (length + 1) * 100}%` }"
@mousedown="onMousedown"
@focus="event => (event.currentTarget as HTMLElement)?.blur()"
>
Expand Down
8 changes: 6 additions & 2 deletions packages/client/internals/PrintSlide.vue
Expand Up @@ -16,10 +16,14 @@ const clicks0 = createFixedClicks(route, isPrintWithClicks.value ? 0 : CLICKS_MA
:nav="useFixedNav(route, clicks0)"
/>
<template v-if="isPrintWithClicks">
<!--
clicks0.total can be any number >=0 when rendering.
So total-clicksStart can be negative in intermediate states.
-->
<PrintSlideClick
v-for="i of clicks0.total"
v-for="i in Math.max(0, clicks0.total - clicks0.clicksStart)"
:key="i"
:nav="useFixedNav(route, createFixedClicks(route, i))"
:nav="useFixedNav(route, createFixedClicks(route, i + clicks0.clicksStart))"
/>
</template>
</template>
1 change: 1 addition & 0 deletions packages/types/src/types.ts
Expand Up @@ -151,6 +151,7 @@ export type ClicksMap = Map<ClicksElement, ClicksInfo>

export interface ClicksContext {
current: number
readonly clicksStart: number
readonly relativeOffsets: ClicksRelativeEls
readonly map: ClicksMap
resolve: (at: string | number, size?: number) => {
Expand Down

0 comments on commit f7cf8fa

Please sign in to comment.