@slidev/client 0.48.0-beta.0 → 0.48.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/builtin/CodeBlockWrapper.vue +4 -3
  2. package/builtin/KaTexBlockWrapper.vue +4 -3
  3. package/builtin/RenderWhen.vue +3 -3
  4. package/builtin/SlideCurrentNo.vue +2 -3
  5. package/builtin/SlidesTotal.vue +3 -4
  6. package/builtin/SlidevVideo.vue +8 -6
  7. package/builtin/Toc.vue +3 -3
  8. package/builtin/Tweet.vue +4 -15
  9. package/builtin/VClickGap.vue +3 -5
  10. package/builtin/VClicks.ts +9 -9
  11. package/composables/useClicks.ts +16 -13
  12. package/composables/useTweetScript.ts +17 -0
  13. package/constants.ts +56 -8
  14. package/context.ts +70 -0
  15. package/internals/DrawingControls.vue +66 -46
  16. package/internals/DrawingLayer.vue +3 -2
  17. package/internals/Editor.vue +68 -34
  18. package/internals/IconButton.vue +15 -0
  19. package/internals/InfoDialog.vue +1 -1
  20. package/internals/Modal.vue +1 -1
  21. package/internals/NavControls.vue +37 -68
  22. package/internals/NoteDisplay.vue +1 -1
  23. package/internals/NoteEditor.vue +10 -6
  24. package/internals/NoteStatic.vue +5 -6
  25. package/internals/PrintContainer.vue +3 -2
  26. package/internals/PrintSlideClick.vue +3 -2
  27. package/internals/RecordingControls.vue +10 -14
  28. package/internals/RecordingDialog.vue +2 -3
  29. package/internals/SlideContainer.vue +7 -6
  30. package/internals/SlideWrapper.ts +28 -12
  31. package/internals/SlidesOverview.vue +19 -9
  32. package/logic/drawings.ts +6 -3
  33. package/logic/nav.ts +6 -6
  34. package/logic/note.ts +7 -7
  35. package/main.ts +5 -3
  36. package/modules/context.ts +4 -3
  37. package/modules/{directives.ts → v-click.ts} +15 -15
  38. package/modules/v-mark.ts +159 -0
  39. package/package.json +21 -13
  40. package/{internals/EntrySelect.vue → pages/entry.vue} +7 -0
  41. package/{internals/NotesView.vue → pages/notes.vue} +9 -12
  42. package/pages/overview.vue +157 -0
  43. package/{internals/Play.vue → pages/play.vue} +10 -10
  44. package/{internals/PresenterPrint.vue → pages/presenter/print.vue} +7 -5
  45. package/{internals/Presenter.vue → pages/presenter.vue} +24 -22
  46. package/{internals/Print.vue → pages/print.vue} +2 -2
  47. package/routes.ts +25 -19
  48. package/setup/codemirror.ts +7 -0
  49. package/state/index.ts +11 -9
  50. package/styles/index.css +5 -0
  51. package/styles/layouts-base.css +6 -4
  52. package/styles/vars.css +1 -0
  53. package/uno.config.ts +6 -2
  54. package/internals/HiddenText.vue +0 -9
@@ -14,11 +14,12 @@ Learn more: https://sli.dev/guide/syntax.html#line-highlighting
14
14
  <script setup lang="ts">
15
15
  import { parseRangeString } from '@slidev/parser/core'
16
16
  import { useClipboard } from '@vueuse/core'
17
- import { computed, inject, onMounted, onUnmounted, ref, watchEffect } from 'vue'
17
+ import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
18
18
  import type { PropType } from 'vue'
19
19
  import { configs } from '../env'
20
20
  import { makeId } from '../logic/utils'
21
- import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET, injectionClicksContext } from '../constants'
21
+ import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET } from '../constants'
22
+ import { useSlideContext } from '../context'
22
23
 
