1$(function () { 2 var roomPlannerConfig = window.__FDN_ROOM_PLANNER__ || {}; 3 var V3_ENABLED = roomPlannerConfig.v3Enabled !== false; 4 var TRY_V3_BANNER_ENABLED = V3_ENABLED && !!roomPlannerConfig.tryV3BannerEnabled; 5 6 var REOPEN_V3_KEY = "fdn-room-planner-reopen-v3"; 7 // In-memory only so dismiss sticks for this page load (timers won't re-show), 8 // but the prompt returns on the next navigation. 9 var tryV3BannerDismissed = false; 10 var tryV3BannerTimer = null; 11 12 function dismissTryV3Banner(banner) { 13 tryV3BannerDismissed = true; 14 if (tryV3BannerTimer) { 15 clearTimeout(tryV3BannerTimer); 16 tryV3BannerTimer = null; 17 } 18 if (banner && banner.parentNode) { 19 banner.remove(); 20 } 21 } 22 23 // V3 is the default engine. A user who picks "Use classic planner" gets V2 24 // until they switch back, and `rpEngine` in the URL overrides both. 25 function resolveEngine() { 26 if (!V3_ENABLED) { 27 return "v2"; 28 } 29 30 var requested = (getQueryParamIgnoreCase("rpEngine") || "").toLowerCase(); 31 32 if (requested === "v2" || requested === "v3") { 33 return requested; 34 } 35 36 var preference = window.FdnRoomPlannerEngine && FdnRoomPlannerEngine.get(); 37 38 return preference === "v2" ? "v2" : "v3"; 39 } 40 41 function setEnginePreference(engine) { 42 if (window.FdnRoomPlannerEngine && typeof FdnRoomPlannerEngine.set === "function") { 43 FdnRoomPlannerEngine.set(engine); 44 } 45 } 46 47 function getQueryParamIgnoreCase(name) { 48 var target = String(name).toLowerCase(); 49 var params = new URLSearchParams(window.location.search); 50 var matched = null; 51 params.forEach(function (value, key) { 52 if (matched === null && String(key).toLowerCase() === target) { 53 matched = value; 54 } 55 }); 56 return matched; 57 } 58 59 function hasQueryParamIgnoreCase(name) { 60 return getQueryParamIgnoreCase(name) !== null; 61 } 62 63 function consumeReopenV3AfterLogin() { 64 try { 65 if (sessionStorage.getItem(REOPEN_V3_KEY) === "1") { 66 sessionStorage.removeItem(REOPEN_V3_KEY); 67 return true; 68 } 69 } catch (e) { 70 // ignore 71 } 72 return false; 73 } 74 75 function shouldOpenV3OnAutoOpen() { 76 if (!V3_ENABLED) { 77 consumeReopenV3AfterLogin(); 78 return false; 79 } 80 81 var engine = (getQueryParamIgnoreCase("rpEngine") || "").toLowerCase(); 82 if (engine === "v2") { 83 consumeReopenV3AfterLogin(); 84 return false; 85 } 86 if (engine === "v3") { 87 return true; 88 } 89 if (consumeReopenV3AfterLogin()) { 90 return true; 91 } 92 93 return resolveEngine() === "v3"; 94 } 95 96 function closeV2Planner() { 97 var $close = $(".room-planner-close--trigger"); 98 if ($close.length) { 99 $close.first().trigger("click"); 100 return; 101 } 102 $(".room-planner-v2").removeClass("__open"); 103 $("body").css("overflow", ""); 104 } 105 106 function openV3(data) { 107 if (!V3_ENABLED) { 108 openV2(data); 109 return; 110 } 111 112 if (window.RoomPlannerV3Launcher && typeof RoomPlannerV3Launcher.open === "function") { 113 RoomPlannerV3Launcher.open(data); 114 } else { 115 console.error("RoomPlannerV3Launcher is not available."); 116 } 117 } 118 119 function switchToV3(data) { 120 if (!V3_ENABLED) { 121 return; 122 } 123 124 setEnginePreference("v3"); 125 closeV2Planner(); 126 openV3(data); 127 } 128 129 function ensureTryV3BannerStyles() { 130 if (document.getElementById("fdn-try-v3-banner-styles")) { 131 return; 132 } 133 var style = document.createElement("style"); 134 style.id = "fdn-try-v3-banner-styles"; 135 style.textContent = 136 ".fdn-try-v3-banner{position:absolute;
136left:50%;bottom:12px;transform:translateX(-50%);z-index:200;" + 137 "display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;max-width:min(560px,calc(100% - 24px));" + 138 "padding:.65rem .9rem;background:#1f2937;color:#f8fafc;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.28);" + 139 "font:500 13px/1.35 system-ui,-apple-system,Segoe UI,sans-serif}" + 140 ".fdn-try-v3-banner__text{flex:1 1 180px}" + 141 ".fdn-try-v3-banner__actions{display:flex;gap:.4rem;align-items:center}" + 142 ".fdn-try-v3-banner button{border:0;border-radius:6px;cursor:pointer;padding:.35rem .7rem;font:600 12px/1.2 inherit}" + 143 ".fdn-try-v3-banner__try{background:#4b89dc;color:#fff}" + 144 ".fdn-try-v3-banner__dismiss{background:transparent;color:#cbd5e1}"; 145 document.head.appendChild(style); 146 } 147 148 function ensureTryV3Banner() { 149 if (!TRY_V3_BANNER_ENABLED || tryV3BannerDismissed) { 150 return; 151 } 152 153 var root = document.querySelector(".room-planner-v2"); 154 if (!root || !root.classList.contains("__open")) { 155 return; 156 } 157 158 if (root.querySelector(".fdn-try-v3-banner")) { 159 return; 160 } 161 162 ensureTryV3BannerStyles(); 163 164 var banner = document.createElement("div"); 165 banner.className = "fdn-try-v3-banner"; 166 banner.setAttribute("role", "status"); 167 banner.innerHTML = 168 '<div class="fdn-try-v3-banner__text">You\u2019re using the classic room planner. The new one has faster editing and clearer measurements.</div>' + 169 '<div class="fdn-try-v3-banner__actions">' + 170 '<button type="button" class="fdn-try-v3-banner__try">Use new planner</button>' + 171 '<button type="button" class="fdn-try-v3-banner__dismiss">Not now</button>' + 172 "</div>"; 173 174 banner.querySelector(".fdn-try-v3-banner__try").addEventListener("click", function () { 175 switchToV3(null); 176 }); 177 banner.querySelector(".fdn-try-v3-banner__dismiss").addEventListener("click", function () { 178 dismissTryV3Banner(banner); 179 }); 180 181 root.appendChild(banner); 182 } 183 184 function scheduleTryV3Banner(delayMs) { 185 if (tryV3BannerDismissed) { 186 return; 187 } 188 189 if (tryV3BannerTimer) { 190 clearTimeout(tryV3BannerTimer); 191 } 192 193 tryV3BannerTimer = setTimeout(function () { 194 tryV3BannerTimer = null; 195 ensureTryV3Banner(); 196 }, typeof delayMs === "number" ? delayMs : 100); 197 } 198 199 // Site triggers route to the preferred engine (V3 unless the user opted into classic V2). 200 function openPlanner(data) { 201 var canUseV3 = V3_ENABLED 202 && !!(window.RoomPlannerV3Launcher && typeof RoomPlannerV3Launcher.open === "function"); 203 204 if (resolveEngine() === "v2" || !canUseV3) { 205 openV2(data); 206 return; 207 } 208 209 // Snippets may have already kicked off classic open â close it before V3. 210 closeV2Planner(); 211 openV3(data); 212 } 213 214 $(".room-planner--trigger").on("click", function () { 215 openPlanner(); 216 }); 217 218 $("body").on("click", ".js-add-to-room-plan", function () { 219 var data = $(this).data("room-plan-item"); 220 openPlanner(data); 221 if (typeof laurentideTracker !== "undefined") { 222 Laurentide_AddToRoomplan(data); 223 } 224 }); 225 226 var scriptsLoaded = false; 227 var isCurrentlyLoading = false; 228 var pendingInitData = null; 229 var roomPlannerInitCalled = false; 230 231 function openV2(data) { 232 init(data); 233 } 234 235 var init = function (data) { 236 pendingInitData = data; 237 238 if (!scriptsLoaded && !isCurrentlyLoading) { 239 console.log("init (load)"); 240 loadRoomPlanner(); 241 } else if (!isCurrentlyLoading && window.RoomPlannerV2) { 242 // Auto-open snippets poll open() every 150ms. After scripts load, the first 243 // init is async (templates + delayed loadCanvas). A second init before 244 // resizeCanvas flips false double-bootstraps the canvas. 245 if (roomPlannerInitCalled && !$(".room-planner-main .canvas-container").length) { 246 console.log("init (ignored â bootstrap in progress)"); 247 return; 248 } 249 if ($(".room-planner-main .canvas-container").length && window.RoomPlannerV2.open) { 250 console.log("init (re-open existing)"); 251 RoomPlannerV2.open(pendingInitData); 252 pendingInitData = null; 253 scheduleTryV3Banner(100); 254 return; 255 } 256 console.log("init (open)"); 257 roomPlannerInitCalled = true; 258 RoomPlannerV2.init(pendingInitData); 259 pendingInitData = null; 260 scheduleTryV3Banner(250); 261 } 262 }; 263 264 // Legacy name still used by dealer CodeSnippets / auto-open polls. Route through the 265 // engine preference so those callers respect V3 default / classic opt-in. 266 window.RoomPlannerV2Launcher = window.RoomPlannerV2Launcher || {}; 267 window.RoomPlannerV2Launcher.open = function (data) { 268 openPlanner(data); 269 }; 270 window.RoomPlannerV2Launcher.openClassic = function (data) { 271 openV2(data); 272 }; 273 274 // Engine-aware entry point for host pages / legacy globals.
275 window.RoomPlannerLauncher = window.RoomPlannerLauncher || {}; 276 window.RoomPlannerLauncher.open = function (data) { 277 openPlanner(data); 278 }; 279 window.RoomPlannerLauncher.resolveEngine = resolveEngine; 280 281 function shouldAutoOpenRoomPlanner() { 282 if (!hasQueryParamIgnoreCase("autoOpenRoomPlanner")) { 283 return false; 284 } 285 var value = (getQueryParamIgnoreCase("autoOpenRoomPlanner") || "true").toLowerCase(); 286 return value !== "false" && value !== "0"; 287 } 288 289 if (shouldAutoOpenRoomPlanner()) { 290 window.setTimeout(function () { 291 if (shouldOpenV3OnAutoOpen() && window.RoomPlannerV3Launcher) { 292 openV3(); 293 return; 294 } 295 openPlanner(); 296 }, 0); 297 } 298 299 function loadScript(src) { 300 return new Promise((resolve, reject) => { 301 const script = document.createElement("script"); 302 script.src = src; 303 script.async = false; // preserve execution order 304 script.onload = () => resolve(src); 305 script.onerror = () => reject(new Error(`Failed to load ${src}`)); 306 document.head.appendChild(script); 307 }); 308 } 309 310 function loadCss(href) { 311 return new Promise((resolve, reject) => { 312 // Prevent duplicate loads 313 if (document.querySelector(`link[href="${href}"]`)) { 314 resolve(href); 315 return; 316 } 317 318 const link = document.createElement("link"); 319 link.rel = "stylesheet"; 320 link.href = href; 321 322 link.onload = () => resolve(href); 323 link.onerror = () => reject(new Error(`Failed to load CSS ${href}`)); 324 325 document.head.appendChild(link); 326 }); 327 } 328 329 async function loadRoomPlanner() { 330 const startTime = performance.now(); 331 isCurrentlyLoading = true; 332 333 try { 334 ensureRoomPlannerTemplatesContainer(); 335 336 var baseUrl = (window.Utils && (Utils.baseUrl || Utils.BaseURL)) || "/"; 337 // Bump when shipping v2 host fixes so browsers don't keep a stale Open-modal script. 338 var assetVersion = "20260917-v3-default"; 339 340 // Load CSS first 341 await loadCss(baseUrl + "components/roomplanner/v2/room-planner-v2.css?v=" + assetVersion); 342 343 await loadScript(baseUrl + "components/roomplanner/v2/jquery-ui-1.11.1.js"); 344 await loadScript(baseUrl + "components/roomplanner/v2/jquery-ui-touch-punch-0.2.2.js"); 345 await loadScript(baseUrl + "components/roomplanner/v2/RoomPlannerFabric.js"); 346 await loadScript(baseUrl + "components/roomplanner/v2/room-planner-v2.js?v=" + assetVersion); 347 348 const endTime = performance.now(); 349 console.log(`loaded rp scripts in ${endTime - startTime}ms`); 350 351 if (!window.RoomPlannerV2 || typeof window.RoomPlannerV2.init !== "function") { 352 throw new Error("RoomPlannerV2 did not initialize after loading room-planner-v2.js"); 353 } 354 355 scriptsLoaded = true; 356 roomPlannerInitCalled = true; 357 window.RoomPlannerV2.init(pendingInitData); 358 pendingInitData = null; 359 scheduleTryV3Banner(250); 360 } catch (err) { 361 console.error(err); 362 } finally { 363 isCurrentlyLoading = false; 364 } 365 } 366 function ensureRoomPlannerTemplatesContainer() { 367 if (!document.getElementById("room-planner-templates")) { 368 const div = document.createElement("div"); 369 div.id = "room-planner-templates"; 370 document.body.appendChild(div); 371 } 372 // Ensure main room planner container exists 373 if (!document.querySelector(".room-planner-v2")) { 374 const plannerDiv = document.createElement("div"); 375 plannerDiv.className = "room-planner-v2"; 376 document.body.appendChild(plannerDiv); 377 } 378 } 379 380 // Watch for V2 open state so banner can appear after delayed open(). 381 if (TRY_V3_BANNER_ENABLED) { 382 var openObserver = new MutationObserver(function () { 383 ensureTryV3Banner(); 384 }); 385 openObserver.observe(document.body, { 386 subtree: true, 387 attributes: true, 388 attributeFilter: ["class"] 389 }); 390 } 391}); 392 393// for backward compatibility 394function AddToRoomPlan(data) {
395 if (window.RoomPlannerLauncher && typeof RoomPlannerLauncher.open === "function") { 396 RoomPlannerLauncher.open(data); 397 return; 398 } 399 if (window.RoomPlannerV2Launcher && typeof RoomPlannerV2Launcher.open === "function") { 400 RoomPlannerV2Launcher.open(data); 401 return; 402 } 403 if (window.RoomPlannerV2) { 404 RoomPlannerV2.AddToRoomPlan(data); 405 } 406}
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.