145 lines
5.5 KiB
TypeScript
145 lines
5.5 KiB
TypeScript
import {
|
|
getSpriteRows,
|
|
normalizeImageRecordForSave,
|
|
normalizeImagesPayloadForSave,
|
|
normalizeTileRecordForSave,
|
|
type JsonObject,
|
|
type JsonValue,
|
|
} from "../editorCore";
|
|
|
|
export type GraphicRole = "tile" | "sprite" | "other";
|
|
|
|
export function normalizeGraphicRoles(value: unknown): string[] {
|
|
if (!Array.isArray(value)) {
|
|
return [];
|
|
}
|
|
return Array.from(new Set(
|
|
value
|
|
.map((entry) => String(entry || "").trim().toLowerCase())
|
|
.filter((entry) => entry === "tile" || entry === "sprite"),
|
|
));
|
|
}
|
|
|
|
export function hydrateImageRecordRows(entry: JsonObject | null | undefined): JsonObject | null {
|
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
return null;
|
|
}
|
|
return {
|
|
...entry,
|
|
rows: getSpriteRows(entry),
|
|
};
|
|
}
|
|
|
|
export function getImageRecordFromPayload(
|
|
imagesPayload: JsonObject | null | undefined,
|
|
recordId: string,
|
|
): JsonObject | null {
|
|
const normalizedId = String(recordId || "").trim();
|
|
if (!normalizedId) {
|
|
return null;
|
|
}
|
|
const records = Array.isArray(imagesPayload?.images) ? imagesPayload.images : [];
|
|
const matchedEntry = (records.find((entry) => (
|
|
entry
|
|
&& typeof entry === "object"
|
|
&& !Array.isArray(entry)
|
|
&& String((entry as JsonObject).id || "").trim() === normalizedId
|
|
)) as JsonObject | undefined) || null;
|
|
return hydrateImageRecordRows(matchedEntry);
|
|
}
|
|
|
|
export function buildImageRecordFromTileRecord(
|
|
record: JsonObject,
|
|
existingRecord?: JsonObject | null,
|
|
cloneValue: <T>(value: T) => T = structuredClone,
|
|
): JsonObject {
|
|
const existing = existingRecord && typeof existingRecord === "object" && !Array.isArray(existingRecord)
|
|
? existingRecord
|
|
: {};
|
|
const existingRoles = normalizeGraphicRoles(existing.roles);
|
|
return normalizeImageRecordForSave({
|
|
...cloneValue(existing),
|
|
id: String(record?.id || existing.id || "").trim(),
|
|
name: String(record?.name || existing.name || "").trim(),
|
|
description: String(record?.description || existing.description || "").trim(),
|
|
width: Math.max(1, Number(record?.width) || Number(existing.width) || 16),
|
|
height: Math.max(1, Number(record?.height) || Number(existing.height) || 16),
|
|
pixelScale: Math.max(1, Number(record?.pixelScale) || Number(existing.pixelScale) || 1),
|
|
opacity: Math.max(0, Math.min(1, Number(record?.opacity ?? existing.opacity ?? 1))),
|
|
rows: Array.isArray(record?.rows)
|
|
? record.rows.map((row) => String(row || ""))
|
|
: getSpriteRows(existing),
|
|
tags: cloneValue(record?.tags) || cloneValue(existing.tags) || [],
|
|
roles: Array.from(new Set([...existingRoles, "tile"])),
|
|
tileSymbol: String(record?.tileSymbol || record?.symbol || existing.tileSymbol || "").trim().charAt(0),
|
|
});
|
|
}
|
|
|
|
export function buildImageRecordFromSpriteRecord(
|
|
record: JsonObject,
|
|
graphicRole: GraphicRole,
|
|
existingRecord?: JsonObject | null,
|
|
cloneValue: <T>(value: T) => T = structuredClone,
|
|
): JsonObject {
|
|
const existing = existingRecord && typeof existingRecord === "object" && !Array.isArray(existingRecord)
|
|
? existingRecord
|
|
: {};
|
|
const existingRoles = normalizeGraphicRoles(existing.roles);
|
|
const wantsSpriteRole = graphicRole !== "other";
|
|
const nextRoles = wantsSpriteRole
|
|
? Array.from(new Set([...existingRoles, "sprite"]))
|
|
: existingRoles.filter((entry) => entry !== "sprite");
|
|
return normalizeImageRecordForSave({
|
|
...cloneValue(existing),
|
|
id: String(record?.id || existing.id || "").trim(),
|
|
name: String(record?.name || existing.name || "").trim(),
|
|
description: String(record?.description || existing.description || "").trim(),
|
|
width: Math.max(1, Number(record?.width) || Number(existing.width) || 16),
|
|
height: Math.max(1, Number(record?.height) || Number(existing.height) || 16),
|
|
pixelScale: Math.max(1, Number(record?.pixelScale) || Number(existing.pixelScale) || 1),
|
|
opacity: Math.max(0, Math.min(1, Number(record?.opacity ?? existing.opacity ?? 1))),
|
|
rows: Array.isArray(record?.rows)
|
|
? record.rows.map((row) => String(row || ""))
|
|
: getSpriteRows(existing),
|
|
tags: cloneValue(record?.tags) || cloneValue(existing.tags) || [],
|
|
roles: nextRoles,
|
|
tileSymbol: String(existing.tileSymbol || record?.tileSymbol || record?.symbol || "").trim().charAt(0),
|
|
});
|
|
}
|
|
|
|
export function normalizeImagesPayloadSnapshot(
|
|
payload: JsonObject | null | undefined,
|
|
cloneValue: <T>(value: T) => T = structuredClone,
|
|
): JsonObject {
|
|
const normalized = (normalizeImagesPayloadForSave(cloneValue(payload || { schemaVersion: 1, images: [] }) as JsonValue) as JsonObject) || {
|
|
schemaVersion: 1,
|
|
images: [],
|
|
};
|
|
const records = Array.isArray(normalized.images) ? normalized.images : [];
|
|
return {
|
|
...normalized,
|
|
images: records.map((entry) => {
|
|
const hydratedEntry = hydrateImageRecordRows(
|
|
entry && typeof entry === "object" && !Array.isArray(entry)
|
|
? entry as JsonObject
|
|
: null,
|
|
);
|
|
return hydratedEntry || entry;
|
|
}),
|
|
};
|
|
}
|
|
|
|
export function buildTileRecordFromImageRecord(entry: JsonObject, cloneValue: <T>(value: T) => T = structuredClone): JsonObject {
|
|
return normalizeTileRecordForSave({
|
|
id: String(entry.id || "").trim(),
|
|
symbol: String(entry.tileSymbol || entry.symbol || "").trim().charAt(0),
|
|
name: String(entry.name || "").trim(),
|
|
description: String(entry.description || "").trim(),
|
|
width: Number(entry.width) || 16,
|
|
height: Number(entry.height) || 16,
|
|
pixelScale: Number(entry.pixelScale) || 1,
|
|
opacity: Number(entry.opacity ?? 1),
|
|
rows: getSpriteRows(entry),
|
|
tags: cloneValue(entry.tags) || [],
|
|
});
|
|
}
|