refactor: pre serve route /
This commit is contained in:
+30
-132
@@ -10,7 +10,7 @@ import TimelineBar from "@/uhm/components/ui/TimelineBar";
|
|||||||
import SelectedGeometryPanel from "@/uhm/components/editor/SelectedGeometryPanel";
|
import SelectedGeometryPanel from "@/uhm/components/editor/SelectedGeometryPanel";
|
||||||
import ReplayTimelineSidebar from "@/uhm/components/editor/ReplayTimelineSidebar";
|
import ReplayTimelineSidebar from "@/uhm/components/editor/ReplayTimelineSidebar";
|
||||||
import ReplayEffectsSidebar from "@/uhm/components/editor/ReplayEffectsSidebar";
|
import ReplayEffectsSidebar from "@/uhm/components/editor/ReplayEffectsSidebar";
|
||||||
import PreviewLayout from "@/uhm/components/preview/PreviewLayout";
|
import PreviewLayout, { type PreviewLayoutHandle } from "@/uhm/components/preview/PreviewLayout";
|
||||||
import WikiSidebarPanel from "@/uhm/components/wiki/WikiSidebarPanel";
|
import WikiSidebarPanel from "@/uhm/components/wiki/WikiSidebarPanel";
|
||||||
import ProjectEntityRefsPanel from "@/uhm/components/editor/ProjectEntityRefsPanel";
|
import ProjectEntityRefsPanel from "@/uhm/components/editor/ProjectEntityRefsPanel";
|
||||||
import EntityWikiBindingsPanel from "@/uhm/components/editor/EntityWikiBindingsPanel";
|
import EntityWikiBindingsPanel from "@/uhm/components/editor/EntityWikiBindingsPanel";
|
||||||
@@ -81,6 +81,10 @@ import {
|
|||||||
normalizeReplaysForCompare,
|
normalizeReplaysForCompare,
|
||||||
normalizeWikisForCompare,
|
normalizeWikisForCompare,
|
||||||
} from "@/uhm/lib/editor/editorPageUtils";
|
} from "@/uhm/lib/editor/editorPageUtils";
|
||||||
|
import {
|
||||||
|
buildEntityLabelContextDraft as buildPreviewEntityLabelContextDraft,
|
||||||
|
buildSnapshotPreviewRelationIndex,
|
||||||
|
} from "@/uhm/lib/preview/relationIndex";
|
||||||
|
|
||||||
const CURRENT_YEAR = new Date().getUTCFullYear();
|
const CURRENT_YEAR = new Date().getUTCFullYear();
|
||||||
const DEFAULT_EDITOR_USER_ID = "local-editor";
|
const DEFAULT_EDITOR_USER_ID = "local-editor";
|
||||||
@@ -99,18 +103,6 @@ type ReplayPreviewSession = {
|
|||||||
mapViewState: ReturnType<MapHandle["getViewState"]>;
|
mapViewState: ReturnType<MapHandle["getViewState"]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PreviewRelationIndex = {
|
|
||||||
entitiesById: Record<string, Entity>;
|
|
||||||
entityGeometriesById: Record<string, FeatureCollection>;
|
|
||||||
entityWikisById: Record<string, Wiki[]>;
|
|
||||||
geometryEntityIds: Record<string, string[]>;
|
|
||||||
wikiEntityIdsById: Record<string, string[]>;
|
|
||||||
wikiEntityIdsBySlug: Record<string, string[]>;
|
|
||||||
wikiById: Record<string, Wiki>;
|
|
||||||
wikiBySlug: Record<string, Wiki>;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<EditorStoreProvider
|
<EditorStoreProvider
|
||||||
@@ -547,7 +539,7 @@ function EditorPageContent() {
|
|||||||
return 420;
|
return 420;
|
||||||
});
|
});
|
||||||
const [isLargeScreen, setIsLargeScreen] = useState(false);
|
const [isLargeScreen, setIsLargeScreen] = useState(false);
|
||||||
const previewLayoutRef = useRef<any>(null);
|
const previewLayoutRef = useRef<PreviewLayoutHandle | null>(null);
|
||||||
|
|
||||||
// Responsive listener for preview sidebar/viewport offsets
|
// Responsive listener for preview sidebar/viewport offsets
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -601,7 +593,7 @@ function EditorPageContent() {
|
|||||||
const [previewWikiCache, setPreviewWikiCache] = useState<Record<string, Wiki>>({});
|
const [previewWikiCache, setPreviewWikiCache] = useState<Record<string, Wiki>>({});
|
||||||
|
|
||||||
const previewRelations = useMemo(() => {
|
const previewRelations = useMemo(() => {
|
||||||
return buildPreviewRelationIndex({
|
return buildSnapshotPreviewRelationIndex({
|
||||||
draft: previewSession?.draft || EMPTY_FEATURE_COLLECTION,
|
draft: previewSession?.draft || EMPTY_FEATURE_COLLECTION,
|
||||||
entities: previewSession?.entities || [],
|
entities: previewSession?.entities || [],
|
||||||
wikis: previewSession?.wikis || [],
|
wikis: previewSession?.wikis || [],
|
||||||
@@ -635,12 +627,12 @@ function EditorPageContent() {
|
|||||||
const activeTimelineYear = isReplayPreviewMode
|
const activeTimelineYear = isReplayPreviewMode
|
||||||
? replayPreviewTimelineYear
|
? replayPreviewTimelineYear
|
||||||
: isViewerPreviewMode
|
: isViewerPreviewMode
|
||||||
? previewSession?.timelineYear ?? timelineDraftYear
|
? replayPreviewTimelineYear
|
||||||
: timelineDraftYear;
|
: timelineDraftYear;
|
||||||
const activeTimelineFilterEnabled = isReplayPreviewMode
|
const activeTimelineFilterEnabled = isReplayPreviewMode
|
||||||
? replayPreviewTimelineFilterEnabled
|
? replayPreviewTimelineFilterEnabled
|
||||||
: isViewerPreviewMode
|
: isViewerPreviewMode
|
||||||
? previewSession?.timelineFilterEnabled ?? timelineFilterEnabled
|
? replayPreviewTimelineFilterEnabled
|
||||||
: timelineFilterEnabled;
|
: timelineFilterEnabled;
|
||||||
|
|
||||||
// Render draft is the only FeatureCollection that decides what appears on the map.
|
// Render draft is the only FeatureCollection that decides what appears on the map.
|
||||||
@@ -817,12 +809,30 @@ function EditorPageContent() {
|
|||||||
|
|
||||||
const activeMapDraft = useMemo(() => {
|
const activeMapDraft = useMemo(() => {
|
||||||
if (isAnyPreviewMode) {
|
if (isAnyPreviewMode) {
|
||||||
return isReplayPreviewMode
|
const previewDraft = isReplayPreviewMode
|
||||||
? replayPreviewDraft
|
? replayPreviewDraft
|
||||||
: (previewSession?.draft || EMPTY_FEATURE_COLLECTION);
|
: (previewSession?.draft || EMPTY_FEATURE_COLLECTION);
|
||||||
|
if (!activeTimelineFilterEnabled) {
|
||||||
|
return previewDraft;
|
||||||
|
}
|
||||||
|
const safeYear = clampYearToFixedRange(Math.trunc(activeTimelineYear));
|
||||||
|
return {
|
||||||
|
...previewDraft,
|
||||||
|
features: previewDraft.features.filter((feature) =>
|
||||||
|
isFeatureVisibleAtYear(feature, safeYear)
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return mapRenderDraft;
|
return mapRenderDraft;
|
||||||
}, [isAnyPreviewMode, isReplayPreviewMode, replayPreviewDraft, previewSession?.draft, mapRenderDraft]);
|
}, [
|
||||||
|
activeTimelineFilterEnabled,
|
||||||
|
activeTimelineYear,
|
||||||
|
isAnyPreviewMode,
|
||||||
|
isReplayPreviewMode,
|
||||||
|
mapRenderDraft,
|
||||||
|
previewSession?.draft,
|
||||||
|
replayPreviewDraft,
|
||||||
|
]);
|
||||||
|
|
||||||
const localFeatureIds = useMemo(() => {
|
const localFeatureIds = useMemo(() => {
|
||||||
const ids = new Set<string | number>();
|
const ids = new Set<string | number>();
|
||||||
@@ -2420,7 +2430,7 @@ function EditorPageContent() {
|
|||||||
const entitiesForLabel = isAnyPreviewMode
|
const entitiesForLabel = isAnyPreviewMode
|
||||||
? previewSession?.entities || []
|
? previewSession?.entities || []
|
||||||
: entities;
|
: entities;
|
||||||
return buildEntityLabelContextDraft(labelContextBaseDraft, entitiesForLabel);
|
return buildPreviewEntityLabelContextDraft(labelContextBaseDraft, entitiesForLabel);
|
||||||
}, [entities, isAnyPreviewMode, labelContextBaseDraft, previewSession?.entities]);
|
}, [entities, isAnyPreviewMode, labelContextBaseDraft, previewSession?.entities]);
|
||||||
|
|
||||||
if (blockedPendingSubmissionId) {
|
if (blockedPendingSubmissionId) {
|
||||||
@@ -2909,118 +2919,6 @@ function readImageAspectRatio(url: string): Promise<number> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPreviewRelationIndex(options: {
|
|
||||||
draft: FeatureCollection;
|
|
||||||
entities: Entity[];
|
|
||||||
wikis: WikiSnapshot[];
|
|
||||||
entityWikiLinks: EntityWikiLinkSnapshot[];
|
|
||||||
wikiCache: Record<string, Wiki>;
|
|
||||||
projectId: string;
|
|
||||||
}): PreviewRelationIndex {
|
|
||||||
const next: PreviewRelationIndex = {
|
|
||||||
entitiesById: {},
|
|
||||||
entityGeometriesById: {},
|
|
||||||
entityWikisById: {},
|
|
||||||
geometryEntityIds: {},
|
|
||||||
wikiEntityIdsById: {},
|
|
||||||
wikiEntityIdsBySlug: {},
|
|
||||||
wikiById: {},
|
|
||||||
wikiBySlug: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const entity of options.entities || []) {
|
|
||||||
const id = String(entity?.id || "").trim();
|
|
||||||
if (!id) continue;
|
|
||||||
next.entitiesById[id] = entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const wikiSnapshot of options.wikis || []) {
|
|
||||||
if (!wikiSnapshot || wikiSnapshot.operation === "delete") continue;
|
|
||||||
const wiki = snapshotWikiToWiki(wikiSnapshot, options.wikiCache, options.projectId);
|
|
||||||
if (!wiki?.id) continue;
|
|
||||||
next.wikiById[wiki.id] = wiki;
|
|
||||||
const slug = String(wiki.slug || "").trim();
|
|
||||||
if (slug) next.wikiBySlug[slug] = wiki;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const feature of options.draft.features || []) {
|
|
||||||
const geometryId = String(feature.properties.id);
|
|
||||||
for (const entityId of normalizeFeatureEntityIds(feature)) {
|
|
||||||
if (!next.entitiesById[entityId]) {
|
|
||||||
next.entitiesById[entityId] = { id: entityId, name: entityId };
|
|
||||||
}
|
|
||||||
pushUniqueString(next.geometryEntityIds, geometryId, entityId);
|
|
||||||
if (!next.entityGeometriesById[entityId]) {
|
|
||||||
next.entityGeometriesById[entityId] = { type: "FeatureCollection", features: [] };
|
|
||||||
}
|
|
||||||
if (!next.entityGeometriesById[entityId].features.some((item) => String(item.properties.id) === geometryId)) {
|
|
||||||
next.entityGeometriesById[entityId].features.push(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const link of options.entityWikiLinks || []) {
|
|
||||||
if (!link || link.operation === "delete") continue;
|
|
||||||
const entityId = String(link.entity_id || "").trim();
|
|
||||||
const wikiId = String(link.wiki_id || "").trim();
|
|
||||||
const entity = next.entitiesById[entityId] || null;
|
|
||||||
const wiki = next.wikiById[wikiId] || null;
|
|
||||||
if (!entity || !wiki) continue;
|
|
||||||
|
|
||||||
if (!next.entityWikisById[entityId]) next.entityWikisById[entityId] = [];
|
|
||||||
if (!next.entityWikisById[entityId].some((item) => item.id === wiki.id)) {
|
|
||||||
next.entityWikisById[entityId].push(wiki);
|
|
||||||
}
|
|
||||||
|
|
||||||
pushUniqueString(next.wikiEntityIdsById, wiki.id, entityId);
|
|
||||||
const slug = String(wiki.slug || "").trim();
|
|
||||||
if (slug) pushUniqueString(next.wikiEntityIdsBySlug, slug, entityId);
|
|
||||||
}
|
|
||||||
|
|
||||||
normalizeRelationArrays(next.geometryEntityIds);
|
|
||||||
normalizeRelationArrays(next.wikiEntityIdsById);
|
|
||||||
normalizeRelationArrays(next.wikiEntityIdsBySlug);
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
function snapshotWikiToWiki(snapshot: WikiSnapshot, wikiCache: Record<string, Wiki>, projectId: string): Wiki {
|
|
||||||
if (typeof snapshot.doc === "string") {
|
|
||||||
return {
|
|
||||||
id: snapshot.id,
|
|
||||||
project_id: projectId,
|
|
||||||
title: snapshot.title,
|
|
||||||
slug: snapshot.slug ?? null,
|
|
||||||
content: snapshot.doc || "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return wikiCache[snapshot.id] || {
|
|
||||||
id: snapshot.id,
|
|
||||||
project_id: projectId,
|
|
||||||
title: snapshot.title,
|
|
||||||
slug: snapshot.slug ?? null,
|
|
||||||
content: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function pushUniqueString(target: Record<string, string[]>, key: string, value: string) {
|
|
||||||
if (!target[key]) {
|
|
||||||
target[key] = [value];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!target[key].includes(value)) {
|
|
||||||
target[key].push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeRelationArrays(target: Record<string, string[]>) {
|
|
||||||
for (const key of Object.keys(target)) {
|
|
||||||
target[key] = Array.from(new Set(target[key]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function isTypingTarget(target: EventTarget | null): boolean {
|
function isTypingTarget(target: EventTarget | null): boolean {
|
||||||
if (!(target instanceof HTMLElement)) return false;
|
if (!(target instanceof HTMLElement)) return false;
|
||||||
const tagName = target.tagName.toLowerCase();
|
const tagName = target.tagName.toLowerCase();
|
||||||
|
|||||||
+20
-103
@@ -25,21 +25,20 @@ import { EMPTY_FEATURE_COLLECTION, WORLD_BBOX } from "@/uhm/lib/map/geo/constant
|
|||||||
import { GEO_TYPE_KEYS } from "@/uhm/lib/map/geo/geoTypeMap";
|
import { GEO_TYPE_KEYS } from "@/uhm/lib/map/geo/geoTypeMap";
|
||||||
import { clampYearToFixedRange, TIMELINE_DEBOUNCE_MS } from "@/uhm/lib/utils/timeline";
|
import { clampYearToFixedRange, TIMELINE_DEBOUNCE_MS } from "@/uhm/lib/utils/timeline";
|
||||||
import type { FeatureCollection } from "@/uhm/types/geo";
|
import type { FeatureCollection } from "@/uhm/types/geo";
|
||||||
|
import {
|
||||||
|
buildEntityLabelContextDraft,
|
||||||
|
buildPublicPreviewRelationIndex,
|
||||||
|
} from "@/uhm/lib/preview/relationIndex";
|
||||||
|
import {
|
||||||
|
EMPTY_PREVIEW_RELATIONS,
|
||||||
|
type PreviewRelationIndex,
|
||||||
|
} from "@/uhm/lib/preview/types";
|
||||||
|
|
||||||
const CURRENT_YEAR = new Date().getUTCFullYear();
|
const CURRENT_YEAR = new Date().getUTCFullYear();
|
||||||
const ENTITY_PAGE_LIMIT = 100;
|
const ENTITY_PAGE_LIMIT = 100;
|
||||||
const WIKI_PAGE_LIMIT = 100;
|
const WIKI_PAGE_LIMIT = 100;
|
||||||
const RELATION_CONCURRENCY = 6;
|
const RELATION_CONCURRENCY = 6;
|
||||||
|
|
||||||
type RelationIndex = {
|
|
||||||
entitiesById: Record<string, Entity>;
|
|
||||||
entityGeometriesById: Record<string, FeatureCollection>;
|
|
||||||
entityWikisById: Record<string, Wiki[]>;
|
|
||||||
geometryEntityIds: Record<string, string[]>;
|
|
||||||
wikiEntityIdsBySlug: Record<string, string[]>;
|
|
||||||
wikiBySlug: Record<string, Wiki>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type LinkEntityPopupState = {
|
type LinkEntityPopupState = {
|
||||||
slug: string;
|
slug: string;
|
||||||
entities: Entity[];
|
entities: Entity[];
|
||||||
@@ -49,15 +48,6 @@ type LinkEntityPopupState = {
|
|||||||
|
|
||||||
type CachedWiki = Wiki & { __fetched?: boolean };
|
type CachedWiki = Wiki & { __fetched?: boolean };
|
||||||
|
|
||||||
const EMPTY_RELATIONS: RelationIndex = {
|
|
||||||
entitiesById: {},
|
|
||||||
entityGeometriesById: {},
|
|
||||||
entityWikisById: {},
|
|
||||||
geometryEntityIds: {},
|
|
||||||
wikiEntityIdsBySlug: {},
|
|
||||||
wikiBySlug: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [data, setData] = useState<FeatureCollection>(EMPTY_FEATURE_COLLECTION);
|
const [data, setData] = useState<FeatureCollection>(EMPTY_FEATURE_COLLECTION);
|
||||||
const [selectedFeatureIds, setSelectedFeatureIds] = useState<(string | number)[]>([]);
|
const [selectedFeatureIds, setSelectedFeatureIds] = useState<(string | number)[]>([]);
|
||||||
@@ -75,7 +65,7 @@ export default function Page() {
|
|||||||
for (const key of GEO_TYPE_KEYS) init[key] = true;
|
for (const key of GEO_TYPE_KEYS) init[key] = true;
|
||||||
return init;
|
return init;
|
||||||
});
|
});
|
||||||
const [relations, setRelations] = useState<RelationIndex>(EMPTY_RELATIONS);
|
const [relations, setRelations] = useState<PreviewRelationIndex>(EMPTY_PREVIEW_RELATIONS);
|
||||||
const [isRelationsLoading, setIsRelationsLoading] = useState(false);
|
const [isRelationsLoading, setIsRelationsLoading] = useState(false);
|
||||||
const [relationsStatus, setRelationsStatus] = useState<string | null>(null);
|
const [relationsStatus, setRelationsStatus] = useState<string | null>(null);
|
||||||
const [relationsProgress, setRelationsProgress] = useState<{ completed: number; total: number }>({
|
const [relationsProgress, setRelationsProgress] = useState<{ completed: number; total: number }>({
|
||||||
@@ -192,18 +182,9 @@ export default function Page() {
|
|||||||
const entities = await fetchAllEntities();
|
const entities = await fetchAllEntities();
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
|
|
||||||
const next: RelationIndex = {
|
const entityGeometriesById: Record<string, FeatureCollection> = {};
|
||||||
entitiesById: {},
|
const entityWikisById: Record<string, Wiki[]> = {};
|
||||||
entityGeometriesById: {},
|
|
||||||
entityWikisById: {},
|
|
||||||
geometryEntityIds: {},
|
|
||||||
wikiEntityIdsBySlug: {},
|
|
||||||
wikiBySlug: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
|
||||||
next.entitiesById[entity.id] = entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
setRelationsProgress({ completed: 0, total: entities.length });
|
setRelationsProgress({ completed: 0, total: entities.length });
|
||||||
|
|
||||||
@@ -214,19 +195,8 @@ export default function Page() {
|
|||||||
]);
|
]);
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
|
|
||||||
next.entityGeometriesById[entity.id] = geometries;
|
entityGeometriesById[entity.id] = geometries;
|
||||||
next.entityWikisById[entity.id] = wikis;
|
entityWikisById[entity.id] = wikis;
|
||||||
|
|
||||||
for (const feature of geometries.features) {
|
|
||||||
pushUniqueString(next.geometryEntityIds, String(feature.properties.id), entity.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const wiki of wikis) {
|
|
||||||
const slug = String(wiki.slug || "").trim();
|
|
||||||
if (!slug.length) continue;
|
|
||||||
next.wikiBySlug[slug] = wiki;
|
|
||||||
pushUniqueString(next.wikiEntityIdsBySlug, slug, entity.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const completed = index + 1;
|
const completed = index + 1;
|
||||||
if (completed === entities.length || completed % 5 === 0) {
|
if (completed === entities.length || completed % 5 === 0) {
|
||||||
@@ -236,8 +206,11 @@ export default function Page() {
|
|||||||
|
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
|
|
||||||
normalizeRelationArrays(next.geometryEntityIds);
|
const next = buildPublicPreviewRelationIndex({
|
||||||
normalizeRelationArrays(next.wikiEntityIdsBySlug);
|
entities,
|
||||||
|
entityGeometriesById,
|
||||||
|
entityWikisById,
|
||||||
|
});
|
||||||
|
|
||||||
setRelations(next);
|
setRelations(next);
|
||||||
setWikiCache((prev) => ({ ...next.wikiBySlug, ...prev }));
|
setWikiCache((prev) => ({ ...next.wikiBySlug, ...prev }));
|
||||||
@@ -275,8 +248,8 @@ export default function Page() {
|
|||||||
? relations.entityGeometriesById[activeEntityId] || EMPTY_FEATURE_COLLECTION
|
? relations.entityGeometriesById[activeEntityId] || EMPTY_FEATURE_COLLECTION
|
||||||
: EMPTY_FEATURE_COLLECTION;
|
: EMPTY_FEATURE_COLLECTION;
|
||||||
const mapLabelContextDraft = useMemo(
|
const mapLabelContextDraft = useMemo(
|
||||||
() => buildEntityLabelContextDraft(data, relations.geometryEntityIds, relations.entitiesById),
|
() => buildEntityLabelContextDraft(data, relations),
|
||||||
[data, relations.entitiesById, relations.geometryEntityIds]
|
[data, relations]
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeWiki = useMemo(() => {
|
const activeWiki = useMemo(() => {
|
||||||
@@ -937,62 +910,6 @@ async function mapWithConcurrency<T>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushUniqueString(target: Record<string, string[]>, key: string, value: string) {
|
|
||||||
if (!target[key]) {
|
|
||||||
target[key] = [value];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!target[key].includes(value)) {
|
|
||||||
target[key].push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeRelationArrays(target: Record<string, string[]>) {
|
|
||||||
for (const key of Object.keys(target)) {
|
|
||||||
target[key] = Array.from(new Set(target[key]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildEntityLabelContextDraft(
|
|
||||||
draft: FeatureCollection,
|
|
||||||
geometryEntityIds: Record<string, string[]>,
|
|
||||||
entitiesById: Record<string, Entity>
|
|
||||||
): FeatureCollection {
|
|
||||||
if (!draft.features.length) return draft;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...draft,
|
|
||||||
features: draft.features.map((feature) => {
|
|
||||||
const entityIds = geometryEntityIds[String(feature.properties.id)] || [];
|
|
||||||
if (!entityIds.length) return feature;
|
|
||||||
|
|
||||||
const candidates = entityIds.map((id) => {
|
|
||||||
const entity = entitiesById[id] || null;
|
|
||||||
const name = String(entity?.name || id).trim();
|
|
||||||
if (!name) return null;
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
time_start: entity?.time_start ?? null,
|
|
||||||
time_end: entity?.time_end ?? null,
|
|
||||||
};
|
|
||||||
}).filter((candidate) => candidate !== null);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...feature,
|
|
||||||
properties: {
|
|
||||||
...feature.properties,
|
|
||||||
entity_id: entityIds[0] || null,
|
|
||||||
entity_ids: entityIds,
|
|
||||||
entity_name: candidates[0]?.name || null,
|
|
||||||
entity_names: candidates.map((candidate) => candidate.name),
|
|
||||||
entity_label_candidates: candidates,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function clampNumber(value: number, min: number, max: number): number {
|
function clampNumber(value: number, min: number, max: number): number {
|
||||||
if (!Number.isFinite(value)) return min;
|
if (!Number.isFinite(value)) return min;
|
||||||
if (value < min) return min;
|
if (value < min) return min;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "react";
|
||||||
|
import type { RefObject, Dispatch, SetStateAction } from "react";
|
||||||
import { type MapFeaturePayload, type MapHandle } from "@/uhm/components/Map";
|
import { type MapFeaturePayload, type MapHandle } from "@/uhm/components/Map";
|
||||||
|
import type { MapHoverPopupContent } from "@/uhm/components/map/useMapHoverPopup";
|
||||||
import PresentPlaceSearch, { type HistoricalGeometryFocusPayload, type PresentPlaceSelection } from "@/uhm/components/editor/PresentPlaceSearch";
|
import PresentPlaceSearch, { type HistoricalGeometryFocusPayload, type PresentPlaceSelection } from "@/uhm/components/editor/PresentPlaceSearch";
|
||||||
import ReplayPreviewOverlay from "@/uhm/components/editor/ReplayPreviewOverlay";
|
import ReplayPreviewOverlay from "@/uhm/components/editor/ReplayPreviewOverlay";
|
||||||
import ReplayPreviewLayerPanel from "@/uhm/components/editor/ReplayPreviewLayerPanel";
|
import ReplayPreviewLayerPanel from "@/uhm/components/editor/ReplayPreviewLayerPanel";
|
||||||
@@ -10,14 +12,15 @@ import TimelineBar from "@/uhm/components/ui/TimelineBar";
|
|||||||
import RelatedEntityPopup from "./RelatedEntityPopup";
|
import RelatedEntityPopup from "./RelatedEntityPopup";
|
||||||
import PinnedWikiPopup from "./PinnedWikiPopup";
|
import PinnedWikiPopup from "./PinnedWikiPopup";
|
||||||
|
|
||||||
import { type Wiki } from "@/uhm/api/wikis";
|
import { fetchWikiById, type Wiki } from "@/uhm/api/wikis";
|
||||||
import type { Entity } from "@/uhm/api/entities";
|
import type { Entity } from "@/uhm/api/entities";
|
||||||
import type { FeatureCollection } from "@/uhm/types/geo";
|
import type { FeatureCollection } from "@/uhm/types/geo";
|
||||||
import type { BattleReplay } from "@/uhm/types/projects";
|
import type { BattleReplay, EntityWikiLinkSnapshot } from "@/uhm/types/projects";
|
||||||
import type { WikiSnapshot } from "@/uhm/types/wiki";
|
import type { WikiSnapshot } from "@/uhm/types/wiki";
|
||||||
import { type BackgroundLayerVisibility } from "@/uhm/lib/map/styles/backgroundLayers";
|
import { type BackgroundLayerVisibility } from "@/uhm/lib/map/styles/backgroundLayers";
|
||||||
import { EMPTY_FEATURE_COLLECTION } from "@/uhm/lib/map/geo/constants";
|
|
||||||
import { normalizeFeatureEntityIds } from "@/uhm/lib/editor/snapshot/editorSnapshot";
|
import { normalizeFeatureEntityIds } from "@/uhm/lib/editor/snapshot/editorSnapshot";
|
||||||
|
import type { PreviewRelationIndex } from "@/uhm/lib/preview/types";
|
||||||
|
import type { Feature } from "@/uhm/lib/editor/state/useEditorState";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -28,36 +31,43 @@ type Props = {
|
|||||||
replays: BattleReplay[];
|
replays: BattleReplay[];
|
||||||
entities: Entity[];
|
entities: Entity[];
|
||||||
wikis: WikiSnapshot[];
|
wikis: WikiSnapshot[];
|
||||||
|
entityWikiLinks?: EntityWikiLinkSnapshot[];
|
||||||
backgroundVisibility: BackgroundLayerVisibility;
|
backgroundVisibility: BackgroundLayerVisibility;
|
||||||
onBackgroundVisibilityChange: (vis: BackgroundLayerVisibility) => void;
|
onBackgroundVisibilityChange: (vis: BackgroundLayerVisibility) => void;
|
||||||
geometryVisibility: Record<string, boolean>;
|
geometryVisibility: Record<string, boolean>;
|
||||||
onGeometryVisibilityChange: (vis: Record<string, boolean>) => void;
|
onGeometryVisibilityChange: (vis: Record<string, boolean>) => void;
|
||||||
|
viewMode?: "local" | "global";
|
||||||
|
onViewModeChange?: (mode: "local" | "global") => void;
|
||||||
|
globalGeometries?: FeatureCollection;
|
||||||
|
isGlobalLoading?: boolean;
|
||||||
|
baseline?: FeatureCollection;
|
||||||
activeReplay?: BattleReplay | null;
|
activeReplay?: BattleReplay | null;
|
||||||
|
selectedStageId?: number | null;
|
||||||
|
selectedStepIndex?: number | null;
|
||||||
autoplayMode?: "start" | "selection" | null;
|
autoplayMode?: "start" | "selection" | null;
|
||||||
|
|
||||||
replayPreview: any;
|
replayPreview: any;
|
||||||
|
mapHandleRef?: RefObject<MapHandle | null>;
|
||||||
previewRelations: PreviewRelationIndex;
|
previewRelations: PreviewRelationIndex;
|
||||||
previewActiveEntityId: string | null;
|
previewActiveEntityId: string | null;
|
||||||
setPreviewActiveEntityId: (id: string | null) => void;
|
setPreviewActiveEntityId: (id: string | null) => void;
|
||||||
setPreviewEntityFocusToken: React.Dispatch<React.SetStateAction<number>>;
|
previewEntityFocusToken?: number;
|
||||||
|
setPreviewEntityFocusToken: Dispatch<SetStateAction<number>>;
|
||||||
previewSidebarWidth: number;
|
previewSidebarWidth: number;
|
||||||
setPreviewSidebarWidth: React.Dispatch<React.SetStateAction<number>>;
|
setPreviewSidebarWidth: Dispatch<SetStateAction<number>>;
|
||||||
previewWikiCache: Record<string, Wiki>;
|
previewWikiCache: Record<string, Wiki>;
|
||||||
setPreviewWikiCache: React.Dispatch<React.SetStateAction<Record<string, Wiki>>>;
|
setPreviewWikiCache: Dispatch<SetStateAction<Record<string, Wiki>>>;
|
||||||
|
isLargeScreen?: boolean;
|
||||||
|
setIsLargeScreen?: Dispatch<SetStateAction<boolean>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PreviewRelationIndex = {
|
export type PreviewLayoutHandle = {
|
||||||
entitiesById: Record<string, Entity>;
|
handleFeatureClick: (payload: MapFeaturePayload | null) => void;
|
||||||
entityGeometriesById: Record<string, FeatureCollection>;
|
getHoverPopupContent: (feature: Feature) => MapHoverPopupContent | null;
|
||||||
entityWikisById: Record<string, Wiki[]>;
|
handlePlaySelectedReplay: (replay: BattleReplay) => void;
|
||||||
geometryEntityIds: Record<string, string[]>;
|
|
||||||
wikiEntityIdsById: Record<string, string[]>;
|
|
||||||
wikiEntityIdsBySlug: Record<string, string[]>;
|
|
||||||
wikiById: Record<string, Wiki>;
|
|
||||||
wikiBySlug: Record<string, Wiki>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PreviewLayout = forwardRef<any, Props>(({
|
const PreviewLayout = forwardRef<PreviewLayoutHandle, Props>(({
|
||||||
projectId,
|
projectId,
|
||||||
mode,
|
mode,
|
||||||
onModeChange,
|
onModeChange,
|
||||||
@@ -636,6 +646,8 @@ const PreviewLayout = forwardRef<any, Props>(({
|
|||||||
isLoading={false}
|
isLoading={false}
|
||||||
disabled={isReplayPreviewMode}
|
disabled={isReplayPreviewMode}
|
||||||
statusText={null}
|
statusText={null}
|
||||||
|
filterEnabled={replayPreview.timelineFilterEnabled}
|
||||||
|
onFilterEnabledChange={replayPreview.setTimelineFilterEnabled}
|
||||||
style={
|
style={
|
||||||
isReplayPreviewWikiSidebarOpen
|
isReplayPreviewWikiSidebarOpen
|
||||||
? { right: `${previewSidebarWidth + 32}px` }
|
? { right: `${previewSidebarWidth + 32}px` }
|
||||||
@@ -704,3 +716,10 @@ function computeFixedPopupPosition(rect: DOMRect, width: number, height: number)
|
|||||||
|
|
||||||
return { top, left };
|
return { top, left };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clampNumber(value: number, min: number, max: number): number {
|
||||||
|
if (!Number.isFinite(value)) return min;
|
||||||
|
if (value < min) return min;
|
||||||
|
if (value > max) return max;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
import type { Entity } from "@/uhm/api/entities";
|
||||||
|
import type { Wiki } from "@/uhm/api/wikis";
|
||||||
|
import { normalizeFeatureEntityIds } from "@/uhm/lib/editor/snapshot/editorSnapshot";
|
||||||
|
import { normalizeTimelineYearValue } from "@/uhm/lib/utils/timeline";
|
||||||
|
import type { EntityWikiLinkSnapshot } from "@/uhm/types/projects";
|
||||||
|
import type { FeatureCollection } from "@/uhm/types/geo";
|
||||||
|
import type { WikiSnapshot } from "@/uhm/types/wiki";
|
||||||
|
import type { PreviewRelationIndex } from "./types";
|
||||||
|
|
||||||
|
export function buildSnapshotPreviewRelationIndex(options: {
|
||||||
|
draft: FeatureCollection;
|
||||||
|
entities: Entity[];
|
||||||
|
wikis: WikiSnapshot[];
|
||||||
|
entityWikiLinks: EntityWikiLinkSnapshot[];
|
||||||
|
wikiCache: Record<string, Wiki>;
|
||||||
|
projectId: string;
|
||||||
|
}): PreviewRelationIndex {
|
||||||
|
const next = createEmptyPreviewRelationIndex();
|
||||||
|
|
||||||
|
for (const entity of options.entities || []) {
|
||||||
|
const id = String(entity?.id || "").trim();
|
||||||
|
if (!id) continue;
|
||||||
|
next.entitiesById[id] = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const wikiSnapshot of options.wikis || []) {
|
||||||
|
if (!wikiSnapshot || wikiSnapshot.operation === "delete") continue;
|
||||||
|
const wiki = snapshotWikiToWiki(wikiSnapshot, options.wikiCache, options.projectId);
|
||||||
|
if (!wiki?.id) continue;
|
||||||
|
next.wikiById[wiki.id] = wiki;
|
||||||
|
const slug = String(wiki.slug || "").trim();
|
||||||
|
if (slug) next.wikiBySlug[slug] = wiki;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const feature of options.draft.features || []) {
|
||||||
|
const geometryId = String(feature.properties.id);
|
||||||
|
for (const entityId of normalizeFeatureEntityIds(feature)) {
|
||||||
|
if (!next.entitiesById[entityId]) {
|
||||||
|
next.entitiesById[entityId] = { id: entityId, name: entityId };
|
||||||
|
}
|
||||||
|
pushUniqueString(next.geometryEntityIds, geometryId, entityId);
|
||||||
|
pushFeatureForEntity(next, entityId, feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const link of options.entityWikiLinks || []) {
|
||||||
|
if (!link || link.operation === "delete") continue;
|
||||||
|
const entityId = String(link.entity_id || "").trim();
|
||||||
|
const wikiId = String(link.wiki_id || "").trim();
|
||||||
|
const entity = next.entitiesById[entityId] || null;
|
||||||
|
const wiki = next.wikiById[wikiId] || null;
|
||||||
|
if (!entity || !wiki) continue;
|
||||||
|
|
||||||
|
pushWikiForEntity(next, entityId, wiki);
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePreviewRelationArrays(next);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPublicPreviewRelationIndex(options: {
|
||||||
|
entities: Entity[];
|
||||||
|
entityGeometriesById: Record<string, FeatureCollection>;
|
||||||
|
entityWikisById: Record<string, Wiki[]>;
|
||||||
|
}): PreviewRelationIndex {
|
||||||
|
const next = createEmptyPreviewRelationIndex();
|
||||||
|
|
||||||
|
for (const entity of options.entities || []) {
|
||||||
|
const id = String(entity?.id || "").trim();
|
||||||
|
if (!id) continue;
|
||||||
|
next.entitiesById[id] = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [entityId, geometries] of Object.entries(options.entityGeometriesById || {})) {
|
||||||
|
const id = String(entityId || "").trim();
|
||||||
|
if (!id) continue;
|
||||||
|
if (!next.entitiesById[id]) next.entitiesById[id] = { id, name: id };
|
||||||
|
|
||||||
|
for (const feature of geometries.features || []) {
|
||||||
|
const geometryId = String(feature.properties.id);
|
||||||
|
pushUniqueString(next.geometryEntityIds, geometryId, id);
|
||||||
|
pushFeatureForEntity(next, id, feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [entityId, wikis] of Object.entries(options.entityWikisById || {})) {
|
||||||
|
const id = String(entityId || "").trim();
|
||||||
|
if (!id) continue;
|
||||||
|
if (!next.entitiesById[id]) next.entitiesById[id] = { id, name: id };
|
||||||
|
|
||||||
|
for (const wiki of wikis || []) {
|
||||||
|
if (!wiki?.id) continue;
|
||||||
|
next.wikiById[wiki.id] = wiki;
|
||||||
|
const slug = String(wiki.slug || "").trim();
|
||||||
|
if (slug) next.wikiBySlug[slug] = wiki;
|
||||||
|
pushWikiForEntity(next, id, wiki);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
normalizePreviewRelationArrays(next);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildEntityLabelContextDraft(
|
||||||
|
draft: FeatureCollection,
|
||||||
|
relationsOrEntities: PreviewRelationIndex | Entity[]
|
||||||
|
): FeatureCollection {
|
||||||
|
if (!draft.features.length) return draft;
|
||||||
|
|
||||||
|
const resolveEntityIds = Array.isArray(relationsOrEntities)
|
||||||
|
? (feature: FeatureCollection["features"][number]) => normalizeFeatureEntityIds(feature)
|
||||||
|
: (feature: FeatureCollection["features"][number]) =>
|
||||||
|
relationsOrEntities.geometryEntityIds[String(feature.properties.id)] || normalizeFeatureEntityIds(feature);
|
||||||
|
|
||||||
|
const entityById = new globalThis.Map<string, Entity>();
|
||||||
|
if (Array.isArray(relationsOrEntities)) {
|
||||||
|
for (const entity of relationsOrEntities || []) {
|
||||||
|
const id = String(entity?.id || "").trim();
|
||||||
|
if (!id) continue;
|
||||||
|
entityById.set(id, entity);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const [id, entity] of Object.entries(relationsOrEntities.entitiesById)) {
|
||||||
|
if (!id || !entity) continue;
|
||||||
|
entityById.set(id, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...draft,
|
||||||
|
features: draft.features.map((feature) => {
|
||||||
|
const entityIds = resolveEntityIds(feature);
|
||||||
|
if (!entityIds.length) return feature;
|
||||||
|
|
||||||
|
const candidates = entityIds.map((id) => {
|
||||||
|
const entity = entityById.get(id) || null;
|
||||||
|
const name = String(entity?.name || id).trim();
|
||||||
|
if (!name) return null;
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
time_start: normalizeTimelineYearValue(entity?.time_start),
|
||||||
|
time_end: normalizeTimelineYearValue(entity?.time_end),
|
||||||
|
};
|
||||||
|
}).filter((candidate) => candidate !== null);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...feature,
|
||||||
|
properties: {
|
||||||
|
...feature.properties,
|
||||||
|
entity_id: entityIds[0] || null,
|
||||||
|
entity_ids: entityIds,
|
||||||
|
entity_name: candidates[0]?.name || null,
|
||||||
|
entity_names: candidates.map((candidate) => candidate.name),
|
||||||
|
entity_label_candidates: candidates,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmptyPreviewRelationIndex(): PreviewRelationIndex {
|
||||||
|
return {
|
||||||
|
entitiesById: {},
|
||||||
|
entityGeometriesById: {},
|
||||||
|
entityWikisById: {},
|
||||||
|
geometryEntityIds: {},
|
||||||
|
wikiEntityIdsById: {},
|
||||||
|
wikiEntityIdsBySlug: {},
|
||||||
|
wikiById: {},
|
||||||
|
wikiBySlug: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushUniqueString(target: Record<string, string[]>, key: string, value: string) {
|
||||||
|
if (!target[key]) {
|
||||||
|
target[key] = [value];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!target[key].includes(value)) {
|
||||||
|
target[key].push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizePreviewRelationArrays(target: PreviewRelationIndex | Record<string, string[]>) {
|
||||||
|
if (isPreviewRelationIndex(target)) {
|
||||||
|
normalizeRecordArrays(target.geometryEntityIds);
|
||||||
|
normalizeRecordArrays(target.wikiEntityIdsById);
|
||||||
|
normalizeRecordArrays(target.wikiEntityIdsBySlug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
normalizeRecordArrays(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushFeatureForEntity(
|
||||||
|
target: PreviewRelationIndex,
|
||||||
|
entityId: string,
|
||||||
|
feature: FeatureCollection["features"][number]
|
||||||
|
) {
|
||||||
|
if (!target.entityGeometriesById[entityId]) {
|
||||||
|
target.entityGeometriesById[entityId] = { type: "FeatureCollection", features: [] };
|
||||||
|
}
|
||||||
|
const geometryId = String(feature.properties.id);
|
||||||
|
if (!target.entityGeometriesById[entityId].features.some((item) => String(item.properties.id) === geometryId)) {
|
||||||
|
target.entityGeometriesById[entityId].features.push(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushWikiForEntity(target: PreviewRelationIndex, entityId: string, wiki: Wiki) {
|
||||||
|
if (!target.entityWikisById[entityId]) target.entityWikisById[entityId] = [];
|
||||||
|
if (!target.entityWikisById[entityId].some((item) => item.id === wiki.id)) {
|
||||||
|
target.entityWikisById[entityId].push(wiki);
|
||||||
|
}
|
||||||
|
|
||||||
|
pushUniqueString(target.wikiEntityIdsById, wiki.id, entityId);
|
||||||
|
const slug = String(wiki.slug || "").trim();
|
||||||
|
if (slug) pushUniqueString(target.wikiEntityIdsBySlug, slug, entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function snapshotWikiToWiki(snapshot: WikiSnapshot, wikiCache: Record<string, Wiki>, projectId: string): Wiki {
|
||||||
|
if (typeof snapshot.doc === "string") {
|
||||||
|
return {
|
||||||
|
id: snapshot.id,
|
||||||
|
project_id: projectId,
|
||||||
|
title: snapshot.title,
|
||||||
|
slug: snapshot.slug ?? null,
|
||||||
|
content: snapshot.doc || "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return wikiCache[snapshot.id] || {
|
||||||
|
id: snapshot.id,
|
||||||
|
project_id: projectId,
|
||||||
|
title: snapshot.title,
|
||||||
|
slug: snapshot.slug ?? null,
|
||||||
|
content: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRecordArrays(target: Record<string, string[]>) {
|
||||||
|
for (const key of Object.keys(target)) {
|
||||||
|
target[key] = Array.from(new Set(target[key]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPreviewRelationIndex(value: PreviewRelationIndex | Record<string, string[]>): value is PreviewRelationIndex {
|
||||||
|
return "geometryEntityIds" in value && "wikiEntityIdsById" in value;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import type { Entity } from "@/uhm/api/entities";
|
||||||
|
import type { Wiki } from "@/uhm/api/wikis";
|
||||||
|
import type { FeatureCollection } from "@/uhm/types/geo";
|
||||||
|
|
||||||
|
export type PreviewDataScope = "project-snapshot" | "public-atlas";
|
||||||
|
|
||||||
|
export type PreviewRelationIndex = {
|
||||||
|
entitiesById: Record<string, Entity>;
|
||||||
|
entityGeometriesById: Record<string, FeatureCollection>;
|
||||||
|
entityWikisById: Record<string, Wiki[]>;
|
||||||
|
geometryEntityIds: Record<string, string[]>;
|
||||||
|
wikiEntityIdsById: Record<string, string[]>;
|
||||||
|
wikiEntityIdsBySlug: Record<string, string[]>;
|
||||||
|
wikiById: Record<string, Wiki>;
|
||||||
|
wikiBySlug: Record<string, Wiki>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EMPTY_PREVIEW_RELATIONS: PreviewRelationIndex = {
|
||||||
|
entitiesById: {},
|
||||||
|
entityGeometriesById: {},
|
||||||
|
entityWikisById: {},
|
||||||
|
geometryEntityIds: {},
|
||||||
|
wikiEntityIdsById: {},
|
||||||
|
wikiEntityIdsBySlug: {},
|
||||||
|
wikiById: {},
|
||||||
|
wikiBySlug: {},
|
||||||
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user