返回 slidev
NavControls.vue
根目录 / packages / client / internals / NavControls.vue
1 <script setup lang="ts">
2 import { computed, ref, shallowRef } from 'vue'
3 import CustomNavControls from '#slidev/custom-nav-controls'
4 import { useDrawings } from '../composables/useDrawings'
5 import { useNav } from '../composables/useNav'
6 import { configs } from '../env'
7 import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
8 import { activeElement, breakpoints, cursorStyle, fullscreen, hasViewerCssFilter, presenterLayout, showEditor, showInfoDialog, showPresenterCursor, toggleOverview, togglePresenterCursor, togglePresenterLayout } from '../state'
9 import { downloadPDF } from '../utils'
10 import IconButton from './IconButton.vue'
11 import MenuButton from './MenuButton.vue'
12 import Settings from './Settings.vue'
13 import SyncControls from './SyncControls.vue'
14
15 import VerticalDivider from './VerticalDivider.vue'
16
17 const props = defineProps({
18 persist: {
19 default: false,
20 },
21 })
22
23 const {
24 currentSlideNo,
25 hasNext,
26 hasPrev,
27 isEmbedded,
28 isPresenter,
29 isPresenterAvailable,
30 next,
31 prev,
32 total,
33 enterPresenter,
34 exitPresenter,
35 } = useNav()
36 const {
37 brush,
38 drawingEnabled,
39 } = useDrawings()
40
41 const md = breakpoints.smaller('md')
42 const { isFullscreen, toggle: toggleFullscreen } = fullscreen
43
44 const root = ref<HTMLDivElement>()
45 function onMouseLeave() {
46 if (root.value && activeElement.value && root.value.contains(activeElement.value))
47 activeElement.value.blur()
48 }
49
50 const barStyle = computed(() => props.persist
51 ? 'text-$slidev-controls-foreground bg-transparent'
52 : 'rounded-md bg-main shadow-xl border border-main')
53
54 const RecordingControls = shallowRef<any>()
55 if (__SLIDEV_FEATURE_RECORD__)
56 import('./RecordingControls.vue').then(v => RecordingControls.value = v.default)
57 </script>
58
59 <template>
60 <nav ref="root" class="flex flex-col">
61 <div
62 class="flex flex-wrap-reverse text-xl gap-0.5 p-1 lg:p-2"
63 :class="barStyle"
64 @mouseleave="onMouseLeave"
65 >
66 <IconButton v-if="!isEmbedded" :title="isFullscreen ? 'Close fullscreen' : 'Enter fullscreen'" @click="toggleFullscreen">
67 <div v-if="isFullscreen" class="i-carbon:minimize" />
68 <div v-else class="i-carbon:maximize" />
69 </IconButton>
70 <IconButton :disabled="!hasPrev" title="Go to previous slide" @click="prev">
71 <div class="i-carbon:arrow-left" />
72 </IconButton>
73 <IconButton :disabled="!hasNext" title="Go to next slide" @click="next">
74 <div class="i-carbon:arrow-right" />
75 </IconButton>
76 <IconButton v-if="!isEmbedded" title="Show slide overview" @click="toggleOverview()">
77 <div class="i-carbon:apps" />
78 </IconButton>
79 <IconButton
80 v-if="!isColorSchemaConfigured"
81 :title="isDark ? 'Switch to light mode theme' : 'Switch to dark mode theme'"
82 @click="toggleDark()"
83 >
84 <div v-if="isDark" class="i-carbon-moon" />
85 <div v-else class="i-carbon-sun" />
86 </IconButton>
87
88 <VerticalDivider />
89
90 <template v-if="!isEmbedded">
91 <template v-if="!isPresenter && !md && RecordingControls">
92 <RecordingControls />
93 <VerticalDivider />
94 </template>
95
96 <IconButton
97 v-if="isPresenter"
98 :title="cursorStyle === 'laser'
99 ? (showPresenterCursor ? 'Disable laser pointer' : 'Enable laser pointer')
100 : (showPresenterCursor ? 'Hide presenter cursor' : 'Show presenter cursor')"
101 :active="showPresenterCursor"
102 @click="togglePresenterCursor()"
103 >
104 <template v-if="cursorStyle === 'laser'">
105 <div v-if="showPresenterCursor" class="i-carbon-magic-wand-filled color-red-600 dark:color-red-400" />
106 <div v-else class="i-carbon-magic-wand" />
107 </template>
108 <template v-else>
109 <div v-if="showPresenterCursor" class="i-ph-cursor-fill" />
110 <div v-else class="i-ph-cursor-duotone" />
111 </template>
112 </IconButton>
113 <IconButton
114 v-if="__SLIDEV_FEATURE_DRAWINGS__ && (!configs.drawings.presenterOnly || isPresenter)"
115 class="relative"
116 :title="drawingEnabled ? 'Hide drawing toolbar' : 'Show drawing toolbar'"
117 :active="drawingEnabled"
118 @click="drawingEnabled = !drawingEnabled"
119 >
120 <div class="i-carbon:pen" />
121 <div
122 v-if="drawingEnabled"
123 class="absolute left-1 right-1 bottom-0 h-0.7 rounded-full"
124 :style="{ background: brush.color }"
125 />
126 </IconButton>
127
128 <VerticalDivider />
129
130 <IconButton v-if="isPresenter" title="Play Mode" @click="exitPresenter">
131 <div class="i-carbon:presentation-file" />
132 </IconButton>
133 <IconButton v-if="__SLIDEV_FEATURE_PRESENTER__ && isPresenterAvailable" title="Presenter Mode" @click="enterPresenter">
134 <div class="i-carbon:user-speaker" />
135 </IconButton>
136 <IconButton
137 v-if="__DEV__ && __SLIDEV_FEATURE_EDITOR__"
138 :title="showEditor ? 'Hide editor' : 'Show editor'"
139 class="lt-md:hidden"
140 @click="showEditor = !showEditor"
141 >
142 <div class="i-carbon:text-annotation-toggle" />
143 </IconButton>
144 </template>
145
146 <template v-if="!__DEV__">
147 <IconButton v-if="configs.download" title="Download as PDF" @click="downloadPDF">
148 <div class="i-carbon:download" />
149 </IconButton>
150 </template>
151
152 <template v-if="__SLIDEV_FEATURE_BROWSER_EXPORTER__ && !isEmbedded && !isPresenter">
153 <IconButton title="Browser Exporter" to="/export">
154 <div class="i-carbon:document-pdf" />
155 </IconButton>
156 </template>
157
158 <IconButton
159 v-if="!isPresenter && configs.info && !isEmbedded"
160 title="Show info"
161 @click="showInfoDialog = !showInfoDialog"
162 >
163 <div class="i-carbon:information" />
164 </IconButton>
165
166 <template v-if="!isEmbedded">
167 <VerticalDivider />
168
169 <IconButton v-if="isPresenter" title="Toggle Presenter Layout" class="aspect-ratio-initial flex items-center" @click="togglePresenterLayout">
170 <div class="i-carbon:template" />
171 {{ presenterLayout }}
172 </IconButton>
173
174 <SyncControls v-if="__SLIDEV_FEATURE_PRESENTER__" />
175
176 <MenuButton>
177 <template #button="{ value }">
178 <IconButton title="More Options" :active="value">
179 <div class="i-carbon:settings-adjust" />
180 <div v-if="hasViewerCssFilter" w-2 h-2 bg-primary rounded-full absolute top-0.5 right-0.5 />
181 </IconButton>
182 </template>
183 <template #menu>
184 <Settings />
185 </template>
186 </MenuButton>
187 </template>
188
189 <VerticalDivider v-if="!isEmbedded" />
190
191 <div class="px2 my-auto">
192 <span class="text-lg">{{ currentSlideNo }}</span>
193 <span class="opacity-50 text-sm"> / {{ total }}</span>
194 </div>
195
196 <CustomNavControls />
197 </div>
198 </nav>
199 </template>
200
200 lines Plain Text