From 4bfa6a58895e6d2cf24fb891ec416bd9c0b03678 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 1 Oct 2025 09:41:07 +0200 Subject: [PATCH] =?UTF-8?q?Erweitert=20die=20Funktionalit=C3=A4t=20in=20Co?= =?UTF-8?q?urtDrawingRender.vue=20und=20CourtDrawingTool.vue=20zur=20Verbe?= =?UTF-8?q?sserung=20der=20Zeichnungslogik.=20F=C3=BCgt=20neue=20Offset-Pa?= =?UTF-8?q?rameter=20f=C3=BCr=20Zielkreise=20hinzu=20und=20optimiert=20die?= =?UTF-8?q?=20Berechnung=20der=20Zielpositionen.=20Entfernt=20die=20Schalt?= =?UTF-8?q?fl=C3=A4chen=20f=C3=BCr=20das=20manuelle=20Speichern=20in=20Cou?= =?UTF-8?q?rtDrawingTool.vue=20zugunsten=20einer=20automatischen=20Speiche?= =?UTF-8?q?rung.=20Aktualisiert=20die=20Benutzeroberfl=C3=A4che=20in=20Pre?= =?UTF-8?q?definedActivities.vue=20zur=20Unterst=C3=BCtzung=20der=20neuen?= =?UTF-8?q?=20Zeichnungsdaten-Logik.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/CourtDrawingRender.vue | 99 +++++++++++-------- frontend/src/components/CourtDrawingTool.vue | 60 ++++++++--- frontend/src/views/PredefinedActivities.vue | 4 - 3 files changed, 105 insertions(+), 58 deletions(-) diff --git a/frontend/src/components/CourtDrawingRender.vue b/frontend/src/components/CourtDrawingRender.vue index 295c80e..4481ae0 100644 --- a/frontend/src/components/CourtDrawingRender.vue +++ b/frontend/src/components/CourtDrawingRender.vue @@ -66,14 +66,27 @@ export default { primaryColor: '#d32f2f', // rechts -> target (rot) secondaryColor: '#1565c0', // zurück (blau) width: 6, - headLength: 24 + headLength: 24, + vhOffsetX: 5, + vhOffsetY: 8, + rhOffsetX: 10, + rhOffsetY: 0 }, targetCircles: { radius: 13, + rightXOffset: 20, + middleXOffset: 40, topYOffset: 45, bottomYOffset: 45, transparency: 0.25 }, + leftTargetCircles: { + radius: 10, + leftXOffset: 20, + rightXOffset: 40, + topYOffset: 40, + bottomYOffset: 40 + }, hitMarker: { radius: 10.5, fill: '#ffffff', @@ -223,17 +236,17 @@ export default { ctx.stroke(); }, computeRightTargetPosition(number) { - // replicate the same geometry as in the drawing tool + // Geometrie wie im Tool: nutzt rightXOffset/middleXOffset const tableWidth = this.config.table.width; const tableHeight = this.config.table.height; const tableX = (this.config.canvas.width - tableWidth) / 2; const tableY = (this.config.canvas.height - tableHeight) / 2; - const centerX = tableX + tableWidth / 2; const cfg = this.config.targetCircles; - const x1 = tableX + tableWidth - 30; // rightmost column (1,2,3) - const x3 = centerX + (x1 - centerX) * 0.35; // near net (7,8,9) - const x2 = (x1 + x3) / 2; // middle column (4,5,6) + const x1 = tableX + tableWidth - cfg.rightXOffset; // (1,2,3) + const x3 = tableX + tableWidth / 2 + cfg.middleXOffset; // (7,8,9) + const xdiff = x3 - x1; + const x2 = x3 - xdiff / 2; // (4,5,6) const positions = { 1: { x: x1, y: tableY + cfg.topYOffset }, @@ -249,22 +262,23 @@ export default { return positions[number] || null; }, getStartPoint() { - const cfg = this.config.startCircles; - const tableWidth = this.config.table.width; - const tableHeight = this.config.table.height; - const tableX = (this.config.canvas.width - tableWidth) / 2; - const tableY = (this.config.canvas.height - tableHeight) / 2; - const map = { - AS1: 'top', AS2: 'middle', AS3: 'bottom', AS: 'middle' - }; - const selected = map[this.drawingData?.selectedStartPosition] || 'middle'; - const circleX = tableX - cfg.x; // Links vom Tisch - const topY = tableY + cfg.topYOffset; - const midY = tableY + tableHeight / 2; - const botY = tableY + tableHeight - cfg.bottomYOffset; - const y = selected === 'top' ? topY : selected === 'bottom' ? botY : midY; - // arrow should start slightly to the right of the start circle - return { x: circleX + cfg.radius + 6, y }; + // Startpunkt wie im Tool abhängig von Schlagseite (VH/RH) + const sc = this.config.startCircles; + const ar = this.config.arrows; + const tblW = this.config.table.width; + const tblH = this.config.table.height; + const tableX = (this.config.canvas.width - tblW) / 2; + const tableY = (this.config.canvas.height - tblH) / 2; + const circleX = tableX - sc.x; // Kreis links vor dem Tisch + const map = { AS1: 'top', AS2: 'middle', AS3: 'bottom', AS: 'middle' }; + const pos = map[this.drawingData?.selectedStartPosition] || 'middle'; + const y = pos === 'top' ? tableY + sc.topYOffset : pos === 'bottom' ? tableY + tblH - sc.bottomYOffset : tableY + tblH / 2; + const isVH = (this.drawingData?.strokeType || 'VH') === 'VH'; + const startX = isVH + ? circleX + sc.radius + ar.vhOffsetX + : circleX + sc.radius + ar.rhOffsetX; + const startYOffset = isVH ? ar.vhOffsetY : ar.rhOffsetY; + return { x: startX, y: y + startYOffset }; }, drawArrow(ctx, from, to, color, label) { const { width, headLength } = this.config.arrows; @@ -318,7 +332,8 @@ export default { const spinAbbrev = this.abbrevSpin(this.drawingData.spinType); // Text gehört an die Quelle (ohne "target") const sourceLabel = `${strokeSide} ${spinAbbrev}`.trim(); - this.drawArrow(ctx, from, to, this.config.arrows.primaryColor); + const toEnd = { x: to.x - this.config.targetCircles.radius, y: to.y }; + this.drawArrow(ctx, from, toEnd, this.config.arrows.primaryColor); // Unter dem Startkreis beschriften const startCenter = this.getStartCircleCenter(); this.drawLabelBelow(ctx, sourceLabel, startCenter); @@ -328,18 +343,20 @@ export default { const leftTarget = this.drawingData.nextStrokeTargetPosition ? Number(this.drawingData.nextStrokeTargetPosition) : null; if (tp && leftTarget) { // source near previous right target - const sourceRight = this.computeRightTargetPosition(tp); + const sourceRightCenter = this.computeRightTargetPosition(tp); // left target mapping: mirror scheme to left half - const toLeft = this.computeLeftTargetPosition(leftTarget); + const toLeftCenter = this.computeLeftTargetPosition(leftTarget); // Zielmarkierung links - this.drawHitMarker(ctx, toLeft); + this.drawHitMarker(ctx, toLeftCenter); const side = this.drawingData.nextStrokeSide || ''; const type = this.drawingData.nextStrokeType || ''; // Text gehört ans Ziel (ohne "extra target") const targetLabel = `${side} ${type}`.trim(); + const sourceRight = { x: sourceRightCenter.x - this.config.targetCircles.radius, y: sourceRightCenter.y }; + const toLeft = { x: toLeftCenter.x + this.config.leftTargetCircles.radius, y: toLeftCenter.y }; this.drawArrow(ctx, sourceRight, toLeft, this.config.arrows.secondaryColor); // Unter dem rechten Ziel (target der ersten Linie) beschriften - this.drawLabelBelow(ctx, targetLabel, sourceRight); + this.drawLabelBelow(ctx, targetLabel, sourceRightCenter); } }, getStartCircleCenter() { @@ -370,28 +387,32 @@ export default { ctx.stroke(); }, computeLeftTargetPosition(number) { - // mirror target grid to left side + // Spiegelung wie im Tool: nutzt leftTargetCircles Offsets const tableWidth = this.config.table.width; const tableHeight = this.config.table.height; const tableX = (this.config.canvas.width - tableWidth) / 2; const tableY = (this.config.canvas.height - tableHeight) / 2; - const centerX = tableX + tableWidth / 2; - const cfg = this.config.targetCircles; + const cfg = this.config.leftTargetCircles; - const x1 = tableX + 30; // leftmost column - const x3 = centerX - (centerX - x1) * 0.35; // near net - const x2 = (x1 + x3) / 2; + const x1 = tableX + cfg.leftXOffset; // linke Spalte (Lang) + const x3 = tableX + tableWidth / 2 - cfg.rightXOffset; // nah am Netz (Kurz) + const xdiff = x3 - x1; + const x2 = x3 - xdiff / 2; + // Gespiegelte Y-Zuordnung wie im Tool: + // 1,4,7 = unten (VH gespiegelt) + // 2,5,8 = mitte + // 3,6,9 = oben (RH gespiegelt) const positions = { - 1: { x: x1, y: tableY + cfg.topYOffset }, + 1: { x: x1, y: tableY + tableHeight - cfg.bottomYOffset }, 2: { x: x1, y: tableY + tableHeight / 2 }, - 3: { x: x1, y: tableY + tableHeight - cfg.bottomYOffset }, - 4: { x: x2, y: tableY + cfg.topYOffset }, + 3: { x: x1, y: tableY + cfg.topYOffset }, + 4: { x: x2, y: tableY + tableHeight - cfg.bottomYOffset }, 5: { x: x2, y: tableY + tableHeight / 2 }, - 6: { x: x2, y: tableY + tableHeight - cfg.bottomYOffset }, - 7: { x: x3, y: tableY + cfg.topYOffset }, + 6: { x: x2, y: tableY + cfg.topYOffset }, + 7: { x: x3, y: tableY + tableHeight - cfg.bottomYOffset }, 8: { x: x3, y: tableY + tableHeight / 2 }, - 9: { x: x3, y: tableY + tableHeight - cfg.bottomYOffset } + 9: { x: x3, y: tableY + cfg.topYOffset } }; return positions[number] || null; }, diff --git a/frontend/src/components/CourtDrawingTool.vue b/frontend/src/components/CourtDrawingTool.vue index 0ec848f..76cdafb 100644 --- a/frontend/src/components/CourtDrawingTool.vue +++ b/frontend/src/components/CourtDrawingTool.vue @@ -172,11 +172,7 @@ - -
- - -
+ @@ -323,13 +319,16 @@ export default { }, watch: { strokeType() { + this.emitDrawingData(); this.updateTextFields(); }, spinType() { this.drawCourt(); // Neu zeichnen wenn sich Schnittoption ändert + this.emitDrawingData(); this.updateTextFields(); }, targetPosition() { + this.emitDrawingData(); this.updateTextFields(); }, selectedStartPosition() { @@ -337,6 +336,7 @@ export default { if (this.canvas && this.ctx) { this.drawCourt(); } + this.emitDrawingData(); }, drawingData: { handler(newVal, oldVal) { @@ -889,6 +889,7 @@ export default { this.selectedCirclePosition = clickedCircle.circlePosition; this.selectedStartPosition = clickedCircle.position; this.drawCourt(); // Neu zeichnen für Kreis-Hervorhebung + this.emitDrawingData(); this.updateTextFields(); return; } @@ -898,13 +899,14 @@ export default { if (clickedTarget) { this.targetPosition = clickedTarget; - // Wenn keine Startposition ausgewählt ist, setze eine Standard-Startposition + // Wenn keine Startposition ausgewählt ist, setze standardmäßig die mittlere Startposition (AS2) if (!this.selectedStartPosition) { - this.selectedStartPosition = 'AS'; + this.selectedStartPosition = 'AS2'; this.selectedCirclePosition = 'middle'; } this.drawCourt(); // Neu zeichnen für Zielposition-Hervorhebung + this.emitDrawingData(); this.updateTextFields(); return; } @@ -915,6 +917,7 @@ export default { if (clickedLeftTarget) { this.nextStrokeTargetPosition = clickedLeftTarget; this.drawCourt(); + this.emitDrawingData(); this.updateTextFields(); return; } @@ -943,9 +946,9 @@ export default { // Prüfe Abstand zu den Kreisen const circles = [ - { x: circleX, y: topCircleY, position: 'AS', circlePosition: 'top' }, - { x: circleX, y: middleCircleY, position: 'AS', circlePosition: 'middle' }, - { x: circleX, y: bottomCircleY, position: 'AS', circlePosition: 'bottom' } + { x: circleX, y: topCircleY, position: 'AS1', circlePosition: 'top' }, + { x: circleX, y: middleCircleY, position: 'AS2', circlePosition: 'middle' }, + { x: circleX, y: bottomCircleY, position: 'AS3', circlePosition: 'bottom' } ]; for (const circle of circles) { @@ -1252,27 +1255,38 @@ export default { 'Gegenläufer': 'GL' }; - // Label-Mapping für Zielpositionen - const targetLabelMap = { + // Suffix-Mapping für Startposition (an AS direkt anhängen, ohne Leerzeichen) + const startSuffixMap = { + 'AS1': 'vL', + 'AS2': 'vM', + 'AS3': 'vR' + }; + const startSuffix = startSuffixMap[this.selectedStartPosition] || ''; + const startCode = `AS${startSuffix}`; + + // Label-Mapping für erstes Ziel (Hauptpfeil): altes Schema + const mainTargetLabelMap = { '1': 'VH L', '2': 'M L', '3': 'RH L', '4': 'VH H', '5': 'M H', '6': 'RH H', '7': 'VH K', '8': 'M K', '9': 'RH K' }; + // Label-Mapping für Zusatzschläge: wie Hauptziel (VH L, M L, RH L ...) + const additionalTargetLabelMap = mainTargetLabelMap; - let code = `${this.selectedStartPosition} ${this.strokeType}`; + let code = `${startCode} ${this.strokeType}`; if (this.spinType) { const spinCode = spinCodeMap[this.spinType] || this.spinType; code += ` ${spinCode}`; } if (this.targetPosition) { - const targetLabel = targetLabelMap[this.targetPosition] || this.targetPosition; + const targetLabel = mainTargetLabelMap[this.targetPosition] || this.targetPosition; code += ` → ${targetLabel}`; } // Zusätzliche Schläge hinzufügen if (this.additionalStrokes.length > 0) { this.additionalStrokes.forEach(stroke => { - const targetLabel = targetLabelMap[stroke.targetPosition] || stroke.targetPosition; + const targetLabel = additionalTargetLabelMap[stroke.targetPosition] || stroke.targetPosition; code += ` / ${stroke.side} ${stroke.type} → ${targetLabel}`; }); } @@ -1342,6 +1356,7 @@ export default { // Canvas neu zeichnen this.drawCourt(); + this.emitDrawingData(); }, updateTextFields() { @@ -1358,6 +1373,21 @@ export default { description: description }); } + , + emitDrawingData() { + 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 + }; + this.$emit('update-drawing-data', drawingData); + } } } diff --git a/frontend/src/views/PredefinedActivities.vue b/frontend/src/views/PredefinedActivities.vue index ae71ad1..8b69b8d 100644 --- a/frontend/src/views/PredefinedActivities.vue +++ b/frontend/src/views/PredefinedActivities.vue @@ -78,15 +78,11 @@
Übungszeichnung erstellen