Skip to content

Commit

Permalink
feat: introduce list overview
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Feb 24, 2024
1 parent b8f63cc commit 667028e
Show file tree
Hide file tree
Showing 19 changed files with 194 additions and 35 deletions.
2 changes: 1 addition & 1 deletion demo/composable-vue/components/DarkToggle.vue
Expand Up @@ -4,7 +4,7 @@ import { isDark, toggleDark } from '@slidev/client/logic/dark.ts'

<template>
<button
class="bg-$slidev-theme-primary rounded border-b-2 border-green-900 text-sm px-2 pt-1.5 pb-1 inline-block !outline-none hover:bg-opacity-85"
class="bg-primary rounded border-b-2 border-green-900 text-sm px-2 pt-1.5 pb-1 inline-block !outline-none hover:bg-opacity-85"
@click="toggleDark"
>
<div class="flex">
Expand Down
2 changes: 1 addition & 1 deletion demo/composable-vue/slides.md
Expand Up @@ -92,7 +92,7 @@ layout: center
<a class="!border-none" href="https://github.com/vueuse/vueuse" target="__blank"><img class="mt-2 h-4 inline mx-0.5" alt="GitHub stars" src="https://img.shields.io/github/stars/vueuse/vueuse?style=social"></a>
</div>
</div>
<div class="border-l border-gray-400 border-opacity-25 !all:leading-12 !all:list-none my-auto">
<div class="border-l border-main !all:leading-12 !all:list-none my-auto">

