119
src/components/annotation/WaveformAnnotationLayer.vue
Normal file
119
src/components/annotation/WaveformAnnotationLayer.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import type { RenderedAnnotation } from './types'
|
||||
import { ANNOTATION_TEXT_FONT, ANNOTATION_TEXT_LINE_HEIGHT } from './markup'
|
||||
|
||||
interface Props {
|
||||
annotations: RenderedAnnotation[]
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<{
|
||||
(event: 'contextmenu', annotationId: string, mouseEvent: MouseEvent): void
|
||||
}>()
|
||||
|
||||
function handleContextMenu(annotationId: string, event: MouseEvent) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
emit('contextmenu', annotationId, event)
|
||||
}
|
||||
|
||||
function markerId(annotationId: string) {
|
||||
return `waveform-annotation-arrow-${annotationId.replace(/[^a-zA-Z0-9_-]/g, '-')}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<g v-if="props.visible" class="waveform-annotation-layer">
|
||||
<g
|
||||
v-for="rendered in props.annotations"
|
||||
:key="rendered.annotation.id"
|
||||
class="waveform-annotation"
|
||||
:data-annotation-id="rendered.annotation.id"
|
||||
:data-placement="rendered.placement"
|
||||
@contextmenu="handleContextMenu(rendered.annotation.id, $event)"
|
||||
>
|
||||
<defs>
|
||||
<marker
|
||||
:id="markerId(rendered.annotation.id)"
|
||||
markerWidth="8"
|
||||
markerHeight="8"
|
||||
refX="8"
|
||||
refY="4"
|
||||
orient="auto"
|
||||
markerUnits="userSpaceOnUse"
|
||||
>
|
||||
<path d="M0,0 L8,4 L0,8 Z" :fill="rendered.style.borderColor" />
|
||||
</marker>
|
||||
</defs>
|
||||
<line
|
||||
class="waveform-annotation__arrow"
|
||||
:x1="rendered.box.lineEndX"
|
||||
:y1="rendered.box.lineEndY"
|
||||
:x2="rendered.anchorX"
|
||||
:y2="rendered.anchorY"
|
||||
:stroke="rendered.style.borderColor"
|
||||
:marker-end="`url(#${markerId(rendered.annotation.id)})`"
|
||||
/>
|
||||
<rect
|
||||
class="waveform-annotation__box"
|
||||
:x="rendered.box.x"
|
||||
:y="rendered.box.y"
|
||||
:width="rendered.box.width"
|
||||
:height="rendered.box.height"
|
||||
:fill="rendered.style.backgroundColor"
|
||||
:stroke="rendered.style.borderColor"
|
||||
/>
|
||||
<text
|
||||
class="waveform-annotation__text"
|
||||
:x="rendered.box.x + rendered.box.width / 2"
|
||||
:y="
|
||||
rendered.box.y +
|
||||
rendered.box.height / 2 -
|
||||
((rendered.lines.length - 1) * ANNOTATION_TEXT_LINE_HEIGHT) / 2
|
||||
"
|
||||
:fill="rendered.style.textColor"
|
||||
:style="{ font: ANNOTATION_TEXT_FONT }"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="central"
|
||||
>
|
||||
<tspan
|
||||
v-for="(line, index) in rendered.lines"
|
||||
:key="`${rendered.annotation.id}-${index}`"
|
||||
:x="rendered.box.x + rendered.box.width / 2"
|
||||
:dy="index === 0 ? 0 : ANNOTATION_TEXT_LINE_HEIGHT"
|
||||
>
|
||||
{{ line }}
|
||||
</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.waveform-annotation-layer {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.waveform-annotation {
|
||||
pointer-events: auto;
|
||||
cursor: context-menu;
|
||||
}
|
||||
|
||||
.waveform-annotation__arrow {
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.waveform-annotation__box {
|
||||
stroke-width: 1;
|
||||
rx: 3;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.waveform-annotation__text {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user