Skip to content

Commit

Permalink
feat: add undo button and add SvgShape component
Browse files Browse the repository at this point in the history
  • Loading branch information
robertrosman committed May 8, 2024
1 parent a0cb38d commit ebcdee1
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 56 deletions.
28 changes: 28 additions & 0 deletions src/components/SvgShape.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script setup lang="ts">
import type { Shape } from '@/types';
import { getArrowId } from '@/utils/getArrowId';
const props = defineProps<{
shape: Shape
}>()
</script>

<template>
<rect v-if="shape.type === 'rectangle'" :x="shape.x" :y="shape.y" :width="shape.width" :height="shape.height"
:stroke="shape.color" :stroke-width="shape.thickness" />

<line v-if="shape.type === 'line'" :x1="shape.x1" :y1="shape.y1" :x2="shape.x2" :y2="shape.y2" :stroke="shape.color"
:stroke-width="shape.thickness" />

<line v-if="shape.type === 'arrow'" :x1="shape.x1" :y1="shape.y1" :x2="shape.x2" :y2="shape.y2"
:stroke="shape.color" :stroke-width="shape.thickness" :marker-end="`url(#${getArrowId(shape)})`" />
</template>

<style scoped>
rect,
line {
stroke-linecap: round;
stroke-linejoin: round;
fill-opacity: 0;
}
</style>
93 changes: 37 additions & 56 deletions src/components/VueDraw.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@
import { useElementBounding, usePointerSwipe } from '@vueuse/core'
import { computed, ref } from 'vue';
import type { Crop, Settings, Shape, Tool } from '../types'
import { getArrowId } from '@/utils/getArrowId';
import SvgShape from './SvgShape.vue';
const emit = defineEmits<{
(e: 'crop', crop: Crop | undefined): void
(e: 'crop', crop: Crop | undefined): void
}>()
const settings = defineModel<Settings>("settings", { default: () => ({
tool: "line",
thickness: 5,
color: "red"
})})
const settings = defineModel<Settings>("settings", {
default: () => ({
tool: "line",
thickness: 5,
color: "#c82d2d"
})
})
const history = defineModel<Shape[]>("history", { default: []})
const history = defineModel<Shape[]>("history", { default: [] })
const crop = defineModel<Crop>("crop", { default: undefined})
const crop = defineModel<Crop>("crop", { default: undefined })
const container = ref()
const svgRef = ref()
Expand Down Expand Up @@ -53,7 +57,7 @@ const allShapes = computed(() => activeShape.value ? [
] : history.value)
const { posStart, posEnd, distanceX, distanceY, isSwiping } = usePointerSwipe(svgRef, {
const { posStart, posEnd, distanceX, distanceY, isSwiping } = usePointerSwipe(svgRef, {
threshold: 0,
onSwipe(e) {
if (settings.value.tool === 'crop') {
Expand All @@ -75,7 +79,7 @@ const { posStart, posEnd, distanceX, distanceY, isSwiping } = usePointerSwipe(sv
else {
if (activeShape.value) {
history.value.push(activeShape.value)
activeShape.value === undefined
activeShape.value = undefined
}
}
}
Expand All @@ -91,57 +95,38 @@ function setTool(tool: Tool) {
settings.value.tool = tool
}
function arrowId(shape: Shape) {
return `arrow-${shape.color.replace(/[^a-z0-9]/gi, '')}`
function undo() {
if (history.value.length) {
history.value = history.value.slice(0, -1)
}
}
const arrowMarkers = computed(() => [...new Set(
const arrowMarkers = computed(() =>
allShapes.value
.filter(shape => shape.type === 'arrow')
.filter((shape, index, self) => self.findIndex(s => arrowId(s) === arrowId(shape)) === index) // Unique matches only
.filter((shape, index, self) => self.findIndex(s => getArrowId(s) === getArrowId(shape)) === index) // Unique matches only
.map(shape => ({
id: arrowId(shape),
id: getArrowId(shape),
color: shape.color
})
)
)])
}))
)
</script>

<template>
<div ref="container" class="container">
<svg
ref="svgRef"
:width="width"
:height="height"
class="absolute inset-0"
:viewBox="`0 0 ${width} ${height}`"
xmlns="http://www.w3.org/2000/svg"
>
<template v-for="shape, i in allShapes">
<rect :key="i" v-if="shape.type === 'rectangle'" :x="shape.x" :y="shape.y" :width="shape.width" :height="shape.height" :stroke="shape.color" :stroke-width="shape.thickness" />
<line :key="i" v-if="shape.type === 'line'" :x1="shape.x1" :y1="shape.y1" :x2="shape.x2" :y2="shape.y2" :stroke="shape.color" :stroke-width="shape.thickness" />
<line :key="i" v-if="shape.type === 'arrow'" :x1="shape.x1" :y1="shape.y1" :x2="shape.x2" :y2="shape.y2" :stroke="shape.color" :stroke-width="shape.thickness" :marker-end="`url(#${arrowId(shape)})`" />
</template>
<path
v-if="crop"
class="overlay"
:d="`
<svg ref="svgRef" :width="width" :height="height" class="absolute inset-0" :viewBox="`0 0 ${width} ${height}`"
xmlns="http://www.w3.org/2000/svg">
<svg-shape v-for="shape, i in history" :key="i" :shape="shape" />
<svg-shape v-if="activeShape" :shape="activeShape" />
<path v-if="crop" class="overlay" :d="`
M 0,0 V ${height} H ${width} V 0 Z
M ${crop.x},${crop.y} H ${crop.x + crop.width} V ${crop.y + crop.height} H ${crop.x} Z
`"
/>
`" />
<defs>
<marker v-for="marker in arrowMarkers"
:id="marker.id"
:key="marker.id"
viewBox="0 0 10 10"
refX="2.5"
refY="2.5"
markerWidth="8"
markerHeight="8"
orient="auto-start-reverse"
>
<marker v-for="marker in arrowMarkers" :id="marker.id" :key="marker.id" viewBox="0 0 10 10" refX="2.5"
refY="2.5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
<polygon points="0,5 1.7,2.5 0,0 5,2.5" :fill="marker.color"></polygon>
</marker>
</defs>
Expand All @@ -151,8 +136,9 @@ const arrowMarkers = computed(() => [...new Set(
<button @click="setTool('line')">Line</button>
<button @click="setTool('arrow')">Arrow</button>
<button @click="setTool('rectangle')">Rectangle</button>
<input type="range" min="1" max="10" v-model="settings.thickness"/>
<input type="color" v-model="settings.color"/>
<input type="range" min="1" max="10" v-model="settings.thickness" />
<input type="color" v-model="settings.color" />
<button @click="undo">Undo</button>
</div>
</div>
</template>
Expand All @@ -162,16 +148,11 @@ const arrowMarkers = computed(() => [...new Set(
position: relative;
cursor: crosshair;
}
svg {
z-index: 100;
}
rect, line {
stroke-linecap: round;
stroke-linejoin: round;
fill-opacity: 0;
}
.overlay {
opacity: 0.5;
fill: #020202;
Expand All @@ -189,6 +170,6 @@ rect, line {
stroke-linejoin: miter;
stroke-opacity: 1;
stroke-dasharray: 4, 8;
stroke-dashoffset:0;
stroke-dashoffset: 0;
}
</style>
5 changes: 5 additions & 0 deletions src/utils/getArrowId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Shape } from "@/types";

export function getArrowId(shape: Shape) {
return `arrow-${shape.color.replace(/[^a-z0-9]/gi, '')}`
}

0 comments on commit ebcdee1

Please sign in to comment.