PageSourceSearch

https://bikesontransit.org/js/map-view/api.js

js bikesontransit.org collected 2026-09-27 01:48:28 UTC 6,004 bytes, 198 lines download raw bytes

1/* =====================================================================
2   js/map-view/api.js
3   Dedicated API layer for Map View.
4   Handles fetching project bundles, presentations, and other async data.
5   ===================================================================== */
6
7/**
8 * Fetches the full project bundle by Slug.
9 * Used for public URLs: /p/my-map
10 */
11export async function fetchProjectBySlug(slug, username) {
12    if (!slug) throw new Error("Slug is required");
13
14    try {
15        // features=0: skip the feature payload — map-view fetches
16        // /api/features in parallel instead (see bootstrap in index.js).
17        // On large projects the bundle otherwise ships every feature
18        // twice, once here and once via the authoritative refetch.
19        // slim=1: strip the two embedded settings copies of the layer
20        // list — map-view reads the top-level `layers` array only.
21        const qs = new URLSearchParams({ features: '0', slim: '1' });
22        if (username) qs.set('owner', username);
23        const response = await fetch(`/api/projects/slug/${slug}?${qs}`);
24        if (!response.ok) {
25            throw new Error(`Project not found (Status: ${response.status})`);
26        }
27        return await response.json();
28    } catch (err) {
29        console.error("[API] fetchProjectBySlug failed:", err);
30        throw err;
31    }
32}
33
34/**
35 * Fetches the full project bundle by ID.
36 * Used for editor preview or ID-based routing: /map-view/123
37 */
38export async function fetchProjectById(id) {
39    if (!id) throw new Error("ID is required");
40
41    try {
42        // Assuming your backend supports this, or we adapt the route
43        const response = await fetch(`/api/projects/${id}/bundle`); 
44        // Note: If /bundle doesn't exist, we might need to chain requests, 
45        // but typically you want a single endpoint for the viewer.
46        
47        if (!response.ok) {
48            throw new Error(`Project not found (Status: ${response.status})`);
49        }
50        return await response.json();
51    } catch (err) {
52        console.error("[API] fetchProjectById failed:", err);
53        throw err;
54    }
55}
56
57
58/**
59 * [NEW] Fetches current user session to determine Admin/Guest status.
60 */
61export async function fetchSession() {
62    try {
63        const r = await fetch('/api/session-info');
64        if (!r.ok) return { loggedIn: false, role: 'Guest' };
65        return await r.json();
66    } catch (err) {
67        console.warn("[API] Session fetch failed", err);
68        return { loggedIn: false, role: 'Guest' };
69    }
70}
71
72export async function fetchJson(url, options = {}) {
73    const r = await fetch(url, options);
74
75    if (!r.ok) {
76        const err = new Error(`HTTP ${r.status}`);
77        err.status = r.status;
78
79        // 🔕 404 is often "no data yet" — don't scream about it
80        if (r.status !== 404) {
81            console.error(`[API] fetchJson failed for ${url}:`, err);
82        }
83
84        throw err;
85    }
86
87    return await r.json();
88}
89/**
90 * Fetches presentations associated with the project.
91 * Originally found in initPresentationsModal logic.
92 */
93export async function fetchPresentations(projectId) {
94    if (!projectId) return [];
95
96    try {
97        const response = await fetch(`/api/presentations/project/${projectId}`);
98        if (!response.ok) return [];
99        
100        const data = await response.json();
101        return Array.isArray(data) ? data : [];
102    } catch (err) {
103        console.warn("[API] fetchPresentations failed:", err);
104        return [];
105    }
106}
107
108/**
109 * Fetches public presentations for a project by slug.
110 * Used by the map-view Presentations menu button.
111 * No auth required — returns only is_public=1 presentations.
112 */
113export async function fetchPublicPresentations(projectSlug) {
114  if (!projectSlug) return [];
115  try {
116    const res = await fetch(`/api/presentations/public-list/${projectSlug}`);
117    if (!res.ok) return [];
118    const data = await res.json();
119    return Array.isArray(data) ? data : [];
120  } catch (err) {
121    console.warn('[API] fetchPublicPresentations failed:', err);
122    return [];
123  }
124}
125
126// api.js
127
128function normalizeGeometry(feature) {
129  // API already sends GeoJSON geometry
130  if (feature.geometry && feature.geometry.type) {
131    return feature.geometry;
132  }
133
134  // legacy fallback (just in case)
135  if (feature.geo_shape && feature.geo_shape.type) {
136    return feature.geo_shape;
137  }
138
139  return null;
140}
141
142export async function fetchFeatures(params = {}) {
143  // 🔑 Admins/editors need hidden features (is_hidden=1) included
144  //     so they can see and un-hide them. The server filters them out
145  //     unless include_hidden=1 is explicitly passed.
146  const role = window.wmState?.user?.projectRole || 'viewer';
147  if ((role === 'admin' || role === 'editor') && !params.include_hidden) {
148    params = { ...params, include_hidden: '1' };
149  }
150
151  const qs = new URLSearchParams(params).toString();
152  const url = qs ? `/api/features?${qs}` : '/api/features';
153
154  const res = await fetch(url, { credentials: 'include' });
155
156  if (!res.ok) {
157    console.warn('[fetchFeatures] HTTP error', res.status);
158    return [];
159  }
160
161  const json = await res.json();
162
163  // ✅ SUPPORT BOTH API SHAPES
164  const rawFeatures = Array.isArray(json)
165    ? json
166    : Array.isArray(json?.features)
167      ? json.features
168      : [];
169
170  if (!rawFeatures.length) {
171    console.warn('[fetchFeatures] No features returned', json);
172    return [];
173  }
174
175  const features = rawFeatures.map(f => {
176    const geometry = normalizeGeometry(f);
177
178    if (!geometry) {
179      console.warn('[fetchFeatures] Feature has no geometry', f.id);
180    }
181
182    return {
183      ...f,
184      geometry
185    };
186  });
187
188  console.log('[fetchFeatures] Loaded features:', features.length);
189  return features;
190}
191
192export async function fetchIcons(projectId) {
193  if (!projectId) return [];
194  const res = await fetch(`/api/icons/${projectId}`);
195  if (!res.ok) throw new Error('Failed to fetch icons');
196  const data = await res.json();
197  return data.icons || [];
198}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.