refactor: consolidate redundant geo-types and implement legacy key mapping in GeoTypeMap
This commit is contained in:
+123
-47
@@ -1062,7 +1062,7 @@ function EditorPageContent() {
|
||||
// Xóa pending submission để backend cho phép mở editor lại.
|
||||
const unlockByDeletingPendingSubmission = useCallback(async () => {
|
||||
if (!blockedPendingSubmissionId) return;
|
||||
const confirmed = window.confirm("Xoa submission PENDING de unlock editor? Hanh dong nay khong the hoan tac.");
|
||||
const confirmed = window.confirm("Bạn chắc chắn muốn xóa Submition? - việc này không làm hỏng project của bạn");
|
||||
if (!confirmed) return;
|
||||
try {
|
||||
setIsOpeningSection(true);
|
||||
@@ -1966,6 +1966,127 @@ function EditorPageContent() {
|
||||
[entities, labelContextBaseDraft]
|
||||
);
|
||||
|
||||
if (blockedPendingSubmissionId) {
|
||||
return (
|
||||
<div style={{ display: "flex", minHeight: "100vh", width: "100vw", background: "#0b1220", color: "white", padding: "40px", alignItems: "center", justifyContent: "center" }}>
|
||||
<div style={{ maxWidth: 640, width: "100%", background: "#0f172a", border: "1px solid #1e293b", borderRadius: 12, padding: 32, boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.3)" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 16 }}>
|
||||
<svg style={{ width: 28, height: 28, color: "#ef4444" }} fill="none" viewBox="0 0 24 24" stroke="currentColor" width="28" height="28">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m0 0v2m0-2h2m-2 0H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<h2 style={{ margin: 0, fontSize: 20, fontWeight: 700 }}>Editor đang bị khóa</h2>
|
||||
</div>
|
||||
<div style={{ fontSize: 14, color: "#94a3b8", lineHeight: "1.6" }}>
|
||||
Project này đang có submission ở trạng thái <b style={{ color: "#ef4444" }}>PENDING</b> (id: <code style={{ color: "#f1f5f9", background: "#1e293b", padding: "2px 6px", borderRadius: 4 }}>{blockedPendingSubmissionId}</code>). Theo quy trình làm việc, khi submission đang pending thì không được tạo submission/commit mới và không được vào editor.
|
||||
</div>
|
||||
<div style={{ marginTop: 24, display: "flex", gap: 12 }}>
|
||||
<button
|
||||
onClick={unlockByDeletingPendingSubmission}
|
||||
disabled={isOpeningSection}
|
||||
style={{
|
||||
padding: "10px 16px",
|
||||
borderRadius: 6,
|
||||
border: "none",
|
||||
background: isOpeningSection ? "#334155" : "#ef4444",
|
||||
color: "white",
|
||||
fontWeight: 600,
|
||||
fontSize: 14,
|
||||
cursor: isOpeningSection ? "not-allowed" : "pointer",
|
||||
transition: "background 0.2s",
|
||||
}}
|
||||
onMouseEnter={(e) => { if (!isOpeningSection) e.currentTarget.style.background = "#dc2626"; }}
|
||||
onMouseLeave={(e) => { if (!isOpeningSection) e.currentTarget.style.background = "#ef4444"; }}
|
||||
>
|
||||
Xóa submission pending để unlock
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push("/user/projects")}
|
||||
style={{
|
||||
padding: "10px 16px",
|
||||
borderRadius: 6,
|
||||
border: "1px solid #334155",
|
||||
background: "#1e293b",
|
||||
color: "#f1f5f9",
|
||||
fontWeight: 600,
|
||||
fontSize: 14,
|
||||
cursor: "pointer",
|
||||
transition: "background 0.2s",
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.background = "#334155"}
|
||||
onMouseLeave={(e) => e.currentTarget.style.background = "#1e293b"}
|
||||
>
|
||||
Quay lại danh sách projects
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isOpeningSection || !activeSection) {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", minHeight: "100vh", width: "100vw", background: "#0b1220", color: "white", alignItems: "center", justifyContent: "center", gap: "16px" }}>
|
||||
{!activeSection && !isOpeningSection ? (
|
||||
<div style={{ maxWidth: 480, textAlign: "center", padding: "20px" }}>
|
||||
<h2 style={{ fontSize: "18px", fontWeight: "600", marginBottom: "8px", color: "#ef4444" }}>Lỗi tải Project</h2>
|
||||
<div style={{ fontSize: "14px", color: "#94a3b8", marginBottom: "20px" }}>
|
||||
{entityStatus || "Không thể tải thông tin dự án. Vui lòng thử lại hoặc quay lại danh sách."}
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "12px", justifyContent: "center" }}>
|
||||
<button
|
||||
onClick={openProject}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: 6,
|
||||
background: "#3b82f6",
|
||||
color: "white",
|
||||
border: "none",
|
||||
fontWeight: "600",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
Thử lại
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push("/user/projects")}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
borderRadius: 6,
|
||||
background: "#1e293b",
|
||||
color: "#f1f5f9",
|
||||
border: "1px solid #334155",
|
||||
fontWeight: "600",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
Quay lại
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="premium-spinner" style={{
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
border: "3px solid rgba(255, 255, 255, 0.1)",
|
||||
borderRadius: "50%",
|
||||
borderTopColor: "#3b82f6",
|
||||
animation: "spin 1s linear infinite"
|
||||
}} />
|
||||
<style>{`
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
<div style={{ fontSize: "15px", fontWeight: "500", color: "#94a3b8" }}>
|
||||
Đang tải dữ liệu bản đồ...
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", minHeight: "100vh" }}>
|
||||
{!isReplayEditMode && !isReplayPreviewMode ? (
|
||||
@@ -1980,7 +2101,7 @@ function EditorPageContent() {
|
||||
onRestoreCommit={restoreCommit}
|
||||
isSaving={isSaving}
|
||||
isSubmitting={isSubmitting}
|
||||
sectionTitle={activeSection?.title || "Đang tải project"}
|
||||
sectionTitle={activeSection.title || "Đang tải project"}
|
||||
projectStatus={projectState?.status || "editing"}
|
||||
commitTitle={commitTitle}
|
||||
onCommitTitleChange={setCommitTitle}
|
||||
@@ -2031,49 +2152,6 @@ function EditorPageContent() {
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{blockedPendingSubmissionId ? (
|
||||
<div style={{ flex: 1, minHeight: "100vh", background: "#0b1220", color: "white", padding: "24px" }}>
|
||||
<div style={{ maxWidth: 720 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 700 }}>Editor dang bi khoa</h2>
|
||||
<div style={{ marginTop: 10, fontSize: 13, color: "#cbd5e1" }}>
|
||||
Project nay dang co submission o trang thai <b>PENDING</b> (id:{" "}
|
||||
<code style={{ color: "white" }}>{blockedPendingSubmissionId}</code>). Theo BE moi, khi
|
||||
submission dang pending thi khong duoc tao submission/commit moi va khong duoc vao editor.
|
||||
</div>
|
||||
<div style={{ marginTop: 14, display: "flex", gap: 10, alignItems: "center" }}>
|
||||
<button
|
||||
onClick={unlockByDeletingPendingSubmission}
|
||||
disabled={isOpeningSection}
|
||||
style={{
|
||||
padding: "10px 12px",
|
||||
borderRadius: 6,
|
||||
border: "1px solid #334155",
|
||||
background: isOpeningSection ? "#334155" : "#ef4444",
|
||||
color: "white",
|
||||
cursor: isOpeningSection ? "not-allowed" : "pointer",
|
||||
}}
|
||||
>
|
||||
Xoa submission pending de unlock
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push("/user/projects")}
|
||||
style={{
|
||||
padding: "10px 12px",
|
||||
borderRadius: 6,
|
||||
border: "1px solid #334155",
|
||||
background: "#111827",
|
||||
color: "white",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
Quay lai danh sach projects
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!blockedPendingSubmissionId ? (
|
||||
<div style={{ flex: 1, position: "relative", minHeight: "100vh" }}>
|
||||
{isBackgroundVisibilityReady ? (
|
||||
<Map
|
||||
@@ -2175,14 +2253,12 @@ function EditorPageContent() {
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!isReplayEditMode && !isReplayPreviewMode ? (
|
||||
<>
|
||||
<ResizeHandle
|
||||
title="Resize right panel"
|
||||
onDrag={(deltaX) => {
|
||||
// dragging handle (between map and right panel): moving right increases right panel width
|
||||
setRightPanelWidth((prev) => clampNumber(prev - deltaX, 260, 720));
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -48,33 +48,22 @@ Geotype render hiện được tập trung ở `getAllGeotypeLayers(...)` trong
|
||||
Các type đang được register:
|
||||
|
||||
- `defense_line`
|
||||
- `attack_route`
|
||||
- `military_route`
|
||||
- `retreat_route`
|
||||
- `invasion_route`
|
||||
- `migration_route`
|
||||
- `refugee_route`
|
||||
- `trade_route`
|
||||
- `shipping_route`
|
||||
- `country`
|
||||
- `state`
|
||||
- `empire`
|
||||
- `kingdom`
|
||||
- `faction`
|
||||
- `war`
|
||||
- `battle`
|
||||
- `civilization`
|
||||
- `rebellion_zone`
|
||||
- `person_deathplace`
|
||||
- `person_birthplace`
|
||||
- `person_activity`
|
||||
- `person_event`
|
||||
- `temple`
|
||||
- `capital`
|
||||
- `city`
|
||||
- `fortress`
|
||||
- `castle`
|
||||
- `fortification`
|
||||
- `ruin`
|
||||
- `port`
|
||||
- `bridge`
|
||||
|
||||
`GEOMETRY_TYPE_OPTIONS` trong `src/uhm/lib/map/geo/geometryTypeOptions.ts` phải khớp với tập geotype này nếu muốn user chọn được từ UI.
|
||||
|
||||
|
||||
@@ -74,14 +74,7 @@ export function useProjectCommands(options: Options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orphanGeometries = findOrphanGeometries(options.editor.mainDraft);
|
||||
if (orphanGeometries.length > 0) {
|
||||
const firstOrphan = orphanGeometries[0];
|
||||
state.setSelectedFeatureIds([firstOrphan.id]);
|
||||
state.setEntityFormStatus("Geometry này chưa bind entity.");
|
||||
state.setEntityStatus(formatOrphanGeometryMessage("Commit", orphanGeometries));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const geometryChanges = options.editor.buildPayload();
|
||||
state.setIsSaving(true);
|
||||
@@ -221,14 +214,7 @@ export function useProjectCommands(options: Options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orphanGeometries = findOrphanGeometries(options.editor.mainDraft);
|
||||
if (orphanGeometries.length > 0) {
|
||||
const firstOrphan = orphanGeometries[0];
|
||||
state.setSelectedFeatureIds([firstOrphan.id]);
|
||||
state.setEntityFormStatus("Geometry này chưa bind entity.");
|
||||
state.setEntityStatus(formatOrphanGeometryMessage("Submit", orphanGeometries));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
state.setIsSubmitting(true);
|
||||
state.setEntityStatus(null);
|
||||
@@ -305,33 +291,7 @@ export function useProjectCommands(options: Options) {
|
||||
};
|
||||
}
|
||||
|
||||
type OrphanGeometry = {
|
||||
id: Feature["properties"]["id"];
|
||||
label: string;
|
||||
};
|
||||
|
||||
function findOrphanGeometries(draft: FeatureCollection): OrphanGeometry[] {
|
||||
const rows: OrphanGeometry[] = [];
|
||||
|
||||
for (const feature of draft.features || []) {
|
||||
const entityIds = normalizeFeatureEntityIds(feature);
|
||||
if (entityIds.length > 0) continue;
|
||||
|
||||
const id = feature.properties.id;
|
||||
rows.push({
|
||||
id,
|
||||
label: String(id),
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function formatOrphanGeometryMessage(action: "Commit" | "Submit", rows: OrphanGeometry[]): string {
|
||||
const sample = rows.slice(0, 8).map((row) => row.label).join(", ");
|
||||
const more = rows.length > 8 ? `, ... (+${rows.length - 8})` : "";
|
||||
return `Không thể ${action}: còn ${rows.length} geometry chưa bind entity. Hãy bind entity cho: ${sample}${more}.`;
|
||||
}
|
||||
|
||||
function toEditorSessionSnapshot(snapshot: EditorSnapshot): EditorSnapshot {
|
||||
return {
|
||||
|
||||
@@ -23,6 +23,7 @@ export function initCircle(
|
||||
|
||||
// Xóa dữ liệu preview circle trên map.
|
||||
const clearPreview = () => {
|
||||
if (!map.isStyleLoaded()) return;
|
||||
(map.getSource("draw-circle-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
||||
EMPTY_PREVIEW
|
||||
);
|
||||
@@ -32,7 +33,7 @@ export function initCircle(
|
||||
const releaseDragPan = () => {
|
||||
if (!dragPanDisabledByCircle) return;
|
||||
dragPanDisabledByCircle = false;
|
||||
if (!map.dragPan.isEnabled()) {
|
||||
if (map.isStyleLoaded() && !map.dragPan.isEnabled()) {
|
||||
map.dragPan.enable();
|
||||
}
|
||||
};
|
||||
@@ -53,6 +54,7 @@ export function initCircle(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!map.isStyleLoaded()) return;
|
||||
const ring = buildCircleRing(center, radiusMeters, CIRCLE_SEGMENTS);
|
||||
(map.getSource("draw-circle-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
||||
type: "FeatureCollection",
|
||||
@@ -91,7 +93,7 @@ export function initCircle(
|
||||
const onMouseMove = (e: maplibregl.MapMouseEvent) => {
|
||||
const canvas = map.getCanvas();
|
||||
if (getMode() !== "add-circle") {
|
||||
if (canvas.style.cursor === "crosshair") {
|
||||
if (canvas && canvas.style.cursor === "crosshair") {
|
||||
canvas.style.cursor = "";
|
||||
}
|
||||
if (isDragging) {
|
||||
@@ -100,7 +102,9 @@ export function initCircle(
|
||||
return;
|
||||
}
|
||||
|
||||
if (canvas) {
|
||||
canvas.style.cursor = "crosshair";
|
||||
}
|
||||
if (!isDragging || !center) return;
|
||||
|
||||
radiusMeters = distanceMeters(center, [e.lngLat.lng, e.lngLat.lat]);
|
||||
@@ -150,13 +154,20 @@ export function initCircle(
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
|
||||
const cleanup = () => {
|
||||
try {
|
||||
map.off("mousedown", onMouseDown);
|
||||
map.off("mousemove", onMouseMove);
|
||||
map.off("mouseup", onMouseUp);
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
resetDrawingState();
|
||||
if (map.getCanvas().style.cursor === "crosshair") {
|
||||
map.getCanvas().style.cursor = "";
|
||||
if (map.isStyleLoaded()) {
|
||||
const canvas = map.getCanvas();
|
||||
if (canvas && canvas.style.cursor === "crosshair") {
|
||||
canvas.style.cursor = "";
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export function initDrawing(
|
||||
let coords: [number, number][] = [];
|
||||
|
||||
const clearPreview = () => {
|
||||
if (!map.isStyleLoaded()) return;
|
||||
(map.getSource("draw-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
||||
type: "FeatureCollection",
|
||||
features: [],
|
||||
@@ -39,6 +40,7 @@ export function initDrawing(
|
||||
function update(c: [number, number][]) {
|
||||
const closed = closePolygon(c);
|
||||
|
||||
if (!map.isStyleLoaded()) return;
|
||||
(map.getSource("draw-preview") as maplibregl.GeoJSONSource)?.setData({
|
||||
type: "FeatureCollection",
|
||||
features: [
|
||||
@@ -130,12 +132,18 @@ export function initDrawing(
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
|
||||
const cleanup = () => {
|
||||
try {
|
||||
if (map.isStyleLoaded()) {
|
||||
map.boxZoom.enable();
|
||||
map.doubleClickZoom.enable();
|
||||
}
|
||||
map.off("click", onClick);
|
||||
map.off("mousemove", onMove);
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
cancelDrawing();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -37,7 +37,7 @@ export function createEditingEngine(options: {
|
||||
setDeleteVertexMode(false);
|
||||
hideContextMenu();
|
||||
const map = mapRef.current;
|
||||
if (!map) return;
|
||||
if (!map || !map.isStyleLoaded()) return;
|
||||
const empty: GeoJSON.FeatureCollection = { type: "FeatureCollection", features: [] };
|
||||
(map.getSource("edit-shape") as maplibregl.GeoJSONSource | undefined)?.setData(empty);
|
||||
(map.getSource("edit-handles") as maplibregl.GeoJSONSource | undefined)?.setData(empty);
|
||||
@@ -47,7 +47,7 @@ export function createEditingEngine(options: {
|
||||
const updateEditSources = () => {
|
||||
const editing = editingRef.current;
|
||||
const map = mapRef.current;
|
||||
if (!editing || !map) return;
|
||||
if (!editing || !map || !map.isStyleLoaded()) return;
|
||||
|
||||
let shape: GeoJSON.FeatureCollection<GeoJSON.Polygon>;
|
||||
let handles: GeoJSON.FeatureCollection<GeoJSON.Point>;
|
||||
@@ -143,7 +143,7 @@ export function createEditingEngine(options: {
|
||||
const setDeleteVertexMode = (enabled: boolean) => {
|
||||
deleteVertexModeRef.current = enabled;
|
||||
const map = mapRef.current;
|
||||
if (!map?.getLayer("edit-handles-circle")) return;
|
||||
if (!map || !map.isStyleLoaded() || !map.getLayer("edit-handles-circle")) return;
|
||||
map.setPaintProperty("edit-handles-circle", "circle-color", enabled ? "#ef4444" : "#f97316");
|
||||
map.setPaintProperty("edit-handles-circle", "circle-stroke-color", enabled ? "#7f1d1d" : "#0f172a");
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ export function initLine(
|
||||
|
||||
// Xóa dữ liệu preview line.
|
||||
const clearPreview = () => {
|
||||
if (!map.isStyleLoaded()) return;
|
||||
(map.getSource("draw-line-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
||||
EMPTY_PREVIEW
|
||||
);
|
||||
@@ -36,6 +37,7 @@ export function initLine(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!map.isStyleLoaded()) return;
|
||||
(map.getSource("draw-line-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
||||
type: "FeatureCollection",
|
||||
features: [
|
||||
@@ -91,13 +93,15 @@ export function initLine(
|
||||
if (coords.length) {
|
||||
cancelLine();
|
||||
}
|
||||
if (canvas.style.cursor === "crosshair") {
|
||||
if (canvas && canvas.style.cursor === "crosshair") {
|
||||
canvas.style.cursor = "";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (canvas) {
|
||||
canvas.style.cursor = "crosshair";
|
||||
}
|
||||
if (coords.length === 0) return;
|
||||
|
||||
const lngLat = e.originalEvent.shiftKey || e.originalEvent.altKey
|
||||
@@ -133,12 +137,19 @@ export function initLine(
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
|
||||
const cleanup = () => {
|
||||
try {
|
||||
map.off("click", onClick);
|
||||
map.off("mousemove", onMove);
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
cancelLine();
|
||||
if (map.getCanvas().style.cursor === "crosshair") {
|
||||
map.getCanvas().style.cursor = "";
|
||||
if (map.isStyleLoaded()) {
|
||||
const canvas = map.getCanvas();
|
||||
if (canvas && canvas.style.cursor === "crosshair") {
|
||||
canvas.style.cursor = "";
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export function initPath(
|
||||
|
||||
// Xóa dữ liệu preview path.
|
||||
const clearPreview = () => {
|
||||
if (!map.isStyleLoaded()) return;
|
||||
(map.getSource("draw-path-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
||||
EMPTY_PREVIEW
|
||||
);
|
||||
@@ -30,6 +31,7 @@ export function initPath(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!map.isStyleLoaded()) return;
|
||||
(map.getSource("draw-path-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
||||
type: "FeatureCollection",
|
||||
features: [
|
||||
@@ -92,13 +94,15 @@ export function initPath(
|
||||
if (coords.length) {
|
||||
cancelPath();
|
||||
}
|
||||
if (canvas.style.cursor === "crosshair") {
|
||||
if (canvas && canvas.style.cursor === "crosshair") {
|
||||
canvas.style.cursor = "";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (canvas) {
|
||||
canvas.style.cursor = "crosshair";
|
||||
}
|
||||
if (coords.length === 0) return;
|
||||
|
||||
const lngLat = e.originalEvent.shiftKey || e.originalEvent.altKey
|
||||
@@ -134,12 +138,19 @@ export function initPath(
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
|
||||
const cleanup = () => {
|
||||
try {
|
||||
map.off("click", onClick);
|
||||
map.off("mousemove", onMove);
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
cancelPath();
|
||||
if (map.getCanvas().style.cursor === "crosshair") {
|
||||
map.getCanvas().style.cursor = "";
|
||||
if (map.isStyleLoaded()) {
|
||||
const canvas = map.getCanvas();
|
||||
if (canvas && canvas.style.cursor === "crosshair") {
|
||||
canvas.style.cursor = "";
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -41,10 +41,17 @@ export function initPoint(
|
||||
map.on("mousemove", onMove);
|
||||
|
||||
return () => {
|
||||
try {
|
||||
map.off("click", onClick);
|
||||
map.off("mousemove", onMove);
|
||||
if (map.getCanvas().style.cursor === "crosshair") {
|
||||
map.getCanvas().style.cursor = "";
|
||||
if (map.isStyleLoaded()) {
|
||||
const canvas = map.getCanvas();
|
||||
if (canvas && canvas.style.cursor === "crosshair") {
|
||||
canvas.style.cursor = "";
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ export function initSelect(
|
||||
}
|
||||
|
||||
function setSelectionStateForId(id: string | number, selected: boolean) {
|
||||
if (!map.isStyleLoaded()) return;
|
||||
for (const source of FEATURE_STATE_SOURCES) {
|
||||
if (!map.getSource(source)) continue;
|
||||
map.setFeatureState({ source, id }, { selected });
|
||||
@@ -194,13 +195,19 @@ export function initSelect(
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
try {
|
||||
map.off("click", onClick);
|
||||
map.off("mousemove", onMove);
|
||||
if (hasContextActions) {
|
||||
map.off("contextmenu", onRightClick);
|
||||
}
|
||||
if (map.isStyleLoaded()) {
|
||||
clearSelection(false);
|
||||
}
|
||||
hideContextMenu();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,33 +1,28 @@
|
||||
[
|
||||
{ "type_key": "defense_line", "geo_type_code": 1 },
|
||||
{ "type_key": "attack_route", "geo_type_code": 2 },
|
||||
{ "type_key": "military_route", "geo_type_code": 2 },
|
||||
{ "type_key": "retreat_route", "geo_type_code": 3 },
|
||||
{ "type_key": "invasion_route", "geo_type_code": 4 },
|
||||
{ "type_key": "migration_route", "geo_type_code": 5 },
|
||||
{ "type_key": "refugee_route", "geo_type_code": 6 },
|
||||
{ "type_key": "trade_route", "geo_type_code": 7 },
|
||||
{ "type_key": "shipping_route", "geo_type_code": 8 },
|
||||
|
||||
{ "type_key": "country", "geo_type_code": 9, "fixed": true },
|
||||
{ "type_key": "state", "geo_type_code": 10 },
|
||||
{ "type_key": "empire", "geo_type_code": 11 },
|
||||
{ "type_key": "kingdom", "geo_type_code": 12 },
|
||||
{ "type_key": "faction", "geo_type_code": 28 },
|
||||
|
||||
{ "type_key": "war", "geo_type_code": 13 },
|
||||
{ "type_key": "battle", "geo_type_code": 14 },
|
||||
{ "type_key": "civilization", "geo_type_code": 15 },
|
||||
{ "type_key": "rebellion_zone", "geo_type_code": 16 },
|
||||
|
||||
{ "type_key": "person_deathplace", "geo_type_code": 17 },
|
||||
{ "type_key": "person_birthplace", "geo_type_code": 18 },
|
||||
{ "type_key": "person_activity", "geo_type_code": 19 },
|
||||
{ "type_key": "person_event", "geo_type_code": 17 },
|
||||
{ "type_key": "person_event", "geo_type_code": 18 },
|
||||
{ "type_key": "person_event", "geo_type_code": 19 },
|
||||
|
||||
{ "type_key": "temple", "geo_type_code": 20 },
|
||||
{ "type_key": "capital", "geo_type_code": 21 },
|
||||
{ "type_key": "city", "geo_type_code": 22 },
|
||||
{ "type_key": "fortress", "geo_type_code": 23 },
|
||||
{ "type_key": "castle", "geo_type_code": 24 },
|
||||
|
||||
{ "type_key": "fortification", "geo_type_code": 23 },
|
||||
{ "type_key": "fortification", "geo_type_code": 24 },
|
||||
|
||||
{ "type_key": "ruin", "geo_type_code": 25 },
|
||||
{ "type_key": "port", "geo_type_code": 26 },
|
||||
{ "type_key": "bridge", "geo_type_code": 27 }
|
||||
{ "type_key": "port", "geo_type_code": 26 }
|
||||
]
|
||||
|
||||
@@ -40,6 +40,15 @@ export function geoTypeCodeToTypeKey(code: number | null | undefined): string |
|
||||
return KEY_BY_CODE.get(Math.trunc(code)) ?? null;
|
||||
}
|
||||
|
||||
const DEPRECATED_MAPPING: Record<string, string> = {
|
||||
attack_route: "military_route",
|
||||
person_birthplace: "person_event",
|
||||
person_deathplace: "person_event",
|
||||
person_activity: "person_event",
|
||||
fortress: "fortification",
|
||||
castle: "fortification",
|
||||
};
|
||||
|
||||
export function normalizeGeoTypeKey(value: unknown): string | null {
|
||||
if (typeof value === "number") {
|
||||
return geoTypeCodeToTypeKey(value);
|
||||
@@ -54,5 +63,14 @@ export function normalizeGeoTypeKey(value: unknown): string | null {
|
||||
return geoTypeCodeToTypeKey(Number(normalized));
|
||||
}
|
||||
|
||||
if (normalized in DEPRECATED_MAPPING) {
|
||||
return DEPRECATED_MAPPING[normalized];
|
||||
}
|
||||
|
||||
const code = CODE_BY_KEY.get(normalized);
|
||||
if (code !== undefined) {
|
||||
return KEY_BY_CODE.get(code) ?? normalized;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -62,37 +62,25 @@ const RAW_GEOMETRY_TYPE_OPTIONS: Array<{
|
||||
geometryPreset: GeometryPreset;
|
||||
}> = [
|
||||
{ value: "defense_line", label: "Defense Line", groupId: "line", geometryPreset: "line" },
|
||||
|
||||
{ value: "attack_route", label: "Attack Route", groupId: "line", geometryPreset: "line" },
|
||||
{ value: "military_route", label: "Military Route", groupId: "line", geometryPreset: "line" },
|
||||
{ value: "retreat_route", label: "Retreat Route", groupId: "line", geometryPreset: "line" },
|
||||
{ value: "invasion_route", label: "Invasion Route", groupId: "line", geometryPreset: "line" },
|
||||
{ value: "migration_route", label: "Migration Route", groupId: "line", geometryPreset: "line" },
|
||||
{ value: "refugee_route", label: "Refugee Route", groupId: "line", geometryPreset: "line" },
|
||||
{ value: "trade_route", label: "Trade Route", groupId: "line", geometryPreset: "line" },
|
||||
{ value: "shipping_route", label: "Shipping Route", groupId: "line", geometryPreset: "line" },
|
||||
|
||||
{ value: "country", label: "Country", groupId: "polygon", geometryPreset: "polygon" },
|
||||
{ value: "state", label: "State", groupId: "polygon", geometryPreset: "polygon" },
|
||||
{ value: "empire", label: "Empire", groupId: "polygon", geometryPreset: "polygon" },
|
||||
{ value: "kingdom", label: "Kingdom", groupId: "polygon", geometryPreset: "polygon" },
|
||||
{ value: "faction", label: "Faction", groupId: "polygon", geometryPreset: "polygon" },
|
||||
|
||||
{ value: "war", label: "War", groupId: "circle", geometryPreset: "circle-area" },
|
||||
{ value: "battle", label: "Battle", groupId: "circle", geometryPreset: "circle-area" },
|
||||
{ value: "civilization", label: "Civilization", groupId: "circle", geometryPreset: "circle-area" },
|
||||
{ value: "rebellion_zone", label: "Rebellion Zone", groupId: "circle", geometryPreset: "circle-area" },
|
||||
|
||||
{ value: "person_deathplace", label: "Person Deathplace", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "person_birthplace", label: "Person Birthplace", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "person_activity", label: "Person Activity", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "person_event", label: "Person Event", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "temple", label: "Temple", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "capital", label: "Capital", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "city", label: "City", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "fortress", label: "Fortress", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "castle", label: "Castle", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "fortification", label: "Fortification", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "ruin", label: "Ruin", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "port", label: "Port", groupId: "point", geometryPreset: "point" },
|
||||
{ value: "bridge", label: "Bridge", groupId: "point", geometryPreset: "point" },
|
||||
];
|
||||
|
||||
export const GEOMETRY_TYPE_OPTIONS: GeometryTypeOption[] = RAW_GEOMETRY_TYPE_OPTIONS.map((item) => ({
|
||||
|
||||
@@ -3,33 +3,22 @@ export const TYPE_MATCH_EXPR: maplibregl.ExpressionSpecification = ["coalesce",
|
||||
export { ensurePointGeotypeIcons } from "./shared/pointStyle";
|
||||
|
||||
import { getDefenseLineLayers } from "./geotypes/defense_line";
|
||||
import { getAttackRouteLayers } from "./geotypes/attack_route";
|
||||
import { getMilitaryRouteLayers } from "./geotypes/military_route";
|
||||
import { getRetreatRouteLayers } from "./geotypes/retreat_route";
|
||||
import { getInvasionRouteLayers } from "./geotypes/invasion_route";
|
||||
import { getMigrationRouteLayers } from "./geotypes/migration_route";
|
||||
import { getRefugeeRouteLayers } from "./geotypes/refugee_route";
|
||||
import { getTradeRouteLayers } from "./geotypes/trade_route";
|
||||
import { getShippingRouteLayers } from "./geotypes/shipping_route";
|
||||
import { getCountryLayers } from "./geotypes/country";
|
||||
import { getStateLayers } from "./geotypes/state";
|
||||
import { getEmpireLayers } from "./geotypes/empire";
|
||||
import { getKingdomLayers } from "./geotypes/kingdom";
|
||||
import { getFactionLayers } from "./geotypes/faction";
|
||||
import { getWarLayers } from "./geotypes/war";
|
||||
import { getBattleLayers } from "./geotypes/battle";
|
||||
import { getCivilizationLayers } from "./geotypes/civilization";
|
||||
import { getRebellionZoneLayers } from "./geotypes/rebellion_zone";
|
||||
import { getPersonDeathplaceLayers } from "./geotypes/person_deathplace";
|
||||
import { getPersonBirthplaceLayers } from "./geotypes/person_birthplace";
|
||||
import { getPersonActivityLayers } from "./geotypes/person_activity";
|
||||
import { getPersonEventLayers } from "./geotypes/person_event";
|
||||
import { getTempleLayers } from "./geotypes/temple";
|
||||
import { getCapitalLayers } from "./geotypes/capital";
|
||||
import { getCityLayers } from "./geotypes/city";
|
||||
import { getFortressLayers } from "./geotypes/fortress";
|
||||
import { getCastleLayers } from "./geotypes/castle";
|
||||
import { getFortificationLayers } from "./geotypes/fortification";
|
||||
import { getRuinLayers } from "./geotypes/ruin";
|
||||
import { getPortLayers } from "./geotypes/port";
|
||||
import { getBridgeLayers } from "./geotypes/bridge";
|
||||
import { getLineLabelLayers } from "./shared/lineLabels";
|
||||
import { getPolygonLabelLayers } from "./shared/polygonLabels";
|
||||
|
||||
@@ -39,32 +28,21 @@ export function getAllGeotypeLayers(sourceId: string, pathArrowSourceId?: string
|
||||
return [
|
||||
...getCountryLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getStateLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getEmpireLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getKingdomLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getFactionLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getWarLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getBattleLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getCivilizationLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getRebellionZoneLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getDefenseLineLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getAttackRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getMilitaryRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getRetreatRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getInvasionRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getMigrationRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getRefugeeRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getTradeRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getShippingRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getPersonDeathplaceLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getPersonBirthplaceLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getPersonActivityLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getPersonEventLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getTempleLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getCapitalLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getCityLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getFortressLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getCastleLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getFortificationLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getRuinLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getPortLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||
...getBridgeLayers(sourceId, pathArrowSourceId, pointSourceId)
|
||||
...getPortLayers(sourceId, pathArrowSourceId, pointSourceId)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPointGeotypeLayers } from "../shared/pointStyle";
|
||||
|
||||
export function getBridgeLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void sourceId;
|
||||
void pathArrowSourceId;
|
||||
return buildPointGeotypeLayers("bridge", pointSourceId!);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPointGeotypeLayers } from "../shared/pointStyle";
|
||||
|
||||
export function getCastleLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void sourceId;
|
||||
void pathArrowSourceId;
|
||||
return buildPointGeotypeLayers("castle", pointSourceId!);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPolygonGeotypeLayers } from "../shared/styleBuilders";
|
||||
|
||||
export function getCivilizationLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void pathArrowSourceId;
|
||||
void pointSourceId;
|
||||
return buildPolygonGeotypeLayers(sourceId, {
|
||||
typeId: "civilization",
|
||||
fillColor: "#14b8a6",
|
||||
strokeColor: "#134e4a",
|
||||
fillOpacity: 0.34,
|
||||
});
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPolygonGeotypeLayers } from "../shared/styleBuilders";
|
||||
|
||||
export function getEmpireLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void pathArrowSourceId;
|
||||
void pointSourceId;
|
||||
return buildPolygonGeotypeLayers(sourceId, {
|
||||
typeId: "empire",
|
||||
fillColor: "#f59e0b",
|
||||
strokeColor: "#92400e",
|
||||
fillOpacity: 0.36,
|
||||
strokeWidth: { z1: 1.8, z4: 2.6, z6: 3.4 },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPointGeotypeLayers } from "../shared/pointStyle";
|
||||
|
||||
export function getFortificationLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void sourceId;
|
||||
void pathArrowSourceId;
|
||||
return buildPointGeotypeLayers("fortification", pointSourceId!);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPointGeotypeLayers } from "../shared/pointStyle";
|
||||
|
||||
export function getFortressLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void sourceId;
|
||||
void pathArrowSourceId;
|
||||
return buildPointGeotypeLayers("fortress", pointSourceId!);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildLineGeotypeLayers } from "../shared/styleBuilders";
|
||||
|
||||
export function getInvasionRouteLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void pointSourceId;
|
||||
return buildLineGeotypeLayers(sourceId, pathArrowSourceId, {
|
||||
typeId: "invasion_route",
|
||||
color: "#be123c",
|
||||
strokeColor: "#4c0519",
|
||||
width: { z1: 2.8, z4: 4.1, z6: 5.4 },
|
||||
arrowOpacity: 0.9,
|
||||
});
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPolygonGeotypeLayers } from "../shared/styleBuilders";
|
||||
|
||||
export function getKingdomLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void pathArrowSourceId;
|
||||
void pointSourceId;
|
||||
return buildPolygonGeotypeLayers(sourceId, {
|
||||
typeId: "kingdom",
|
||||
fillColor: "#8b5cf6",
|
||||
strokeColor: "#6d28d9",
|
||||
fillOpacity: 0.34,
|
||||
});
|
||||
}
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildLineGeotypeLayers } from "../shared/styleBuilders";
|
||||
|
||||
export function getAttackRouteLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
export function getMilitaryRouteLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void pointSourceId;
|
||||
return buildLineGeotypeLayers(sourceId, pathArrowSourceId, {
|
||||
typeId: "attack_route",
|
||||
typeId: "military_route",
|
||||
color: "#ef4444",
|
||||
strokeColor: "#7f1d1d",
|
||||
width: { z1: 2.6, z4: 3.8, z6: 5 },
|
||||
@@ -1,8 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPointGeotypeLayers } from "../shared/pointStyle";
|
||||
|
||||
export function getPersonActivityLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void sourceId;
|
||||
void pathArrowSourceId;
|
||||
return buildPointGeotypeLayers("person_activity", pointSourceId!);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPointGeotypeLayers } from "../shared/pointStyle";
|
||||
|
||||
export function getPersonBirthplaceLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void sourceId;
|
||||
void pathArrowSourceId;
|
||||
return buildPointGeotypeLayers("person_birthplace", pointSourceId!);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPointGeotypeLayers } from "../shared/pointStyle";
|
||||
|
||||
export function getPersonDeathplaceLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void sourceId;
|
||||
void pathArrowSourceId;
|
||||
return buildPointGeotypeLayers("person_deathplace", pointSourceId!);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPointGeotypeLayers } from "../shared/pointStyle";
|
||||
|
||||
export function getPersonEventLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void sourceId;
|
||||
void pathArrowSourceId;
|
||||
return buildPointGeotypeLayers("person_event", pointSourceId!);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildLineGeotypeLayers } from "../shared/styleBuilders";
|
||||
|
||||
export function getRefugeeRouteLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void pointSourceId;
|
||||
return buildLineGeotypeLayers(sourceId, pathArrowSourceId, {
|
||||
typeId: "refugee_route",
|
||||
color: "#f97316",
|
||||
strokeColor: "#9a3412",
|
||||
dasharray: [1, 2],
|
||||
opacity: 0.84,
|
||||
arrowOpacity: 0.72,
|
||||
});
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildLineGeotypeLayers } from "../shared/styleBuilders";
|
||||
|
||||
export function getShippingRouteLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void pointSourceId;
|
||||
return buildLineGeotypeLayers(sourceId, pathArrowSourceId, {
|
||||
typeId: "shipping_route",
|
||||
color: "#0ea5e9",
|
||||
strokeColor: "#075985",
|
||||
width: { z1: 2.4, z4: 3.5, z6: 4.7 },
|
||||
dasharray: [7, 4],
|
||||
arrowOpacity: 0.8,
|
||||
});
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { LayerSpecification } from "maplibre-gl";
|
||||
import { buildPolygonGeotypeLayers } from "../shared/styleBuilders";
|
||||
|
||||
export function getWarLayers(sourceId: string, pathArrowSourceId?: string, pointSourceId?: string): LayerSpecification[] {
|
||||
void pathArrowSourceId;
|
||||
void pointSourceId;
|
||||
return buildPolygonGeotypeLayers(sourceId, {
|
||||
typeId: "war",
|
||||
fillColor: "#dc2626",
|
||||
strokeColor: "#7f1d1d",
|
||||
fillOpacity: 0.26,
|
||||
dasharray: [5, 2],
|
||||
});
|
||||
}
|
||||
@@ -2,30 +2,23 @@ import maplibregl, { LayerSpecification } from "maplibre-gl";
|
||||
import { MAP_EMPHASIS_TEXT_FONT_STACK } from "./textFonts";
|
||||
|
||||
export const POINT_GEOTYPE_IDS = [
|
||||
"person_birthplace",
|
||||
"person_deathplace",
|
||||
"person_activity",
|
||||
"person_event",
|
||||
"temple",
|
||||
"capital",
|
||||
"city",
|
||||
"fortress",
|
||||
"castle",
|
||||
"fortification",
|
||||
"ruin",
|
||||
"port",
|
||||
"bridge",
|
||||
] as const;
|
||||
|
||||
export type PointGeotypeId = (typeof POINT_GEOTYPE_IDS)[number];
|
||||
|
||||
export const POINT_GEOTYPE_ICON_PATHS: Partial<Record<PointGeotypeId, string>> = {
|
||||
person_birthplace: "/images/mapIcon/point/house.png",
|
||||
person_deathplace: "/images/mapIcon/point/tombstone.png",
|
||||
person_activity: "/images/mapIcon/point/flag.png",
|
||||
person_event: "/images/mapIcon/point/flag.png",
|
||||
temple: "/images/mapIcon/point/temple.png",
|
||||
capital: "/images/mapIcon/point/capital.png",
|
||||
city: "/images/mapIcon/point/city.png",
|
||||
fortress: "/images/mapIcon/point/fortress.png",
|
||||
castle: "/images/mapIcon/point/castle.png",
|
||||
fortification: "/images/mapIcon/point/castle.png",
|
||||
ruin: "/images/mapIcon/point/ruin.png",
|
||||
};
|
||||
|
||||
@@ -57,21 +50,7 @@ const POINT_GEOMETRY_FILTER: maplibregl.ExpressionSpecification = [
|
||||
];
|
||||
|
||||
const POINT_STYLE_CONFIG: Record<PointGeotypeId, PointStyleConfig> = {
|
||||
person_birthplace: {
|
||||
fill: "#22c55e",
|
||||
rim: "#166534",
|
||||
iconScale: 1,
|
||||
haloRadius: 15,
|
||||
drawGlyph: drawHouseGlyph,
|
||||
},
|
||||
person_deathplace: {
|
||||
fill: "#b91c1c",
|
||||
rim: "#450a0a",
|
||||
iconScale: 1,
|
||||
haloRadius: 15,
|
||||
drawGlyph: drawMemorialGlyph,
|
||||
},
|
||||
person_activity: {
|
||||
person_event: {
|
||||
fill: "#f97316",
|
||||
rim: "#9a3412",
|
||||
iconScale: 0.98,
|
||||
@@ -99,14 +78,7 @@ const POINT_STYLE_CONFIG: Record<PointGeotypeId, PointStyleConfig> = {
|
||||
haloRadius: 15,
|
||||
drawGlyph: drawCityGlyph,
|
||||
},
|
||||
fortress: {
|
||||
fill: "#64748b",
|
||||
rim: "#334155",
|
||||
iconScale: 1.04,
|
||||
haloRadius: 16,
|
||||
drawGlyph: drawShieldGlyph,
|
||||
},
|
||||
castle: {
|
||||
fortification: {
|
||||
fill: "#7c3aed",
|
||||
rim: "#4c1d95",
|
||||
iconScale: 1.04,
|
||||
@@ -127,13 +99,6 @@ const POINT_STYLE_CONFIG: Record<PointGeotypeId, PointStyleConfig> = {
|
||||
haloRadius: 15,
|
||||
drawGlyph: drawAnchorGlyph,
|
||||
},
|
||||
bridge: {
|
||||
fill: "#b45309",
|
||||
rim: "#7c2d12",
|
||||
iconScale: 1,
|
||||
haloRadius: 14,
|
||||
drawGlyph: drawBridgeGlyph,
|
||||
},
|
||||
};
|
||||
|
||||
export function buildPointGeotypeLayers(
|
||||
@@ -320,53 +285,9 @@ function drawGlyphWithOutline(
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawHouseGlyph(ctx: CanvasRenderingContext2D) {
|
||||
const img = preloadedImages["person_birthplace"];
|
||||
if (img && loadedImageKeys.has("person_birthplace")) {
|
||||
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
||||
} else {
|
||||
ctx.lineWidth = 3.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(22, 34);
|
||||
ctx.lineTo(32, 24);
|
||||
ctx.lineTo(42, 34);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.rect(25.5, 34, 13, 9);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(32, 43);
|
||||
ctx.lineTo(32, 36.5);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawMemorialGlyph(ctx: CanvasRenderingContext2D) {
|
||||
const img = preloadedImages["person_deathplace"];
|
||||
if (img && loadedImageKeys.has("person_deathplace")) {
|
||||
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
||||
} else {
|
||||
ctx.lineWidth = 3.6;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(32, 22);
|
||||
ctx.lineTo(32, 43);
|
||||
ctx.moveTo(25, 28.5);
|
||||
ctx.lineTo(39, 28.5);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = 2.4;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(24, 45);
|
||||
ctx.lineTo(40, 45);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawFlagGlyph(ctx: CanvasRenderingContext2D) {
|
||||
const img = preloadedImages["person_activity"];
|
||||
if (img && loadedImageKeys.has("person_activity")) {
|
||||
const img = preloadedImages["person_event"];
|
||||
if (img && loadedImageKeys.has("person_event")) {
|
||||
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
||||
} else {
|
||||
ctx.lineWidth = 3.2;
|
||||
@@ -462,32 +383,9 @@ function drawCityGlyph(ctx: CanvasRenderingContext2D) {
|
||||
}
|
||||
}
|
||||
|
||||
function drawShieldGlyph(ctx: CanvasRenderingContext2D) {
|
||||
const img = preloadedImages["fortress"];
|
||||
if (img && loadedImageKeys.has("fortress")) {
|
||||
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
||||
} else {
|
||||
ctx.lineWidth = 3.2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(32, 22.5);
|
||||
ctx.lineTo(41, 26.5);
|
||||
ctx.lineTo(39, 37.5);
|
||||
ctx.lineTo(32, 43);
|
||||
ctx.lineTo(25, 37.5);
|
||||
ctx.lineTo(23, 26.5);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(32, 25);
|
||||
ctx.lineTo(32, 39);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawCastleGlyph(ctx: CanvasRenderingContext2D) {
|
||||
const img = preloadedImages["castle"];
|
||||
if (img && loadedImageKeys.has("castle")) {
|
||||
const img = preloadedImages["fortification"];
|
||||
if (img && loadedImageKeys.has("fortification")) {
|
||||
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
||||
} else {
|
||||
ctx.lineWidth = 3;
|
||||
|
||||
@@ -27,50 +27,34 @@ export const COUNTRY_FILL_COLOR_EXPRESSION: maplibregl.ExpressionSpecification =
|
||||
export const POLYGON_FILL_BY_TYPE: Record<string, string> = {
|
||||
country: "#2563eb",
|
||||
state: "#0ea5e9",
|
||||
empire: "#f59e0b",
|
||||
kingdom: "#d97706",
|
||||
war: "#dc2626",
|
||||
battle: "#f43f5e",
|
||||
civilization: "#14b8a6",
|
||||
rebellion_zone: "#7c3aed",
|
||||
};
|
||||
|
||||
export const POLYGON_STROKE_BY_TYPE: Record<string, string> = {
|
||||
country: "#1e3a8a",
|
||||
state: "#0c4a6e",
|
||||
empire: "#7c2d12",
|
||||
kingdom: "#9a3412",
|
||||
war: "#7f1d1d",
|
||||
battle: "#9f1239",
|
||||
civilization: "#134e4a",
|
||||
rebellion_zone: "#4c1d95",
|
||||
};
|
||||
|
||||
export const POLYGON_OPACITY_BY_TYPE: Record<string, number> = {
|
||||
war: 0.3,
|
||||
battle: 0.34,
|
||||
civilization: 0.38,
|
||||
rebellion_zone: 0.32,
|
||||
};
|
||||
|
||||
export const LINE_COLOR_BY_TYPE: Record<string, string> = {
|
||||
defense_line: "#f97316",
|
||||
attack_route: "#ef4444",
|
||||
military_route: "#ef4444",
|
||||
retreat_route: "#94a3b8",
|
||||
invasion_route: "#b91c1c",
|
||||
migration_route: "#0ea5e9",
|
||||
refugee_route: "#06b6d4",
|
||||
trade_route: "#eab308",
|
||||
shipping_route: "#2563eb",
|
||||
};
|
||||
|
||||
export const PATH_RENDER_BY_TYPE: Record<string, boolean> = {
|
||||
attack_route: true,
|
||||
military_route: true,
|
||||
retreat_route: true,
|
||||
invasion_route: true,
|
||||
migration_route: true,
|
||||
refugee_route: true,
|
||||
trade_route: true,
|
||||
shipping_route: true,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user