From 091599b7450c4c3ec1c23082d98b8b8583292520 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Tue, 23 Sep 2025 08:39:13 +0200 Subject: [PATCH] =?UTF-8?q?Erweitert=20die=20Funktionalit=C3=A4t=20in=20Pr?= =?UTF-8?q?edefinedActivityImageController.js,=20um=20Zeichnungsdaten=20au?= =?UTF-8?q?s=20dem=20Request=20zu=20extrahieren=20und=20in=20der=20Datenba?= =?UTF-8?q?nk=20zu=20speichern.=20Aktualisiert=20das=20Datenmodell=20in=20?= =?UTF-8?q?PredefinedActivityImage.js,=20um=20ein=20neues=20Feld=20f=C3=BC?= =?UTF-8?q?r=20Zeichnungsdaten=20hinzuzuf=C3=BCgen.=20Passt=20die=20Routen?= =?UTF-8?q?=20in=20predefinedActivityRoutes.js=20an,=20um=20die=20neue=20P?= =?UTF-8?q?UT-Methode=20f=C3=BCr=20das=20Hochladen=20von=20Bildern=20zu=20?= =?UTF-8?q?unterst=C3=BCtzen.=20Integriert=20die=20Zeichnungsdaten=20in=20?= =?UTF-8?q?die=20Aktivit=C3=A4tenlogik=20in=20diaryDateActivityService.js?= =?UTF-8?q?=20und=20aktualisiert=20die=20Benutzeroberfl=C3=A4che=20in=20Co?= =?UTF-8?q?urtDrawingTool.vue=20zur=20Unterst=C3=BCtzung=20von=20Zeichnung?= =?UTF-8?q?sdaten.=20Verbessert=20die=20Handhabung=20von=20Bild-Uploads=20?= =?UTF-8?q?in=20PredefinedActivities.vue=20und=20implementiert=20die=20Log?= =?UTF-8?q?ik=20zum=20Laden=20von=20Zeichnungsdaten=20beim=20Bearbeiten=20?= =?UTF-8?q?von=20Aktivit=C3=A4ten.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../predefinedActivityImageController.js | 5 + ...ing_data_to_predefined_activity_images.sql | 11 + backend/models/PredefinedActivityImage.js | 5 + backend/routes/predefinedActivityRoutes.js | 1 + backend/services/diaryDateActivityService.js | 90 +++- frontend/src/components/CourtDrawingTool.vue | 400 +++++++++++++++--- frontend/src/views/DiaryView.vue | 9 +- frontend/src/views/PredefinedActivities.vue | 137 +++++- 8 files changed, 562 insertions(+), 96 deletions(-) create mode 100644 backend/migrations/add_drawing_data_to_predefined_activity_images.sql diff --git a/backend/controllers/predefinedActivityImageController.js b/backend/controllers/predefinedActivityImageController.js index 9966af7..80e3e94 100644 --- a/backend/controllers/predefinedActivityImageController.js +++ b/backend/controllers/predefinedActivityImageController.js @@ -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 diff --git a/backend/migrations/add_drawing_data_to_predefined_activity_images.sql b/backend/migrations/add_drawing_data_to_predefined_activity_images.sql new file mode 100644 index 0000000..0eadc30 --- /dev/null +++ b/backend/migrations/add_drawing_data_to_predefined_activity_images.sql @@ -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`; diff --git a/backend/models/PredefinedActivityImage.js b/backend/models/PredefinedActivityImage.js index c385931..cc0db92 100644 --- a/backend/models/PredefinedActivityImage.js +++ b/backend/models/PredefinedActivityImage.js @@ -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, diff --git a/backend/routes/predefinedActivityRoutes.js b/backend/routes/predefinedActivityRoutes.js index 42873a7..ce74def 100644 --- a/backend/routes/predefinedActivityRoutes.js +++ b/backend/routes/predefinedActivityRoutes.js @@ -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); diff --git a/backend/services/diaryDateActivityService.js b/backend/services/diaryDateActivityService.js index f546a0c..92136c1 100644 --- a/backend/services/diaryDateActivityService.js +++ b/backend/services/diaryDateActivityService.js @@ -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) { diff --git a/frontend/src/components/CourtDrawingTool.vue b/frontend/src/components/CourtDrawingTool.vue index dabc61b..3119c7b 100644 --- a/frontend/src/components/CourtDrawingTool.vue +++ b/frontend/src/components/CourtDrawingTool.vue @@ -2,14 +2,9 @@

Tischtennis-Übungszeichnung

-
- - - -
-
- -
+
+ +
-
+
- - +
Übung konfigurieren
+ Aufschlag:
-
+
+
@@ -94,55 +97,62 @@
+ + +
+ + + +
@@ -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 { diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 7d24f50..ecd4a33 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -345,7 +345,7 @@ + accident.accident}} - +