Skip to content

Commit

Permalink
feat: draggable arrow!!!!
Browse files Browse the repository at this point in the history
  • Loading branch information
kermanx committed May 28, 2024
1 parent 91778e1 commit 9e7e1d6
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 77 deletions.
8 changes: 6 additions & 2 deletions demo/starter/slides.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ database "MySql" {
---
foo: bar
dragPos:
square: 691,33,167,_,-16
square: 691,32,167,_,-16
---

# Draggable Elements
Expand All @@ -588,14 +588,18 @@ Double-click on the draggable elements to edit their positions.
</v-drag>
```

<v-drag pos="671,205,253,_,-15">
<v-drag pos="663,206,261,_,-15">
<div text-center text-3xl border border-main rounded>
Double-click me!
</div>
</v-drag>

<img v-drag="'square'" src="https://sli.dev/logo.png">

###### Draggable Arrow

<v-drag-arrow two-way pos="71,428,245,56" op70 />

---
src: ./pages/multiple-entries.md
hide: false
Expand Down
28 changes: 21 additions & 7 deletions packages/client/builtin/Arrow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Simple Arrow
-->

<script setup lang="ts">
import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core'
import { makeId } from '../logic/utils'

defineProps<{
Expand All @@ -21,30 +23,33 @@ defineProps<{
twoWay?: boolean
}>()

const emit = defineEmits(['dblclick', 'clickOutside'])

const id = makeId()

const markerAttrs = {
markerUnits: 'strokeWidth',
markerWidth: 10,
markerHeight: 7,
refX: 9,
refY: 3.5,
orient: 'auto',
}

const clickArea = ref<HTMLElement>()
onClickOutside(clickArea, () => emit('clickOutside'))
</script>

<template>
<svg
class="absolute left-0 top-0 pointer-events-none"
class="absolute left-0 top-0"
:width="Math.max(+x1, +x2) + 50"
:height="Math.max(+y1, +y2) + 50"
>
<defs>
<marker :id="id" v-bind="markerAttrs">
<polygon points="0 0, 10 3.5, 0 7" :fill="color || 'currentColor'" />
<marker :id="id" markerWidth="10" refX="9" v-bind="markerAttrs">
<polygon points="0 0, 10 3.5, 0 7" :fill="color || 'currentColor'" @dblclick="emit('dblclick')" />
</marker>
<marker v-if="twoWay" :id="`${id}-rev`" v-bind="markerAttrs">
<polygon points="10 0, 0 3.5, 10 7" :fill="color || 'currentColor'" />
<marker v-if="twoWay" :id="`${id}-rev`" markerWidth="20" refX="11" v-bind="markerAttrs">
<polygon points="20 0, 10 3.5, 20 7" :fill="color || 'currentColor'" @dblclick="emit('dblclick')" />
</marker>
</defs>
<line
Expand All @@ -53,6 +58,15 @@ const markerAttrs = {
:stroke-width="width || 2"
:marker-end="`url(#${id})`"
:marker-start="twoWay ? `url(#${id}-rev)` : 'none'"
@dblclick="emit('dblclick')"
/>
<line
ref="clickArea"
:x1 :y1 :x2 :y2
stroke="transparent"
stroke-linecap="round"
:stroke-width="20"
@dblclick="emit('dblclick')"
/>
</svg>
</template>
4 changes: 2 additions & 2 deletions packages/client/builtin/VDrag.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const props = defineProps<{
markdownSource?: DragElementMarkdownSource
}>()
const { id, container, containerStyle, mounted, unmounted, startDragging } = useDragElement(null, props.pos, props.markdownSource)
const { dragId, container, containerStyle, mounted, unmounted, startDragging } = useDragElement(null, props.pos, props.markdownSource)
onMounted(mounted)
onUnmounted(unmounted)
Expand All @@ -17,7 +17,7 @@ onUnmounted(unmounted)
<template>
<div
ref="container"
:data-drag-id="id"
:data-drag-id="dragId"
:style="containerStyle"
class="p-1"
@dblclick="startDragging"
Expand Down
36 changes: 36 additions & 0 deletions packages/client/builtin/VDragArrow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted } from 'vue'
import type { DragElementMarkdownSource } from '../composables/useDragElements'
import { useDragElement } from '../composables/useDragElements'
import Arrow from './Arrow.vue'
const props = defineProps<{
pos?: string
markdownSource?: DragElementMarkdownSource
width?: number | string
color?: string
twoWay?: boolean
}>()
const { dragId, mounted, unmounted, startDragging, stopDragging, x0, y0, width, height } = useDragElement(null, props.pos ?? '100,100,300,300', props.markdownSource, true)
onMounted(mounted)
onUnmounted(unmounted)
const x1 = computed(() => x0.value - width.value / 2)
const y1 = computed(() => y0.value - height.value / 2)
const x2 = computed(() => x0.value + width.value / 2)
const y2 = computed(() => y0.value + height.value / 2)
</script>

<template>
<Arrow
:x1 :y1 :x2 :y2
:data-drag-id="dragId"
:width="props.width"
:color="props.color"
:two-way="props.twoWay"
@dblclick="startDragging"
@click-outside="stopDragging"
/>
</template>
74 changes: 41 additions & 33 deletions packages/client/composables/useDragElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ export function useDragElementsUpdater(no: number) {
let replaced = false

section = type === 'prop'
? section.replace(/<(v-?drag)(.*?)>/gi, (full, tag, attrs, index) => {
// eslint-disable-next-line regexp/no-super-linear-backtracking
? section.replace(/<(v-?drag-?\w*)(.*?)>/gi, (full, tag, attrs, index) => {
if (index === idx) {
replaced = true
const posMatch = attrs.match(/pos=".*?"/)
Expand Down Expand Up @@ -112,7 +113,7 @@ export function useDragElementsUpdater(no: number) {
}
}

export function useDragElement(directive: DirectiveBinding | null, posRaw?: string | number | number[], markdownSource?: DragElementMarkdownSource) {
export function useDragElement(directive: DirectiveBinding | null, posRaw?: string | number | number[], markdownSource?: DragElementMarkdownSource, isArrow?: boolean) {
function inject<T>(key: InjectionKey<T> | string): T | undefined {
return directive
? directiveInject(directive, key)
Expand All @@ -129,7 +130,7 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
const enabled = ['slide', 'presenter'].includes(renderContext.value)

let dataSource: DragElementDataSource = directive ? 'directive' : 'prop'
let id: string = makeId()
let dragId: string = makeId()
let pos: number[] | undefined
if (Array.isArray(posRaw)) {
pos = posRaw
Expand All @@ -139,8 +140,8 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
}
else if (posRaw != null) {
dataSource = 'frontmatter'
id = `${posRaw}`
posRaw = frontmatter?.dragPos?.[id]
dragId = `${posRaw}`
posRaw = frontmatter?.dragPos?.[dragId]
pos = (posRaw as string)?.split(',').map(Number)
}

Expand All @@ -149,12 +150,12 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri

const watchStopHandles: WatchStopHandle[] = [stopWatchBounds]

const autoHeight = posRaw != null && !Number.isFinite(pos?.[3])
const autoHeight = !isArrow && posRaw != null && !Number.isFinite(pos?.[3])
pos ??= [Number.NaN, Number.NaN, 0]
const width = ref(pos[2])
const x0 = ref(pos[0] + pos[2] / 2)

const rotate = ref(pos[4] ?? 0)
const rotate = ref(isArrow ? 0 : (pos[4] ?? 0))
const rotateRad = computed(() => rotate.value * Math.PI / 180)
const rotateSin = computed(() => Math.sin(rotateRad.value))
const rotateCos = computed(() => Math.cos(rotateRad.value))
Expand All @@ -163,7 +164,9 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
const bounds = ref({ left: 0, top: 0, width: 0, height: 0 })
const actualHeight = ref(0)
function updateBounds() {
const rect = container.value!.getBoundingClientRect()
if (!container.value)
return
const rect = container.value.getBoundingClientRect()
bounds.value = {
left: rect.left / zoom.value,
top: rect.top / zoom.value,
Expand All @@ -175,32 +178,36 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
watchStopHandles.push(watch(width, updateBounds, { flush: 'post' }))

const configuredHeight = ref(pos[3] ?? 0)
const height = computed({
get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,
set: v => !autoHeight && (configuredHeight.value = v),
})
const configuredY0 = ref(pos[1])
const y0 = computed({
get: () => configuredY0.value + height.value / 2,
set: v => configuredY0.value = v - height.value / 2,
})

const containerStyle = computed<CSSProperties>(() => {
const height = autoHeight
? computed({
get: () => (autoHeight ? actualHeight.value : configuredHeight.value) || 0,
set: v => !autoHeight && (configuredHeight.value = v),
})
: configuredHeight
const configuredY0 = autoHeight ? ref(pos[1]) : ref(pos[1] + pos[3] / 2)
const y0 = autoHeight
? computed({
get: () => configuredY0.value + height.value / 2,
set: v => configuredY0.value = v - height.value / 2,
})
: configuredY0

const containerStyle = computed(() => {
return Number.isFinite(x0.value)
? {
position: 'absolute',
zIndex: 100,
left: `${x0.value - width.value / 2}px`,
top: `${y0.value - height.value / 2}px`,
width: `${width.value}px`,
height: autoHeight ? undefined : `${height.value}px`,
transformOrigin: 'center center',
transform: `rotate(${rotate.value}deg)`,
}
position: 'absolute',
zIndex: 100,
left: `${x0.value - width.value / 2}px`,
top: `${y0.value - height.value / 2}px`,
width: `${width.value}px`,
height: autoHeight ? undefined : `${height.value}px`,
transformOrigin: 'center center',
transform: `rotate(${rotate.value}deg)`,
} satisfies CSSProperties
: {
position: 'absolute',
zIndex: 100,
}
position: 'absolute',
zIndex: 100,
} satisfies CSSProperties
})

watchStopHandles.push(
Expand All @@ -218,15 +225,16 @@ export function useDragElement(directive: DirectiveBinding | null, posRaw?: stri
if (dataSource === 'directive')
posStr = `[${posStr}]`

updater.value(id, posStr, dataSource, markdownSource)
updater.value(dragId, posStr, dataSource, markdownSource)
},
),
)

const state = {
id,
dragId,
dataSource,
markdownSource,
isArrow,
zoom,
autoHeight,
x0,
Expand Down
Loading

0 comments on commit 9e7e1d6

Please sign in to comment.