import { GeoApiClient } from "../libs/geo-api-client/dist/index.js"; class BrowserStorage { getItem(key) { return localStorage.getItem(key); } setItem(key, value) { localStorage.setItem(key, value); } removeItem(key) { localStorage.removeItem(key); } } const statusEl = document.getElementById("status"); const apiBaseEl = document.getElementById("apiBase"); const publicKeyPreviewEl = document.getElementById("publicKeyPreview"); const collectionInfoEl = document.getElementById("collectionInfo"); const collectionNameEl = document.getElementById("collectionName"); const assetFileEl = document.getElementById("assetFile"); const assetNameEl = document.getElementById("assetName"); const assetDescEl = document.getElementById("assetDesc"); const assetsListEl = document.getElementById("assetsList"); let client = new GeoApiClient(apiBaseEl.value.trim(), new BrowserStorage()); let keys = null; let accessToken = ""; let collectionId = ""; let selectedLatLng = null; const markers = new Map(); const map = L.map("map").setView([28.4636, -16.2518], 10); L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "© OpenStreetMap", }).addTo(map); let pendingMarker = null; map.on("click", (event) => { selectedLatLng = event.latlng; if (pendingMarker) map.removeLayer(pendingMarker); pendingMarker = L.marker(event.latlng, { title: "Pending feature position" }).addTo(map); setStatus(`Selected location: ${event.latlng.lat.toFixed(5)}, ${event.latlng.lng.toFixed(5)}`); }); function setStatus(message) { statusEl.textContent = message; } function extFromFilename(name) { const idx = name.lastIndexOf("."); if (idx <= 0) return ""; return name.slice(idx + 1).toLowerCase(); } function kindFromExt(ext) { return ext === "gltf" || ext === "glb" ? "3d" : "image"; } async function sha256Hex(file) { const buffer = await file.arrayBuffer(); const digest = await crypto.subtle.digest("SHA-256", buffer); return Array.from(new Uint8Array(digest)) .map((b) => b.toString(16).padStart(2, "0")) .join(""); } function setClientBase(baseUrl) { const normalized = baseUrl.trim().replace(/\/+$/g, ""); client = new GeoApiClient(normalized, new BrowserStorage()); if (accessToken) client.setAccessToken(accessToken); localStorage.setItem("geo_api_base", normalized); setStatus(`API base updated: ${normalized}`); } function buildMapShareLink(feature, asset) { const coords = feature?.geometry?.coordinates; if (!Array.isArray(coords) || coords.length < 2) { return client.resolveRelativeLink(asset.link); } const url = new URL(window.location.origin + window.location.pathname); url.searchParams.set("shared", "1"); url.searchParams.set("lng", String(coords[0])); url.searchParams.set("lat", String(coords[1])); url.searchParams.set("kind", asset.kind || "3d"); url.searchParams.set("public", asset.isPublic ? "1" : "0"); url.searchParams.set("assetId", asset.id); url.searchParams.set("assetLink", client.resolveRelativeLink(asset.link)); return url.toString(); } function renderSharedAssetFromQuery() { const params = new URLSearchParams(window.location.search); if (params.get("shared") !== "1") return; const lng = Number(params.get("lng")); const lat = Number(params.get("lat")); if (!Number.isFinite(lng) || !Number.isFinite(lat)) return; const kind = params.get("kind") === "image" ? "image" : "3d"; const isPublic = params.get("public") !== "0"; const assetId = params.get("assetId") || "shared-asset"; const marker = L.marker([lat, lng]).addTo(map); marker.bindPopup(`Shared ${kind} asset (${isPublic ? "public" : "private"}): ${assetId}`).openPopup(); map.setView([lat, lng], Math.max(map.getZoom(), 14)); setStatus("Shared object loaded on map."); } async function ensureKeys() { keys = await client.ensureKeysInStorage(); publicKeyPreviewEl.textContent = `Public key: ${keys.publicKey.slice(0, 24)}...`; } async function register() { if (!keys) await ensureKeys(); await client.registerBySigningServiceKey(keys.publicKey, keys.privateKey); } async function login() { if (!keys) await ensureKeys(); accessToken = await client.loginWithSignature(keys.publicKey, keys.privateKey); client.setAccessToken(accessToken); await refreshFeatures(); } async function ensureCollection() { if (collectionId) return collectionId; const created = await client.createCollection(collectionNameEl.value.trim() || "3D objects demo"); collectionId = created.id; collectionInfoEl.textContent = `${created.name} (${created.id})`; return collectionId; } function renderAssets(features) { assetsListEl.innerHTML = ""; for (const feature of features) { const assets = Array.isArray(feature.properties?.assets) ? feature.properties.assets : []; for (const asset of assets) { const card = document.createElement("div"); card.className = "asset-card"; const absoluteLink = client.resolveRelativeLink(asset.link); card.innerHTML = `