Änderung: Erweiterung der Taxi-Map-Logik und Verbesserung der Benutzeroberfläche
Änderungen: - Hinzufügung neuer Modelle für TaxiMapTile, TaxiStreetName und TaxiMapTileStreet zur Unterstützung der Tile- und Straßennamen-Logik. - Anpassung der TaxiMap- und TaxiMapService-Logik zur Verwaltung von Tiles und Straßennamen. - Implementierung von Methoden zur Upsert-Logik für Tiles und Straßennamen in der TaxiMapService. - Verbesserung der Benutzeroberfläche in TaxiToolsView.vue zur Anzeige und Bearbeitung von Straßennamen und zusätzlichen Elementen. Diese Anpassungen verbessern die Funktionalität und Benutzererfahrung im Taxi-Minispiel erheblich, indem sie eine detailliertere Verwaltung von Karten und Straßennamen ermöglichen.
This commit is contained in:
@@ -94,7 +94,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Tile-Palette (rechts) -->
|
||||
<div class="tile-palette">
|
||||
<div class="tile-palette tool-panel">
|
||||
<h4>{{ $t('admin.taxiTools.mapEditor.tilePalette') }}</h4>
|
||||
<div class="tile-grid">
|
||||
<div
|
||||
@@ -113,6 +113,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Straßennamen (drittes Panel) -->
|
||||
<div v-if="showStreetNamePanel" class="tool-panel street-names">
|
||||
<h4>{{ $t('admin.taxiTools.mapEditor.streetNames') }}</h4>
|
||||
<div class="panel-body">
|
||||
<div class="form-group-mini" v-if="supportsHorizontalName">
|
||||
<label class="arrow-label">↔</label>
|
||||
<input type="text" :value="selectedCell?.streetNameH || ''"
|
||||
@input="onChangeStreetName('h', $event.target.value)" />
|
||||
</div>
|
||||
<div class="form-group-mini" v-if="supportsVerticalName">
|
||||
<label class="arrow-label">↕</label>
|
||||
<input type="text" :value="selectedCell?.streetNameV || ''"
|
||||
@input="onChangeStreetName('v', $event.target.value)" />
|
||||
</div>
|
||||
<div class="form-actions-mini" v-if="showContinueOtherButton">
|
||||
<button type="button" class="btn btn-primary mini" @click="continueStreetNames">
|
||||
{{ $t('admin.taxiTools.mapEditor.continueOther') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zusätzliche Elemente (viertes Panel) -->
|
||||
<div v-if="showExtraElementsPanel" class="tool-panel extra-elements">
|
||||
<h4>{{ $t('admin.taxiTools.mapEditor.extraElements') }}</h4>
|
||||
<div class="panel-body placeholder">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -188,7 +216,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Tile-Palette (rechts) -->
|
||||
<div class="tile-palette">
|
||||
<div class="tile-palette tool-panel">
|
||||
<h4>{{ $t('admin.taxiTools.mapEditor.tilePalette') }}</h4>
|
||||
<div class="tile-grid">
|
||||
<div
|
||||
@@ -207,6 +235,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Straßennamen (drittes Panel) -->
|
||||
<div v-if="showStreetNamePanel" class="tool-panel street-names">
|
||||
<h4>{{ $t('admin.taxiTools.mapEditor.streetNames') }}</h4>
|
||||
<div class="panel-body">
|
||||
<div class="form-group-mini" v-if="supportsHorizontalName">
|
||||
<label class="arrow-label">↔</label>
|
||||
<input type="text" :value="selectedCell?.streetNameH || ''"
|
||||
@input="onChangeStreetName('h', $event.target.value)" />
|
||||
</div>
|
||||
<div class="form-group-mini" v-if="supportsVerticalName">
|
||||
<label class="arrow-label">↕</label>
|
||||
<input type="text" :value="selectedCell?.streetNameV || ''"
|
||||
@input="onChangeStreetName('v', $event.target.value)" />
|
||||
</div>
|
||||
<div class="form-actions-mini" v-if="showContinueOtherButton">
|
||||
<button type="button" class="btn btn-primary mini" @click="continueStreetNames">
|
||||
{{ $t('admin.taxiTools.mapEditor.continueOther') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Zusätzliche Elemente (viertes Panel) -->
|
||||
<div v-if="showExtraElementsPanel" class="tool-panel extra-elements">
|
||||
<h4>{{ $t('admin.taxiTools.mapEditor.extraElements') }}</h4>
|
||||
<div class="panel-body placeholder">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -254,7 +310,7 @@ export default {
|
||||
mapForm: {
|
||||
name: '',
|
||||
description: '',
|
||||
mapData: []
|
||||
// mapData entfernt – Tiles werden separat gesendet
|
||||
},
|
||||
boardCells: {}, // { "x,y": { tileType: "horizontal", x: 0, y: 0 } }
|
||||
availableTileTypes: [
|
||||
@@ -286,6 +342,38 @@ export default {
|
||||
if (Object.keys(this.boardCells).length === 0) return 0;
|
||||
return this.maxY - this.minY + 1;
|
||||
},
|
||||
selectedCell() {
|
||||
if (!this.selectedCellKey) return null;
|
||||
return this.boardCells[this.selectedCellKey] || null;
|
||||
},
|
||||
showStreetNamePanel() {
|
||||
// Panel nur anzeigen, wenn Zelle selektiert ist und der Tile-Typ Namen unterstützt
|
||||
if (!this.selectedCell) return false;
|
||||
const type = this.selectedCell.tileType;
|
||||
return this.supportsHorizontal(type) || this.supportsVertical(type);
|
||||
},
|
||||
showExtraElementsPanel() {
|
||||
// Gleiche Logik wie Straßenpanel – nur sichtbar, wenn eine Zelle selektiert ist
|
||||
return !!this.selectedCell;
|
||||
},
|
||||
supportsHorizontalName() {
|
||||
if (!this.selectedCell) return false;
|
||||
return this.supportsHorizontal(this.selectedCell.tileType);
|
||||
},
|
||||
supportsVerticalName() {
|
||||
if (!this.selectedCell) return false;
|
||||
return this.supportsVertical(this.selectedCell.tileType);
|
||||
},
|
||||
showContinueOtherButton() {
|
||||
if (!this.selectedCell) return false;
|
||||
const hAllowed = this.supportsHorizontalName;
|
||||
const vAllowed = this.supportsVerticalName;
|
||||
if (!(hAllowed && vAllowed)) return false;
|
||||
const hasH = !!this.selectedCell.streetNameH;
|
||||
const hasV = !!this.selectedCell.streetNameV;
|
||||
// genau einer gesetzt
|
||||
return (hasH && !hasV) || (!hasH && hasV);
|
||||
},
|
||||
|
||||
minX() {
|
||||
if (Object.keys(this.boardCells).length === 0) return 0;
|
||||
@@ -352,6 +440,170 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
supportsHorizontal(tileType) {
|
||||
// Horizontalen Straßennamen erlauben bei: horizontal, cross, tleft, tright, tup, tdown, corns wo horizontaler Ast existiert
|
||||
const allowed = ['horizontal', 'cross', 'tleft', 'tright', 'tup', 'tdown', 'cornertopleft', 'cornerbottomleft', 'cornertopright', 'cornerbottomright', 'fuelhorizontal'];
|
||||
return allowed.includes(tileType);
|
||||
},
|
||||
supportsVertical(tileType) {
|
||||
// Vertikalen Straßennamen erlauben bei: vertical, cross, tup, tdown, etc.
|
||||
const allowed = ['vertical', 'cross', 'tleft', 'tright', 'tup', 'tdown', 'cornertopleft', 'cornerbottomleft', 'cornertopright', 'cornerbottomright', 'fuelvertical'];
|
||||
return allowed.includes(tileType);
|
||||
},
|
||||
continueStreetNames() {
|
||||
if (!this.selectedCell) return;
|
||||
// Wenn nur einer der beiden Namen existiert, kopiere in das andere Feld
|
||||
const hasH = !!this.selectedCell.streetNameH;
|
||||
const hasV = !!this.selectedCell.streetNameV;
|
||||
if (hasH && !hasV) {
|
||||
const value = this.selectedCell.streetNameH;
|
||||
this.onChangeStreetName('v', value);
|
||||
} else if (!hasH && hasV) {
|
||||
const value = this.selectedCell.streetNameV;
|
||||
this.onChangeStreetName('h', value);
|
||||
}
|
||||
},
|
||||
onChangeStreetName(dir, value) {
|
||||
if (!this.selectedCell) return;
|
||||
const key = dir === 'h' ? 'streetNameH' : 'streetNameV';
|
||||
// Vue reaktiv: bestehendes Objekt erweitern
|
||||
const updated = { ...(this.boardCells[this.selectedCellKey] || {}), [key]: value };
|
||||
this.$set ? this.$set(this.boardCells, this.selectedCellKey, updated) : (this.boardCells[this.selectedCellKey] = updated);
|
||||
// Namen entlang der Achse bis zu Abschluss-Tiles fortführen
|
||||
this.propagateStreetNameChain(this.selectedCellKey, dir, value);
|
||||
},
|
||||
continueStreetName(dir) {
|
||||
if (!this.selectedCell) return;
|
||||
const name = dir === 'h' ? (this.selectedCell.streetNameH || '') : (this.selectedCell.streetNameV || '');
|
||||
if (!name) return;
|
||||
this.propagateStreetNameChain(this.selectedCellKey, dir, name);
|
||||
},
|
||||
|
||||
// Führt Namen entlang der Achse fort, bis ein Abschluss-Tile erreicht ist
|
||||
propagateStreetNameChain(startKey, dir, name) {
|
||||
const start = this.boardCells[startKey];
|
||||
if (!start || !start.tileType) return;
|
||||
const { x, y } = start;
|
||||
const keyField = dir === 'h' ? 'streetNameH' : 'streetNameV';
|
||||
|
||||
const walk = (sx, sy, sign) => {
|
||||
let cx = sx;
|
||||
let cy = sy;
|
||||
while (true) {
|
||||
if (dir === 'h') cx += (sign === 'right' ? 1 : -1); else cy += (sign === 'down' ? 1 : -1);
|
||||
const key = `${cx},${cy}`;
|
||||
const cell = this.boardCells[key];
|
||||
if (!cell || !cell.tileType) break;
|
||||
const status = this.classifyNeighbor(dir, sign, cell.tileType);
|
||||
if (status === 'none') break;
|
||||
const upd = { ...cell, [keyField]: name };
|
||||
this.$set ? this.$set(this.boardCells, key, upd) : (this.boardCells[key] = upd);
|
||||
if (status === 'terminal') break;
|
||||
}
|
||||
};
|
||||
|
||||
const dirs = this.allowedDirections(start.tileType);
|
||||
if (dir === 'h') {
|
||||
if (dirs.right) walk(x, y, 'right');
|
||||
if (dirs.left) walk(x, y, 'left');
|
||||
} else {
|
||||
if (dirs.down) walk(x, y, 'down');
|
||||
if (dirs.up) walk(x, y, 'up');
|
||||
}
|
||||
},
|
||||
|
||||
classifyNeighbor(dir, sign, type) {
|
||||
const isCornerRight = (t) => /corner.*right$/.test(t);
|
||||
const isCornerLeft = (t) => /corner.*left$/.test(t);
|
||||
const isCornerTop = (t) => /cornertop.*$/.test(t);
|
||||
const isCornerBottom= (t) => /cornerbottom.*$/.test(t);
|
||||
if (dir === 'h') {
|
||||
// horizontal folgen können: corner<xxx>right, horizontal, tdown, tup, cross, fuelhorizontal, sowie corner<xxx>left und tleft als Abschluss
|
||||
if (sign === 'right') {
|
||||
if (isCornerLeft(type) || type === 'tleft') return 'terminal';
|
||||
if (isCornerRight(type) || ['horizontal','tdown','tup','cross','fuelhorizontal'].includes(type)) return 'continue';
|
||||
return 'none';
|
||||
} else { // left
|
||||
if (isCornerRight(type) || type === 'tright') return 'terminal';
|
||||
if (isCornerLeft(type) || ['horizontal','tdown','tup','cross','fuelhorizontal'].includes(type)) return 'continue';
|
||||
return 'none';
|
||||
}
|
||||
} else {
|
||||
// vertikal können folgen: cornerbottom<xxx>, vertical, tleft, tright, cross, fuelvertical, cornerup<xxx>
|
||||
// Abschluss bei Richtung down: cornerup<xxx> und tup (wir setzen noch, führen aber nicht weiter)
|
||||
if (sign === 'down') {
|
||||
if (isCornerTop(type) || type === 'tup') return 'terminal';
|
||||
if (isCornerBottom(type) || ['vertical','tleft','tright','cross','fuelvertical'].includes(type)) return 'continue';
|
||||
return 'none';
|
||||
} else { // up
|
||||
if (isCornerBottom(type) || type === 'tdown') return 'terminal';
|
||||
if (isCornerTop(type) || ['vertical','tleft','tright','cross','fuelvertical'].includes(type)) return 'continue';
|
||||
return 'none';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
allowedDirections(tileType) {
|
||||
// Gibt zurück, in welche Richtungen dieses Tile Anschlüsse hat
|
||||
switch (tileType) {
|
||||
case 'cornertopleft': return { left: true, up: true, right: false, down: false };
|
||||
case 'cornertopright': return { right: true, up: true, left: false, down: false };
|
||||
case 'cornerbottomleft': return { left: true, down: true, right: false, up: false };
|
||||
case 'cornerbottomright': return { right: true, down: true, left: false, up: false };
|
||||
case 'horizontal':
|
||||
case 'fuelhorizontal': return { left: true, right: true, up: false, down: false };
|
||||
case 'vertical':
|
||||
case 'fuelvertical': return { up: true, down: true, left: false, right: false };
|
||||
case 'cross': return { left: true, right: true, up: true, down: true };
|
||||
case 'tup': return { up: true, left: true, right: true, down: false };
|
||||
case 'tdown': return { down: true, left: true, right: true, up: false };
|
||||
case 'tleft': return { left: true, up: true, down: true, right: false };
|
||||
case 'tright': return { right: true, up: true, down: true, left: false };
|
||||
default: return { left: false, right: false, up: false, down: false };
|
||||
}
|
||||
},
|
||||
|
||||
propagateStreetNameFrom(startKey, dir, name) {
|
||||
const keyField = dir === 'h' ? 'streetNameH' : 'streetNameV';
|
||||
const step = dir === 'h' ? { dx: 1, dy: 0 } : { dx: 0, dy: 1 };
|
||||
const oppositeStep = { dx: -step.dx, dy: -step.dy };
|
||||
// in positive Richtung fortführen
|
||||
this.walkAndSet(startKey, step, dir, keyField, name);
|
||||
// in negative Richtung fortführen
|
||||
this.walkAndSet(startKey, oppositeStep, dir, keyField, name);
|
||||
},
|
||||
|
||||
walkAndSet(startKey, step, dir, keyField, name) {
|
||||
const canContinue = (tileType, dir) => {
|
||||
switch (tileType) {
|
||||
case 'horizontal': return dir === 'h';
|
||||
case 'vertical': return dir === 'v';
|
||||
case 'cross': return true;
|
||||
case 'fuelhorizontal': return dir === 'h';
|
||||
case 'fuelvertical': return dir === 'v';
|
||||
case 'cornertopleft':
|
||||
case 'cornertopright':
|
||||
case 'cornerbottomleft':
|
||||
case 'cornerbottomright':
|
||||
return false; // Kurve blockt die Fortführung
|
||||
case 'tup': return dir === 'h';
|
||||
case 'tdown': return dir === 'h';
|
||||
case 'tleft': return dir === 'v';
|
||||
case 'tright': return dir === 'v';
|
||||
default: return false;
|
||||
}
|
||||
};
|
||||
let [x, y] = startKey.split(',').map(Number);
|
||||
while (true) {
|
||||
x += step.dx; y += step.dy;
|
||||
const key = `${x},${y}`;
|
||||
const cell = this.boardCells[key];
|
||||
if (!cell || !cell.tileType) break;
|
||||
if (!canContinue(cell.tileType, dir)) break;
|
||||
const updated = { ...cell, [keyField]: name };
|
||||
this.$set ? this.$set(this.boardCells, key, updated) : (this.boardCells[key] = updated);
|
||||
}
|
||||
},
|
||||
async loadMaps() {
|
||||
try {
|
||||
const user = this.$store.getters.user;
|
||||
@@ -402,11 +654,27 @@ export default {
|
||||
this.mapForm = {
|
||||
name: map.name,
|
||||
description: map.description,
|
||||
mapData: map.mapData || []
|
||||
};
|
||||
|
||||
if (map.mapData && map.mapData.length > 0) {
|
||||
this.createBoardFromData(map.mapData);
|
||||
// Falls Tiles aus Backend mitkommen, Board daraus erstellen
|
||||
if (Array.isArray(map.tiles) && map.tiles.length > 0) {
|
||||
this.boardCells = {};
|
||||
for (const t of map.tiles) {
|
||||
const key = `${t.x},${t.y}`;
|
||||
this.boardCells[key] = { tileType: t.tileType, x: t.x, y: t.y };
|
||||
}
|
||||
// Straßennamen aus tileStreets übernehmen
|
||||
if (Array.isArray(map.tileStreets)) {
|
||||
for (const s of map.tileStreets) {
|
||||
const key = `${s.x},${s.y}`;
|
||||
if (this.boardCells[key]) {
|
||||
this.boardCells[key] = {
|
||||
...this.boardCells[key],
|
||||
streetNameH: s.streetNameH?.name || null,
|
||||
streetNameV: s.streetNameV?.name || null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.initializeBoard();
|
||||
}
|
||||
@@ -433,9 +701,8 @@ export default {
|
||||
// Wenn eine Position ausgewählt ist, platziere das Tile
|
||||
if (this.selectedCellKey !== null) {
|
||||
this.placeTile(this.selectedCellKey, tileType);
|
||||
// Setze Auswahl zurück nach dem Platzieren
|
||||
this.selectedCellKey = null;
|
||||
this.selectedTileType = null;
|
||||
// Nach dem Setzen bleibt die Zelle ausgewählt
|
||||
// Tile-Typ-Auswahl bleibt erhalten, um mehrere gleiche Tiles zu setzen
|
||||
}
|
||||
},
|
||||
|
||||
@@ -453,6 +720,9 @@ export default {
|
||||
y: y
|
||||
};
|
||||
|
||||
// Nach dem Setzen: Zelle ausgewählt lassen
|
||||
this.selectedCellKey = cellKey;
|
||||
|
||||
// Erweitere das Board um benachbarte leere Zellen falls nötig
|
||||
this.expandBoardIfNeeded(x, y, tileType);
|
||||
},
|
||||
@@ -681,13 +951,19 @@ export default {
|
||||
|
||||
async saveMap() {
|
||||
try {
|
||||
// Tiles in Array-Form bringen
|
||||
const tiles = Object.values(this.boardCells)
|
||||
.filter(c => !!c.tileType)
|
||||
.map(c => ({ x: c.x, y: c.y, tileType: c.tileType }));
|
||||
const tileStreetNames = this.collectTileStreetNames();
|
||||
const mapData = {
|
||||
...this.mapForm,
|
||||
width: this.boardWidth,
|
||||
height: this.boardHeight,
|
||||
tileSize: 50,
|
||||
mapTypeId: 1, // Standard Map-Typ
|
||||
mapData: this.generateMapData()
|
||||
mapTypeId: 1,
|
||||
tiles,
|
||||
tileStreetNames
|
||||
};
|
||||
|
||||
let savedMap;
|
||||
@@ -718,6 +994,18 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
collectTileStreetNames() {
|
||||
const result = {};
|
||||
for (const [key, cell] of Object.entries(this.boardCells)) {
|
||||
if (!cell.tileType) continue;
|
||||
const { streetNameH, streetNameV } = cell;
|
||||
if (streetNameH || streetNameV) {
|
||||
result[key] = { streetNameH: streetNameH || null, streetNameV: streetNameV || null };
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
async deleteMap(mapId) {
|
||||
if (confirm('Möchtest du diese Map wirklich löschen?')) {
|
||||
try {
|
||||
@@ -941,6 +1229,44 @@ export default {
|
||||
flex: 0 0 300px;
|
||||
}
|
||||
|
||||
/* Vereinheitlichte Tool-Panels rechts */
|
||||
.tool-panel {
|
||||
flex: 0 0 220px;
|
||||
}
|
||||
.tool-panel h4 {
|
||||
margin: 0 0 8px 0; /* weniger Abstand oben/unten */
|
||||
color: #F9A22C;
|
||||
font-size: 18px;
|
||||
}
|
||||
.panel-body.placeholder {
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
.form-group-mini {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.arrow-label {
|
||||
width: 28px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
.form-group-mini input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.btn.mini {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tile-palette h4 {
|
||||
margin-bottom: 15px;
|
||||
color: #F9A22C;
|
||||
@@ -953,7 +1279,7 @@ export default {
|
||||
grid-template-rows: repeat(4, 50px);
|
||||
gap: 5px;
|
||||
border: 2px solid #ddd;
|
||||
padding: 10px;
|
||||
padding: 6px; /* weniger Innenabstand, damit oben/unten kompakter */
|
||||
background: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user