refactor: consolidate redundant geo-types and implement legacy key mapping in GeoTypeMap
This commit is contained in:
+228
-152
@@ -495,7 +495,7 @@ function EditorPageContent() {
|
|||||||
initialMapViewState: previewSession?.mapViewState ?? null,
|
initialMapViewState: previewSession?.mapViewState ?? null,
|
||||||
selectedStageId: previewSession?.selectedStageId ?? replaySelection.stageId,
|
selectedStageId: previewSession?.selectedStageId ?? replaySelection.stageId,
|
||||||
selectedStepIndex: previewSession?.selectedStepIndex ?? replaySelection.stepIndex,
|
selectedStepIndex: previewSession?.selectedStepIndex ?? replaySelection.stepIndex,
|
||||||
onSelectStep: () => {},
|
onSelectStep: () => { },
|
||||||
});
|
});
|
||||||
const {
|
const {
|
||||||
hiddenGeometryIds: replayPreviewHiddenGeometryIds,
|
hiddenGeometryIds: replayPreviewHiddenGeometryIds,
|
||||||
@@ -788,7 +788,7 @@ function EditorPageContent() {
|
|||||||
// QUY TẮC: Geo chọn đầu tiên là geo main.
|
// QUY TẮC: Geo chọn đầu tiên là geo main.
|
||||||
const finalSelectedIds = Array.from(new Set([...selectedFeatureIds, featureId]));
|
const finalSelectedIds = Array.from(new Set([...selectedFeatureIds, featureId]));
|
||||||
const triggerId = selectedFeatureIds.length > 0 ? selectedFeatureIds[0] : featureId;
|
const triggerId = selectedFeatureIds.length > 0 ? selectedFeatureIds[0] : featureId;
|
||||||
|
|
||||||
setReplayFeatureId(triggerId);
|
setReplayFeatureId(triggerId);
|
||||||
setReplaySelection({ stageId: null, stepIndex: null });
|
setReplaySelection({ stageId: null, stepIndex: null });
|
||||||
editor.switchReplayContext(triggerId, finalSelectedIds);
|
editor.switchReplayContext(triggerId, finalSelectedIds);
|
||||||
@@ -997,7 +997,7 @@ function EditorPageContent() {
|
|||||||
if (isReplayEditMode && hideOutside) {
|
if (isReplayEditMode && hideOutside) {
|
||||||
// Trong mode replay, ta chỉ hiển thị những gì có trong draft của replay đó
|
// Trong mode replay, ta chỉ hiển thị những gì có trong draft của replay đó
|
||||||
const currentReplayFeatureIds = new Set(editor.draft.features.map(f => String(f.properties.id)));
|
const currentReplayFeatureIds = new Set(editor.draft.features.map(f => String(f.properties.id)));
|
||||||
|
|
||||||
// Ẩn tất cả các geo KHÔNG nằm trong draft replay hiện tại
|
// Ẩn tất cả các geo KHÔNG nằm trong draft replay hiện tại
|
||||||
Object.keys(visibility).forEach(fid => {
|
Object.keys(visibility).forEach(fid => {
|
||||||
if (fid === String(replayFeatureId)) {
|
if (fid === String(replayFeatureId)) {
|
||||||
@@ -1062,7 +1062,7 @@ function EditorPageContent() {
|
|||||||
// Xóa pending submission để backend cho phép mở editor lại.
|
// Xóa pending submission để backend cho phép mở editor lại.
|
||||||
const unlockByDeletingPendingSubmission = useCallback(async () => {
|
const unlockByDeletingPendingSubmission = useCallback(async () => {
|
||||||
if (!blockedPendingSubmissionId) return;
|
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;
|
if (!confirmed) return;
|
||||||
try {
|
try {
|
||||||
setIsOpeningSection(true);
|
setIsOpeningSection(true);
|
||||||
@@ -1599,7 +1599,7 @@ function EditorPageContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const prevBindingIds = normalizeFeatureBindingIds(targetFeature);
|
const prevBindingIds = normalizeFeatureBindingIds(targetFeature);
|
||||||
|
|
||||||
// Merge prevBindingIds with sourceIds (which are strings of selected features)
|
// Merge prevBindingIds with sourceIds (which are strings of selected features)
|
||||||
// filter out targetId itself (we can't bind a geometry to itself)
|
// filter out targetId itself (we can't bind a geometry to itself)
|
||||||
const newSources = sourceIds.map(String).filter((x) => x !== idStr);
|
const newSources = sourceIds.map(String).filter((x) => x !== idStr);
|
||||||
@@ -1966,6 +1966,127 @@ function EditorPageContent() {
|
|||||||
[entities, labelContextBaseDraft]
|
[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 (
|
return (
|
||||||
<div style={{ display: "flex", minHeight: "100vh" }}>
|
<div style={{ display: "flex", minHeight: "100vh" }}>
|
||||||
{!isReplayEditMode && !isReplayPreviewMode ? (
|
{!isReplayEditMode && !isReplayPreviewMode ? (
|
||||||
@@ -1980,7 +2101,7 @@ function EditorPageContent() {
|
|||||||
onRestoreCommit={restoreCommit}
|
onRestoreCommit={restoreCommit}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
sectionTitle={activeSection?.title || "Đang tải project"}
|
sectionTitle={activeSection.title || "Đang tải project"}
|
||||||
projectStatus={projectState?.status || "editing"}
|
projectStatus={projectState?.status || "editing"}
|
||||||
commitTitle={commitTitle}
|
commitTitle={commitTitle}
|
||||||
onCommitTitleChange={setCommitTitle}
|
onCommitTitleChange={setCommitTitle}
|
||||||
@@ -2019,8 +2140,8 @@ function EditorPageContent() {
|
|||||||
previewPlaybackSpeed={1}
|
previewPlaybackSpeed={1}
|
||||||
onPlayPreviewFromStart={() => openReplayPreview("start")}
|
onPlayPreviewFromStart={() => openReplayPreview("start")}
|
||||||
onPlayPreviewFromSelection={() => openReplayPreview("selection")}
|
onPlayPreviewFromSelection={() => openReplayPreview("selection")}
|
||||||
onStopPreview={() => {}}
|
onStopPreview={() => { }}
|
||||||
onResetPreview={() => {}}
|
onResetPreview={() => { }}
|
||||||
/>
|
/>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
title="Resize left panel"
|
title="Resize left panel"
|
||||||
@@ -2031,158 +2152,113 @@ function EditorPageContent() {
|
|||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{blockedPendingSubmissionId ? (
|
<div style={{ flex: 1, position: "relative", minHeight: "100vh" }}>
|
||||||
<div style={{ flex: 1, minHeight: "100vh", background: "#0b1220", color: "white", padding: "24px" }}>
|
{isBackgroundVisibilityReady ? (
|
||||||
<div style={{ maxWidth: 720 }}>
|
<Map
|
||||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 700 }}>Editor dang bi khoa</h2>
|
ref={mapHandleRef}
|
||||||
<div style={{ marginTop: 10, fontSize: 13, color: "#cbd5e1" }}>
|
mode={mode}
|
||||||
Project nay dang co submission o trang thai <b>PENDING</b> (id:{" "}
|
onSetMode={setMode}
|
||||||
<code style={{ color: "white" }}>{blockedPendingSubmissionId}</code>). Theo BE moi, khi
|
renderDraft={mapRenderDraft}
|
||||||
submission dang pending thi khong duoc tao submission/commit moi va khong duoc vao editor.
|
labelContextDraft={mapLabelContextDraft}
|
||||||
</div>
|
labelTimelineYear={activeTimelineFilterEnabled ? activeTimelineYear : null}
|
||||||
<div style={{ marginTop: 14, display: "flex", gap: 10, alignItems: "center" }}>
|
selectedFeatureIds={selectedFeatureIds}
|
||||||
<button
|
onSelectFeatureIds={setSelectedFeatureIds}
|
||||||
onClick={unlockByDeletingPendingSubmission}
|
onCreateFeature={handleCreateFeature}
|
||||||
disabled={isOpeningSection}
|
onDeleteFeature={(id) => {
|
||||||
style={{
|
if (Array.isArray(id)) {
|
||||||
padding: "10px 12px",
|
editor.deleteFeatures(id);
|
||||||
borderRadius: 6,
|
} else {
|
||||||
border: "1px solid #334155",
|
editor.deleteFeature(id);
|
||||||
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
|
|
||||||
ref={mapHandleRef}
|
|
||||||
mode={mode}
|
|
||||||
onSetMode={setMode}
|
|
||||||
renderDraft={mapRenderDraft}
|
|
||||||
labelContextDraft={mapLabelContextDraft}
|
|
||||||
labelTimelineYear={activeTimelineFilterEnabled ? activeTimelineYear : null}
|
|
||||||
selectedFeatureIds={selectedFeatureIds}
|
|
||||||
onSelectFeatureIds={setSelectedFeatureIds}
|
|
||||||
onCreateFeature={handleCreateFeature}
|
|
||||||
onDeleteFeature={(id) => {
|
|
||||||
if (Array.isArray(id)) {
|
|
||||||
editor.deleteFeatures(id);
|
|
||||||
} else {
|
|
||||||
editor.deleteFeature(id);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onHideFeature={handleHideGeometryLocal}
|
|
||||||
onUpdateFeature={editor.updateFeature}
|
|
||||||
backgroundVisibility={backgroundVisibility}
|
|
||||||
geometryVisibility={effectiveGeometryVisibility}
|
|
||||||
applyGeometryBindingFilter={isReplayEditMode || isReplayPreviewMode ? false : geometryBindingFilterEnabled}
|
|
||||||
highlightFeatures={null}
|
|
||||||
focusFeatureCollection={geometryFocusRequest?.collection || null}
|
|
||||||
focusRequestKey={geometryFocusRequest?.key ?? null}
|
|
||||||
focusPadding={96}
|
|
||||||
imageOverlay={imageOverlay}
|
|
||||||
onImageOverlayChange={setImageOverlay}
|
|
||||||
onBindGeometries={handleBindGeometries}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div style={{ width: "100%", height: "100%", background: "#0b1220" }} />
|
|
||||||
)}
|
|
||||||
{isReplayPreviewMode ? (
|
|
||||||
<ReplayPreviewOverlay
|
|
||||||
isPreviewMode={true}
|
|
||||||
isPlaying={replayPreview.isPlaying}
|
|
||||||
title={replayPreview.title}
|
|
||||||
descriptions={replayPreview.descriptions}
|
|
||||||
subtitle={replayPreview.subtitle}
|
|
||||||
dialog={replayPreview.dialog}
|
|
||||||
image={replayPreview.image}
|
|
||||||
toasts={replayPreview.toasts}
|
|
||||||
sidebarOpen={replayPreview.sidebarOpen}
|
|
||||||
playbackSpeed={replayPreview.playbackSpeed}
|
|
||||||
activeStepLabel={replayPreviewActiveStepLabel}
|
|
||||||
activeStepNumber={replayPreview.activeStepNumber}
|
|
||||||
totalSteps={replayPreview.totalSteps}
|
|
||||||
onPlayPreview={replayPreview.playFromStart}
|
|
||||||
onStopPreview={replayPreview.stopPreview}
|
|
||||||
onResetPreview={replayPreview.resetPreview}
|
|
||||||
onExitPreview={exitReplayPreview}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{isReplayPreviewMode && replayPreview.sidebarOpen ? (
|
|
||||||
<aside
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 16,
|
|
||||||
right: 16,
|
|
||||||
bottom: 16,
|
|
||||||
width: 420,
|
|
||||||
maxWidth: "calc(100vw - 2rem)",
|
|
||||||
zIndex: 16,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PublicWikiSidebar
|
|
||||||
entity={null}
|
|
||||||
wiki={replayPreviewActiveWiki}
|
|
||||||
isLoading={isPreviewWikiLoading}
|
|
||||||
error={replayPreview.activeWikiId ? previewWikiError : "Chưa có wiki được chọn trong step này."}
|
|
||||||
onClose={() => {
|
|
||||||
setPreviewWikiError(null);
|
|
||||||
replayPreview.closeWikiPanel();
|
|
||||||
}}
|
|
||||||
onWikiLinkRequest={handleReplayPreviewWikiLinkRequest}
|
|
||||||
/>
|
|
||||||
</aside>
|
|
||||||
) : null}
|
|
||||||
{!isReplayPreviewMode || replayPreview.timelineVisible ? (
|
|
||||||
<TimelineBar
|
|
||||||
year={activeTimelineYear}
|
|
||||||
onYearChange={
|
|
||||||
isReplayPreviewMode
|
|
||||||
? replayPreview.setTimelineYear
|
|
||||||
: handleTimelineYearChange
|
|
||||||
}
|
|
||||||
isLoading={false}
|
|
||||||
disabled={false}
|
|
||||||
statusText={null}
|
|
||||||
filterEnabled={activeTimelineFilterEnabled}
|
|
||||||
onFilterEnabledChange={
|
|
||||||
isReplayPreviewMode
|
|
||||||
? replayPreview.setTimelineFilterEnabled
|
|
||||||
: setTimelineFilterEnabled
|
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
|
onHideFeature={handleHideGeometryLocal}
|
||||||
|
onUpdateFeature={editor.updateFeature}
|
||||||
|
backgroundVisibility={backgroundVisibility}
|
||||||
|
geometryVisibility={effectiveGeometryVisibility}
|
||||||
|
applyGeometryBindingFilter={isReplayEditMode || isReplayPreviewMode ? false : geometryBindingFilterEnabled}
|
||||||
|
highlightFeatures={null}
|
||||||
|
focusFeatureCollection={geometryFocusRequest?.collection || null}
|
||||||
|
focusRequestKey={geometryFocusRequest?.key ?? null}
|
||||||
|
focusPadding={96}
|
||||||
|
imageOverlay={imageOverlay}
|
||||||
|
onImageOverlayChange={setImageOverlay}
|
||||||
|
onBindGeometries={handleBindGeometries}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{ width: "100%", height: "100%", background: "#0b1220" }} />
|
||||||
|
)}
|
||||||
|
{isReplayPreviewMode ? (
|
||||||
|
<ReplayPreviewOverlay
|
||||||
|
isPreviewMode={true}
|
||||||
|
isPlaying={replayPreview.isPlaying}
|
||||||
|
title={replayPreview.title}
|
||||||
|
descriptions={replayPreview.descriptions}
|
||||||
|
subtitle={replayPreview.subtitle}
|
||||||
|
dialog={replayPreview.dialog}
|
||||||
|
image={replayPreview.image}
|
||||||
|
toasts={replayPreview.toasts}
|
||||||
|
sidebarOpen={replayPreview.sidebarOpen}
|
||||||
|
playbackSpeed={replayPreview.playbackSpeed}
|
||||||
|
activeStepLabel={replayPreviewActiveStepLabel}
|
||||||
|
activeStepNumber={replayPreview.activeStepNumber}
|
||||||
|
totalSteps={replayPreview.totalSteps}
|
||||||
|
onPlayPreview={replayPreview.playFromStart}
|
||||||
|
onStopPreview={replayPreview.stopPreview}
|
||||||
|
onResetPreview={replayPreview.resetPreview}
|
||||||
|
onExitPreview={exitReplayPreview}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{isReplayPreviewMode && replayPreview.sidebarOpen ? (
|
||||||
|
<aside
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 16,
|
||||||
|
right: 16,
|
||||||
|
bottom: 16,
|
||||||
|
width: 420,
|
||||||
|
maxWidth: "calc(100vw - 2rem)",
|
||||||
|
zIndex: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PublicWikiSidebar
|
||||||
|
entity={null}
|
||||||
|
wiki={replayPreviewActiveWiki}
|
||||||
|
isLoading={isPreviewWikiLoading}
|
||||||
|
error={replayPreview.activeWikiId ? previewWikiError : "Chưa có wiki được chọn trong step này."}
|
||||||
|
onClose={() => {
|
||||||
|
setPreviewWikiError(null);
|
||||||
|
replayPreview.closeWikiPanel();
|
||||||
|
}}
|
||||||
|
onWikiLinkRequest={handleReplayPreviewWikiLinkRequest}
|
||||||
/>
|
/>
|
||||||
) : null}
|
</aside>
|
||||||
</div>
|
) : null}
|
||||||
) : null}
|
{!isReplayPreviewMode || replayPreview.timelineVisible ? (
|
||||||
|
<TimelineBar
|
||||||
|
year={activeTimelineYear}
|
||||||
|
onYearChange={
|
||||||
|
isReplayPreviewMode
|
||||||
|
? replayPreview.setTimelineYear
|
||||||
|
: handleTimelineYearChange
|
||||||
|
}
|
||||||
|
isLoading={false}
|
||||||
|
disabled={false}
|
||||||
|
statusText={null}
|
||||||
|
filterEnabled={activeTimelineFilterEnabled}
|
||||||
|
onFilterEnabledChange={
|
||||||
|
isReplayPreviewMode
|
||||||
|
? replayPreview.setTimelineFilterEnabled
|
||||||
|
: setTimelineFilterEnabled
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
{!isReplayEditMode && !isReplayPreviewMode ? (
|
{!isReplayEditMode && !isReplayPreviewMode ? (
|
||||||
<>
|
<>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
title="Resize right panel"
|
title="Resize right panel"
|
||||||
onDrag={(deltaX) => {
|
onDrag={(deltaX) => {
|
||||||
// dragging handle (between map and right panel): moving right increases right panel width
|
|
||||||
setRightPanelWidth((prev) => clampNumber(prev - deltaX, 260, 720));
|
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:
|
Các type đang được register:
|
||||||
|
|
||||||
- `defense_line`
|
- `defense_line`
|
||||||
- `attack_route`
|
- `military_route`
|
||||||
- `retreat_route`
|
- `retreat_route`
|
||||||
- `invasion_route`
|
|
||||||
- `migration_route`
|
- `migration_route`
|
||||||
- `refugee_route`
|
|
||||||
- `trade_route`
|
- `trade_route`
|
||||||
- `shipping_route`
|
|
||||||
- `country`
|
- `country`
|
||||||
- `state`
|
- `state`
|
||||||
- `empire`
|
|
||||||
- `kingdom`
|
|
||||||
- `faction`
|
- `faction`
|
||||||
- `war`
|
|
||||||
- `battle`
|
- `battle`
|
||||||
- `civilization`
|
|
||||||
- `rebellion_zone`
|
- `rebellion_zone`
|
||||||
- `person_deathplace`
|
- `person_event`
|
||||||
- `person_birthplace`
|
|
||||||
- `person_activity`
|
|
||||||
- `temple`
|
- `temple`
|
||||||
- `capital`
|
- `capital`
|
||||||
- `city`
|
- `city`
|
||||||
- `fortress`
|
- `fortification`
|
||||||
- `castle`
|
|
||||||
- `ruin`
|
- `ruin`
|
||||||
- `port`
|
- `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.
|
`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;
|
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();
|
const geometryChanges = options.editor.buildPayload();
|
||||||
state.setIsSaving(true);
|
state.setIsSaving(true);
|
||||||
@@ -221,14 +214,7 @@ export function useProjectCommands(options: Options) {
|
|||||||
return;
|
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.setIsSubmitting(true);
|
||||||
state.setEntityStatus(null);
|
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 {
|
function toEditorSessionSnapshot(snapshot: EditorSnapshot): EditorSnapshot {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export function initCircle(
|
|||||||
|
|
||||||
// Xóa dữ liệu preview circle trên map.
|
// Xóa dữ liệu preview circle trên map.
|
||||||
const clearPreview = () => {
|
const clearPreview = () => {
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
(map.getSource("draw-circle-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
(map.getSource("draw-circle-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
||||||
EMPTY_PREVIEW
|
EMPTY_PREVIEW
|
||||||
);
|
);
|
||||||
@@ -32,7 +33,7 @@ export function initCircle(
|
|||||||
const releaseDragPan = () => {
|
const releaseDragPan = () => {
|
||||||
if (!dragPanDisabledByCircle) return;
|
if (!dragPanDisabledByCircle) return;
|
||||||
dragPanDisabledByCircle = false;
|
dragPanDisabledByCircle = false;
|
||||||
if (!map.dragPan.isEnabled()) {
|
if (map.isStyleLoaded() && !map.dragPan.isEnabled()) {
|
||||||
map.dragPan.enable();
|
map.dragPan.enable();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -53,6 +54,7 @@ export function initCircle(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
const ring = buildCircleRing(center, radiusMeters, CIRCLE_SEGMENTS);
|
const ring = buildCircleRing(center, radiusMeters, CIRCLE_SEGMENTS);
|
||||||
(map.getSource("draw-circle-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
(map.getSource("draw-circle-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
@@ -91,7 +93,7 @@ export function initCircle(
|
|||||||
const onMouseMove = (e: maplibregl.MapMouseEvent) => {
|
const onMouseMove = (e: maplibregl.MapMouseEvent) => {
|
||||||
const canvas = map.getCanvas();
|
const canvas = map.getCanvas();
|
||||||
if (getMode() !== "add-circle") {
|
if (getMode() !== "add-circle") {
|
||||||
if (canvas.style.cursor === "crosshair") {
|
if (canvas && canvas.style.cursor === "crosshair") {
|
||||||
canvas.style.cursor = "";
|
canvas.style.cursor = "";
|
||||||
}
|
}
|
||||||
if (isDragging) {
|
if (isDragging) {
|
||||||
@@ -100,7 +102,9 @@ export function initCircle(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.style.cursor = "crosshair";
|
if (canvas) {
|
||||||
|
canvas.style.cursor = "crosshair";
|
||||||
|
}
|
||||||
if (!isDragging || !center) return;
|
if (!isDragging || !center) return;
|
||||||
|
|
||||||
radiusMeters = distanceMeters(center, [e.lngLat.lng, e.lngLat.lat]);
|
radiusMeters = distanceMeters(center, [e.lngLat.lng, e.lngLat.lat]);
|
||||||
@@ -150,13 +154,20 @@ export function initCircle(
|
|||||||
document.addEventListener("keydown", onKeyDown);
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
map.off("mousedown", onMouseDown);
|
try {
|
||||||
map.off("mousemove", onMouseMove);
|
map.off("mousedown", onMouseDown);
|
||||||
map.off("mouseup", onMouseUp);
|
map.off("mousemove", onMouseMove);
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
map.off("mouseup", onMouseUp);
|
||||||
resetDrawingState();
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
if (map.getCanvas().style.cursor === "crosshair") {
|
resetDrawingState();
|
||||||
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][] = [];
|
let coords: [number, number][] = [];
|
||||||
|
|
||||||
const clearPreview = () => {
|
const clearPreview = () => {
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
(map.getSource("draw-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
(map.getSource("draw-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: [],
|
features: [],
|
||||||
@@ -39,6 +40,7 @@ export function initDrawing(
|
|||||||
function update(c: [number, number][]) {
|
function update(c: [number, number][]) {
|
||||||
const closed = closePolygon(c);
|
const closed = closePolygon(c);
|
||||||
|
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
(map.getSource("draw-preview") as maplibregl.GeoJSONSource)?.setData({
|
(map.getSource("draw-preview") as maplibregl.GeoJSONSource)?.setData({
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: [
|
features: [
|
||||||
@@ -130,12 +132,18 @@ export function initDrawing(
|
|||||||
document.addEventListener("keydown", onKeyDown);
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
map.boxZoom.enable();
|
try {
|
||||||
map.doubleClickZoom.enable();
|
if (map.isStyleLoaded()) {
|
||||||
map.off("click", onClick);
|
map.boxZoom.enable();
|
||||||
map.off("mousemove", onMove);
|
map.doubleClickZoom.enable();
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
}
|
||||||
cancelDrawing();
|
map.off("click", onClick);
|
||||||
|
map.off("mousemove", onMove);
|
||||||
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
|
cancelDrawing();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function createEditingEngine(options: {
|
|||||||
setDeleteVertexMode(false);
|
setDeleteVertexMode(false);
|
||||||
hideContextMenu();
|
hideContextMenu();
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
if (!map) return;
|
if (!map || !map.isStyleLoaded()) return;
|
||||||
const empty: GeoJSON.FeatureCollection = { type: "FeatureCollection", features: [] };
|
const empty: GeoJSON.FeatureCollection = { type: "FeatureCollection", features: [] };
|
||||||
(map.getSource("edit-shape") as maplibregl.GeoJSONSource | undefined)?.setData(empty);
|
(map.getSource("edit-shape") as maplibregl.GeoJSONSource | undefined)?.setData(empty);
|
||||||
(map.getSource("edit-handles") 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 updateEditSources = () => {
|
||||||
const editing = editingRef.current;
|
const editing = editingRef.current;
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
if (!editing || !map) return;
|
if (!editing || !map || !map.isStyleLoaded()) return;
|
||||||
|
|
||||||
let shape: GeoJSON.FeatureCollection<GeoJSON.Polygon>;
|
let shape: GeoJSON.FeatureCollection<GeoJSON.Polygon>;
|
||||||
let handles: GeoJSON.FeatureCollection<GeoJSON.Point>;
|
let handles: GeoJSON.FeatureCollection<GeoJSON.Point>;
|
||||||
@@ -143,7 +143,7 @@ export function createEditingEngine(options: {
|
|||||||
const setDeleteVertexMode = (enabled: boolean) => {
|
const setDeleteVertexMode = (enabled: boolean) => {
|
||||||
deleteVertexModeRef.current = enabled;
|
deleteVertexModeRef.current = enabled;
|
||||||
const map = mapRef.current;
|
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-color", enabled ? "#ef4444" : "#f97316");
|
||||||
map.setPaintProperty("edit-handles-circle", "circle-stroke-color", enabled ? "#7f1d1d" : "#0f172a");
|
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.
|
// Xóa dữ liệu preview line.
|
||||||
const clearPreview = () => {
|
const clearPreview = () => {
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
(map.getSource("draw-line-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
(map.getSource("draw-line-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
||||||
EMPTY_PREVIEW
|
EMPTY_PREVIEW
|
||||||
);
|
);
|
||||||
@@ -36,6 +37,7 @@ export function initLine(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
(map.getSource("draw-line-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
(map.getSource("draw-line-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: [
|
features: [
|
||||||
@@ -91,13 +93,15 @@ export function initLine(
|
|||||||
if (coords.length) {
|
if (coords.length) {
|
||||||
cancelLine();
|
cancelLine();
|
||||||
}
|
}
|
||||||
if (canvas.style.cursor === "crosshair") {
|
if (canvas && canvas.style.cursor === "crosshair") {
|
||||||
canvas.style.cursor = "";
|
canvas.style.cursor = "";
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.style.cursor = "crosshair";
|
if (canvas) {
|
||||||
|
canvas.style.cursor = "crosshair";
|
||||||
|
}
|
||||||
if (coords.length === 0) return;
|
if (coords.length === 0) return;
|
||||||
|
|
||||||
const lngLat = e.originalEvent.shiftKey || e.originalEvent.altKey
|
const lngLat = e.originalEvent.shiftKey || e.originalEvent.altKey
|
||||||
@@ -133,12 +137,19 @@ export function initLine(
|
|||||||
document.addEventListener("keydown", onKeyDown);
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
map.off("click", onClick);
|
try {
|
||||||
map.off("mousemove", onMove);
|
map.off("click", onClick);
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
map.off("mousemove", onMove);
|
||||||
cancelLine();
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
if (map.getCanvas().style.cursor === "crosshair") {
|
cancelLine();
|
||||||
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.
|
// Xóa dữ liệu preview path.
|
||||||
const clearPreview = () => {
|
const clearPreview = () => {
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
(map.getSource("draw-path-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
(map.getSource("draw-path-preview") as maplibregl.GeoJSONSource | undefined)?.setData(
|
||||||
EMPTY_PREVIEW
|
EMPTY_PREVIEW
|
||||||
);
|
);
|
||||||
@@ -30,6 +31,7 @@ export function initPath(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
(map.getSource("draw-path-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
(map.getSource("draw-path-preview") as maplibregl.GeoJSONSource | undefined)?.setData({
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: [
|
features: [
|
||||||
@@ -92,13 +94,15 @@ export function initPath(
|
|||||||
if (coords.length) {
|
if (coords.length) {
|
||||||
cancelPath();
|
cancelPath();
|
||||||
}
|
}
|
||||||
if (canvas.style.cursor === "crosshair") {
|
if (canvas && canvas.style.cursor === "crosshair") {
|
||||||
canvas.style.cursor = "";
|
canvas.style.cursor = "";
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.style.cursor = "crosshair";
|
if (canvas) {
|
||||||
|
canvas.style.cursor = "crosshair";
|
||||||
|
}
|
||||||
if (coords.length === 0) return;
|
if (coords.length === 0) return;
|
||||||
|
|
||||||
const lngLat = e.originalEvent.shiftKey || e.originalEvent.altKey
|
const lngLat = e.originalEvent.shiftKey || e.originalEvent.altKey
|
||||||
@@ -134,12 +138,19 @@ export function initPath(
|
|||||||
document.addEventListener("keydown", onKeyDown);
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
map.off("click", onClick);
|
try {
|
||||||
map.off("mousemove", onMove);
|
map.off("click", onClick);
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
map.off("mousemove", onMove);
|
||||||
cancelPath();
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
if (map.getCanvas().style.cursor === "crosshair") {
|
cancelPath();
|
||||||
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);
|
map.on("mousemove", onMove);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
map.off("click", onClick);
|
try {
|
||||||
map.off("mousemove", onMove);
|
map.off("click", onClick);
|
||||||
if (map.getCanvas().style.cursor === "crosshair") {
|
map.off("mousemove", onMove);
|
||||||
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) {
|
function setSelectionStateForId(id: string | number, selected: boolean) {
|
||||||
|
if (!map.isStyleLoaded()) return;
|
||||||
for (const source of FEATURE_STATE_SOURCES) {
|
for (const source of FEATURE_STATE_SOURCES) {
|
||||||
if (!map.getSource(source)) continue;
|
if (!map.getSource(source)) continue;
|
||||||
map.setFeatureState({ source, id }, { selected });
|
map.setFeatureState({ source, id }, { selected });
|
||||||
@@ -194,13 +195,19 @@ export function initSelect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
map.off("click", onClick);
|
try {
|
||||||
map.off("mousemove", onMove);
|
map.off("click", onClick);
|
||||||
if (hasContextActions) {
|
map.off("mousemove", onMove);
|
||||||
map.off("contextmenu", onRightClick);
|
if (hasContextActions) {
|
||||||
|
map.off("contextmenu", onRightClick);
|
||||||
|
}
|
||||||
|
if (map.isStyleLoaded()) {
|
||||||
|
clearSelection(false);
|
||||||
|
}
|
||||||
|
hideContextMenu();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
}
|
}
|
||||||
clearSelection(false);
|
|
||||||
hideContextMenu();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,33 +1,28 @@
|
|||||||
[
|
[
|
||||||
{ "type_key": "defense_line", "geo_type_code": 1 },
|
{ "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": "retreat_route", "geo_type_code": 3 },
|
||||||
{ "type_key": "invasion_route", "geo_type_code": 4 },
|
|
||||||
{ "type_key": "migration_route", "geo_type_code": 5 },
|
{ "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": "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": "country", "geo_type_code": 9, "fixed": true },
|
||||||
{ "type_key": "state", "geo_type_code": 10 },
|
{ "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": "faction", "geo_type_code": 28 },
|
||||||
|
|
||||||
{ "type_key": "war", "geo_type_code": 13 },
|
|
||||||
{ "type_key": "battle", "geo_type_code": 14 },
|
{ "type_key": "battle", "geo_type_code": 14 },
|
||||||
{ "type_key": "civilization", "geo_type_code": 15 },
|
|
||||||
{ "type_key": "rebellion_zone", "geo_type_code": 16 },
|
{ "type_key": "rebellion_zone", "geo_type_code": 16 },
|
||||||
|
|
||||||
{ "type_key": "person_deathplace", "geo_type_code": 17 },
|
{ "type_key": "person_event", "geo_type_code": 17 },
|
||||||
{ "type_key": "person_birthplace", "geo_type_code": 18 },
|
{ "type_key": "person_event", "geo_type_code": 18 },
|
||||||
{ "type_key": "person_activity", "geo_type_code": 19 },
|
{ "type_key": "person_event", "geo_type_code": 19 },
|
||||||
|
|
||||||
{ "type_key": "temple", "geo_type_code": 20 },
|
{ "type_key": "temple", "geo_type_code": 20 },
|
||||||
{ "type_key": "capital", "geo_type_code": 21 },
|
{ "type_key": "capital", "geo_type_code": 21 },
|
||||||
{ "type_key": "city", "geo_type_code": 22 },
|
{ "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": "ruin", "geo_type_code": 25 },
|
||||||
{ "type_key": "port", "geo_type_code": 26 },
|
{ "type_key": "port", "geo_type_code": 26 }
|
||||||
{ "type_key": "bridge", "geo_type_code": 27 }
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -40,6 +40,15 @@ export function geoTypeCodeToTypeKey(code: number | null | undefined): string |
|
|||||||
return KEY_BY_CODE.get(Math.trunc(code)) ?? null;
|
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 {
|
export function normalizeGeoTypeKey(value: unknown): string | null {
|
||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
return geoTypeCodeToTypeKey(value);
|
return geoTypeCodeToTypeKey(value);
|
||||||
@@ -54,5 +63,14 @@ export function normalizeGeoTypeKey(value: unknown): string | null {
|
|||||||
return geoTypeCodeToTypeKey(Number(normalized));
|
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;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,37 +62,25 @@ const RAW_GEOMETRY_TYPE_OPTIONS: Array<{
|
|||||||
geometryPreset: GeometryPreset;
|
geometryPreset: GeometryPreset;
|
||||||
}> = [
|
}> = [
|
||||||
{ value: "defense_line", label: "Defense Line", groupId: "line", geometryPreset: "line" },
|
{ value: "defense_line", label: "Defense Line", groupId: "line", geometryPreset: "line" },
|
||||||
|
{ value: "military_route", label: "Military Route", groupId: "line", geometryPreset: "line" },
|
||||||
{ value: "attack_route", label: "Attack Route", groupId: "line", geometryPreset: "line" },
|
|
||||||
{ value: "retreat_route", label: "Retreat 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: "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: "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: "country", label: "Country", groupId: "polygon", geometryPreset: "polygon" },
|
||||||
{ value: "state", label: "State", 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: "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: "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: "rebellion_zone", label: "Rebellion Zone", groupId: "circle", geometryPreset: "circle-area" },
|
||||||
|
|
||||||
{ value: "person_deathplace", label: "Person Deathplace", groupId: "point", geometryPreset: "point" },
|
{ value: "person_event", label: "Person Event", 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: "temple", label: "Temple", groupId: "point", geometryPreset: "point" },
|
{ value: "temple", label: "Temple", groupId: "point", geometryPreset: "point" },
|
||||||
{ value: "capital", label: "Capital", groupId: "point", geometryPreset: "point" },
|
{ value: "capital", label: "Capital", groupId: "point", geometryPreset: "point" },
|
||||||
{ value: "city", label: "City", groupId: "point", geometryPreset: "point" },
|
{ value: "city", label: "City", groupId: "point", geometryPreset: "point" },
|
||||||
{ value: "fortress", label: "Fortress", groupId: "point", geometryPreset: "point" },
|
{ value: "fortification", label: "Fortification", groupId: "point", geometryPreset: "point" },
|
||||||
{ value: "castle", label: "Castle", groupId: "point", geometryPreset: "point" },
|
|
||||||
{ value: "ruin", label: "Ruin", groupId: "point", geometryPreset: "point" },
|
{ value: "ruin", label: "Ruin", groupId: "point", geometryPreset: "point" },
|
||||||
{ value: "port", label: "Port", 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) => ({
|
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";
|
export { ensurePointGeotypeIcons } from "./shared/pointStyle";
|
||||||
|
|
||||||
import { getDefenseLineLayers } from "./geotypes/defense_line";
|
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 { getRetreatRouteLayers } from "./geotypes/retreat_route";
|
||||||
import { getInvasionRouteLayers } from "./geotypes/invasion_route";
|
|
||||||
import { getMigrationRouteLayers } from "./geotypes/migration_route";
|
import { getMigrationRouteLayers } from "./geotypes/migration_route";
|
||||||
import { getRefugeeRouteLayers } from "./geotypes/refugee_route";
|
|
||||||
import { getTradeRouteLayers } from "./geotypes/trade_route";
|
import { getTradeRouteLayers } from "./geotypes/trade_route";
|
||||||
import { getShippingRouteLayers } from "./geotypes/shipping_route";
|
|
||||||
import { getCountryLayers } from "./geotypes/country";
|
import { getCountryLayers } from "./geotypes/country";
|
||||||
import { getStateLayers } from "./geotypes/state";
|
import { getStateLayers } from "./geotypes/state";
|
||||||
import { getEmpireLayers } from "./geotypes/empire";
|
|
||||||
import { getKingdomLayers } from "./geotypes/kingdom";
|
|
||||||
import { getFactionLayers } from "./geotypes/faction";
|
import { getFactionLayers } from "./geotypes/faction";
|
||||||
import { getWarLayers } from "./geotypes/war";
|
|
||||||
import { getBattleLayers } from "./geotypes/battle";
|
import { getBattleLayers } from "./geotypes/battle";
|
||||||
import { getCivilizationLayers } from "./geotypes/civilization";
|
|
||||||
import { getRebellionZoneLayers } from "./geotypes/rebellion_zone";
|
import { getRebellionZoneLayers } from "./geotypes/rebellion_zone";
|
||||||
import { getPersonDeathplaceLayers } from "./geotypes/person_deathplace";
|
import { getPersonEventLayers } from "./geotypes/person_event";
|
||||||
import { getPersonBirthplaceLayers } from "./geotypes/person_birthplace";
|
|
||||||
import { getPersonActivityLayers } from "./geotypes/person_activity";
|
|
||||||
import { getTempleLayers } from "./geotypes/temple";
|
import { getTempleLayers } from "./geotypes/temple";
|
||||||
import { getCapitalLayers } from "./geotypes/capital";
|
import { getCapitalLayers } from "./geotypes/capital";
|
||||||
import { getCityLayers } from "./geotypes/city";
|
import { getCityLayers } from "./geotypes/city";
|
||||||
import { getFortressLayers } from "./geotypes/fortress";
|
import { getFortificationLayers } from "./geotypes/fortification";
|
||||||
import { getCastleLayers } from "./geotypes/castle";
|
|
||||||
import { getRuinLayers } from "./geotypes/ruin";
|
import { getRuinLayers } from "./geotypes/ruin";
|
||||||
import { getPortLayers } from "./geotypes/port";
|
import { getPortLayers } from "./geotypes/port";
|
||||||
import { getBridgeLayers } from "./geotypes/bridge";
|
|
||||||
import { getLineLabelLayers } from "./shared/lineLabels";
|
import { getLineLabelLayers } from "./shared/lineLabels";
|
||||||
import { getPolygonLabelLayers } from "./shared/polygonLabels";
|
import { getPolygonLabelLayers } from "./shared/polygonLabels";
|
||||||
|
|
||||||
@@ -39,32 +28,21 @@ export function getAllGeotypeLayers(sourceId: string, pathArrowSourceId?: string
|
|||||||
return [
|
return [
|
||||||
...getCountryLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getCountryLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getStateLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getStateLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getEmpireLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getKingdomLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getFactionLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getFactionLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getWarLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getBattleLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getBattleLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getCivilizationLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getRebellionZoneLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getRebellionZoneLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getDefenseLineLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getDefenseLineLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getAttackRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getMilitaryRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getRetreatRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getRetreatRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getInvasionRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getMigrationRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getMigrationRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getRefugeeRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getTradeRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getTradeRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getShippingRouteLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getPersonEventLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getPersonDeathplaceLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getPersonBirthplaceLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getPersonActivityLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getTempleLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getTempleLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getCapitalLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getCapitalLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getCityLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getCityLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getFortressLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getFortificationLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getCastleLayers(sourceId, pathArrowSourceId, pointSourceId),
|
|
||||||
...getRuinLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getRuinLayers(sourceId, pathArrowSourceId, pointSourceId),
|
||||||
...getPortLayers(sourceId, pathArrowSourceId, pointSourceId),
|
...getPortLayers(sourceId, pathArrowSourceId, pointSourceId)
|
||||||
...getBridgeLayers(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 { LayerSpecification } from "maplibre-gl";
|
||||||
import { buildLineGeotypeLayers } from "../shared/styleBuilders";
|
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;
|
void pointSourceId;
|
||||||
return buildLineGeotypeLayers(sourceId, pathArrowSourceId, {
|
return buildLineGeotypeLayers(sourceId, pathArrowSourceId, {
|
||||||
typeId: "attack_route",
|
typeId: "military_route",
|
||||||
color: "#ef4444",
|
color: "#ef4444",
|
||||||
strokeColor: "#7f1d1d",
|
strokeColor: "#7f1d1d",
|
||||||
width: { z1: 2.6, z4: 3.8, z6: 5 },
|
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";
|
import { MAP_EMPHASIS_TEXT_FONT_STACK } from "./textFonts";
|
||||||
|
|
||||||
export const POINT_GEOTYPE_IDS = [
|
export const POINT_GEOTYPE_IDS = [
|
||||||
"person_birthplace",
|
"person_event",
|
||||||
"person_deathplace",
|
|
||||||
"person_activity",
|
|
||||||
"temple",
|
"temple",
|
||||||
"capital",
|
"capital",
|
||||||
"city",
|
"city",
|
||||||
"fortress",
|
"fortification",
|
||||||
"castle",
|
|
||||||
"ruin",
|
"ruin",
|
||||||
"port",
|
"port",
|
||||||
"bridge",
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type PointGeotypeId = (typeof POINT_GEOTYPE_IDS)[number];
|
export type PointGeotypeId = (typeof POINT_GEOTYPE_IDS)[number];
|
||||||
|
|
||||||
export const POINT_GEOTYPE_ICON_PATHS: Partial<Record<PointGeotypeId, string>> = {
|
export const POINT_GEOTYPE_ICON_PATHS: Partial<Record<PointGeotypeId, string>> = {
|
||||||
person_birthplace: "/images/mapIcon/point/house.png",
|
person_event: "/images/mapIcon/point/flag.png",
|
||||||
person_deathplace: "/images/mapIcon/point/tombstone.png",
|
|
||||||
person_activity: "/images/mapIcon/point/flag.png",
|
|
||||||
temple: "/images/mapIcon/point/temple.png",
|
temple: "/images/mapIcon/point/temple.png",
|
||||||
capital: "/images/mapIcon/point/capital.png",
|
capital: "/images/mapIcon/point/capital.png",
|
||||||
city: "/images/mapIcon/point/city.png",
|
city: "/images/mapIcon/point/city.png",
|
||||||
fortress: "/images/mapIcon/point/fortress.png",
|
fortification: "/images/mapIcon/point/castle.png",
|
||||||
castle: "/images/mapIcon/point/castle.png",
|
|
||||||
ruin: "/images/mapIcon/point/ruin.png",
|
ruin: "/images/mapIcon/point/ruin.png",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,21 +50,7 @@ const POINT_GEOMETRY_FILTER: maplibregl.ExpressionSpecification = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const POINT_STYLE_CONFIG: Record<PointGeotypeId, PointStyleConfig> = {
|
const POINT_STYLE_CONFIG: Record<PointGeotypeId, PointStyleConfig> = {
|
||||||
person_birthplace: {
|
person_event: {
|
||||||
fill: "#22c55e",
|
|
||||||
rim: "#166534",
|
|
||||||
iconScale: 1,
|
|
||||||
haloRadius: 15,
|
|
||||||
drawGlyph: drawHouseGlyph,
|
|
||||||
},
|
|
||||||
person_deathplace: {
|
|
||||||
fill: "#b91c1c",
|
|
||||||
rim: "#450a0a",
|
|
||||||
iconScale: 1,
|
|
||||||
haloRadius: 15,
|
|
||||||
drawGlyph: drawMemorialGlyph,
|
|
||||||
},
|
|
||||||
person_activity: {
|
|
||||||
fill: "#f97316",
|
fill: "#f97316",
|
||||||
rim: "#9a3412",
|
rim: "#9a3412",
|
||||||
iconScale: 0.98,
|
iconScale: 0.98,
|
||||||
@@ -99,14 +78,7 @@ const POINT_STYLE_CONFIG: Record<PointGeotypeId, PointStyleConfig> = {
|
|||||||
haloRadius: 15,
|
haloRadius: 15,
|
||||||
drawGlyph: drawCityGlyph,
|
drawGlyph: drawCityGlyph,
|
||||||
},
|
},
|
||||||
fortress: {
|
fortification: {
|
||||||
fill: "#64748b",
|
|
||||||
rim: "#334155",
|
|
||||||
iconScale: 1.04,
|
|
||||||
haloRadius: 16,
|
|
||||||
drawGlyph: drawShieldGlyph,
|
|
||||||
},
|
|
||||||
castle: {
|
|
||||||
fill: "#7c3aed",
|
fill: "#7c3aed",
|
||||||
rim: "#4c1d95",
|
rim: "#4c1d95",
|
||||||
iconScale: 1.04,
|
iconScale: 1.04,
|
||||||
@@ -127,13 +99,6 @@ const POINT_STYLE_CONFIG: Record<PointGeotypeId, PointStyleConfig> = {
|
|||||||
haloRadius: 15,
|
haloRadius: 15,
|
||||||
drawGlyph: drawAnchorGlyph,
|
drawGlyph: drawAnchorGlyph,
|
||||||
},
|
},
|
||||||
bridge: {
|
|
||||||
fill: "#b45309",
|
|
||||||
rim: "#7c2d12",
|
|
||||||
iconScale: 1,
|
|
||||||
haloRadius: 14,
|
|
||||||
drawGlyph: drawBridgeGlyph,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function buildPointGeotypeLayers(
|
export function buildPointGeotypeLayers(
|
||||||
@@ -320,53 +285,9 @@ function drawGlyphWithOutline(
|
|||||||
ctx.restore();
|
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) {
|
function drawFlagGlyph(ctx: CanvasRenderingContext2D) {
|
||||||
const img = preloadedImages["person_activity"];
|
const img = preloadedImages["person_event"];
|
||||||
if (img && loadedImageKeys.has("person_activity")) {
|
if (img && loadedImageKeys.has("person_event")) {
|
||||||
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
||||||
} else {
|
} else {
|
||||||
ctx.lineWidth = 3.2;
|
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) {
|
function drawCastleGlyph(ctx: CanvasRenderingContext2D) {
|
||||||
const img = preloadedImages["castle"];
|
const img = preloadedImages["fortification"];
|
||||||
if (img && loadedImageKeys.has("castle")) {
|
if (img && loadedImageKeys.has("fortification")) {
|
||||||
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
ctx.drawImage(img, 0, 0, ICON_CANVAS_SIZE, ICON_CANVAS_SIZE);
|
||||||
} else {
|
} else {
|
||||||
ctx.lineWidth = 3;
|
ctx.lineWidth = 3;
|
||||||
|
|||||||
@@ -27,50 +27,34 @@ export const COUNTRY_FILL_COLOR_EXPRESSION: maplibregl.ExpressionSpecification =
|
|||||||
export const POLYGON_FILL_BY_TYPE: Record<string, string> = {
|
export const POLYGON_FILL_BY_TYPE: Record<string, string> = {
|
||||||
country: "#2563eb",
|
country: "#2563eb",
|
||||||
state: "#0ea5e9",
|
state: "#0ea5e9",
|
||||||
empire: "#f59e0b",
|
|
||||||
kingdom: "#d97706",
|
|
||||||
war: "#dc2626",
|
|
||||||
battle: "#f43f5e",
|
battle: "#f43f5e",
|
||||||
civilization: "#14b8a6",
|
|
||||||
rebellion_zone: "#7c3aed",
|
rebellion_zone: "#7c3aed",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POLYGON_STROKE_BY_TYPE: Record<string, string> = {
|
export const POLYGON_STROKE_BY_TYPE: Record<string, string> = {
|
||||||
country: "#1e3a8a",
|
country: "#1e3a8a",
|
||||||
state: "#0c4a6e",
|
state: "#0c4a6e",
|
||||||
empire: "#7c2d12",
|
|
||||||
kingdom: "#9a3412",
|
|
||||||
war: "#7f1d1d",
|
|
||||||
battle: "#9f1239",
|
battle: "#9f1239",
|
||||||
civilization: "#134e4a",
|
|
||||||
rebellion_zone: "#4c1d95",
|
rebellion_zone: "#4c1d95",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POLYGON_OPACITY_BY_TYPE: Record<string, number> = {
|
export const POLYGON_OPACITY_BY_TYPE: Record<string, number> = {
|
||||||
war: 0.3,
|
|
||||||
battle: 0.34,
|
battle: 0.34,
|
||||||
civilization: 0.38,
|
|
||||||
rebellion_zone: 0.32,
|
rebellion_zone: 0.32,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LINE_COLOR_BY_TYPE: Record<string, string> = {
|
export const LINE_COLOR_BY_TYPE: Record<string, string> = {
|
||||||
defense_line: "#f97316",
|
defense_line: "#f97316",
|
||||||
attack_route: "#ef4444",
|
military_route: "#ef4444",
|
||||||
retreat_route: "#94a3b8",
|
retreat_route: "#94a3b8",
|
||||||
invasion_route: "#b91c1c",
|
|
||||||
migration_route: "#0ea5e9",
|
migration_route: "#0ea5e9",
|
||||||
refugee_route: "#06b6d4",
|
|
||||||
trade_route: "#eab308",
|
trade_route: "#eab308",
|
||||||
shipping_route: "#2563eb",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PATH_RENDER_BY_TYPE: Record<string, boolean> = {
|
export const PATH_RENDER_BY_TYPE: Record<string, boolean> = {
|
||||||
attack_route: true,
|
military_route: true,
|
||||||
retreat_route: true,
|
retreat_route: true,
|
||||||
invasion_route: true,
|
|
||||||
migration_route: true,
|
migration_route: true,
|
||||||
refugee_route: true,
|
|
||||||
trade_route: true,
|
trade_route: true,
|
||||||
shipping_route: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user