Erweitert die Funktionalität in CourtDrawingRender.vue und CourtDrawingTool.vue zur Verbesserung der Zeichnungslogik. Fügt neue Offset-Parameter für Zielkreise hinzu und optimiert die Berechnung der Zielpositionen. Entfernt die Schaltflächen für das manuelle Speichern in CourtDrawingTool.vue zugunsten einer automatischen Speicherung. Aktualisiert die Benutzeroberfläche in PredefinedActivities.vue zur Unterstützung der neuen Zeichnungsdaten-Logik.

This commit is contained in:
Torsten Schulz (local)
2025-10-01 09:41:07 +02:00
parent f4187512ba
commit 4bfa6a5889
3 changed files with 105 additions and 58 deletions

View File

@@ -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;
},

View File

@@ -172,11 +172,7 @@
</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>
</div>
<!-- Buttons entfernt: automatische Speicherung aktiv -->
</div>
</template>
@@ -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);
}
}
}
</script>

View File

@@ -78,15 +78,11 @@
<div class="drawing-section">
<h5>Übungszeichnung erstellen</h5>
<CourtDrawingTool
v-model="editModel.drawingData"
:activity-id="editModel.id"
:drawing-data="editModel.drawingData"
:allow-image-upload="false"
@save="onDrawingSave"
@update-fields="onUpdateFields"
@update-drawing-data="onUpdateDrawingData"
@upload-image="onDrawingImageUpload"
@image-uploaded="onImageUploaded"
/>
</div>