Erweitert die Funktionalität in PredefinedActivityImageController.js, um Zeichnungsdaten aus dem Request zu extrahieren und in der Datenbank zu speichern. Aktualisiert das Datenmodell in PredefinedActivityImage.js, um ein neues Feld für Zeichnungsdaten hinzuzufügen. Passt die Routen in predefinedActivityRoutes.js an, um die neue PUT-Methode für das Hochladen von Bildern zu unterstützen. Integriert die Zeichnungsdaten in die Aktivitätenlogik in diaryDateActivityService.js und aktualisiert die Benutzeroberfläche in CourtDrawingTool.vue zur Unterstützung von Zeichnungsdaten. Verbessert die Handhabung von Bild-Uploads in PredefinedActivities.vue und implementiert die Logik zum Laden von Zeichnungsdaten beim Bearbeiten von Aktivitäten.
This commit is contained in:
@@ -33,10 +33,15 @@ export const uploadPredefinedActivityImage = async (req, res) => {
|
||||
.jpeg({ quality: 85 })
|
||||
.toFile(filePath);
|
||||
|
||||
// Extrahiere Zeichnungsdaten aus dem Request
|
||||
const drawingData = req.body.drawingData ? JSON.parse(req.body.drawingData) : null;
|
||||
console.log('[uploadPredefinedActivityImage] - drawingData:', drawingData);
|
||||
|
||||
const imageRecord = await PredefinedActivityImage.create({
|
||||
predefinedActivityId: id,
|
||||
imagePath: filePath,
|
||||
mimeType: 'image/jpeg',
|
||||
drawingData: drawingData ? JSON.stringify(drawingData) : null,
|
||||
});
|
||||
|
||||
// Optional: als imageLink am Activity-Datensatz setzen
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Migration: Add drawing_data column to predefined_activity_images table
|
||||
-- Date: 2025-09-22
|
||||
-- Description: Adds drawing_data column to store Court Drawing Tool metadata
|
||||
|
||||
ALTER TABLE `predefined_activity_images`
|
||||
ADD COLUMN `drawing_data` TEXT NULL
|
||||
COMMENT 'JSON string containing drawing metadata for Court Drawing Tool'
|
||||
AFTER `mime_type`;
|
||||
|
||||
-- Verify the column was added
|
||||
DESCRIBE `predefined_activity_images`;
|
||||
@@ -19,6 +19,11 @@ const PredefinedActivityImage = sequelize.define('PredefinedActivityImage', {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
drawingData: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'JSON string containing drawing metadata for Court Drawing Tool'
|
||||
},
|
||||
}, {
|
||||
tableName: 'predefined_activity_images',
|
||||
timestamps: true,
|
||||
|
||||
@@ -23,6 +23,7 @@ router.get('/', authenticate, getAllPredefinedActivities);
|
||||
router.get('/:id', authenticate, getPredefinedActivityById);
|
||||
router.put('/:id', authenticate, updatePredefinedActivity);
|
||||
router.post('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.put('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.delete('/:id/image/:imageId', authenticate, deletePredefinedActivityImage);
|
||||
router.get('/search/query', authenticate, searchPredefinedActivities);
|
||||
router.post('/merge', authenticate, mergePredefinedActivities);
|
||||
|
||||
@@ -2,6 +2,7 @@ import DiaryDateActivity from '../models/DiaryDateActivity.js';
|
||||
import GroupActivity from '../models/GroupActivity.js';
|
||||
import Group from '../models/Group.js';
|
||||
import PredefinedActivity from '../models/PredefinedActivity.js';
|
||||
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
@@ -16,11 +17,19 @@ class DiaryDateActivityService {
|
||||
if (!predefinedActivity) {
|
||||
predefinedActivity = await PredefinedActivity.create({
|
||||
name: data.activity,
|
||||
description: '',
|
||||
duration: data.duration
|
||||
description: data.description || '',
|
||||
duration: data.duration && data.duration !== '' ? parseInt(data.duration) : null
|
||||
});
|
||||
}
|
||||
restData.predefinedActivityId = predefinedActivity.id;
|
||||
|
||||
// Bereinige duration-Feld für DiaryDateActivity
|
||||
if (restData.duration === '' || restData.duration === undefined) {
|
||||
restData.duration = null;
|
||||
} else if (typeof restData.duration === 'string') {
|
||||
restData.duration = parseInt(restData.duration);
|
||||
}
|
||||
|
||||
const maxOrderId = await DiaryDateActivity.max('orderId', {
|
||||
where: { diaryDateId: data.diaryDateId }
|
||||
});
|
||||
@@ -54,8 +63,8 @@ class DiaryDateActivityService {
|
||||
console.log('[DiaryDateActivityService::updateActivity] - creating new PredefinedActivity');
|
||||
predefinedActivity = await PredefinedActivity.create({
|
||||
name: data.customActivityName,
|
||||
description: '',
|
||||
duration: data.duration || activity.duration
|
||||
description: data.description || '',
|
||||
duration: data.duration && data.duration !== '' ? parseInt(data.duration) : (activity.duration || null)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,7 +140,7 @@ class DiaryDateActivityService {
|
||||
}
|
||||
|
||||
async getActivities(userToken, clubId, diaryDateId) {
|
||||
console.log('[DiaryDateActivityService::getActivities] - check user access');
|
||||
console.log('[DiaryDateActivityService::getActivities] - check user access - SERVER RESTARTED');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log(`[DiaryDateActivityService::getActivities] - fetch activities for diaryDateId: ${diaryDateId}`);
|
||||
const activities = await DiaryDateActivity.findAll({
|
||||
@@ -141,6 +150,12 @@ class DiaryDateActivityService {
|
||||
{
|
||||
model: PredefinedActivity,
|
||||
as: 'predefinedActivity',
|
||||
include: [
|
||||
{
|
||||
model: PredefinedActivityImage,
|
||||
as: 'images'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: GroupActivity,
|
||||
@@ -159,7 +174,70 @@ class DiaryDateActivityService {
|
||||
]
|
||||
});
|
||||
console.log(`[DiaryDateActivityService::getActivities] - found ${activities.length} activities`);
|
||||
return activities;
|
||||
|
||||
// Füge imageUrl zu jeder PredefinedActivity hinzu
|
||||
console.log('[DiaryDateActivityService::getActivities] - Adding imageUrl to activities');
|
||||
const activitiesWithImages = await Promise.all(activities.map(async activity => {
|
||||
// Konvertiere zu JSON und zurück, um alle Eigenschaften zu serialisieren
|
||||
const activityData = activity.toJSON();
|
||||
|
||||
if (activityData.predefinedActivity) {
|
||||
console.log(JSON.parse(JSON.stringify(activityData)));
|
||||
// Hole die erste verfügbare Image-ID direkt aus der Datenbank
|
||||
const allImages = await PredefinedActivityImage.findAll({
|
||||
where: { predefinedActivityId: activityData.predefinedActivity.id },
|
||||
order: [['createdAt', 'ASC']]
|
||||
});
|
||||
|
||||
console.log(`Activity ${activityData.predefinedActivity.id}: allImages =`, allImages.map(img => ({ id: img.id, activityId: img.predefinedActivityId, hasDrawingData: !!img.drawingData })));
|
||||
|
||||
const firstImage = allImages.length > 0 ? allImages[0] : null;
|
||||
console.log(`Activity ${activityData.predefinedActivity.id}: firstImage =`, firstImage?.id);
|
||||
|
||||
// Füge Zeichnungsdaten hinzu, falls vorhanden
|
||||
if (firstImage && firstImage.drawingData) {
|
||||
try {
|
||||
activityData.predefinedActivity.drawingData = JSON.parse(firstImage.drawingData);
|
||||
console.log(`Activity ${activityData.predefinedActivity.id}: drawingData loaded:`, activityData.predefinedActivity.drawingData);
|
||||
} catch (error) {
|
||||
console.error(`Activity ${activityData.predefinedActivity.id}: Error parsing drawingData:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (firstImage) {
|
||||
// Füge sowohl imageUrl als auch imageLink mit Image-ID hinzu
|
||||
activityData.predefinedActivity.imageUrl = `/api/predefined-activities/${activityData.predefinedActivity.id}/image/${firstImage.id}`;
|
||||
activityData.predefinedActivity.imageLink = `/api/predefined-activities/${activityData.predefinedActivity.id}/image/${firstImage.id}`;
|
||||
} else {
|
||||
// Fallback: Verwende den Basis-Pfad ohne Image-ID
|
||||
activityData.predefinedActivity.imageUrl = `/api/predefined-activities/${activityData.predefinedActivity.id}/image`;
|
||||
activityData.predefinedActivity.imageLink = `/api/predefined-activities/${activityData.predefinedActivity.id}/image`;
|
||||
}
|
||||
}
|
||||
|
||||
// Auch für GroupActivities
|
||||
if (activityData.groupActivities && activityData.groupActivities.length > 0) {
|
||||
for (const groupActivity of activityData.groupActivities) {
|
||||
if (groupActivity.groupPredefinedActivity) {
|
||||
// Hole die erste verfügbare Image-ID direkt aus der Datenbank
|
||||
const firstImage = await PredefinedActivityImage.findOne({
|
||||
where: { predefinedActivityId: groupActivity.groupPredefinedActivity.id },
|
||||
order: [['createdAt', 'ASC']]
|
||||
});
|
||||
|
||||
if (firstImage) {
|
||||
groupActivity.groupPredefinedActivity.imageUrl = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image/${firstImage.id}`;
|
||||
groupActivity.groupPredefinedActivity.imageLink = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image/${firstImage.id}`;
|
||||
} else {
|
||||
groupActivity.groupPredefinedActivity.imageUrl = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image`;
|
||||
groupActivity.groupPredefinedActivity.imageLink = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return activityData;
|
||||
}));
|
||||
return activitiesWithImages;
|
||||
}
|
||||
|
||||
async addGroupActivity(userToken, clubId, diaryDateId, groupId, activity) {
|
||||
|
||||
@@ -2,14 +2,9 @@
|
||||
<div class="court-drawing-tool">
|
||||
<div class="tool-header">
|
||||
<h4>Tischtennis-Übungszeichnung</h4>
|
||||
<div class="tool-controls">
|
||||
<button @click="clearCanvas" class="btn-secondary btn-small">Löschen</button>
|
||||
<button @click="saveDrawing" class="btn-primary btn-small">Speichern</button>
|
||||
<button @click="loadDrawing" class="btn-secondary btn-small">Laden</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="canvas-container">
|
||||
</div>
|
||||
|
||||
<div class="canvas-container">
|
||||
<canvas
|
||||
ref="drawingCanvas"
|
||||
@mousedown="startDrawing"
|
||||
@@ -19,73 +14,81 @@
|
||||
:width="config.canvas.width"
|
||||
:height="config.canvas.height"
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Startposition und Schlagart Auswahl -->
|
||||
<!-- Startposition und Schlagart Auswahl -->
|
||||
<div class="exercise-selection" v-if="selectedStartPosition">
|
||||
<h5>Übung konfigurieren</h5>
|
||||
<div class="selection-group">
|
||||
<!-- Schlagart Auswahl -->
|
||||
<div class="stroke-selection">
|
||||
<span>Aufschlag:</span>
|
||||
<div class="stroke-buttons">
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': strokeType === 'VH', 'btn-secondary': strokeType !== 'VH' }]"
|
||||
:class="['btn-small', 'btn-stroke', { 'btn-primary': strokeType === 'VH', 'btn-secondary': strokeType !== 'VH' }]"
|
||||
@click="strokeType = 'VH'"
|
||||
title="Vorhand"
|
||||
>
|
||||
Vorhand (VH)
|
||||
VH
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': strokeType === 'RH', 'btn-secondary': strokeType !== 'RH' }]"
|
||||
:class="['btn-small', 'btn-stroke', { 'btn-primary': strokeType === 'RH', 'btn-secondary': strokeType !== 'RH' }]"
|
||||
@click="strokeType = 'RH'"
|
||||
title="Rückhand"
|
||||
>
|
||||
Rückhand (RH)
|
||||
RH
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schnittoption Auswahl -->
|
||||
<div class="spin-selection">
|
||||
<div class="spin-buttons">
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': spinType === 'Unterschnitt', 'btn-secondary': spinType !== 'Unterschnitt' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': spinType === 'Unterschnitt', 'btn-secondary': spinType !== 'Unterschnitt' }]"
|
||||
@click="spinType = 'Unterschnitt'"
|
||||
title="Unterschnitt"
|
||||
>
|
||||
Unterschnitt
|
||||
US
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': spinType === 'Überschnitt', 'btn-secondary': spinType !== 'Überschnitt' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': spinType === 'Überschnitt', 'btn-secondary': spinType !== 'Überschnitt' }]"
|
||||
@click="spinType = 'Überschnitt'"
|
||||
title="Überschnitt"
|
||||
>
|
||||
Überschnitt
|
||||
OS
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': spinType === 'Seitschnitt', 'btn-secondary': spinType !== 'Seitschnitt' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': spinType === 'Seitschnitt', 'btn-secondary': spinType !== 'Seitschnitt' }]"
|
||||
@click="spinType = 'Seitschnitt'"
|
||||
title="Seitschnitt"
|
||||
>
|
||||
Seitschnitt
|
||||
SR
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': spinType === 'Seitunterschnitt', 'btn-secondary': spinType !== 'Seitunterschnitt' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': spinType === 'Seitunterschnitt', 'btn-secondary': spinType !== 'Seitunterschnitt' }]"
|
||||
@click="spinType = 'Seitunterschnitt'"
|
||||
title="Seitunterschnitt"
|
||||
>
|
||||
Seitunterschnitt
|
||||
SU
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': spinType === 'Gegenläufer', 'btn-secondary': spinType !== 'Gegenläufer' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': spinType === 'Gegenläufer', 'btn-secondary': spinType !== 'Gegenläufer' }]"
|
||||
@click="spinType = 'Gegenläufer'"
|
||||
title="Gegenläufer"
|
||||
>
|
||||
Gegenläufer
|
||||
GL
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Zusätzliche Schläge hinzufügen -->
|
||||
<div class="additional-strokes" v-if="strokeType && spinType && targetPosition">
|
||||
@@ -94,55 +97,62 @@
|
||||
<div class="next-stroke-type">
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': nextStrokeSide === 'VH', 'btn-secondary': nextStrokeSide !== 'VH' }]"
|
||||
:class="['btn-small', 'btn-stroke', { 'btn-primary': nextStrokeSide === 'VH', 'btn-secondary': nextStrokeSide !== 'VH' }]"
|
||||
@click="nextStrokeSide = 'VH'"
|
||||
title="Vorhand"
|
||||
>
|
||||
VH (Vorhand)
|
||||
VH
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': nextStrokeSide === 'RH', 'btn-secondary': nextStrokeSide !== 'RH' }]"
|
||||
:class="['btn-small', 'btn-stroke', { 'btn-primary': nextStrokeSide === 'RH', 'btn-secondary': nextStrokeSide !== 'RH' }]"
|
||||
@click="nextStrokeSide = 'RH'"
|
||||
title="Rückhand"
|
||||
>
|
||||
RH (Rückhand)
|
||||
RH
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="next-stroke-buttons">
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': nextStrokeType === 'US', 'btn-secondary': nextStrokeType !== 'US' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': nextStrokeType === 'US', 'btn-secondary': nextStrokeType !== 'US' }]"
|
||||
@click="nextStrokeType = 'US'"
|
||||
title="Schupf"
|
||||
>
|
||||
US (Schupf)
|
||||
US
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': nextStrokeType === 'OS', 'btn-secondary': nextStrokeType !== 'OS' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': nextStrokeType === 'OS', 'btn-secondary': nextStrokeType !== 'OS' }]"
|
||||
@click="nextStrokeType = 'OS'"
|
||||
title="Konter"
|
||||
>
|
||||
OS (Konter)
|
||||
OS
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': nextStrokeType === 'TS', 'btn-secondary': nextStrokeType !== 'TS' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': nextStrokeType === 'TS', 'btn-secondary': nextStrokeType !== 'TS' }]"
|
||||
@click="nextStrokeType = 'TS'"
|
||||
title="Topspin"
|
||||
>
|
||||
TS (Topspin)
|
||||
TS
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': nextStrokeType === 'FL', 'btn-secondary': nextStrokeType !== 'FL' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': nextStrokeType === 'FL', 'btn-secondary': nextStrokeType !== 'FL' }]"
|
||||
@click="nextStrokeType = 'FL'"
|
||||
title="Flip"
|
||||
>
|
||||
FL (Flip)
|
||||
FL
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn-small', { 'btn-primary': nextStrokeType === 'BL', 'btn-secondary': nextStrokeType !== 'BL' }]"
|
||||
:class="['btn-small', 'btn-stroke-type', { 'btn-primary': nextStrokeType === 'BL', 'btn-secondary': nextStrokeType !== 'BL' }]"
|
||||
@click="nextStrokeType = 'BL'"
|
||||
title="Block"
|
||||
>
|
||||
BL (Block)
|
||||
BL
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -161,6 +171,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Buttons ganz nach unten -->
|
||||
<div class="tool-controls">
|
||||
<button @click="clearCanvas" class="btn-secondary btn-small">Löschen</button>
|
||||
<button @click="saveDrawing" class="btn-primary btn-small">Speichern</button>
|
||||
<button @click="loadDrawing" class="btn-secondary btn-small">Laden</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -171,6 +188,14 @@ export default {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
activityId: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
},
|
||||
drawingData: { // Added for loading saved drawing data
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -309,31 +334,73 @@ export default {
|
||||
if (this.canvas && this.ctx) {
|
||||
this.drawCourt();
|
||||
}
|
||||
},
|
||||
drawingData: {
|
||||
handler(newVal, oldVal) {
|
||||
if (this.drawingData) {
|
||||
this.loadDrawingFromMetadata();
|
||||
} else if (oldVal && !newVal) {
|
||||
// drawingData wurde auf null gesetzt - reset alle Werte und zeichne leeres Canvas
|
||||
console.log('CourtDrawingTool: drawingData set to null, resetting all values');
|
||||
this.resetAllValues();
|
||||
this.clearCanvas();
|
||||
this.drawCourt(true); // forceRedraw = true
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initCanvas();
|
||||
this.drawCourt();
|
||||
if (this.value) {
|
||||
this.loadDrawingFromData(this.value);
|
||||
}
|
||||
console.log('CourtDrawingTool: mounted');
|
||||
this.$nextTick(() => {
|
||||
this.initCanvas();
|
||||
this.drawCourt();
|
||||
if (this.value) {
|
||||
this.loadDrawingFromData(this.value);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
initCanvas() {
|
||||
console.log('CourtDrawingTool: initCanvas called');
|
||||
this.canvas = this.$refs.drawingCanvas;
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.ctx.lineCap = this.config.pen.cap;
|
||||
this.ctx.lineJoin = this.config.pen.join;
|
||||
console.log('CourtDrawingTool: canvas =', this.canvas);
|
||||
if (this.canvas) {
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
console.log('CourtDrawingTool: ctx =', this.ctx);
|
||||
this.ctx.lineCap = this.config.pen.cap;
|
||||
this.ctx.lineJoin = this.config.pen.join;
|
||||
} else {
|
||||
console.error('CourtDrawingTool: Canvas not found!');
|
||||
}
|
||||
},
|
||||
|
||||
drawCourt() {
|
||||
drawCourt(forceRedraw = false) {
|
||||
console.log('CourtDrawingTool: drawCourt called, forceRedraw:', forceRedraw);
|
||||
const ctx = this.ctx;
|
||||
const canvas = this.canvas;
|
||||
const config = this.config;
|
||||
|
||||
// Hintergrund
|
||||
ctx.fillStyle = '#f0f0f0';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
if (!ctx || !canvas) {
|
||||
console.error('CourtDrawingTool: Canvas or context not available');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('CourtDrawingTool: Drawing court...');
|
||||
console.log('Canvas dimensions:', canvas.width, 'x', canvas.height);
|
||||
|
||||
// Hintergrund immer zeichnen wenn forceRedraw=true, sonst nur wenn Canvas leer ist
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const isEmpty = imageData.data.every(pixel => pixel === 0);
|
||||
|
||||
if (forceRedraw || isEmpty) {
|
||||
// Hintergrund
|
||||
ctx.fillStyle = '#f0f0f0';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
console.log('Background drawn');
|
||||
} else {
|
||||
console.log('Canvas not empty, skipping background');
|
||||
}
|
||||
|
||||
// Tischtennis-Tisch
|
||||
const tableWidth = config.table.width;
|
||||
@@ -341,6 +408,9 @@ export default {
|
||||
const tableX = (canvas.width - tableWidth) / 2;
|
||||
const tableY = (canvas.height - tableHeight) / 2;
|
||||
|
||||
console.log('Table dimensions:', tableWidth, 'x', tableHeight);
|
||||
console.log('Table position:', tableX, ',', tableY);
|
||||
|
||||
// Tischtennis-Tisch Hintergrund
|
||||
ctx.fillStyle = config.table.color;
|
||||
ctx.fillRect(tableX, tableY, tableWidth, tableHeight);
|
||||
@@ -425,6 +495,15 @@ export default {
|
||||
|
||||
circles.forEach(circle => {
|
||||
const isSelected = this.selectedCirclePosition === circle.position;
|
||||
const hasSelection = this.selectedCirclePosition !== null;
|
||||
|
||||
// Transparenz setzen für nicht-ausgewählte Kreise
|
||||
if (hasSelection && !isSelected) {
|
||||
ctx.globalAlpha = 0.3; // Teiltransparent für nicht-ausgewählte
|
||||
} else {
|
||||
ctx.globalAlpha = 1.0; // Vollständig sichtbar für ausgewählten
|
||||
}
|
||||
|
||||
// Kreis füllen
|
||||
ctx.fillStyle = isSelected ? config.startCircles.selectedColor : config.startCircles.unselectedColor;
|
||||
ctx.beginPath();
|
||||
@@ -435,6 +514,9 @@ export default {
|
||||
ctx.strokeStyle = isSelected ? config.startCircles.selectedBorderColor : config.startCircles.unselectedBorderColor;
|
||||
ctx.lineWidth = isSelected ? config.startCircles.selectedBorderWidth : config.startCircles.unselectedBorderWidth;
|
||||
ctx.stroke();
|
||||
|
||||
// Transparenz zurücksetzen
|
||||
ctx.globalAlpha = 1.0;
|
||||
});
|
||||
|
||||
// Zielpositionen (9 Kreise mit Zahlen) - nur anzeigen wenn Schnittoption gewählt
|
||||
@@ -600,7 +682,7 @@ export default {
|
||||
const midX = (startX + targetX - targetRadius) / 2;
|
||||
const midY = (actualStartY + targetY) / 2 - config.arrow.counterOffset;
|
||||
|
||||
ctx.fillText(this.exerciseCounter.toString(), midX, midY);
|
||||
ctx.fillText("1", midX, midY);
|
||||
|
||||
// Linie zeichnen
|
||||
ctx.beginPath();
|
||||
@@ -694,9 +776,9 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pfeil zeichnen
|
||||
ctx.strokeStyle = config.arrow.color;
|
||||
ctx.fillStyle = config.arrow.color;
|
||||
// Pfeil zeichnen (von rechts nach links = blau)
|
||||
ctx.strokeStyle = '#007bff'; // Blau
|
||||
ctx.fillStyle = '#007bff'; // Blau
|
||||
ctx.lineWidth = config.arrow.width;
|
||||
ctx.lineCap = config.arrow.cap;
|
||||
|
||||
@@ -709,7 +791,7 @@ export default {
|
||||
const endY = targetY;
|
||||
|
||||
// Übungsnummer über der Linie zeichnen
|
||||
ctx.fillStyle = config.arrow.color;
|
||||
ctx.fillStyle = '#007bff'; // Blau
|
||||
ctx.font = config.arrow.counterFont;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
@@ -718,7 +800,7 @@ export default {
|
||||
const midX = (startX + endX) / 2;
|
||||
const midY = (startY + endY) / 2 - config.arrow.counterOffset;
|
||||
|
||||
ctx.fillText((this.exerciseCounter + 1).toString(), midX, midY);
|
||||
ctx.fillText("2", midX, midY);
|
||||
|
||||
// Linie zeichnen
|
||||
ctx.beginPath();
|
||||
@@ -974,10 +1056,77 @@ export default {
|
||||
this.drawCourt();
|
||||
},
|
||||
|
||||
saveDrawing() {
|
||||
const dataURL = this.canvas.toDataURL('image/png');
|
||||
this.$emit('input', dataURL);
|
||||
this.$emit('save', dataURL);
|
||||
testDraw() {
|
||||
console.log('CourtDrawingTool: testDraw called');
|
||||
console.log('Canvas:', this.canvas);
|
||||
console.log('Context:', this.ctx);
|
||||
|
||||
if (!this.canvas || !this.ctx) {
|
||||
console.error('Canvas or context not available, trying to reinitialize...');
|
||||
this.initCanvas();
|
||||
}
|
||||
|
||||
if (this.canvas && this.ctx) {
|
||||
console.log('Drawing simple test...');
|
||||
|
||||
// Einfacher Test: Roter Kreis
|
||||
this.ctx.fillStyle = 'red';
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(300, 200, 50, 0, 2 * Math.PI);
|
||||
this.ctx.fill();
|
||||
|
||||
console.log('Red circle drawn');
|
||||
} else {
|
||||
console.error('Still no canvas or context available');
|
||||
}
|
||||
},
|
||||
|
||||
async saveDrawing() {
|
||||
console.log('CourtDrawingTool: saveDrawing called');
|
||||
|
||||
try {
|
||||
const dataURL = this.canvas.toDataURL('image/png');
|
||||
console.log('CourtDrawingTool: dataURL created, length:', dataURL.length);
|
||||
|
||||
this.$emit('input', dataURL);
|
||||
|
||||
// Erstelle Zeichnungsdaten für Metadaten
|
||||
const drawingData = {
|
||||
selectedStartPosition: this.selectedStartPosition,
|
||||
strokeType: this.strokeType,
|
||||
spinType: this.spinType,
|
||||
targetPosition: this.targetPosition,
|
||||
nextStrokeType: this.nextStrokeType,
|
||||
nextStrokeSide: this.nextStrokeSide,
|
||||
nextStrokeTargetPosition: this.nextStrokeTargetPosition,
|
||||
exerciseCounter: this.exerciseCounter,
|
||||
additionalStrokes: this.additionalStrokes,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
console.log('CourtDrawingTool: drawingData created:', drawingData);
|
||||
|
||||
// Konvertiere DataURL zu Blob für Upload
|
||||
const response = await fetch(dataURL);
|
||||
const blob = await response.blob();
|
||||
console.log('CourtDrawingTool: blob created, size:', blob.size);
|
||||
|
||||
// Erstelle File-Objekt
|
||||
const file = new File([blob], `exercise-${Date.now()}.png`, { type: 'image/png' });
|
||||
console.log('CourtDrawingTool: file created:', file);
|
||||
console.log('CourtDrawingTool: file type:', file.type);
|
||||
console.log('CourtDrawingTool: file size:', file.size);
|
||||
|
||||
// Emittiere das File und die Zeichnungsdaten für Upload
|
||||
console.log('CourtDrawingTool: emitting upload-image event');
|
||||
this.$emit('upload-image', file, drawingData);
|
||||
console.log('CourtDrawingTool: upload-image event emitted');
|
||||
|
||||
// Emittiere das File für Upload (Parent-Komponente macht den Upload)
|
||||
console.log('CourtDrawingTool: File ready for upload');
|
||||
} catch (error) {
|
||||
console.error('CourtDrawingTool: Error in saveDrawing:', error);
|
||||
}
|
||||
},
|
||||
|
||||
loadDrawing() {
|
||||
@@ -997,6 +1146,67 @@ export default {
|
||||
input.click();
|
||||
},
|
||||
|
||||
resetAllValues() {
|
||||
console.log('CourtDrawingTool: Resetting all values to initial state');
|
||||
this.selectedStartPosition = null;
|
||||
this.selectedCirclePosition = null;
|
||||
this.strokeType = null;
|
||||
this.spinType = null;
|
||||
this.targetPosition = null;
|
||||
this.nextStrokeType = null;
|
||||
this.nextStrokeSide = null;
|
||||
this.nextStrokeTargetPosition = null;
|
||||
this.exerciseCounter = 1;
|
||||
this.additionalStrokes = [];
|
||||
this.updateTextFields();
|
||||
},
|
||||
|
||||
loadDrawingFromMetadata() {
|
||||
if (this.drawingData) {
|
||||
console.log('CourtDrawingTool: Loading drawing from metadata:', this.drawingData);
|
||||
|
||||
// Lade alle Zeichnungsdaten
|
||||
this.selectedStartPosition = this.drawingData.selectedStartPosition || null;
|
||||
this.strokeType = this.drawingData.strokeType || null;
|
||||
this.spinType = this.drawingData.spinType || null;
|
||||
this.targetPosition = this.drawingData.targetPosition || null;
|
||||
this.nextStrokeType = this.drawingData.nextStrokeType || null;
|
||||
this.nextStrokeSide = this.drawingData.nextStrokeSide || null;
|
||||
this.nextStrokeTargetPosition = this.drawingData.nextStrokeTargetPosition || null;
|
||||
this.exerciseCounter = this.drawingData.exerciseCounter || 1;
|
||||
this.additionalStrokes = this.drawingData.additionalStrokes || [];
|
||||
|
||||
// Setze selectedCirclePosition basierend auf selectedStartPosition
|
||||
if (this.selectedStartPosition === 'AS') {
|
||||
this.selectedCirclePosition = 'middle'; // Standard für AS
|
||||
} else if (this.selectedStartPosition === 'AS1') {
|
||||
this.selectedCirclePosition = 'top';
|
||||
} else if (this.selectedStartPosition === 'AS2') {
|
||||
this.selectedCirclePosition = 'middle';
|
||||
} else if (this.selectedStartPosition === 'AS3') {
|
||||
this.selectedCirclePosition = 'bottom';
|
||||
}
|
||||
|
||||
console.log('CourtDrawingTool: Loaded values:', {
|
||||
selectedStartPosition: this.selectedStartPosition,
|
||||
selectedCirclePosition: this.selectedCirclePosition,
|
||||
strokeType: this.strokeType,
|
||||
spinType: this.spinType,
|
||||
targetPosition: this.targetPosition
|
||||
});
|
||||
|
||||
// Aktualisiere die Textfelder
|
||||
this.updateTextFields();
|
||||
|
||||
// Zeichne das Canvas neu
|
||||
this.$nextTick(() => {
|
||||
this.drawCourt();
|
||||
});
|
||||
|
||||
console.log('CourtDrawingTool: Drawing loaded from metadata');
|
||||
}
|
||||
},
|
||||
|
||||
loadDrawingFromData(dataURL) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
@@ -1173,12 +1383,14 @@ canvas {
|
||||
|
||||
|
||||
.btn-small {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-size: 0.75rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
min-width: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@@ -1203,6 +1415,51 @@ canvas {
|
||||
border-color: #5a6268;
|
||||
}
|
||||
|
||||
/* Schlagart-Buttons (VH/RH) - Grün */
|
||||
.btn-stroke {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.btn-stroke:hover {
|
||||
background-color: #218838;
|
||||
border-color: #218838;
|
||||
}
|
||||
|
||||
.btn-stroke.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
.btn-stroke.btn-secondary:hover {
|
||||
background-color: #5a6268;
|
||||
border-color: #5a6268;
|
||||
}
|
||||
|
||||
/* Schlagtyp-Buttons (US/OS/TS/FL/BL) - Orange Hintergrund */
|
||||
.btn-stroke-type {
|
||||
background: #fd7e14;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-stroke-type:hover {
|
||||
background: #e8650e;
|
||||
border-color: #e8650e;
|
||||
}
|
||||
|
||||
.btn-stroke-type.btn-secondary {
|
||||
color: white !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.btn-stroke-type.btn-secondary:hover {
|
||||
background-color: #e8650e !important;
|
||||
border-color: #e8650e !important;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
@@ -1236,6 +1493,10 @@ input[type="range"] {
|
||||
|
||||
.stroke-selection {
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.stroke-selection label {
|
||||
@@ -1248,7 +1509,7 @@ input[type="range"] {
|
||||
.stroke-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.spin-selection {
|
||||
@@ -1265,7 +1526,7 @@ input[type="range"] {
|
||||
.spin-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.additional-strokes {
|
||||
@@ -1274,21 +1535,20 @@ input[type="range"] {
|
||||
|
||||
.next-stroke-selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.next-stroke-type {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.5rem;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.next-stroke-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.exercise-info {
|
||||
|
||||
@@ -345,7 +345,7 @@
|
||||
+ accident.accident}}</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schnell hinzufügen Dialog -->
|
||||
<div v-if="showQuickAddDialog" class="modal-overlay" @click.self="closeQuickAddDialog">
|
||||
@@ -1123,7 +1123,12 @@ export default {
|
||||
},
|
||||
|
||||
showActivityImage(imageLink) {
|
||||
this.imageUrl = imageLink;
|
||||
// Erstelle vollständige URL mit korrektem Port
|
||||
if (imageLink && imageLink.startsWith('/api/')) {
|
||||
this.imageUrl = `http://localhost:3000${imageLink}`;
|
||||
} else {
|
||||
this.imageUrl = imageLink;
|
||||
}
|
||||
this.showImage = true;
|
||||
},
|
||||
async loadMemberImage(member) {
|
||||
|
||||
@@ -77,11 +77,15 @@
|
||||
<!-- Zeichen-Tool -->
|
||||
<div class="drawing-section">
|
||||
<h5>Übungszeichnung erstellen</h5>
|
||||
<CourtDrawingTool
|
||||
v-model="editModel.drawingData"
|
||||
@save="onDrawingSave"
|
||||
@update-fields="onUpdateFields"
|
||||
/>
|
||||
<CourtDrawingTool
|
||||
v-model="editModel.drawingData"
|
||||
:activity-id="editModel.id"
|
||||
:drawing-data="editModel.drawingData"
|
||||
@save="onDrawingSave"
|
||||
@update-fields="onUpdateFields"
|
||||
@upload-image="onDrawingImageUpload"
|
||||
@image-uploaded="onImageUploaded"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="image-list" v-if="images && images.length">
|
||||
@@ -121,6 +125,7 @@ export default {
|
||||
editModel: null,
|
||||
images: [],
|
||||
selectedFile: null,
|
||||
selectedDrawingData: null,
|
||||
mergeSourceId: '',
|
||||
mergeTargetId: '',
|
||||
};
|
||||
@@ -154,7 +159,35 @@ export default {
|
||||
const r = await apiClient.get(`/predefined-activities/${a.id}`);
|
||||
const { images, ...activity } = r.data;
|
||||
this.images = images || [];
|
||||
this.editModel = { ...activity };
|
||||
|
||||
// Lade Zeichnungsdaten aus dem ersten Bild, falls vorhanden
|
||||
let drawingData = null;
|
||||
if (images && images.length > 0 && images[0].drawingData) {
|
||||
try {
|
||||
drawingData = JSON.parse(images[0].drawingData);
|
||||
console.log('PredefinedActivities: Loaded drawingData:', drawingData);
|
||||
} catch (error) {
|
||||
console.error('PredefinedActivities: Error parsing drawingData:', error);
|
||||
}
|
||||
} else {
|
||||
// Keine Bilder vorhanden - setze drawingData explizit auf null
|
||||
console.log('PredefinedActivities: No images found, setting drawingData to null');
|
||||
drawingData = null;
|
||||
}
|
||||
|
||||
this.editModel = { ...activity, drawingData };
|
||||
},
|
||||
async reloadImages() {
|
||||
if (this.editModel && this.editModel.id) {
|
||||
try {
|
||||
const r = await apiClient.get(`/predefined-activities/${this.editModel.id}`);
|
||||
const { images } = r.data;
|
||||
this.images = images || [];
|
||||
console.log('Images reloaded:', this.images);
|
||||
} catch (error) {
|
||||
console.error('Error reloading images:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
formatItem(a) {
|
||||
return `${a.code ? '[' + a.code + '] ' : ''}${a.name}`;
|
||||
@@ -188,6 +221,9 @@ export default {
|
||||
},
|
||||
async save() {
|
||||
if (!this.editModel) return;
|
||||
|
||||
console.log('Save: selectedFile =', this.selectedFile);
|
||||
|
||||
if (this.editModel.id) {
|
||||
const { id, ...payload } = this.editModel;
|
||||
const r = await apiClient.put(`/predefined-activities/${id}`, payload);
|
||||
@@ -195,29 +231,68 @@ export default {
|
||||
} else {
|
||||
const r = await apiClient.post('/predefined-activities', this.editModel);
|
||||
this.editModel = r.data;
|
||||
// Nach dem Erstellen einer neuen Aktivität, falls ein Bild ausgewählt wurde, hochladen
|
||||
if (this.selectedFile) {
|
||||
await this.uploadImage();
|
||||
}
|
||||
}
|
||||
|
||||
// Nach dem Speichern (sowohl CREATE als auch UPDATE): Bild hochladen falls vorhanden
|
||||
if (this.selectedFile) {
|
||||
console.log('Uploading image after save...');
|
||||
await this.uploadImage();
|
||||
} else {
|
||||
console.log('No selectedFile to upload');
|
||||
}
|
||||
|
||||
await this.reload();
|
||||
},
|
||||
onFileChange(e) {
|
||||
this.selectedFile = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
||||
},
|
||||
imageUrl(img) {
|
||||
return `/api/predefined-activities/${this.editModel.id}/image/${img.id}`;
|
||||
return `http://localhost:3000/api/predefined-activities/${this.editModel.id}/image/${img.id}`;
|
||||
},
|
||||
async uploadImage() {
|
||||
if (!this.editModel || !this.editModel.id || !this.selectedFile) return;
|
||||
if (!this.editModel || !this.editModel.id || !this.selectedFile) {
|
||||
console.log('Upload skipped: editModel=', this.editModel, 'selectedFile=', this.selectedFile);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting image upload...');
|
||||
console.log('editModel:', this.editModel);
|
||||
console.log('selectedActivity:', this.selectedActivity);
|
||||
console.log('Activity ID (editModel.id):', this.editModel.id);
|
||||
console.log('Activity ID (selectedActivity.id):', this.selectedActivity?.id);
|
||||
console.log('File:', this.selectedFile);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('image', this.selectedFile);
|
||||
await apiClient.post(`/predefined-activities/${this.editModel.id}/image`, fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
// Nach Upload Details neu laden
|
||||
await this.select(this.editModel);
|
||||
this.selectedFile = null;
|
||||
|
||||
// Füge Zeichnungsdaten hinzu, falls vorhanden
|
||||
if (this.selectedDrawingData) {
|
||||
fd.append('drawingData', JSON.stringify(this.selectedDrawingData));
|
||||
console.log('Added drawingData to FormData:', this.selectedDrawingData);
|
||||
}
|
||||
|
||||
// Verwende PUT für Updates, POST für neue Activities
|
||||
const isUpdate = this.selectedActivity && this.selectedActivity.id === this.editModel.id;
|
||||
const method = isUpdate ? 'put' : 'post';
|
||||
|
||||
console.log('Using method:', method);
|
||||
|
||||
try {
|
||||
const response = await apiClient[method](`/predefined-activities/${this.editModel.id}/image`, fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
console.log('Upload successful:', response);
|
||||
|
||||
// Nach Upload Details neu laden
|
||||
await this.select(this.editModel);
|
||||
this.selectedFile = null;
|
||||
|
||||
// Bildliste explizit aktualisieren
|
||||
await this.reloadImages();
|
||||
} catch (error) {
|
||||
console.error('Upload failed:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async deleteImage(imageId) {
|
||||
if (!this.editModel || !this.editModel.id) return;
|
||||
@@ -244,6 +319,7 @@ export default {
|
||||
this.editModel.imageLink = drawingData;
|
||||
}
|
||||
}
|
||||
// Nicht automatisch speichern, nur wenn User explizit "Speichern" klickt
|
||||
},
|
||||
onUpdateFields(fields) {
|
||||
if (this.editModel) {
|
||||
@@ -251,6 +327,31 @@ export default {
|
||||
this.editModel.name = fields.name;
|
||||
this.editModel.description = fields.description;
|
||||
}
|
||||
},
|
||||
|
||||
async onDrawingImageUpload(file, drawingData) {
|
||||
console.log('onDrawingImageUpload called with file:', file);
|
||||
console.log('onDrawingImageUpload called with drawingData:', drawingData);
|
||||
console.log('File type:', file?.type);
|
||||
console.log('File size:', file?.size);
|
||||
console.log('File name:', file?.name);
|
||||
|
||||
// Setze das File und die Zeichnungsdaten für den Upload
|
||||
this.selectedFile = file;
|
||||
this.selectedDrawingData = drawingData;
|
||||
console.log('selectedFile set to:', this.selectedFile);
|
||||
console.log('selectedDrawingData set to:', this.selectedDrawingData);
|
||||
|
||||
// Upload wird erst beim Speichern durchgeführt
|
||||
console.log('File and drawing data ready for upload when saving');
|
||||
},
|
||||
|
||||
async onImageUploaded() {
|
||||
console.log('Image uploaded successfully, refreshing image list...');
|
||||
// Bildliste aktualisieren
|
||||
if (this.editModel && this.editModel.id) {
|
||||
await this.select(this.editModel);
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
|
||||
Reference in New Issue
Block a user