Skip to content

Commit

Permalink
feat: experimental notes page
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Feb 3, 2023
1 parent 9f2d633 commit 07a3bda
Show file tree
Hide file tree
Showing 17 changed files with 152 additions and 48 deletions.
2 changes: 1 addition & 1 deletion demo/composable-vue/slides.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ Sets of reusable logic, separation of concerns.
```ts
export function useDark(options: UseDarkOptions = {}) {
const preferredDark = usePreferredDark() // <--
const store = useStorage('vueuse-dark', 'auto') // <--
const store = useLocalStorage('vueuse-dark', 'auto') // <--

return computed<boolean>({
get() {
Expand Down
4 changes: 2 additions & 2 deletions packages/client/internals/Draggable.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useDraggable, useStorage } from '@vueuse/core'
import { useDraggable, useLocalStorage } from '@vueuse/core'
const props = defineProps<{
storageKey?: string
Expand All @@ -10,7 +10,7 @@ const props = defineProps<{
const el = ref<HTMLElement | null>(null)
const initial = props.initial ?? { x: 0, y: 0 }
const point = props.storageKey
? useStorage(props.storageKey, initial)
? useLocalStorage(props.storageKey, initial)
: ref(initial)
const { style } = useDraggable(el, { initialValue: point })
</script>
Expand Down
2 changes: 1 addition & 1 deletion packages/client/internals/NoteEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ onClickOutside(input, () => {
v-if="!editing && note"
:class="props.class"
:note="note"
:note-html="info?.notesHTML"
:note-html="info?.noteHTML"
@click="switchNoteEdit"
/>
<textarea
Expand Down
2 changes: 1 addition & 1 deletion packages/client/internals/NoteStatic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const props = defineProps<{
}>()
const note = computed(() => currentRoute.value?.meta?.slide?.note)
const noteHtml = computed(() => currentRoute.value?.meta?.slide?.notesHTML)
const noteHtml = computed(() => currentRoute.value?.meta?.slide?.noteHTML)
</script>

<template>
Expand Down
67 changes: 67 additions & 0 deletions packages/client/internals/NotesView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script setup lang="ts">
import { useHead } from '@vueuse/head'
import { computed } from 'vue'
import { useLocalStorage } from '@vueuse/core'
import { configs } from '../env'
import { sharedState } from '../state/shared'
import { total } from '../logic/nav'
import { rawRoutes } from '../routes'
import NoteViewer from './NoteViewer.vue'
const slideTitle = configs.titleTemplate.replace('%s', configs.title || 'Slidev')
useHead({
title: `Notes - ${slideTitle}`,
})
const fontSize = useLocalStorage('slidev-notes-font-size', 18)
const pageNo = computed(() => sharedState.lastUpdate?.type === 'viewer' ? sharedState.viewerPage : sharedState.page)
const route = computed(() => rawRoutes.find(i => i.path === `${pageNo.value}`))
const note = computed(() => route.value?.meta?.slide?.note)
const noteHtml = computed(() => route.value?.meta?.slide?.noteHTML)
function increaseFontSize() {
fontSize.value = fontSize.value + 1
}
function decreaseFontSize() {
fontSize.value = fontSize.value - 1
}
</script>

<template>
<div
class="fixed top-0 left-0 h-2px bg-teal-500 transition-all duration-500"
:style="{ width: `${(pageNo - 1) / total * 100}%` }"
/>
<div class="h-full flex flex-col">
<div
class="px-5 flex-auto h-full overflow-auto"
:style="{ fontSize: `${fontSize}px` }"
>
<NoteViewer
v-if="note"
:note="note"
:note-html="noteHtml"
/>
<div v-else class="prose overflow-auto outline-none opacity-50">
<p>
No notes for Slide {{ pageNo }}.
</p>
</div>
</div>
<div class="flex-none">
<div class="flex gap-1 justify-center items-center">
<button class="icon-btn" @click="increaseFontSize">
<carbon:add />
</button>
Font Size
<button class="icon-btn" @click="decreaseFontSize">
<carbon:subtract />
</button>
</div>
<div class="p2 text-center">
{{ pageNo }} / {{ total }}
</div>
</div>
</div>
</template>
2 changes: 1 addition & 1 deletion packages/client/internals/Presenter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ onMounted(() => {
class="h-full w-full"
>
<SlideWrapper
:is="nextSlide.route?.component"
:is="nextSlide.route?.component as any"
v-model:clicks-elements="nextTabElements"
:clicks="nextSlide.clicks"
:clicks-disabled="false"
Expand Down
4 changes: 2 additions & 2 deletions packages/client/internals/PresenterPrint.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ useHead({ title: `Notes - ${configs.title}` })
const slidesWithNote = computed(() => rawRoutes
.slice(0, -1)
.map(route => route.meta?.slide)
.filter(slide => slide !== undefined && slide.notesHTML !== ''))
.filter(slide => slide !== undefined && slide.noteHTML !== ''))
</script>

<template>
Expand All @@ -55,7 +55,7 @@ const slidesWithNote = computed(() => rawRoutes
<div class="flex-auto" />
</div>
</h2>
<NoteViewer :note-html="slide!.notesHTML" class="max-w-full" />
<NoteViewer :note-html="slide!.noteHTML" class="max-w-full" />
</div>
<hr v-if="index < slidesWithNote.length - 1" class="border-gray-400/50 mb-8">
</div>
Expand Down
6 changes: 3 additions & 3 deletions packages/client/internals/WebCamera.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script setup lang="ts">
import { useDraggable, useEventListener, useStorage } from '@vueuse/core'
import { useDraggable, useEventListener, useLocalStorage } from '@vueuse/core'
import { computed, onMounted, ref, watchEffect } from 'vue'
import { currentCamera } from '../state'
import { recorder } from '../logic/recording'
const size = useStorage('slidev-webcam-size', Math.round(Math.min(window.innerHeight, (window.innerWidth) / 8)))
const position = useStorage('slidev-webcam-pos', {
const size = useLocalStorage('slidev-webcam-size', Math.round(Math.min(window.innerHeight, (window.innerWidth) / 8)))
const position = useLocalStorage('slidev-webcam-pos', {
x: window.innerWidth - size.value - 30,
y: window.innerHeight - size.value - 30,
}, undefined, { deep: true })
Expand Down
4 changes: 2 additions & 2 deletions packages/client/logic/dark.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { isClient, usePreferredDark, useStorage, useToggle } from '@vueuse/core'
import { isClient, useLocalStorage, usePreferredDark, useToggle } from '@vueuse/core'
import { computed, watch } from 'vue'
import { configs } from '../env'

const preferredDark = usePreferredDark()
const store = useStorage('slidev-color-schema', 'auto')
const store = useLocalStorage('slidev-color-schema', 'auto')

export const isColorSchemaConfigured = computed(() => configs.colorSchema !== 'auto')
export const isDark = computed<boolean>({
Expand Down
8 changes: 4 additions & 4 deletions packages/client/logic/drawings.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { computed, markRaw, nextTick, reactive, ref, watch } from 'vue'
import type { Brush, Options as DrauuOptions, DrawingMode } from 'drauu'
import { createDrauu } from 'drauu'
import { toReactive, useStorage } from '@vueuse/core'
import { toReactive, useLocalStorage } from '@vueuse/core'
import { drawingState, onPatch, patch } from '../state/drawings'
import { configs } from '../env'
import { currentPage, isPresenter } from './nav'
Expand All @@ -16,14 +16,14 @@ export const brushColors = [
'#000000',
]

export const drawingEnabled = useStorage('slidev-drawing-enabled', false)
export const drawingPinned = useStorage('slidev-drawing-pinned', false)
export const drawingEnabled = useLocalStorage('slidev-drawing-enabled', false)
export const drawingPinned = useLocalStorage('slidev-drawing-pinned', false)
export const canUndo = ref(false)
export const canRedo = ref(false)
export const canClear = ref(false)
export const isDrawing = ref(false)

export const brush = toReactive(useStorage<Brush>('slidev-drawing-brush', {
export const brush = toReactive(useLocalStorage<Brush>('slidev-drawing-brush', {
color: brushColors[0],
size: 4,
mode: 'stylus',
Expand Down
4 changes: 2 additions & 2 deletions packages/client/logic/recording.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Ref } from 'vue'
import { nextTick, ref, shallowRef, watch } from 'vue'
import { useDevicesList, useEventListener, useStorage } from '@vueuse/core'
import { useDevicesList, useEventListener, useLocalStorage } from '@vueuse/core'
import { isTruthy } from '@antfu/utils'
import type RecorderType from 'recordrtc'
import type { Options as RecorderOptions } from 'recordrtc'
Expand All @@ -11,7 +11,7 @@ type MimeType = Defined<RecorderOptions['mimeType']>

export const recordingName = ref('')
export const recordCamera = ref(true)
export const mimeType = useStorage<MimeType>('slidev-record-mimetype', 'video/webm')
export const mimeType = useLocalStorage<MimeType>('slidev-record-mimetype', 'video/webm')

export const mimeExtMap: Record<string, string> = {
'video/webm': 'webm',
Expand Down
39 changes: 22 additions & 17 deletions packages/client/routes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { RouteRecordRaw } from 'vue-router'
import type { SlideTransition } from '@slidev/types'
import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
import Play from './internals/Play.vue'
import Print from './internals/Print.vue'
Expand All @@ -25,24 +24,31 @@ export const routes: RouteRecordRaw[] = [
]

if (__SLIDEV_FEATURE_PRESENTER__) {
function passwordGuard(to: RouteLocationNormalized) {
if (!_configs.remote || _configs.remote === to.query.password)
return true
if (_configs.remote && to.query.password === undefined) {
// eslint-disable-next-line no-alert
const password = prompt('Enter password')
if (_configs.remote === password)
return true
}
if (to.params.no)
return { path: `/${to.params.no}` }
return { path: '' }
}
routes.push({ path: '/presenter/print', component: () => import('./internals/PresenterPrint.vue') })
routes.push({
name: 'notes',
path: '/notes',
component: () => import('./internals/NotesView.vue'),
beforeEnter: passwordGuard,
})
routes.push({
name: 'presenter',
path: '/presenter/:no',
component: () => import('./internals/Presenter.vue'),
beforeEnter: (to) => {
if (!_configs.remote || _configs.remote === to.query.password)
return true
if (_configs.remote && to.query.password === undefined) {
// eslint-disable-next-line no-alert
const password = prompt('Enter password')
if (_configs.remote === password)
return true
}
if (to.params.no)
return { path: `/${to.params.no}` }
return { path: '' }
},
beforeEnter: passwordGuard,
})
routes.push({
path: '/presenter',
Expand All @@ -65,14 +71,13 @@ declare module 'vue-router' {
start: number
end: number
note?: string
notesHTML?: string
noteHTML?: string
id: number
no: number
filepath: string
title?: string
level?: number
}
transition?: string | SlideTransition
// private fields
__clicksElements: HTMLElement[]
__preloaded?: boolean
Expand Down
17 changes: 16 additions & 1 deletion packages/client/setup/root.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* __imports__ */
import { watch } from 'vue'
import { useHead, useHtmlAttrs } from '@vueuse/head'
import { nanoid } from 'nanoid'
import { configs } from '../env'
import { initSharedState, onPatch, patch } from '../state/shared'
import { initDrawingState } from '../state/drawings'
Expand All @@ -9,7 +10,7 @@ import { router } from '../routes'

export default function setupRoot() {
// @ts-expect-error injected in runtime
// eslint-disable-next-line @typescript-eslint/no-unused-vars

const injection_arg = undefined

/* __injections__ */
Expand All @@ -20,17 +21,31 @@ export default function setupRoot() {
initSharedState(`${title} - shared`)
initDrawingState(`${title} - drawings`)

const id = nanoid()

// update shared state
function updateSharedState() {
if (isPresenter.value) {
patch('page', +currentPage.value)
patch('clicks', clicks.value)
}
else {
patch('viewerPage', +currentPage.value)
patch('viewerClicks', clicks.value)
}
patch('lastUpdate', {
id,
type: isPresenter.value ? 'presenter' : 'viewer',
time: new Date().getTime(),
})
}
router.afterEach(updateSharedState)
watch(clicks, updateSharedState)

onPatch((state) => {
const routePath = router.currentRoute.value.path
if (!routePath.match(/^\/(\d+|presenter)\/?/))
return
if (+state.page !== +currentPage.value || clicks.value !== state.clicks) {
router.replace({
path: getPath(state.page),
Expand Down
16 changes: 8 additions & 8 deletions packages/client/state/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen, useMagicKeys, useStorage, useToggle, useWindowSize } from '@vueuse/core'
import { breakpointsTailwind, isClient, useActiveElement, useBreakpoints, useFullscreen, useLocalStorage, useMagicKeys, useToggle, useWindowSize } from '@vueuse/core'
import { computed, ref } from 'vue'
import { slideAspect } from '../env'

Expand All @@ -20,13 +20,13 @@ export const activeElement = useActiveElement()
export const isInputting = computed(() => ['INPUT', 'TEXTAREA'].includes(activeElement.value?.tagName || '') || activeElement.value?.classList.contains('CodeMirror-code'))
export const isOnFocus = computed(() => ['BUTTON', 'A'].includes(activeElement.value?.tagName || ''))

export const currentCamera = useStorage<string>('slidev-camera', 'default')
export const currentMic = useStorage<string>('slidev-mic', 'default')
export const slideScale = useStorage<number>('slidev-scale', 0)
export const currentCamera = useLocalStorage<string>('slidev-camera', 'default')
export const currentMic = useLocalStorage<string>('slidev-mic', 'default')
export const slideScale = useLocalStorage<number>('slidev-scale', 0)

export const showOverview = useStorage('slidev-show-overview', false)
export const showPresenterCursor = useStorage('slidev-presenter-cursor', true)
export const showEditor = useStorage('slidev-show-editor', false)
export const editorWidth = useStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 100)
export const showOverview = useLocalStorage('slidev-show-overview', false)
export const showPresenterCursor = useLocalStorage('slidev-presenter-cursor', true)
export const showEditor = useLocalStorage('slidev-show-editor', false)
export const editorWidth = useLocalStorage('slidev-editor-width', isClient ? window.innerWidth * 0.4 : 100)

export const toggleOverview = useToggle(showOverview)
19 changes: 18 additions & 1 deletion packages/client/state/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,27 @@ export interface SharedState {
x: number
y: number
}

viewerPage: number
viewerClicks: number

lastUpdate?: {
id: string
type: 'presenter' | 'viewer'
time: number
}
}

const { init, onPatch, patch, state } = createSyncState<SharedState>(serverState, {
page: 1,
clicks: 0,
viewerPage: 1,
viewerClicks: 0,
})
export { init as initSharedState, onPatch, patch, state as sharedState }

export {
init as initSharedState,
onPatch,
patch,
state as sharedState,
}
2 changes: 1 addition & 1 deletion packages/slidev/node/plugins/loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ md.use(mila, {
function prepareSlideInfo(data: SlideInfo): SlideInfoExtended {
return {
...data,
notesHTML: md.render(data?.note || ''),
noteHTML: md.render(data?.note || ''),
}
}

Expand Down
Loading

0 comments on commit 07a3bda

Please sign in to comment.