- Works for both Vue 2 and 3
- Tree-shakeable ESM
Expand Down
3 changes: 1 addition & 2 deletions packages/client/internals/DrawingControls.vue
Expand Up @@ -40,8 +40,7 @@ function setBrushColor(color: typeof brush.color) {

<template>
<Draggable
class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200 z-20"
dark="border border-gray-400 border-opacity-10"
class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200 z-20 border border-main"
:class="drawingEnabled ? '' : drawingPinned ? 'opacity-40 hover:opacity-90' : 'opacity-0 pointer-events-none'"
storage-key="slidev-drawing-pos"
:initial-x="10"
Expand Down
4 changes: 2 additions & 2 deletions packages/client/internals/Editor.vue
Expand Up @@ -172,13 +172,13 @@ throttledWatch(
<div class="flex pb-2 text-xl -mt-1">
<div class="mr-4 rounded flex">
<IconButton
title="Switch to content tab" :class="tab === 'content' ? 'text-$slidev-theme-primary' : ''"
title="Switch to content tab" :class="tab === 'content' ? 'text-primary' : ''"
@click="switchTab('content')"
>
<carbon:account />
</IconButton>
<IconButton
title="Switch to notes tab" :class="tab === 'note' ? 'text-$slidev-theme-primary' : ''"
title="Switch to notes tab" :class="tab === 'note' ? 'text-primary' : ''"
@click="switchTab('note')"
>
<carbon:align-box-bottom-right />
Expand Down
5 changes: 4 additions & 1 deletion packages/client/internals/IconButton.vue
@@ -1,12 +1,15 @@
<script setup lang="ts">
defineProps<{
title: string
icon?: string
}>()
</script>

<template>
<button class="slidev-icon-btn" :title="title" v-bind="$attrs">
<span class="sr-only">{{ title }}</span>
<slot />
<slot>
<div :class="icon" />
</slot>
</button>
</template>
2 changes: 1 addition & 1 deletion packages/client/internals/NavControls.vue
Expand Up @@ -34,7 +34,7 @@ function onMouseLeave() {
const barStyle = computed(() => props.persist
? 'text-$slidev-controls-foreground bg-transparent'
: 'rounded-md bg-main shadow dark:border dark:border-gray-400 dark:border-opacity-10')
: 'rounded-md bg-main shadow dark:border dark:border-main')
const RecordingControls = shallowRef<any>()
if (__SLIDEV_FEATURE_RECORD__)
Expand Down
2 changes: 1 addition & 1 deletion packages/client/internals/NoteDisplay.vue
Expand Up @@ -27,7 +27,7 @@ defineEmits(['click'])
</div>
<div
v-else
class="prose overflow-auto outline-none opacity-50 italic"
class="prose overflow-auto outline-none opacity-50 italic select-none"
:class="props.class"
@click="$emit('click')"
>
Expand Down
8 changes: 5 additions & 3 deletions packages/client/internals/NoteEditor.vue
@@ -1,11 +1,13 @@
<script setup lang="ts">
import { ignorableWatch, onClickOutside, useVModel } from '@vueuse/core'
import { ref, watch, watchEffect } from 'vue'
import { currentSlideId } from '../logic/nav'
import { useDynamicSlideInfo } from '../logic/note'
import NoteDisplay from './NoteDisplay.vue'
const props = defineProps({
no: {
type: Number,
},
class: {
default: '',
},
Expand All @@ -25,15 +27,15 @@ const emit = defineEmits([
])
const editing = useVModel(props, 'editing', emit, { passive: true })
const { info, update } = useDynamicSlideInfo(currentSlideId)
const { info, update } = useDynamicSlideInfo(props.no)
const note = ref('')
let timer: any
const { ignoreUpdates } = ignorableWatch(
note,
(v) => {
const id = currentSlideId.value
const id = props.no
clearTimeout(timer)
timer = setTimeout(() => {
update({ note: v }, id)
Expand Down
11 changes: 5 additions & 6 deletions packages/client/internals/NoteStatic.vue
@@ -1,20 +1,19 @@
<script setup lang="ts">
import { computed } from 'vue'
import { currentRoute } from '../logic/nav'
import { useSlideInfo } from '../logic/note'
import NoteDisplay from './NoteDisplay.vue'
const props = defineProps<{
no?: number
class?: string
}>()
const note = computed(() => currentRoute.value?.meta?.slide?.note)
const noteHtml = computed(() => currentRoute.value?.meta?.slide?.noteHTML)
const { info } = useSlideInfo(props.no)
</script>

<template>
<NoteDisplay
:class="props.class"
:note="note"
:note-html="noteHtml"
:note="info?.note"
:note-html="info?.noteHTML"
/>
</template>
5 changes: 2 additions & 3 deletions packages/client/internals/RecordingDialog.vue
Expand Up @@ -109,7 +109,7 @@ async function start() {
}
input[type="text"] {
@apply border border-gray-400 rounded px-2 py-1;
@apply border border-main rounded px-2 py-1;
}
button {
Expand All @@ -118,8 +118,7 @@ async function start() {
}
button.cancel {
@apply bg-gray-400 text-white px-4 py-1 rounded border-b-2 border-gray-500;
@apply bg-opacity-50 border-opacity-50;
@apply bg-gray-400 bg-opacity-50 text-white px-4 py-1 rounded border-b-2 border-main;
@apply hover:(bg-opacity-75 border-opacity-75)
}
}
Expand Down
23 changes: 17 additions & 6 deletions packages/client/internals/SlidesOverview.vue
Expand Up @@ -7,6 +7,7 @@ import { currentPage, go as goSlide, rawRoutes } from '../logic/nav'
import { currentOverviewPage, overviewRowCount } from '../logic/overview'
import { useFixedClicks } from '../composables/useClicks'
import { getSlideClass } from '../utils'
import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
import SlideContainer from './SlideContainer.vue'
import SlideWrapper from './SlideWrapper'
import DrawingPreview from './DrawingPreview.vue'
Expand Down Expand Up @@ -112,7 +113,7 @@ watchEffect(() => {
>
<div
v-show="value"
class="bg-main !bg-opacity-75 p-16 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
class="bg-main !bg-opacity-75 p-16 py-20 overflow-y-auto backdrop-blur-5px fixed left-0 right-0 top-0 h-[calc(var(--vh,1vh)*100)]"
@click="close()"
>
<div
Expand All @@ -125,8 +126,8 @@ watchEffect(() => {
class="relative"
>
<div
class="inline-block border rounded border-opacity-50 overflow-hidden bg-main hover:border-$slidev-theme-primary transition"
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-$slidev-theme-primary' : 'border-gray-400'"
class="inline-block border rounded overflow-hidden bg-main hover:border-primary transition"
:class="(focus(idx + 1) || currentOverviewPage === idx + 1) ? 'border-primary' : 'border-main'"
:style="themeVars"
@click="go(+route.path)"
>
Expand Down Expand Up @@ -163,7 +164,17 @@ watchEffect(() => {
</div>
</div>
</Transition>
<IconButton v-if="value" title="Close" class="fixed text-2xl top-4 right-4 text-gray-400" @click="close">
<carbon:close />
</IconButton>
<div class="fixed top-4 right-4 text-gray-400 flex items-center gap-4">
<RouterLink
v-if="__DEV__"
to="/overview"
tab-index="-1"
class="border-main border px3 py1 rounded hover:bg-gray/5 hover:text-primary"
>
List overview
</RouterLink>
<IconButton v-if="value" title="Close" class="text-2xl" @click="close">
<carbon:close />
</IconButton>
</div>
</template>
2 changes: 1 addition & 1 deletion packages/client/pages/notes.vue
Expand Up @@ -53,7 +53,7 @@ function decreaseFontSize() {
:placeholder="`No notes for Slide ${pageNo}.`"
/>
</div>
<div class="flex-none border-t border-gray-400 border-opacity-20">
<div class="flex-none border-t border-main">
<div class="flex gap-1 items-center px-6 py-3">
<IconButton :title="isFullscreen ? 'Close fullscreen' : 'Enter fullscreen'" @click="toggleFullscreen">
<carbon:minimize v-if="isFullscreen" />
Expand Down
131 changes: 131 additions & 0 deletions packages/client/pages/overview.vue
@@ -0,0 +1,131 @@
<script setup lang="ts">
import { nextTick, onMounted, reactive, ref } from 'vue'
import { themeVars } from '../env'
import { rawRoutes } from '../logic/nav'
import { useFixedClicks } from '../composables/useClicks'
import { isColorSchemaConfigured, isDark, toggleDark } from '../logic/dark'
import { getSlideClass } from '../utils'
import SlideContainer from '../internals/SlideContainer.vue'
import SlideWrapper from '../internals/SlideWrapper'
import DrawingPreview from '../internals/DrawingPreview.vue'
import NoteDisplay from '../internals/NoteDisplay.vue'
import IconButton from '../internals/IconButton.vue'
const cardWidth = 450
const blocks: Map<number, HTMLElement> = reactive(new Map())
const activeBlocks = ref<number[]>([])
function isElementInViewport(el: HTMLElement) {
const rect = el.getBoundingClientRect()
const delta = 20
return (
rect.top >= 0 - delta
&& rect.left >= 0 - delta
&& rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + delta
&& rect.right <= (window.innerWidth || document.documentElement.clientWidth) + delta
)
}
function checkActiveBlocks() {
const active: number[] = []
Array.from(blocks.entries())
.forEach(([idx, el]) => {
if (isElementInViewport(el))
active.push(idx)
})
activeBlocks.value = active
}
function openSlideInNewTab(path: string) {
const a = document.createElement('a')
a.target = '_blank'
a.href = path
a.click()
}
function scrollToSlide(idx: number) {
const el = blocks.get(idx)
if (el)
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
onMounted(() => {
nextTick(() => {
checkActiveBlocks()
})
})
</script>

<template>
<div class="h-screen w-screen of-hidden flex">
<nav class="h-full flex flex-col border-r border-main p2">
<div class="of-auto flex flex-col flex-auto items-center">
<button
v-for="(route, idx) of rawRoutes"
:key="route.path"
class="relative transition duration-300 w-8 h-8 rounded hover:bg-gray:10 hover:op100"
:class="[
activeBlocks.includes(idx) ? 'op100 text-primary' : 'op20',
]"
@click="scrollToSlide(idx)"
>
<div>{{ idx + 1 }}</div>
</button>
</div>
<IconButton
v-if="!isColorSchemaConfigured"
:title="isDark ? 'Switch to light mode theme' : 'Switch to dark mode theme'"
@click="toggleDark()"
>
<carbon-moon v-if="isDark" />
<carbon-sun v-else />
</IconButton>
</nav>
<main
class="flex-1 h-full of-auto"
:style="`grid-template-columns: repeat(auto-fit,minmax(${cardWidth}px,1fr))`"
@scroll="checkActiveBlocks"
>
<div class="px4 py2 text-orange bg-orange:5">
<span font-bold>List Overview</span> is in beta, feedback is welcome!
</div>
<div
v-for="(route, idx) of rawRoutes"
:key="route.path"
:ref="el => blocks.set(idx, el)"
class="relative border-t border-main of-hidden flex gap-4 min-h-50"
>
<div class="select-none text-3xl op25 my4 w-13 text-right">
{{ idx + 1 }}
</div>
<div
class="border rounded border-main overflow-hidden bg-main my5"
:style="themeVars"
@dblclick="openSlideInNewTab(route.path)"
>
<SlideContainer
:key="route.path"
:width="cardWidth"
:clicks-disabled="true"
class="pointer-events-none"
>
<SlideWrapper
:is="route.component"
v-if="route?.component"
:clicks-context="useFixedClicks(route, 99999)[1]"
:class="getSlideClass(route)"
:route="route"
render-context="overview"
/>
<DrawingPreview :page="+route.path" />
</SlideContainer>
</div>
<NoteDisplay
:note="route.meta?.slide?.note"
:note-html="route.meta?.slide?.noteHTML"
/>
</div>
</main>
</div>
</template>
6 changes: 5 additions & 1 deletion packages/client/pages/presenter.vue
Expand Up @@ -2,7 +2,7 @@
import { useHead } from '@unhead/vue'
import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue'
import { useMouse, useWindowFocus } from '@vueuse/core'
import { clicksContext, currentPage, currentRoute, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
import { clicksContext, currentPage, currentRoute, currentSlideId, hasNext, nextRoute, queryClicks, rawRoutes, total, useSwipeControls } from '../logic/nav'
import { decreasePresenterFontSize, increasePresenterFontSize, presenterLayout, presenterNotesFontSize, showEditor, showOverview, showPresenterCursor } from '../state'
import { configs, themeVars } from '../env'
import { sharedState } from '../state/shared'
Expand Down Expand Up @@ -140,12 +140,16 @@ onMounted(() => {
<div v-else class="grid-section note grid grid-rows-[1fr_min-content] overflow-hidden">
<NoteEditor
v-if="__DEV__"
:key="`edit-${currentSlideId}`"
:no="currentSlideId"
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
:editing="notesEditing"
:style="{ fontSize: `${presenterNotesFontSize}em` }"
/>
<NoteStatic
v-else
:key="`static-${currentSlideId}`"
:no="currentSlideId"
class="w-full max-w-full h-full overflow-auto p-2 lg:p-4"
:style="{ fontSize: `${presenterNotesFontSize}em` }"
/>
Expand Down
2 changes: 1 addition & 1 deletion packages/client/pages/presenter/print.vue
Expand Up @@ -58,7 +58,7 @@ const slidesWithNote = computed(() => rawRoutes
</h2>
<NoteDisplay :note-html="slide!.noteHTML" class="max-w-full" />
</div>
<hr v-if="index < slidesWithNote.length - 1" class="border-gray-400/50 mb-8">
<hr v-if="index < slidesWithNote.length - 1" class="border-main mb-8">
</div>
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions packages/client/routes.ts
Expand Up @@ -57,6 +57,11 @@ if (__SLIDEV_FEATURE_PRESENTER__) {
path: '/entry',
component: () => import('./pages/entry.vue'),
})
routes.push({
name: 'overview',
path: '/overview',
component: () => import('./pages/overview.vue'),
})
routes.push({
name: 'notes',
path: '/notes',
Expand Down

0 comments on commit 667028e

Please sign in to comment.