Skip to content

Commit 407001a

Browse files
committed
添加网页端删除报点功能
渲染范围改为正确的切比雪夫距离
1 parent 65ee578 commit 407001a

5 files changed

Lines changed: 162 additions & 27 deletions

File tree

Minecraft-TeamViewer-Backend

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit f2abc3448c2a1ffda46238feb15b6524a829a99f

src/core/mapProjection.ts

Lines changed: 137 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ type MapProjectionDeps = {
2828
ttlSeconds: number | null;
2929
permanent: boolean;
3030
}) => boolean;
31+
onDeleteTacticalWaypoint?: (payload: {
32+
waypointId: string;
33+
}) => boolean;
3134
};
3235

3336
export function createMapProjection(deps: MapProjectionDeps) {
@@ -186,6 +189,37 @@ export function createMapProjection(deps: MapProjectionDeps) {
186189
};
187190
}
188191

192+
function bindFloatingMenuInteractions(menu: HTMLElement) {
193+
menu.addEventListener('mousedown', (e) => {
194+
e.stopPropagation();
195+
});
196+
menu.addEventListener('click', (e) => {
197+
e.stopPropagation();
198+
});
199+
menu.addEventListener('wheel', (e) => {
200+
e.stopPropagation();
201+
});
202+
menu.addEventListener('contextmenu', (e) => {
203+
e.preventDefault();
204+
e.stopPropagation();
205+
}, true);
206+
}
207+
208+
function positionFloatingMenu(menu: HTMLElement, event: MouseEvent) {
209+
const margin = 12;
210+
const menuRect = menu.getBoundingClientRect();
211+
let left = event.clientX + 14;
212+
let top = event.clientY + 12;
213+
214+
const maxLeft = Math.max(margin, window.innerWidth - menuRect.width - margin);
215+
const maxTop = Math.max(margin, window.innerHeight - menuRect.height - margin);
216+
left = Math.max(margin, Math.min(maxLeft, left));
217+
top = Math.max(margin, Math.min(maxTop, top));
218+
219+
menu.style.left = `${Math.round(left)}px`;
220+
menu.style.top = `${Math.round(top)}px`;
221+
}
222+
189223
function openTacticalMenuAtPointer(
190224
map: any,
191225
event: MouseEvent,
@@ -254,19 +288,7 @@ export function createMapProjection(deps: MapProjectionDeps) {
254288
syncPreviewFromSelection();
255289
});
256290

257-
menu.addEventListener('mousedown', (e) => {
258-
e.stopPropagation();
259-
});
260-
menu.addEventListener('click', (e) => {
261-
e.stopPropagation();
262-
});
263-
menu.addEventListener('wheel', (e) => {
264-
e.stopPropagation();
265-
});
266-
menu.addEventListener('contextmenu', (e) => {
267-
e.preventDefault();
268-
e.stopPropagation();
269-
}, true);
291+
bindFloatingMenuInteractions(menu);
270292

