js-kompo-utils 1.0.2 → 1.0.3
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.
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<vl-form-field v-bind="$_wrapperAttributes">
|
|
3
|
+
<div class="signature-pad-container">
|
|
4
|
+
<canvas
|
|
5
|
+
ref="canvas"
|
|
6
|
+
:width="canvasWidth"
|
|
7
|
+
:height="canvasHeight"
|
|
8
|
+
class="signature-canvas"
|
|
9
|
+
@mousedown="startDrawing"
|
|
10
|
+
@mousemove="draw"
|
|
11
|
+
@mouseup="stopDrawing"
|
|
12
|
+
@mouseleave="stopDrawing"
|
|
13
|
+
@touchstart="startDrawing"
|
|
14
|
+
@touchmove="draw"
|
|
15
|
+
@touchend="stopDrawing"
|
|
16
|
+
></canvas>
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
:class="clearButtonClass"
|
|
20
|
+
@click="clearSignature"
|
|
21
|
+
>
|
|
22
|
+
{{ clearButtonText }}
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
</vl-form-field>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
import Field from 'vue-kompo/js/form/mixins/Field'
|
|
30
|
+
import HasInputAttributes from 'vue-kompo/js/form/mixins/HasInputAttributes'
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
mixins: [Field, HasInputAttributes],
|
|
34
|
+
data() {
|
|
35
|
+
return {
|
|
36
|
+
isDrawing: false,
|
|
37
|
+
context: null,
|
|
38
|
+
lastX: 0,
|
|
39
|
+
lastY: 0,
|
|
40
|
+
isEmpty: true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
computed: {
|
|
44
|
+
penColor() { return this.$_config('penColor') || '#000000' },
|
|
45
|
+
penWidth() { return this.$_config('penWidth') || 2 },
|
|
46
|
+
canvasWidth() { return this.$_config('canvasWidth') || 500 },
|
|
47
|
+
canvasHeight() { return this.$_config('canvasHeight') || 200 },
|
|
48
|
+
backgroundColor() { return this.$_config('backgroundColor') || '#ffffff' },
|
|
49
|
+
clearButtonText() { return this.$_config('clearButtonText') || 'Effacer' },
|
|
50
|
+
clearButtonClass() { return this.$_config('clearButtonClass') || 'px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded text-sm' }
|
|
51
|
+
},
|
|
52
|
+
methods: {
|
|
53
|
+
initCanvas() {
|
|
54
|
+
const canvas = this.$refs.canvas
|
|
55
|
+
this.context = canvas.getContext('2d')
|
|
56
|
+
|
|
57
|
+
// Fill background
|
|
58
|
+
this.context.fillStyle = this.backgroundColor
|
|
59
|
+
this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
|
|
60
|
+
|
|
61
|
+
// Set drawing style
|
|
62
|
+
this.context.strokeStyle = this.penColor
|
|
63
|
+
this.context.lineWidth = this.penWidth
|
|
64
|
+
this.context.lineCap = 'round'
|
|
65
|
+
this.context.lineJoin = 'round'
|
|
66
|
+
|
|
67
|
+
// Load existing signature if any
|
|
68
|
+
if (this.component.value) {
|
|
69
|
+
this.loadSignature(this.component.value)
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
getCoordinates(event) {
|
|
74
|
+
const canvas = this.$refs.canvas
|
|
75
|
+
const rect = canvas.getBoundingClientRect()
|
|
76
|
+
|
|
77
|
+
// Calculer le ratio entre la taille affichée et la taille réelle du canvas
|
|
78
|
+
const scaleX = canvas.width / rect.width
|
|
79
|
+
const scaleY = canvas.height / rect.height
|
|
80
|
+
|
|
81
|
+
if (event.touches && event.touches.length > 0) {
|
|
82
|
+
return {
|
|
83
|
+
x: (event.touches[0].clientX - rect.left) * scaleX,
|
|
84
|
+
y: (event.touches[0].clientY - rect.top) * scaleY
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
x: (event.clientX - rect.left) * scaleX,
|
|
90
|
+
y: (event.clientY - rect.top) * scaleY
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
startDrawing(event) {
|
|
95
|
+
event.preventDefault()
|
|
96
|
+
this.isDrawing = true
|
|
97
|
+
const coords = this.getCoordinates(event)
|
|
98
|
+
this.lastX = coords.x
|
|
99
|
+
this.lastY = coords.y
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
draw(event) {
|
|
103
|
+
if (!this.isDrawing) return
|
|
104
|
+
|
|
105
|
+
event.preventDefault()
|
|
106
|
+
const coords = this.getCoordinates(event)
|
|
107
|
+
|
|
108
|
+
this.context.beginPath()
|
|
109
|
+
this.context.moveTo(this.lastX, this.lastY)
|
|
110
|
+
this.context.lineTo(coords.x, coords.y)
|
|
111
|
+
this.context.stroke()
|
|
112
|
+
|
|
113
|
+
this.lastX = coords.x
|
|
114
|
+
this.lastY = coords.y
|
|
115
|
+
this.isEmpty = false
|
|
116
|
+
|
|
117
|
+
this.saveSignature()
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
stopDrawing() {
|
|
121
|
+
this.isDrawing = false
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
clearSignature() {
|
|
125
|
+
this.context.fillStyle = this.backgroundColor
|
|
126
|
+
this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
|
|
127
|
+
this.isEmpty = true
|
|
128
|
+
this.component.value = null
|
|
129
|
+
this.$emit('input', null)
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
saveSignature() {
|
|
133
|
+
if (!this.isEmpty) {
|
|
134
|
+
const dataURL = this.$refs.canvas.toDataURL('image/png')
|
|
135
|
+
this.component.value = dataURL
|
|
136
|
+
this.$emit('input', dataURL)
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
loadSignature(dataURL) {
|
|
141
|
+
const img = new Image()
|
|
142
|
+
img.onload = () => {
|
|
143
|
+
this.context.drawImage(img, 0, 0)
|
|
144
|
+
this.isEmpty = false
|
|
145
|
+
}
|
|
146
|
+
img.src = dataURL
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
mounted() {
|
|
150
|
+
this.initCanvas()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<style scoped>
|
|
156
|
+
.signature-pad-container {
|
|
157
|
+
display: flex;
|
|
158
|
+
flex-direction: column;
|
|
159
|
+
gap: 0.75rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.signature-canvas {
|
|
163
|
+
border: 1px solid #d1d5db;
|
|
164
|
+
border-radius: 0.5rem;
|
|
165
|
+
cursor: crosshair;
|
|
166
|
+
touch-action: none;
|
|
167
|
+
display: block;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.signature-canvas:hover {
|
|
171
|
+
border-color: #9ca3af;
|
|
172
|
+
}
|
|
173
|
+
</style>
|