Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
presets: [
'@vue/cli-plugin-babel/preset'
]
};
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

183 changes: 183 additions & 0 deletions src/components/CanvasInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<template>
<div class="canvas-container">
<canvas ref="canvas" :width="width" :height="height" @mousedown="startDrawing" @mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing"></canvas>
<div class="canvas-controls">
<button @click="clearCanvas" class="btn btn-secondary">Clear</button>
<input type="color" v-model="currentColor" title="Choose color">
<input type="range" v-model="lineWidth" min="1" max="20" title="Line width">
</div>
</div>
</template>

<script>
export default {
name: 'CanvasInput',
props: {
value: {
type: String,
default: ''
},
width: {
type: Number,
default: 800
},
height: {
type: Number,
default: 400
},
backgroundImage: {
type: String,
default: ''
}
},
data() {
return {
canvas: null,
ctx: null,
isDrawing: false,
currentColor: '#000000',
lineWidth: 5,
lastX: 0,
lastY: 0,
image: null
}
},
mounted() {
this.canvas = this.$refs.canvas;
this.ctx = this.canvas.getContext('2d');
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';

// Load background image if provided
if (this.backgroundImage) {
this.image = new Image();
this.image.onload = () => {
// Draw the image maintaining aspect ratio
const scale = Math.min(
this.width / this.image.width,
this.height / this.image.height
);
const x = (this.width - this.image.width * scale) / 2;
const y = (this.height - this.image.height * scale) / 2;
this.ctx.drawImage(
this.image,
x, y,
this.image.width * scale,
this.image.height * scale
);
};
this.image.src = this.backgroundImage;
}

// Load existing drawing if value exists
if (this.value) {
const img = new Image();
img.onload = () => {
this.ctx.drawImage(img, 0, 0);
};
img.src = this.value;
}
},
methods: {
startDrawing(event) {
this.isDrawing = true;
const rect = this.canvas.getBoundingClientRect();
this.lastX = event.clientX - rect.left;
this.lastY = event.clientY - rect.top;
},
draw(event) {
if (!this.isDrawing) return;

const rect = this.canvas.getBoundingClientRect();
const currentX = event.clientX - rect.left;
const currentY = event.clientY - rect.top;

this.ctx.beginPath();
this.ctx.strokeStyle = this.currentColor;
this.ctx.lineWidth = this.lineWidth;
this.ctx.moveTo(this.lastX, this.lastY);
this.ctx.lineTo(currentX, currentY);
this.ctx.stroke();

this.lastX = currentX;
this.lastY = currentY;

this.$emit('input', this.canvas.toDataURL());
},
stopDrawing() {
this.isDrawing = false;
},
clearCanvas() {
this.ctx.clearRect(0, 0, this.width, this.height);
// Redraw background image if it exists
if (this.image) {
const scale = Math.min(
this.width / this.image.width,
this.height / this.image.height
);
const x = (this.width - this.image.width * scale) / 2;
const y = (this.height - this.image.height * scale) / 2;
this.ctx.drawImage(
this.image,
x, y,
this.image.width * scale,
this.image.height * scale
);
}
this.$emit('input', this.canvas.toDataURL());
}
}
}
</script>

<style scoped>
.canvas-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
margin: 1rem 0;
}

canvas {
border: 1px solid #ccc;
background: white;
cursor: crosshair;
}

.canvas-controls {
display: flex;
gap: 1rem;
align-items: center;
}

input[type="color"] {
width: 50px;
height: 30px;
padding: 0;
border: none;
border-radius: 4px;
cursor: pointer;
}

input[type="range"] {
width: 100px;
}

.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
}

.btn-secondary {
background-color: #6c757d;
color: white;
}

.btn-secondary:hover {
background-color: #5a6268;
}
</style>
56 changes: 35 additions & 21 deletions src/components/InputSelector/InputSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@
:init="init" v-on:valueChanged="sendData"/>
</div>

<!-- If type is canvas drawing -->
<div v-else-if="inputType === 'canvas'">
<CanvasInput
:constraints="valueConstraints"
:selected_language="selected_language"
:init="init"
:backgroundImage="ui.backgroundImage"
v-on:valueChanged="sendData"/>
</div>

<!-- If type is a slider -->
<div v-else-if="inputType === 'slider'">
<SliderInput
Expand Down Expand Up @@ -247,26 +257,25 @@
</template>

<script>
import Radio from '../Inputs/WebRadio/';
import AudioRecord from '../Inputs/WebAudioRecord/';
import TextInput from '../Inputs/WebTextInput/';
import TextArea from '../Inputs/TextArea/';
import IntegerInput from '../Inputs/WebIntegerInput/';
import FloatInput from '../Inputs/WebFloatInput/';
import RangeInput from '../Inputs/RangeInput/';
import DateInput from '../Inputs/YearInput/';
import DocumentUpload from '../Inputs/DocumentUpload';
import MultiTextInput from '../Inputs/MultiTextInput';
import SliderInput from '../Inputs/SliderInput';
import TimeRange from '../Inputs/TimeRange';
import SelectInput from '../Inputs/SelectInput';
// import AudioCheck from '../Inputs/AudioCheck';
import StaticReadOnly from '../Inputs/StaticReadOnly';
import SaveData from '../Inputs/SaveData/SaveData';
import StudySign from '../StudySign/StudySign';
// import Static from '../Inputs/Static';
import EmailInput from '../Inputs/EmailInput';
import ParticipantId from '../Inputs/ParticipantId/ParticipantId';
import Radio from '../Inputs/WebRadio/Radio.vue';
import AudioRecord from '../Inputs/WebAudioRecord/Audio.vue';
import TextInput from '../Inputs/WebTextInput/TextInput.vue';
import TextArea from '../Inputs/TextArea/TextArea.vue';
import IntegerInput from '../Inputs/WebIntegerInput/IntegerInput.vue';
import FloatInput from '../Inputs/WebFloatInput/FloatInput.vue';
import RangeInput from '../Inputs/RangeInput/RangeInput.vue';
import DateInput from '../Inputs/YearInput/YearInput.vue';
import DocumentUpload from '../Inputs/DocumentUpload/DocumentUpload.vue';
import MultiTextInput from '../Inputs/MultiTextInput/MultiTextInput.vue';
import SliderInput from '../Inputs/SliderInput/SliderInput.vue';
import TimeRange from '../Inputs/TimeRange/TimeRange.vue';
import SelectInput from '../Inputs/SelectInput/SelectInput.vue';
import StaticReadOnly from '../Inputs/StaticReadOnly/StaticReadOnly.vue';
import SaveData from '../Inputs/SaveData/SaveData.vue';
import StudySign from '../Inputs/StudySign/StudySign.vue';
import EmailInput from '../Inputs/EmailInput/EmailInput.vue';
import ParticipantId from '../Inputs/ParticipantId/ParticipantId.vue';
import CanvasInput from '../Inputs/CanvasInput/CanvasInput.vue';


export default {
Expand Down Expand Up @@ -308,6 +317,11 @@ export default {
ipAddress: {
type: String,
},
ui: {
type: Object,
required: false,
default: () => ({})
}
},
components: {
ParticipantId,
Expand All @@ -328,7 +342,7 @@ export default {
TimeRange,
SelectInput,
StaticReadOnly,
// Static,
CanvasInput,
},
data() {
return {
Expand Down
Loading
Loading