271293
ttlSelect.addEventListener('change', () => {
272294
customRow.style.display = ttlSelect.value === 'custom' ? 'flex' : 'none';
@@ -303,19 +325,75 @@ export function createMapProjection(deps: MapProjectionDeps) {
303325

304326
document.body.appendChild(menu);
305327
tacticalMenuEl = menu;
328+
positionFloatingMenu(menu, event);
306329

307-
const margin = 12;
308-
const menuRect = menu.getBoundingClientRect();
309-
let left = event.clientX + 14;
310-
let top = event.clientY + 12;
330+
tacticalMenuOutsideClickHandler = (e: MouseEvent) => {
331+
const target = e.target;
332+
if (tacticalMenuEl && target instanceof Node && tacticalMenuEl.contains(target)) {
333+
return;
334+
}
335+
closeTacticalMenu();
336+
};
337+
tacticalMenuEscHandler = (e: KeyboardEvent) => {
338+
if (e.key === 'Escape') {
339+
closeTacticalMenu();
340+
}
341+
};
311342

312-
const maxLeft = Math.max(margin, window.innerWidth - menuRect.width - margin);
313-
const maxTop = Math.max(margin, window.innerHeight - menuRect.height - margin);
314-
left = Math.max(margin, Math.min(maxLeft, left));
315-
top = Math.max(margin, Math.min(maxTop, top));
343+
setTimeout(() => {
344+
if (tacticalMenuOutsideClickHandler) {
345+
document.addEventListener('mousedown', tacticalMenuOutsideClickHandler, true);
346+
}
347+
if (tacticalMenuEscHandler) {
348+
document.addEventListener('keydown', tacticalMenuEscHandler, true);
349+
}
350+
}, 0);
316351

317-
menu.style.left = `${Math.round(left)}px`;
318-
menu.style.top = `${Math.round(top)}px`;
352+
return true;
353+
}
354+
355+
function openWaypointDeleteMenuAtPointer(event: MouseEvent, waypointId: string, waypointLabel: string | null) {
356+
closeTacticalMenu();
357+
358+
const menu = document.createElement('div');
359+
menu.className = 'nodemc-tactical-menu';
360+
menu.innerHTML = `
361+
<div class="nmc-tactical-title">删除战术标点</div>
362+
<label class="nmc-tactical-row">
363+
<span>目标标点</span>
364+
<input class="nmc-tactical-delete-label" type="text" readonly value="${escapeHtml(waypointLabel || waypointId)}" />
365+
</label>
366+
<div class="nmc-tactical-row">
367+
<span>确认删除这个 waypoint 吗?</span>
368+
</div>
369+
<div class="nmc-tactical-actions">
370+
<button type="button" class="nmc-tactical-confirm">确认删除</button>
371+
<button type="button" class="nmc-tactical-cancel">取消</button>
372+
</div>
373+
`;
374+
375+
const confirmBtn = menu.querySelector('.nmc-tactical-confirm') as HTMLButtonElement | null;
376+
const cancelBtn = menu.querySelector('.nmc-tactical-cancel') as HTMLButtonElement | null;
377+
if (!confirmBtn || !cancelBtn) {
378+
return false;
379+
}
380+
381+
bindFloatingMenuInteractions(menu);
382+
383+
cancelBtn.addEventListener('click', () => {
384+
closeTacticalMenu();
385+
});
386+
387+
confirmBtn.addEventListener('click', () => {
388+
if (typeof deps.onDeleteTacticalWaypoint === 'function') {
389+
deps.onDeleteTacticalWaypoint({ waypointId });
390+
}
391+
closeTacticalMenu();
392+
});
393+
394+
document.body.appendChild(menu);
395+
tacticalMenuEl = menu;
396+
positionFloatingMenu(menu, event);
319397

320398
tacticalMenuOutsideClickHandler = (e: MouseEvent) => {
321399
const target = e.target;
@@ -358,6 +436,28 @@ export function createMapProjection(deps: MapProjectionDeps) {
358436
return openTacticalMenuAtPointer(map, event, pos);
359437
}
360438

439+
function maybeHandleWaypointDelete(event: MouseEvent, waypointId: string, waypointLabel: string | null) {
440+
if (!shouldBlockMapLeftRightClick()) return false;
441+
if (!shouldEnableTacticalMapMarking()) return false;
442+
if (event.button !== 2) return false;
443+
if (typeof deps.onDeleteTacticalWaypoint !== 'function') return false;
444+
445+
const id = String(waypointId || '').trim();
446+
if (!id) return false;
447+
448+
return openWaypointDeleteMenuAtPointer(event, id, waypointLabel);
449+
}
450+
451+
function findWaypointTargetInfo(target: EventTarget | null) {
452+
if (!(target instanceof Element)) return null;
453+
const anchor = target.closest('[data-nodemc-waypoint-id]');
454+
if (!(anchor instanceof HTMLElement)) return null;
455+
const waypointId = String(anchor.dataset.nodemcWaypointId || '').trim();
456+
if (!waypointId) return null;
457+
const waypointLabel = String(anchor.dataset.nodemcWaypointLabel || '').trim() || null;
458+
return { waypointId, waypointLabel };
459+
}
460+
361461
function shouldBlockMapLeftRightClick() {
362462
return Boolean(CONFIG.BLOCK_MAP_LEFT_RIGHT_CLICK);
363463
}
@@ -381,6 +481,15 @@ export function createMapProjection(deps: MapProjectionDeps) {
381481
function onGuardedMouseEvent(event: Event) {
382482
const mouseEvent = event as MouseEvent;
383483
if (!shouldInterceptMouseEvent(mouseEvent)) return;
484+
const waypointTarget = findWaypointTargetInfo(mouseEvent.target);
485+
if (waypointTarget && maybeHandleWaypointDelete(mouseEvent, waypointTarget.waypointId, waypointTarget.waypointLabel)) {
486+
mouseEvent.preventDefault();
487+
mouseEvent.stopPropagation();
488+
if (typeof mouseEvent.stopImmediatePropagation === 'function') {
489+
mouseEvent.stopImmediatePropagation();
490+
}
491+
return;
492+
}
384493
maybeHandleTacticalMarkPlacement(mouseEvent);
385494
mouseEvent.preventDefault();
386495
mouseEvent.stopPropagation();
@@ -1008,7 +1117,9 @@ export function createMapProjection(deps: MapProjectionDeps) {
10081117
? `<span class="n-waypoint-icon" style="position:absolute;left:0;top:0;transform:translate(-50%,-50%);background:${color};width:${visual.iconSize}px;height:${visual.iconSize}px;display:inline-block;border-radius:50%;line-height:${visual.iconSize}px;text-align:center;font-size:${Math.max(10, Math.round(visual.iconSize * 0.7))}px;z-index:2;">📍</span>`
10091118
: '';
10101119

1011-
return `<div class="nodemc-waypoint-anchor" style="position:relative;display:inline-block;white-space:nowrap;">${textHtml}${iconHtml}</div>`;
1120+
const safeWaypointId = escapeHtml(String(waypoint && (waypoint.id || waypoint.waypointId) || ''));
1121+
const safeWaypointLabel = escapeHtml(safeName);
1122+
return `<div class="nodemc-waypoint-anchor" data-nodemc-waypoint-id="${safeWaypointId}" data-nodemc-waypoint-label="${safeWaypointLabel}" style="position:relative;display:inline-block;white-space:nowrap;">${textHtml}${iconHtml}</div>`;
10121123
}
10131124

10141125
function upsertWaypoint(map: any, waypointId: string, payload: any) {
@@ -1045,7 +1156,7 @@ export function createMapProjection(deps: MapProjectionDeps) {
10451156
const marker = leafletRef.marker(latLng, {
10461157
icon: leafletRef.divIcon({ className: '', html, iconSize: [0, 0], iconAnchor: [0, 0] }),
10471158
zIndexOffset,
1048-
interactive: false,
1159+
interactive: true,
10491160
keyboard: false,
10501161
});
10511162

@@ -1300,6 +1411,7 @@ export function createMapProjection(deps: MapProjectionDeps) {
13001411

13011412
nextWaypointIds.add(String(wpId));
13021413
upsertWaypoint(map, String(wpId), {
1414+
id: String(wpId),
13031415
x: resolvedPos.x,
13041416
z: resolvedPos.z,
13051417
label: (data as any).label || (data as any).name || (data as any).title || String(wpId),

src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
buildCommandPlayerMarkClearAll,
3131
buildCommandPlayerMarkSet,
3232
buildCommandSameServerFilterSet,
33+
buildCommandTacticalWaypointDelete,
3334
buildCommandTacticalWaypointSet,
3435
} from './network/networkSchemas';
3536
import { createAdminWsClient } from './network/wsClient';
@@ -359,6 +360,14 @@ declare const unsafeWindow: Window | undefined;
359360
}
360361
return ok;
361362
},
363+
onDeleteTacticalWaypoint: ({ waypointId }) => {
364+
const ok = sendAdminCommand(buildCommandTacticalWaypointDelete(waypointId));
365+
if (ok) {
366+
lastErrorText = null;
367+
updateUiStatus();
368+
}
369+
return ok;
370+
},
362371
});
363372

364373
function loadConfigFromStorage() {

src/network/networkSchemas.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,20 @@ export type CommandTacticalWaypointSetPacket = {
198198
dimension: string;
199199
};
200200

201+
export type WaypointsDeletePacket = {
202+
type: 'waypoints_delete';
203+
waypointIds: string[];
204+
};
205+
201206
export type AdminOutboundPacket =
202207
| AdminHandshakePacket
203208
| AdminResyncRequestPacket
204209
| CommandPlayerMarkSetPacket
205210
| CommandPlayerMarkClearPacket
206211
| CommandPlayerMarkClearAllPacket
207212
| CommandSameServerFilterSetPacket
208-
| CommandTacticalWaypointSetPacket;
213+
| CommandTacticalWaypointSetPacket
214+
| WaypointsDeletePacket;
209215

210216
export type AdminAckInboundPacket = {
211217
type: 'admin_ack';
@@ -351,6 +357,13 @@ export function buildCommandTacticalWaypointSet(payload: {
351357
};
352358
}
353359

360+
export function buildCommandTacticalWaypointDelete(waypointId: string): WaypointsDeletePacket {
361+
return {
362+
type: 'waypoints_delete',
363+
waypointIds: [String(waypointId || '').trim()].filter(Boolean),
364+
};
365+
}
366+
354367
export function buildAdminResyncRequest(reason = 'baseline_missing'): AdminResyncRequestPacket {
355368
return {
356369
type: 'resync_req',

src/ui/components/OverlaySettingsPanel.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ function closeHelp() {
312312
<label class="n-check"><input v-model="state.form.SHOW_LABEL_TEAM_INFO" @change="triggerAutoApply" id="nodemc-overlay-show-team-info" type="checkbox" />地图文字显示阵营信息</label>
313313
<label class="n-check"><input v-model="state.form.SHOW_LABEL_TOWN_INFO" @change="triggerAutoApply" id="nodemc-overlay-show-town-info" type="checkbox" />地图文字显示城镇信息</label>
314314
<label class="n-check"><input v-model="state.form.BLOCK_MAP_LEFT_RIGHT_CLICK" @change="triggerAutoApply" id="nodemc-overlay-block-map-click" type="checkbox" />屏蔽原网页地图左/右键功能(保留拖拽与滚轮缩放)</label>
315-
<label class="n-check"><input v-model="state.form.ENABLE_TACTICAL_MAP_MARKING" @change="triggerAutoApply" id="nodemc-overlay-enable-tactical-marking" type="checkbox" />启用战术地图标记(右键选择类型后落点)</label>
315+
<label class="n-check"><input v-model="state.form.ENABLE_TACTICAL_MAP_MARKING" @change="triggerAutoApply" id="nodemc-overlay-enable-tactical-marking" type="checkbox" />启用战术地图标记(右键空白处选择类型后落点,右键已有 waypoint 删除)</label>
316316
<div class="n-row full-width">
317317
<label>战术标记默认有效期(秒,右键时可改为 long 长期)</label>
318318
<input v-model="state.form.TACTICAL_MARK_DEFAULT_TTL_SECONDS" @change="triggerAutoApply" id="nodemc-overlay-tactical-ttl" type="number" min="10" max="86400" step="10" />

0 commit comments

Comments
 (0)