23
24
  const props = defineProps({
24
25
  ranges: {
@@ -47,7 +48,7 @@ const props = defineProps({
47
48
  },
48
49
  })
49
50
 
50
- const clicks = inject(injectionClicksContext)?.value
51
+ const { $clicksContext: clicks } = useSlideContext()
51
52
  const el = ref<HTMLDivElement>()
52
53
  const id = makeId()
53
54
 
@@ -20,11 +20,12 @@ Learn more: https://sli.dev/guide/syntax.html#latex-line-highlighting
20
20
  -->
21
21
 
22
22
  <script setup lang="ts">
23
- import { computed, inject, onMounted, onUnmounted, ref, watchEffect } from 'vue'
23
+ import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
24
24
  import type { PropType } from 'vue'
25
25
  import { parseRangeString } from '@slidev/parser'
26
- import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET, injectionClicksContext } from '../constants'
26
+ import { CLASS_VCLICK_HIDDEN, CLASS_VCLICK_TARGET } from '../constants'
27
27
  import { makeId } from '../logic/utils'
28
+ import { useSlideContext } from '../context'
28
29
 
29
30
  const props = defineProps({
30
31
  ranges: {
@@ -45,7 +46,7 @@ const props = defineProps({
45
46
  },
46
47
  })
47
48
 
48
- const clicks = inject(injectionClicksContext)?.value
49
+ const { $clicksContext: clicks } = useSlideContext()
49
50
  const el = ref<HTMLDivElement>()
50
51
  const id = makeId()
51
52
 
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import type { RenderContext } from '@slidev/types'
3
- import { computed, inject, ref } from 'vue'
3
+ import { computed, ref } from 'vue'
4
4
  import { useElementVisibility } from '@vueuse/core'
5
- import { injectionRenderContext } from '../constants'
5
+ import { useSlideContext } from '../context'
6
6
 
7
7
  type Context = 'main' | 'visible' | RenderContext
8
8
 
@@ -16,7 +16,7 @@ const targetVisible = useElementVisibility(target)
16
16
  // When context has `visible`, we need to wrap the content with a div to track the visibility
17
17
  const needsDomWrapper = Array.isArray(context) ? context.includes('visible') : context === 'visible'
18
18
 
19
- const currentContext = inject(injectionRenderContext)
19
+ const { $renderContext: currentContext } = useSlideContext()
20
20
  const shouldRender = computed(() => {
21
21
  const anyContext = Array.isArray(context) ? context.some(contextMatch) : contextMatch(context)
22
22
  const allConditions = Array.isArray(context) ? context.every(conditionsMatch) : conditionsMatch(context)
@@ -1,8 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { inject } from 'vue'
3
- import { injectionCurrentPage } from '../constants'
2
+ import { useSlideContext } from '../context'
4
3
 
5
- const $page = inject(injectionCurrentPage)
4
+ const { $page } = useSlideContext()
6
5
  </script>
7
6
 
8
7
  <template>
@@ -1,10 +1,9 @@
1
1
  <script setup lang="ts">
2
- import { inject } from 'vue'
3
- import { injectionSlidevContext } from '../constants'
2
+ import { useSlideContext } from '../context'
4
3
 
5
- const $slidev = inject(injectionSlidevContext)
4
+ const { $nav } = useSlideContext()
6
5
  </script>
7
6
 
8
7
  <template>
9
- <span>{{ $slidev.nav.total }}</span>
8
+ <span>{{ $nav.total }}</span>
10
9
  </template>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { computed, inject, onMounted, onUnmounted, ref, watch } from 'vue'
3
- import { injectionClicksContext, injectionRenderContext, injectionRoute, injectionSlidevContext } from '../constants'
2
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
3
+ import { useSlideContext } from '../context'
4
4
 
5
5
  const props = defineProps<{
6
6
  autoPlay?: boolean | 'once' | 'resume' | 'resumeOnce'
@@ -8,10 +8,12 @@ const props = defineProps<{
8
8
  autoReset?: 'slide' | 'click'
9
9
  }>()
10
10
 
11
- const $slidev = inject(injectionSlidevContext)
12
- const route = inject(injectionRoute)
13
- const currentContext = inject(injectionRenderContext)
14
- const clicks = inject(injectionClicksContext)?.value
11
+ const {
12
+ $slidev,
13
+ $clicksContext: clicks,
14
+ $renderContext: currentContext,
15
+ $route: route,
16
+ } = useSlideContext()
15
17
 
16
18
  const video = ref<HTMLMediaElement>()
17
19
  const played = ref(false)
package/builtin/Toc.vue CHANGED
@@ -8,9 +8,9 @@ Usage:
8
8
  <Toc columns='2' maxDepth='3' mode='onlySiblings'/>
9
9
  -->
10
10
  <script setup lang='ts'>
11
- import { computed, inject } from 'vue'
11
+ import { computed } from 'vue'
12
12
  import type { TocItem } from '@slidev/types'
13
- import { injectionSlidevContext } from '../constants'
13
+ import { useSlideContext } from '../context'
14
14
 
15
15
  const props = withDefaults(
16
16
  defineProps<{
@@ -33,7 +33,7 @@ const props = withDefaults(
33
33
  },
34
34
  )
35
35
 
36
- const $slidev = inject(injectionSlidevContext)
36
+ const { $slidev } = useSlideContext()
37
37
 
38
38
  function filterTreeDepth(tree: TocItem[], level = 1): TocItem[] {
39
39
  if (level > Number(props.maxDepth)) {
package/builtin/Tweet.vue CHANGED
@@ -7,9 +7,9 @@ Usage:
7
7
  -->
8
8
 
9
9
  <script setup lang="ts">
10
- import { useScriptTag } from '@vueuse/core'
11
10
  import { getCurrentInstance, onMounted, ref } from 'vue'
12
11
  import { isDark } from '../logic/dark'
12
+ import { useTweetScript } from '../composables/useTweetScript'
13
13
 
14
14
  const props = defineProps<{
15
15
  id: string | number
@@ -41,21 +41,10 @@ async function create() {
41
41
  }
42
42
 
43
43
  // @ts-expect-error global
44
- if (window?.twttr?.widgets) {
44
+ if (window?.twttr?.widgets)
45
45
  onMounted(create)
46
- }
47
- else {
48
- useScriptTag(
49
- 'https://platform.twitter.com/widgets.js',
50
- () => {
51
- if (vm.isMounted)
52
- create()
53
- else
54
- onMounted(create, vm)
55
- },
56
- { async: true },
57
- )
58
- }
46
+ else
47
+ useTweetScript(vm, create)
59
48
  </script>
60
49
 
61
50
  <template>
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { Fragment, inject, onMounted, watchEffect } from 'vue'
3
- import { injectionClicksContext } from '../constants'
2
+ import { Fragment, onMounted, watchEffect } from 'vue'
4
3
  import { makeId } from '../logic/utils'
4
+ import { useSlideContext } from '../context'
5
5
 
6
6
  const props = defineProps({
7
7
  size: {
@@ -10,12 +10,10 @@ const props = defineProps({
10
10
  },
11
11
  })
12
12
 
13
- const clicksRef = inject(injectionClicksContext)
13
+ const { $clicksContext: clicks } = useSlideContext()
14
14
 
15
15
  onMounted(() => {
16
16
  watchEffect((onCleanup) => {
17
- const clicks = clicksRef?.value
18
-
19
17
  if (!clicks || clicks.disabled)
20
18
  return
21
19
 
@@ -62,12 +62,12 @@ export default defineComponent({
62
62
  }) as T
63
63
  }
64
64
 
65
- let defaults = this.$slots.default?.()
65
+ let elements = this.$slots.default?.()
66
66
 
67
- if (!defaults)
67
+ if (!elements)
68
68
  return
69
69
 
70
- defaults = openAllTopLevelSlots(toArray(defaults))
70
+ elements = openAllTopLevelSlots(toArray(elements))
71
71
 
72
72
  const mapSubList = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
73
73
  const vNodes = openAllTopLevelSlots(children).map((i) => {
@@ -116,23 +116,23 @@ export default defineComponent({
116
116
  })
117
117
 
118
118
  // handle ul, ol list
119
- if (defaults.length === 1 && listTags.includes(defaults[0].type as string) && Array.isArray(defaults[0].children))
120
- return h(defaults[0], {}, [mapChildren(defaults[0].children), lastGap()])
119
+ if (elements.length === 1 && listTags.includes(elements[0].type as string) && Array.isArray(elements[0].children))
120
+ return h(elements[0], {}, [...mapChildren(elements[0].children), lastGap()])
121
121
 
122
- if (defaults.length === 1 && defaults[0].type as string === 'table') {
123
- const tableNode = defaults[0]
122
+ if (elements.length === 1 && elements[0].type as string === 'table') {
123
+ const tableNode = elements[0]
124
124
  if (Array.isArray(tableNode.children)) {
125
125
  return h(tableNode, {}, tableNode.children.map((i) => {
126
126
  if (!isVNode(i))
127
127
  return i
128
128
  else if (i.type === 'tbody' && Array.isArray(i.children))
129
- return h(i, {}, [mapChildren(i.children), lastGap()])
129
+ return h(i, {}, [...mapChildren(i.children), lastGap()])
130
130
  else
131
131
  return h(i)
132
132
  }))
133
133
  }
134
134
  }
135
135
 
136
- return [mapChildren(defaults)[0], lastGap()]
136
+ return [...mapChildren(elements), lastGap()]
137
137
  },
138
138
  })
@@ -6,7 +6,7 @@ import type { RouteRecordRaw } from 'vue-router'
6
6
  import { currentRoute, isPrintMode, isPrintWithClicks, queryClicks, routeForceRefresh } from '../logic/nav'
7
7
  import { normalizeAtProp } from '../logic/utils'
8
8
 
9
- function useClicksContextBase(route: RouteRecordRaw | undefined, getCurrent: () => number): ClicksContext {
9
+ function useClicksContextBase(getCurrent: () => number, clicksOverrides?: number): ClicksContext {
10
10
  const relativeOffsets: ClicksContext['relativeOffsets'] = new Map()
11
11
  const map: ClicksContext['map'] = shallowReactive(new Map())
12
12
 
@@ -53,7 +53,7 @@ function useClicksContextBase(route: RouteRecordRaw | undefined, getCurrent: ()
53
53
  get total() {
54
54
  // eslint-disable-next-line no-unused-expressions
55
55
  routeForceRefresh.value
56
- return route?.meta?.clicks
56
+ return clicksOverrides
57
57
  ?? Math.max(0, ...[...map.values()].map(v => v.max || 0))
58
58
  },
59
59
  }
@@ -63,21 +63,24 @@ export function usePrimaryClicks(route: RouteRecordRaw | undefined): ClicksConte
63
63
  if (route?.meta?.__clicksContext)
64
64
  return route.meta.__clicksContext
65
65
  const thisPath = +(route?.path ?? 99999)
66
- const context = useClicksContextBase(route, () => {
67
- const currentPath = +(currentRoute.value?.path ?? 99999)
68
- if (currentPath === thisPath)
69
- return queryClicks.value
70
- else if (currentPath > thisPath)
71
- return 99999
72
- else
73
- return 0
74
- })
66
+ const context = useClicksContextBase(
67
+ () => {
68
+ const currentPath = +(currentRoute.value?.path ?? 99999)
69
+ if (currentPath === thisPath)
70
+ return queryClicks.value
71
+ else if (currentPath > thisPath)
72
+ return 99999
73
+ else
74
+ return 0
75
+ },
76
+ route?.meta?.clicks,
77
+ )
75
78
  if (route?.meta)
76
79
  route.meta.__clicksContext = context
77
80
  return context
78
81
  }
79
82
 
80
- export function useFixedClicks(route: RouteRecordRaw | undefined, currentInit = 0): [Ref<number>, ClicksContext] {
83
+ export function useFixedClicks(route?: RouteRecordRaw | undefined, currentInit = 0): [Ref<number>, ClicksContext] {
81
84
  const current = ref(currentInit)
82
- return [current, useClicksContextBase(route, () => current.value)]
85
+ return [current, useClicksContextBase(() => current.value, route?.meta?.clicks)]
83
86
  }
@@ -0,0 +1,17 @@
1
+ import { createSharedComposable, useScriptTag } from '@vueuse/core'
2
+ import type { ComponentInternalInstance } from 'vue'
3
+ import { onMounted } from 'vue'
4
+
5
+ export const useTweetScript = createSharedComposable(
6
+ (vm: ComponentInternalInstance, create: () => void) =>
7
+ useScriptTag(
8
+ 'https://platform.twitter.com/widgets.js',
9
+ () => {
10
+ if (vm.isMounted)
11
+ create()
12
+ else
13
+ onMounted(create, vm)
14
+ },
15
+ { async: true },
16
+ ),
17
+ )
package/constants.ts CHANGED
@@ -3,14 +3,16 @@ import type { RouteRecordRaw } from 'vue-router'
3
3
  import type { ClicksContext, RenderContext } from '@slidev/types'
4
4
  import type { SlidevContext } from './modules/context'
5
5
 
6
- export const injectionClicksContext: InjectionKey<Ref<ClicksContext>> = Symbol('slidev-clicks-context')
7
- export const injectionCurrentPage: InjectionKey<Ref<number>> = Symbol('slidev-page')
8
- export const injectionSlideScale: InjectionKey<ComputedRef<number>> = Symbol('slidev-slide-scale')
9
- export const injectionSlidevContext: InjectionKey<UnwrapNestedRefs<SlidevContext>> = Symbol('slidev-slidev-context')
10
- export const injectionRoute: InjectionKey<RouteRecordRaw> = Symbol('slidev-route')
11
- export const injectionRenderContext: InjectionKey<Ref<RenderContext>> = Symbol('slidev-render-context')
12
- export const injectionActive: InjectionKey<Ref<boolean>> = Symbol('slidev-active')
13
- export const injectionFrontmatter: InjectionKey<Record<string, any>> = Symbol('slidev-fontmatter')
6
+ // Here we use string literal instead of symbols to make HMR more stable
7
+ // The value of the injections keys are implementation details, you should always use them with the reference to the constant instead of the value
8
+ export const injectionClicksContext = '$$slidev-clicks-context' as unknown as InjectionKey<Ref<ClicksContext>>
9
+ export const injectionCurrentPage = '$$slidev-page' as unknown as InjectionKey<Ref<number>>
10
+ export const injectionSlideScale = '$$slidev-slide-scale' as unknown as InjectionKey<ComputedRef<number>>
11
+ export const injectionSlidevContext = '$$slidev-context' as unknown as InjectionKey<UnwrapNestedRefs<SlidevContext>>
12
+ export const injectionRoute = '$$slidev-route' as unknown as InjectionKey<RouteRecordRaw>
13
+ export const injectionRenderContext = '$$slidev-render-context' as unknown as InjectionKey<Ref<RenderContext>>
14
+ export const injectionActive = '$$slidev-active' as unknown as InjectionKey<Ref<boolean>>
15
+ export const injectionFrontmatter = '$$slidev-fontmatter' as unknown as InjectionKey<Record<string, any>>
14
16
 
15
17
  export const CLASS_VCLICK_TARGET = 'slidev-vclick-target'
16
18
  export const CLASS_VCLICK_HIDDEN = 'slidev-vclick-hidden'
@@ -24,3 +26,49 @@ export const TRUST_ORIGINS = [
24
26
  'localhost',
25
27
  '127.0.0.1',
26
28
  ]
29
+
30
+ export const FRONTMATTER_FIELDS = [
31
+ 'clicks',
32
+ 'disabled',
33
+ 'hide',
34
+ 'hideInToc',
35
+ 'layout',
36
+ 'level',
37
+ 'preload',
38
+ 'routeAlias',
39
+ 'src',
40
+ 'title',
41
+ 'transition',
42
+ 'zoom',
43
+ ]
44
+
45
+ export const HEADMATTER_FIELDS = [
46
+ ...FRONTMATTER_FIELDS,
47
+ 'theme',
48
+ 'titleTemplate',
49
+ 'info',
50
+ 'author',
51
+ 'keywords',
52
+ 'presenter',
53
+ 'download',
54
+ 'exportFilename',
55
+ 'export',
56
+ 'highlighter',
57
+ 'lineNumbers',
58
+ 'monaco',
59
+ 'remoteAssets',
60
+ 'selectable',
61
+ 'record',
62
+ 'colorSchema',
63
+ 'routerMode',
64
+ 'aspectRatio',
65
+ 'canvasWidth',
66
+ 'themeConfig',
67
+ 'favicon',
68
+ 'plantUmlServer',
69
+ 'fonts',
70
+ 'defaults',
71
+ 'drawings',
72
+ 'htmlAttrs',
73
+ 'mdc',
74
+ ]
package/context.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { shallowRef, toRef } from 'vue'
2
+ import { injectLocal, objectOmit, provideLocal } from '@vueuse/core'
3
+ import { useFixedClicks } from './composables/useClicks'
4
+ import {
5
+ FRONTMATTER_FIELDS,
6
+ HEADMATTER_FIELDS,
7
+ injectionClicksContext,
8
+ injectionCurrentPage,
9
+ injectionFrontmatter,
10
+ injectionRenderContext,
11
+ injectionRoute,
12
+ injectionSlidevContext,
13
+ } from './constants'
14
+
15
+ const clicksContextFallback = shallowRef(useFixedClicks()[1])
16
+
17
+ /**
18
+ * Get the current slide context, should be called inside the setup function of a component inside slide
19
+ */
20
+ export function useSlideContext() {
21
+ const $slidev = injectLocal(injectionSlidevContext)!
22
+ const $nav = toRef($slidev, 'nav')
23
+ const $clicksContext = injectLocal(injectionClicksContext, clicksContextFallback)!.value
24
+ const $clicks = toRef($clicksContext, 'current')
25
+ const $page = injectLocal(injectionCurrentPage)!
26
+ const $renderContext = injectLocal(injectionRenderContext)!
27
+ const $frontmatter = injectLocal(injectionFrontmatter, {})
28
+ const $route = injectLocal(injectionRoute, undefined)
29
+
30
+ return {
31
+ $slidev,
32
+ $nav,
33
+ $clicksContext,
34
+ $clicks,
35
+ $page,
36
+ $route,
37
+ $renderContext,
38
+ $frontmatter,
39
+ }
40
+ }
41
+
42
+ export function provideFrontmatter(frontmatter: Record<string, any>) {
43
+ provideLocal(injectionFrontmatter, frontmatter)
44
+
45
+ const {
46
+ $slidev,
47
+ $page,
48
+ } = useSlideContext()
49
+
50
+ // update frontmatter in router to make HMR work better
51
+ const route = $slidev.nav.rawRoutes.find(i => i.path === String($page.value))
52
+ if (route?.meta?.slide?.frontmatter) {
53
+ for (const key of Object.keys(route.meta.slide.frontmatter)) {
54
+ if (!(key in frontmatter))
55
+ delete route.meta.slide.frontmatter[key]
56
+ }
57
+ Object.assign(route.meta.slide.frontmatter, frontmatter)
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Convert frontmatter options to props for v-bind
63
+ * It removes known options fields, and expose an extra `frontmatter` field that contains full frontmatter
64
+ */
65
+ export function frontmatterToProps(frontmatter: Record<string, any>, pageNo: number) {
66
+ return {
67
+ ...objectOmit(frontmatter, pageNo === 0 ? HEADMATTER_FIELDS : FRONTMATTER_FIELDS),
68
+ frontmatter,
69
+ }
70
+ }
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import { Menu } from 'floating-vue'
3
+ import 'floating-vue/dist/style.css'
2
4
  import {
3
5
  brush,
4
6
  brushColors,
@@ -13,7 +15,7 @@ import {
13
15
  } from '../logic/drawings'
14
16
  import VerticalDivider from './VerticalDivider.vue'
15
17
  import Draggable from './Draggable.vue'
16
- import HiddenText from './HiddenText.vue'
18
+ import IconButton from './IconButton.vue'
17
19
 
18
20
  function undo() {
19
21
  drauu.undo()
@@ -21,100 +23,118 @@ function undo() {
21
23
  function redo() {
22
24
  drauu.redo()
23
25
  }
26
+
27
+ let lastDrawingMode: typeof drawingMode.value = 'stylus'
24
28
  function setDrawingMode(mode: typeof drawingMode.value) {
25
29
  drawingMode.value = mode
26
30
  drawingEnabled.value = true
31
+ if (mode !== 'eraseLine')
32
+ lastDrawingMode = mode
27
33
  }
28
34
  function setBrushColor(color: typeof brush.color) {
29
35
  brush.color = color
30
36
  drawingEnabled.value = true
37
+ drawingMode.value = lastDrawingMode
31
38
  }
32
39
  </script>
33
40
 
34
41
  <template>
35
42
  <Draggable
36
- class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200"
37
- dark="border border-gray-400 border-opacity-10"
43
+ class="flex flex-wrap text-xl p-2 gap-1 rounded-md bg-main shadow transition-opacity duration-200 z-20 border border-main"
38
44
  :class="drawingEnabled ? '' : drawingPinned ? 'opacity-40 hover:opacity-90' : 'opacity-0 pointer-events-none'"
39
45
  storage-key="slidev-drawing-pos"
40
46
  :initial-x="10"
41
47
  :initial-y="10"
42
48
  >
43
- <button class="slidev-icon-btn" :class="{ shallow: drawingMode !== 'stylus' }" @click="setDrawingMode('stylus')">
44
- <HiddenText text="Draw with stylus" />
49
+ <IconButton title="Draw with stylus" :class="{ shallow: drawingMode !== 'stylus' }" @click="setDrawingMode('stylus')">
45
50
  <carbon:pen />
46
- </button>
47
- <button class="slidev-icon-btn" :class="{ shallow: drawingMode !== 'line' }" @click="setDrawingMode('line')">
48
- <HiddenText text="Draw a line" />
51
+ </IconButton>
52
+ <IconButton title="Draw a line" :class="{ shallow: drawingMode !== 'line' }" @click="setDrawingMode('line')">
49
53
  <svg width="1em" height="1em" class="-mt-0.5" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
50
54
  <path d="M21.71 3.29a1 1 0 0 0-1.42 0l-18 18a1 1 0 0 0 0 1.42a1 1 0 0 0 1.42 0l18-18a1 1 0 0 0 0-1.42z" fill="currentColor" />
51
55
  </svg>
52
- </button>
53
- <button class="slidev-icon-btn" :class="{ shallow: drawingMode !== 'arrow' }" @click="setDrawingMode('arrow')">
54
- <HiddenText text="Draw an arrow" />
56
+ </IconButton>
57
+ <IconButton title="Draw an arrow" :class="{ shallow: drawingMode !== 'arrow' }" @click="setDrawingMode('arrow')">
55
58
  <carbon:arrow-up-right />
56
- </button>
57
- <button class="slidev-icon-btn" :class="{ shallow: drawingMode !== 'ellipse' }" @click="setDrawingMode('ellipse')">
58
- <HiddenText text="Draw an ellipse" />
59
+ </IconButton>
60
+ <IconButton title="Draw an ellipse" :class="{ shallow: drawingMode !== 'ellipse' }" @click="setDrawingMode('ellipse')">
59
61
  <carbon:radio-button />
60
- </button>
61
- <button class="slidev-icon-btn" :class="{ shallow: drawingMode !== 'rectangle' }" @click="setDrawingMode('rectangle')">
62
- <HiddenText text="Draw a rectangle" />
62
+ </IconButton>
63
+ <IconButton title="Draw a rectangle" :class="{ shallow: drawingMode !== 'rectangle' }" @click="setDrawingMode('rectangle')">
63
64
  <carbon:checkbox />
64
- </button>
65
- <!-- TODO: not sure why it's not working! -->
66
- <!-- <button class="slidev-icon-btn" :class="{ shallow: drawingMode != 'eraseLine' }" @click="setDrawingMode('eraseLine')">
67
- <HiddenText text="Erase" />
65
+ </IconButton>
66
+ <IconButton title="Erase" :class="{ shallow: drawingMode !== 'eraseLine' }" @click="setDrawingMode('eraseLine')">
68
67
  <carbon:erase />
69
- </button> -->
68
+ </IconButton>
70
69
 
71
70
  <VerticalDivider />
72
71
 
73
- <button
72
+ <Menu>
73
+ <IconButton title="Adjust stroke width" :class="{ shallow: drawingMode === 'eraseLine' }">
74
+ <svg viewBox="0 0 32 32" width="1.2em" height="1.2em">
75
+ <line x1="2" y1="15" x2="22" y2="4" stroke="currentColor" stroke-width="1" stroke-linecap="round" />
76
+ <line x1="2" y1="24" x2="28" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
77
+ <line x1="7" y1="31" x2="29" y2="19" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
78
+ </svg>
79
+ </IconButton>
80
+ <template #popper>
81
+ <div class="flex bg-main p-2">
82
+ <div class="inline-block w-7 text-center">
83
+ {{ brush.size }}
84
+ </div>
85
+ <div class="pt-.5">
86
+ <input v-model="brush.size" type="range" min="1" max="15" @change="drawingMode = lastDrawingMode">
87
+ </div>
88
+ </div>
89
+ </template>
90
+ </Menu>
91
+ <IconButton
74
92
  v-for="color of brushColors"
75
93
  :key="color"
76
- class="slidev-icon-btn"
77
- :class="brush.color === color ? 'active' : 'shallow'"
94
+ title="Set brush color"
95
+ :class="brush.color === color && drawingMode !== 'eraseLine' ? 'active' : 'shallow'"
78
96
  @click="setBrushColor(color)"
79
97
  >
80
- <HiddenText text="Set brush color" />
81
98
  <div
82
- class="w-6 h-6 transition-all transform border border-gray-400/50"
83
- :class="brush.color !== color ? 'rounded-1/2 scale-85' : 'rounded-md'"
99
+ class="w-6 h-6 transition-all transform border"
100
+ :class="brush.color !== color ? 'rounded-1/2 scale-85 border-white' : 'rounded-md border-gray-300/50'"
84
101
  :style="drawingEnabled ? { background: color } : { borderColor: color }"
85
102
  />
86
- </button>
103
+ </IconButton>
87
104
 
88
105
  <VerticalDivider />
89
106
 
90
- <button class="slidev-icon-btn" :class="{ disabled: !canUndo }" @click="undo()">
91
- <HiddenText text="Undo" />
107
+ <IconButton title="Undo" :class="{ disabled: !canUndo }" @click="undo()">
92
108
  <carbon:undo />
93
- </button>
94
- <button class="slidev-icon-btn" :class="{ disabled: !canRedo }" @click="redo()">
95
- <HiddenText text="Redo" />
109
+ </IconButton>
110
+ <IconButton title="Redo" :class="{ disabled: !canRedo }" @click="redo()">
96
111
  <carbon:redo />
97
- </button>
98
- <button class="slidev-icon-btn" :class="{ disabled: !canClear }" @click="clearDrauu()">
99
- <HiddenText text="Delete" />
100
- <carbon:delete />
101
- </button>
112
+ </IconButton>
113
+ <IconButton title="Delete" :class="{ disabled: !canClear }" @click="clearDrauu()">
114
+ <carbon:trash-can />
115
+ </IconButton>
102
116
 
103
117
  <VerticalDivider />
104
- <button class="slidev-icon-btn" :class="{ shallow: !drawingPinned }" @click="drawingPinned = !drawingPinned">
105
- <HiddenText :text="drawingPinned ? 'Unpin drawing' : 'Pin drawing'" />
118
+ <IconButton :title="drawingPinned ? 'Unpin drawing' : 'Pin drawing'" :class="{ shallow: !drawingPinned }" @click="drawingPinned = !drawingPinned">
106
119
  <carbon:pin-filled v-show="drawingPinned" class="transform -rotate-45" />
107
120
  <carbon:pin v-show="!drawingPinned" />
108
- </button>
109
- <button
121
+ </IconButton>
122
+ <IconButton
110
123
  v-if="drawingEnabled"
111
- class="slidev-icon-btn"
124
+ :title="drawingPinned ? 'Drawing pinned' : 'Drawing unpinned'"
112
125
  :class="{ shallow: !drawingEnabled }"
113
126
  @click="drawingEnabled = !drawingEnabled"
114
127
  >
115
- <HiddenText :text="drawingPinned ? 'Drawing pinned' : 'Drawing unpinned'" />
116
128
  <carbon:error v-show="drawingPinned" />
117
129
  <carbon:close-outline v-show="!drawingPinned" />
118
- </button>
130
+ </IconButton>
119
131
  </Draggable>
120
132
  </template>
133
+
134
+ <style lang="postcss">
135
+ .v-popper--theme-menu {
136
+ .v-popper__arrow-inner {
137
+ @apply border-main;
138
+ }
139
+ }
140
+ </style>