|
137 | 137 | const currentScript = document.currentScript || document.querySelector("script[src*='gamefoundry-partials.js']"); |
138 | 138 | const assetRoot = currentScript ? new URL("../", currentScript.src) : null; |
139 | 139 | const apiBackedLoginDiagnostic = "Use the API-backed local server for login. Run npm run dev:local-api and open http://127.0.0.1:5501/login.html."; |
140 | | - const adminMainItems = Object.freeze([ |
141 | | - Object.freeze({ label: "Analytics", route: "admin-analytics" }), |
142 | | - Object.freeze({ label: "Branding", route: "admin-branding" }), |
143 | | - Object.freeze({ label: "Controls", route: "admin-controls" }), |
144 | | - Object.freeze({ label: "Environments", route: "admin-environments" }), |
145 | | - Object.freeze({ label: "Game Migration", route: "admin-game-migration" }), |
146 | | - Object.freeze({ label: "Moderation", route: "admin-moderation" }), |
147 | | - Object.freeze({ label: "Platform Settings", route: "admin-platform-settings" }), |
148 | | - Object.freeze({ label: "Ratings", route: "admin-ratings" }), |
149 | | - Object.freeze({ label: "Roles", route: "admin-roles" }), |
150 | | - Object.freeze({ label: "Site Settings", route: "admin-site-settings" }), |
151 | | - Object.freeze({ label: "Themes", route: "admin-themes" }), |
152 | | - Object.freeze({ label: "Tool Votes", route: "admin-tool-votes" }), |
153 | | - Object.freeze({ label: "Users", route: "admin-users" }) |
154 | | - ]); |
155 | | - const localAdminMyStuffItems = Object.freeze([ |
156 | | - Object.freeze({ label: "DB Viewer", route: "admin-db-viewer" }), |
157 | | - Object.freeze({ label: "Design System", route: "admin-design-system" }), |
158 | | - Object.freeze({ label: "Grouping Colors", route: "admin-grouping-colors" }), |
159 | | - Object.freeze({ label: "Notes", href: "/admin/admin-notes.html", localNotes: true }) |
160 | | - ]); |
| 140 | + let navigationAdminMenuCache = null; |
161 | 141 |
|
162 | 142 | function assetUrl(path) { |
163 | 143 | if (!assetRoot) return rootPrefix() + path; |
|
186 | 166 | return rootPrefix() + (routeMap[routeName] || routeName || "index.html"); |
187 | 167 | } |
188 | 168 |
|
| 169 | + function pathHref(path) { |
| 170 | + const normalizedPath = String(path || "").replace(/^\/+/, ""); |
| 171 | + return normalizedPath ? rootPrefix() + normalizedPath : "#"; |
| 172 | + } |
| 173 | + |
189 | 174 | function isLocalDevMode(loginState) { |
190 | 175 | return String(loginState?.mode || "").indexOf("local-") === 0; |
191 | 176 | } |
192 | 177 |
|
| 178 | + function missingNavigationMenu(diagnostic) { |
| 179 | + return { |
| 180 | + adminMainItems: [], |
| 181 | + diagnostic: diagnostic || "Admin navigation API did not return menu data.", |
| 182 | + localAdminMyStuffItems: [], |
| 183 | + source: "missing-api" |
| 184 | + }; |
| 185 | + } |
| 186 | + |
| 187 | + function normalizeNavigationItems(items) { |
| 188 | + return Array.isArray(items) |
| 189 | + ? items.map(function (item) { |
| 190 | + return { |
| 191 | + href: typeof item.href === "string" ? item.href : "", |
| 192 | + label: typeof item.label === "string" ? item.label : "", |
| 193 | + localNotes: item.localNotes === true, |
| 194 | + path: typeof item.path === "string" ? item.path : "", |
| 195 | + route: typeof item.route === "string" ? item.route : "" |
| 196 | + }; |
| 197 | + }).filter(function (item) { |
| 198 | + return item.label && (item.route || item.path || item.href); |
| 199 | + }) |
| 200 | + : []; |
| 201 | + } |
| 202 | + |
| 203 | + function readNavigationAdminMenu() { |
| 204 | + if (navigationAdminMenuCache) { |
| 205 | + return navigationAdminMenuCache; |
| 206 | + } |
| 207 | + try { |
| 208 | + const request = new XMLHttpRequest(); |
| 209 | + request.open("GET", "/api/navigation/admin-menu", false); |
| 210 | + request.setRequestHeader("Accept", "application/json"); |
| 211 | + request.send(null); |
| 212 | + const payload = request.responseText ? JSON.parse(request.responseText) : null; |
| 213 | + if (request.status < 200 || request.status >= 300 || payload?.ok === false) { |
| 214 | + if (request.status === 404 || request.status === 405) { |
| 215 | + throw new Error(localRouteUnavailableDiagnostic("GET", "/api/navigation/admin-menu", request.status)); |
| 216 | + } |
| 217 | + throw new Error(payload?.error || "Navigation API did not return Admin menu data."); |
| 218 | + } |
| 219 | + const data = payload?.data || {}; |
| 220 | + navigationAdminMenuCache = { |
| 221 | + adminMainItems: normalizeNavigationItems(data.adminMainItems), |
| 222 | + diagnostic: "", |
| 223 | + localAdminMyStuffItems: normalizeNavigationItems(data.localAdminMyStuffItems), |
| 224 | + source: data.source || "server-api" |
| 225 | + }; |
| 226 | + return navigationAdminMenuCache; |
| 227 | + } catch (error) { |
| 228 | + navigationAdminMenuCache = missingNavigationMenu(error instanceof Error ? error.message : ""); |
| 229 | + return navigationAdminMenuCache; |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + function menuItemHref(item) { |
| 234 | + if (item.path) { |
| 235 | + return pathHref(item.path); |
| 236 | + } |
| 237 | + if (item.route) { |
| 238 | + return routeHref(item.route); |
| 239 | + } |
| 240 | + return item.href || "#"; |
| 241 | + } |
| 242 | + |
193 | 243 | function createMenuLink(item) { |
194 | 244 | const link = document.createElement("a"); |
195 | 245 | link.dataset.navLink = ""; |
196 | 246 | if (item.route) { |
197 | 247 | link.dataset.route = item.route; |
198 | | - link.href = routeHref(item.route); |
199 | | - } else { |
200 | | - link.href = item.href || "#"; |
201 | 248 | } |
| 249 | + link.href = menuItemHref(item); |
202 | 250 | if (item.localNotes) { |
203 | 251 | link.dataset.adminNotesLocalMenu = ""; |
204 | 252 | } |
205 | 253 | link.textContent = item.label; |
206 | 254 | return link; |
207 | 255 | } |
208 | 256 |
|
209 | | - function createLocalAdminMyStuffMenu() { |
| 257 | + function createLocalAdminMyStuffMenu(items) { |
210 | 258 | const item = document.createElement("div"); |
211 | 259 | item.className = "nav-item nav-popout-item"; |
212 | 260 | item.dataset.adminMyStuffMenu = ""; |
213 | 261 |
|
214 | 262 | const label = document.createElement("a"); |
215 | 263 | label.dataset.adminMyStuffLabel = ""; |
216 | | - label.href = "/admin/admin-notes.html"; |
| 264 | + label.href = items[0] ? menuItemHref(items[0]) : "#"; |
217 | 265 | label.setAttribute("aria-haspopup", "true"); |
218 | 266 | label.textContent = "My Stuff \u25B8"; |
219 | 267 |
|
220 | 268 | const submenu = document.createElement("div"); |
221 | 269 | submenu.className = "sub-menu sub-menu--nested"; |
222 | 270 | submenu.dataset.adminMyStuffSubmenu = ""; |
223 | 271 | submenu.setAttribute("aria-label", "My Stuff"); |
224 | | - localAdminMyStuffItems.forEach(function (menuItem) { |
| 272 | + items.forEach(function (menuItem) { |
225 | 273 | submenu.append(createMenuLink(menuItem)); |
226 | 274 | }); |
227 | 275 |
|
228 | 276 | item.append(label, submenu); |
229 | 277 | return item; |
230 | 278 | } |
231 | 279 |
|
| 280 | + function createAdminNavigationDiagnostic(message) { |
| 281 | + const diagnostic = document.createElement("p"); |
| 282 | + diagnostic.className = "status"; |
| 283 | + diagnostic.dataset.adminNavigationDiagnostic = ""; |
| 284 | + diagnostic.setAttribute("role", "status"); |
| 285 | + diagnostic.textContent = "Admin navigation unavailable: " + (message || "Start the local server API and refresh."); |
| 286 | + return diagnostic; |
| 287 | + } |
| 288 | + |
232 | 289 | function createAdminMenu(loginState) { |
| 290 | + const navigationMenu = readNavigationAdminMenu(); |
233 | 291 | const item = document.createElement("div"); |
234 | 292 | item.className = "nav-item"; |
235 | 293 | item.dataset.adminMenu = ""; |
|
242 | 300 |
|
243 | 301 | const submenu = document.createElement("div"); |
244 | 302 | submenu.className = "sub-menu"; |
245 | | - if (isLocalDevMode(loginState)) { |
| 303 | + if (navigationMenu.diagnostic) { |
| 304 | + submenu.append(createAdminNavigationDiagnostic(navigationMenu.diagnostic)); |
| 305 | + } |
| 306 | + if (isLocalDevMode(loginState) && navigationMenu.localAdminMyStuffItems.length) { |
246 | 307 | const separator = document.createElement("hr"); |
247 | 308 | separator.dataset.adminMyStuffSeparator = ""; |
248 | 309 | separator.setAttribute("role", "separator"); |
249 | 310 | separator.setAttribute("aria-disabled", "true"); |
250 | 311 | separator.tabIndex = -1; |
251 | | - submenu.append(createLocalAdminMyStuffMenu(), separator); |
| 312 | + submenu.append(createLocalAdminMyStuffMenu(navigationMenu.localAdminMyStuffItems), separator); |
252 | 313 | } |
253 | | - adminMainItems.forEach(function (menuItem) { |
| 314 | + navigationMenu.adminMainItems.forEach(function (menuItem) { |
254 | 315 | submenu.append(createMenuLink(menuItem)); |
255 | 316 | }); |
256 | 317 |
|
|
0 commit comments