mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 05:30:46 +08:00
Protect root maps and auto world map; only sub-regions are deletable.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
8fa4b2e4ea
commit
1b592490a5
6 changed files with 70 additions and 34 deletions
|
|
@ -1442,6 +1442,7 @@ const en = {
|
|||
newSubRegionPrompt: "Sub-region name",
|
||||
subRegionNotOnFlat: "The world map has no regions. Create sub-regions on World or a lower-level canvas.",
|
||||
worldMapNoDirectNes: "Cannot add NEs on the world map — add them on a sub-region canvas.",
|
||||
worldMapName: "World map",
|
||||
worldHud: "World {{total}} · close-up {{cached}} · z{{zoom}} · {{lod}}",
|
||||
worldEmptyCoords: "World map has no coordinates yet",
|
||||
worldEmptyCoordsHint:
|
||||
|
|
|
|||
|
|
@ -1432,6 +1432,7 @@ const zh = {
|
|||
newSubRegionPrompt: "子区域名称",
|
||||
subRegionNotOnFlat: "世界地图不支持子区域,请在 World 或下级区域画布上创建。",
|
||||
worldMapNoDirectNes: "世界地图不能直接添加网元,请到某个子区域画布上添加。",
|
||||
worldMapName: "世界地图",
|
||||
worldHud: "世界 {{total}} · 近景 {{cached}} · z{{zoom}} · {{lod}}",
|
||||
worldEmptyCoords: "世界地图还没有坐标",
|
||||
worldEmptyCoordsHint: "码头已有 {{dock}} 个网元,但尚未灌入 Fabric / 世界坐标。可一键应用(不拉 UME)。",
|
||||
|
|
|
|||
|
|
@ -180,7 +180,26 @@ function isWorldDrillFolder(folder: TopologyTreeFolderItem | null | undefined):
|
|||
|
||||
function isWorldFlatViewName(name: string | undefined | null): boolean {
|
||||
const n = String(name || "").trim();
|
||||
return n === "世界地图" || n === "完整世界地图";
|
||||
return n === "世界地图" || n === "完整世界地图" || n === "World map";
|
||||
}
|
||||
|
||||
function displayViewName(name: string | undefined | null, t: (key: string) => string): string {
|
||||
if (isWorldFlatViewName(name)) return t("topology.worldMapName");
|
||||
return String(name || "").trim();
|
||||
}
|
||||
|
||||
/** UME structural folders (not user-deletable): container + World drill. */
|
||||
function isUmeStructuralFolder(folder: TopologyTreeFolderItem | null | undefined): boolean {
|
||||
if (!folder) return false;
|
||||
if (isUmeWorldContainer(folder) || isWorldDrillFolder(folder)) return true;
|
||||
const ext = String(folder.external_ref || "").trim();
|
||||
return ext === "ume:world" || ext === "ume:world:drill";
|
||||
}
|
||||
|
||||
/** UME-synced SBN under World — deletable like a manual sub-region. */
|
||||
function isUmeSyncedSubRegion(folder: TopologyTreeFolderItem | null | undefined): boolean {
|
||||
if (!folder || isUmeStructuralFolder(folder)) return false;
|
||||
return Boolean(String(folder.external_ref || "").trim());
|
||||
}
|
||||
|
||||
/** Visual LOD for 1:1 world coords (~1e5 span). fitView overview is ~0.01. */
|
||||
|
|
@ -2285,7 +2304,7 @@ export function TopologyPage() {
|
|||
/>
|
||||
</span>
|
||||
<span className="topo-map-list__title">
|
||||
{v.name}
|
||||
{displayViewName(v.name, t)}
|
||||
{viewActive && dirty ? " *" : ""}
|
||||
</span>
|
||||
{!umeNav ? (
|
||||
|
|
@ -2395,15 +2414,16 @@ export function TopologyPage() {
|
|||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{!umeNav || containerFolder || Boolean(String(folder.external_ref || "").trim()) ? (
|
||||
{!isUmeStructuralFolder(folder) &&
|
||||
(!folder.is_system || isUmeSyncedSubRegion(folder)) ? (
|
||||
<div className="topo-map-list__actions">
|
||||
{!containerFolder ? (
|
||||
{!folder.is_system ? (
|
||||
<button
|
||||
type="button"
|
||||
className="topo-map-list__icon"
|
||||
title={t("topology.renameRegion")}
|
||||
aria-label={t("topology.renameRegion")}
|
||||
disabled={!!folder.is_system || renameRegionMut.isPending}
|
||||
disabled={renameRegionMut.isPending}
|
||||
onClick={() => promptRenameRegion(folder.id, folder.name)}
|
||||
>
|
||||
<PencilIcon />
|
||||
|
|
@ -2414,18 +2434,12 @@ export function TopologyPage() {
|
|||
className="topo-map-list__icon"
|
||||
title={t("topology.deleteRegion")}
|
||||
aria-label={t("topology.deleteRegion")}
|
||||
disabled={
|
||||
deleteFolderMut.isPending ||
|
||||
(!!folder.is_system &&
|
||||
!containerFolder &&
|
||||
!String(folder.external_ref || "").trim())
|
||||
}
|
||||
disabled={deleteFolderMut.isPending}
|
||||
onClick={() => {
|
||||
const msg = (
|
||||
containerFolder
|
||||
? t("topology.deleteUmeWorldConfirm")
|
||||
: t("topology.deleteRegionConfirm")
|
||||
).replace("{{name}}", folder.name);
|
||||
const msg = t("topology.deleteRegionConfirm").replace(
|
||||
"{{name}}",
|
||||
folder.name,
|
||||
);
|
||||
if (window.confirm(msg)) {
|
||||
deleteFolderMut.mutate(folder.id);
|
||||
}
|
||||
|
|
@ -3991,9 +4005,9 @@ export function TopologyPage() {
|
|||
}, [graphTruncated, truncateReason, activeView?.name, graphQuery.data?.view?.filter?.world_flat, t]);
|
||||
const activeLeafName = useMemo(() => {
|
||||
if (!mapId) return "";
|
||||
if (activeView?.name) return activeView.name;
|
||||
return graphQuery.data?.view?.name || "";
|
||||
}, [mapId, activeView, graphQuery.data?.view?.name]);
|
||||
if (activeView?.name) return displayViewName(activeView.name, t);
|
||||
return displayViewName(graphQuery.data?.view?.name, t);
|
||||
}, [mapId, activeView, graphQuery.data?.view?.name, t]);
|
||||
|
||||
const titleText = useMemo(() => {
|
||||
if (canvasMode) return activeLeafName || t("topology.selectMap");
|
||||
|
|
@ -4798,7 +4812,7 @@ export function TopologyPage() {
|
|||
<LayerGlyph role={isPhysical ? "core" : "aggregation"} size={22} />
|
||||
</span>
|
||||
<span className="topo-region-hex__title">
|
||||
<span className="topo-region-hex__name">{v.name}</span>
|
||||
<span className="topo-region-hex__name">{displayViewName(v.name, t)}</span>
|
||||
<span className="topo-region-hex__meta">{v.node_count || 0}N</span>
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue