(function () { const WIDGET_SHELL_SELECTOR = ".js-gia-widget-shell"; const WIDGET_SPAWN_SELECTOR = ".js-widget-spawn-trigger"; const WIDGET_ACTION_SELECTOR = ".js-gia-widget-action"; const TASKBAR_SELECTOR = "#gia-taskbar"; const TASKBAR_ITEMS_SELECTOR = "#gia-taskbar-items"; const WORKSPACE_STASH_SELECTOR = "#gia-workspace-stash"; const SNAP_ASSISTANT_SELECTOR = "#gia-snap-assistant"; const SNAP_ASSISTANT_OPTIONS_SELECTOR = "#gia-snap-assistant-options"; const SNAP_ASSISTANT_CLOSE_SELECTOR = ".js-gia-snap-assistant-close"; const MOBILE_MEDIA_QUERY = "(max-width: 768px)"; const DESKTOP_MEDIA_QUERY = "(min-width: 1216px)"; const GRID_COLUMNS = 12; const GRID_ROWS = 12; const MIN_QUARTER_TILE_HEIGHT = 340; const MIN_QUARTER_TILE_WIDTH = 420; const assetPromises = { styles: {}, scripts: {}, }; const workspaceState = { order: [], minimized: new Map(), activeWidgetId: "", snapLeftId: "", snapRightId: "", snapAssistantSourceId: "", }; function withDocumentRoot(root) { return root && typeof root.querySelectorAll === "function" ? root : document; } function toArray(value) { return Array.isArray(value) ? value : Array.from(value || []); } function getGridElement() { return window.gridElement || document.getElementById("grid-stack-main"); } function getTaskbar() { return document.querySelector(TASKBAR_SELECTOR); } function getTaskbarItems() { return document.querySelector(TASKBAR_ITEMS_SELECTOR); } function getWorkspaceStash() { return document.querySelector(WORKSPACE_STASH_SELECTOR); } function getSnapAssistant() { return document.querySelector(SNAP_ASSISTANT_SELECTOR); } function getSnapAssistantOptions() { return document.querySelector(SNAP_ASSISTANT_OPTIONS_SELECTOR); } function getGridViewportMetrics() { const gridElement = getGridElement(); const gridColumn = gridElement && gridElement.parentElement ? gridElement.parentElement : null; const main = gridColumn && gridColumn.parentElement ? gridColumn.parentElement : null; const source = gridColumn || main; const rect = source && source.getBoundingClientRect ? source.getBoundingClientRect() : null; return { width: Math.max(0, Math.floor((rect && rect.width) || window.innerWidth || 0)), height: Math.max(0, Math.floor((rect && rect.height) || window.innerHeight || 0)), }; } function getWidgetNode(widgetId) { const id = String(widgetId || "").trim(); return id ? document.getElementById(id) : null; } function syncKnownWidgetOrder() { const ordered = workspaceState.order.filter(function (widgetId, index, items) { return !!widgetId && items.indexOf(widgetId) === index && hasWidget(widgetId); }); toArray(document.querySelectorAll(".grid-stack-item[id]")).forEach(function (node) { if (!ordered.includes(node.id)) { ordered.push(node.id); } }); workspaceState.order = ordered; return ordered; } function getVisibleWidgetNodes() { const gridElement = getGridElement(); return syncKnownWidgetOrder() .map(getWidgetNode) .filter(function (node) { return !!(node && node.parentElement === gridElement); }); } function getVisibleWidgetIds() { return getVisibleWidgetNodes().map(function (node) { return node.id; }); } function hasWidget(widgetId) { return !!getWidgetNode(widgetId); } function executeWidgetScript(scriptNode) { if (!scriptNode) { return; } const replacement = document.createElement("script"); toArray(scriptNode.attributes).forEach(function (attribute) { replacement.setAttribute(attribute.name, attribute.value); }); replacement.text = scriptNode.textContent || ""; document.body.appendChild(replacement); replacement.remove(); scriptNode.remove(); } function parseAssetList(value) { return String(value || "") .split("|") .map(function (item) { return String(item || "").trim(); }) .filter(Boolean); } function findStylesheet(href) { return toArray(document.querySelectorAll('link[rel="stylesheet"]')).find( function (node) { return node.getAttribute("href") === href; } ); } function findScript(src) { return toArray(document.querySelectorAll("script[src]")).find(function (node) { return node.getAttribute("src") === src; }); } function ensureStylesheet(href) { if (!href) { return Promise.resolve(null); } if (assetPromises.styles[href]) { return assetPromises.styles[href]; } const existing = findStylesheet(href); if (existing) { assetPromises.styles[href] = Promise.resolve(existing); return assetPromises.styles[href]; } assetPromises.styles[href] = new Promise(function (resolve) { const node = document.createElement("link"); node.rel = "stylesheet"; node.href = href; node.addEventListener( "load", function () { resolve(node); }, { once: true } ); node.addEventListener( "error", function () { resolve(node); }, { once: true } ); document.head.appendChild(node); }); return assetPromises.styles[href]; } function ensureScript(src) { if (!src) { return Promise.resolve(null); } if (assetPromises.scripts[src]) { return assetPromises.scripts[src]; } const existing = findScript(src); if (existing) { assetPromises.scripts[src] = Promise.resolve(existing); return assetPromises.scripts[src]; } assetPromises.scripts[src] = new Promise(function (resolve) { const node = document.createElement("script"); node.src = src; node.async = false; node.addEventListener( "load", function () { resolve(node); }, { once: true } ); node.addEventListener( "error", function () { resolve(node); }, { once: true } ); document.head.appendChild(node); }); return assetPromises.scripts[src]; } function ensureContainerAssets(container) { if (!container) { return Promise.resolve(); } const styleHrefs = parseAssetList(container.getAttribute("data-gia-style-hrefs")); const scriptSrcs = parseAssetList(container.getAttribute("data-gia-script-srcs")); const styleTasks = styleHrefs.map(function (href) { return ensureStylesheet(href); }); const scriptChain = scriptSrcs.reduce(function (promise, src) { return promise.then(function () { return ensureScript(src); }); }, Promise.resolve()); return Promise.all(styleTasks).then(function () { return scriptChain; }); } function getWidgetTitle(widgetNode) { if (!widgetNode) { return "Window"; } const heading = widgetNode.querySelector(".gia-widget-title"); const title = String(heading ? heading.textContent || "" : "").trim(); return title || "Window"; } function getWidgetIconClass(widgetNode) { if (!widgetNode) { return "fa-solid fa-window-maximize"; } const icon = widgetNode.querySelector(".gia-widget-heading-icon i"); const value = String(icon ? icon.className || "" : "").trim(); return value || "fa-solid fa-window-maximize"; } function clearSnapAssistant() { workspaceState.snapAssistantSourceId = ""; const assistant = getSnapAssistant(); if (assistant) { assistant.classList.add("is-hidden"); } const options = getSnapAssistantOptions(); if (options) { options.innerHTML = ""; } } function normalizeSnapState() { if (!hasWidget(workspaceState.snapLeftId)) { workspaceState.snapLeftId = ""; } if (!hasWidget(workspaceState.snapRightId)) { workspaceState.snapRightId = ""; } if (workspaceState.snapLeftId && workspaceState.snapLeftId === workspaceState.snapRightId) { workspaceState.snapRightId = ""; } if ( workspaceState.snapAssistantSourceId && workspaceState.snapAssistantSourceId !== workspaceState.snapLeftId ) { workspaceState.snapAssistantSourceId = ""; } } function setActiveWidget(widgetId) { const id = String(widgetId || "").trim(); if (!id) { return; } workspaceState.activeWidgetId = id; toArray(document.querySelectorAll(".grid-stack-item.is-gia-active")).forEach( function (node) { node.classList.remove("is-gia-active"); } ); const widgetNode = getWidgetNode(id); if (widgetNode) { widgetNode.classList.add("is-gia-active"); } renderTaskbar(); } function bindWidgetLifecycle(widgetNode) { if (!widgetNode || widgetNode.dataset.giaWidgetBound === "1") { return; } widgetNode.dataset.giaWidgetBound = "1"; widgetNode.addEventListener("pointerdown", function () { setActiveWidget(widgetNode.id); }); } function registerWidget(widgetNode, insertIndex) { if (!widgetNode || !widgetNode.id) { return; } if (!workspaceState.order.includes(widgetNode.id)) { if (typeof insertIndex === "number" && insertIndex >= 0) { const boundedIndex = Math.min(insertIndex, workspaceState.order.length); workspaceState.order.splice(boundedIndex, 0, widgetNode.id); } else { workspaceState.order.push(widgetNode.id); } } bindWidgetLifecycle(widgetNode); setActiveWidget(widgetNode.id); } function renderTaskbar() { const taskbar = getTaskbar(); const itemsNode = getTaskbarItems(); if (!taskbar || !itemsNode) { return; } const widgetIds = syncKnownWidgetOrder(); itemsNode.innerHTML = ""; if (!widgetIds.length) { taskbar.classList.add("is-hidden"); return; } widgetIds.forEach(function (widgetId) { const widgetNode = getWidgetNode(widgetId); const minimized = workspaceState.minimized.has(widgetId); const meta = minimized ? (workspaceState.minimized.get(widgetId) || {}) : { title: getWidgetTitle(widgetNode), iconClass: getWidgetIconClass(widgetNode), }; const item = document.createElement("li"); if (widgetId === workspaceState.activeWidgetId) { item.classList.add("is-active"); } item.classList.add(minimized ? "is-minimized" : "is-open"); const link = document.createElement("a"); link.href = "#"; link.className = "js-gia-taskbar-item"; link.dataset.giaWidgetId = widgetId; link.dataset.giaWidgetState = minimized ? "minimized" : "open"; link.setAttribute( "aria-label", (minimized ? "Restore " : "Minimize ") + String(meta.title || "window") ); const iconWrap = document.createElement("span"); iconWrap.className = "icon is-small"; const icon = document.createElement("i"); icon.className = String(meta.iconClass || "fa-solid fa-window-maximize"); iconWrap.appendChild(icon); const label = document.createElement("span"); label.textContent = String(meta.title || "Window"); link.appendChild(iconWrap); link.appendChild(label); item.appendChild(link); itemsNode.appendChild(item); }); taskbar.classList.remove("is-hidden"); } function buildSnapAssistantOption(widgetId, widgetNode) { const button = document.createElement("button"); button.type = "button"; button.className = "button is-light js-gia-snap-target"; button.dataset.giaWidgetId = widgetId; const leftGroup = document.createElement("span"); leftGroup.className = "is-inline-flex is-align-items-center"; const iconWrap = document.createElement("span"); iconWrap.className = "icon is-small mr-2"; const icon = document.createElement("i"); icon.className = getWidgetIconClass(widgetNode); iconWrap.appendChild(icon); const label = document.createElement("span"); label.textContent = getWidgetTitle(widgetNode); leftGroup.appendChild(iconWrap); leftGroup.appendChild(label); const actionLabel = document.createElement("span"); actionLabel.textContent = "Use"; button.appendChild(leftGroup); button.appendChild(actionLabel); return button; } function renderSnapAssistant() { const assistant = getSnapAssistant(); const options = getSnapAssistantOptions(); if (!assistant || !options) { return; } normalizeSnapState(); const sourceId = workspaceState.snapAssistantSourceId; if (!sourceId) { clearSnapAssistant(); return; } const candidates = getVisibleWidgetNodes().filter(function (node) { return node.id !== sourceId; }); if (!candidates.length) { clearSnapAssistant(); return; } options.innerHTML = ""; candidates.forEach(function (node) { options.appendChild(buildSnapAssistantOption(node.id, node)); }); assistant.classList.remove("is-hidden"); } function getLayoutProfile() { const isMobile = window.matchMedia(MOBILE_MEDIA_QUERY).matches; const isDesktop = window.matchMedia(DESKTOP_MEDIA_QUERY).matches; const viewport = getGridViewportMetrics(); const canFitQuarterTiles = ( isDesktop && viewport.width >= MIN_QUARTER_TILE_WIDTH * 2 && viewport.height >= MIN_QUARTER_TILE_HEIGHT * 2 ); return { isMobile: isMobile, isDesktop: isDesktop, maxVisible: isMobile ? 1 : canFitQuarterTiles ? 4 : 2, }; } function syncGridMetrics() { if (!window.grid || typeof window.grid.cellHeight !== "function") { return; } const gridElement = getGridElement(); if (!gridElement) { return; } const viewport = getGridViewportMetrics(); const height = Math.max(240, viewport.height || 0); const cellHeight = Math.max(32, Math.floor(height / GRID_ROWS)); gridElement.style.height = height + "px"; window.grid.cellHeight(cellHeight); if (typeof window.grid.column === "function") { window.grid.column(GRID_COLUMNS); } } function buildDefaultLayout(widgetNodes, profile) { if (!widgetNodes.length) { return []; } if (profile.isMobile) { return [ { node: widgetNodes[0], x: 0, y: 0, w: GRID_COLUMNS, h: GRID_ROWS, }, ]; } if (widgetNodes.length === 1) { return [ { node: widgetNodes[0], x: 0, y: 0, w: GRID_COLUMNS, h: GRID_ROWS, }, ]; } if (!profile.isDesktop) { return widgetNodes.slice(0, 2).map(function (node, index) { return { node: node, x: 0, y: index * (GRID_ROWS / 2), w: GRID_COLUMNS, h: GRID_ROWS / 2, }; }); } if (widgetNodes.length === 2) { return widgetNodes.map(function (node, index) { return { node: node, x: index * (GRID_COLUMNS / 2), y: 0, w: GRID_COLUMNS / 2, h: GRID_ROWS, }; }); } return widgetNodes.slice(0, 4).map(function (node, index) { return { node: node, x: (index % 2) * (GRID_COLUMNS / 2), y: Math.floor(index / 2) * (GRID_ROWS / 2), w: GRID_COLUMNS / 2, h: GRID_ROWS / 2, }; }); } function buildSnappedLayout(profile) { if (profile.isMobile) { return null; } const leftNode = getWidgetNode(workspaceState.snapLeftId); const rightNode = getWidgetNode(workspaceState.snapRightId); if (!leftNode || !rightNode) { return null; } if (leftNode.parentElement !== getGridElement()) { return null; } if (rightNode.parentElement !== getGridElement()) { return null; } return [ { node: leftNode, x: 0, y: 0, w: GRID_COLUMNS / 2, h: GRID_ROWS, }, { node: rightNode, x: GRID_COLUMNS / 2, y: 0, w: GRID_COLUMNS / 2, h: GRID_ROWS, }, ]; } function detachWidgetFromGrid(widgetNode) { if ( widgetNode && window.grid && typeof window.grid.removeWidget === "function" && widgetNode.parentElement === getGridElement() ) { window.grid.removeWidget(widgetNode, false, false); } } function attachWidgetToGrid(widgetNode) { if (!widgetNode || !window.grid || !getGridElement()) { return; } widgetNode.classList.remove("is-hidden"); if (widgetNode.parentElement !== getGridElement()) { getGridElement().appendChild(widgetNode); } if (typeof window.grid.makeWidget === "function") { window.grid.makeWidget(widgetNode); } if (window.htmx && typeof window.htmx.process === "function") { window.htmx.process(widgetNode); } } function chooseFallbackActiveWidget() { const visibleIds = getVisibleWidgetIds(); if (!visibleIds.length) { workspaceState.activeWidgetId = ""; return; } workspaceState.activeWidgetId = visibleIds[visibleIds.length - 1]; } function minimizeWidget(widgetId, options) { const config = options || {}; const widgetNode = getWidgetNode(widgetId); if (!widgetNode || workspaceState.minimized.has(widgetId)) { return; } workspaceState.minimized.set(widgetId, { title: getWidgetTitle(widgetNode), iconClass: getWidgetIconClass(widgetNode), ts: Date.now(), }); if (workspaceState.activeWidgetId === widgetId) { chooseFallbackActiveWidget(); } if (workspaceState.snapLeftId === widgetId) { workspaceState.snapLeftId = ""; workspaceState.snapRightId = ""; clearSnapAssistant(); } if (workspaceState.snapRightId === widgetId) { workspaceState.snapRightId = ""; clearSnapAssistant(); } detachWidgetFromGrid(widgetNode); const stash = getWorkspaceStash(); if (stash) { stash.appendChild(widgetNode); } widgetNode.classList.add("is-hidden"); renderTaskbar(); if (!config.skipLayout) { layoutWorkspace(); } } function restoreWidget(widgetId, options) { const config = options || {}; const widgetNode = getWidgetNode(widgetId); if (!widgetNode || !workspaceState.minimized.has(widgetId)) { return; } workspaceState.minimized.delete(widgetId); attachWidgetToGrid(widgetNode); registerWidget(widgetNode); renderTaskbar(); if (!config.skipLayout) { layoutWorkspace(); } } function clearWidgetState(widgetId) { workspaceState.order = workspaceState.order.filter(function (item) { return item !== widgetId; }); workspaceState.minimized.delete(widgetId); if (workspaceState.activeWidgetId === widgetId) { workspaceState.activeWidgetId = ""; } if (workspaceState.snapLeftId === widgetId) { workspaceState.snapLeftId = ""; } if (workspaceState.snapRightId === widgetId) { workspaceState.snapRightId = ""; } if (workspaceState.snapAssistantSourceId === widgetId) { clearSnapAssistant(); } } function removeWidget(widgetId) { const widgetNode = getWidgetNode(widgetId); if (!widgetNode) { return; } clearWidgetState(widgetId); if ( window.grid && typeof window.grid.removeWidget === "function" && widgetNode.parentElement === getGridElement() ) { window.grid.removeWidget(widgetNode, true, false); } else { widgetNode.remove(); } chooseFallbackActiveWidget(); renderTaskbar(); layoutWorkspace(); } function enforceVisibleLimit(profile) { normalizeSnapState(); const snappedLayout = buildSnappedLayout(profile); const visibleIds = getVisibleWidgetIds(); const protectedIds = new Set( [ workspaceState.activeWidgetId, workspaceState.snapLeftId, workspaceState.snapRightId, ].filter(Boolean) ); const maxVisible = snappedLayout ? 2 : profile.maxVisible; if (visibleIds.length <= maxVisible) { return; } const removableIds = visibleIds.filter(function (widgetId) { return !protectedIds.has(widgetId); }); while (getVisibleWidgetIds().length > maxVisible && removableIds.length) { minimizeWidget(removableIds.shift(), { skipLayout: true }); } const remainingIds = getVisibleWidgetIds(); while (remainingIds.length > maxVisible) { const candidate = remainingIds.find(function (widgetId) { return widgetId !== workspaceState.activeWidgetId; }); if (!candidate) { break; } minimizeWidget(candidate, { skipLayout: true }); remainingIds.splice(remainingIds.indexOf(candidate), 1); } } function applyLayout(layoutItems) { if (!window.grid || !layoutItems.length) { return; } syncGridMetrics(); if (typeof window.grid.batchUpdate === "function") { window.grid.batchUpdate(true); } layoutItems.forEach(function (item) { window.grid.update(item.node, { x: item.x, y: item.y, w: item.w, h: item.h, }); }); if (typeof window.grid.batchUpdate === "function") { window.grid.batchUpdate(false); } } function layoutWorkspace() { if (!window.grid || !getGridElement()) { return; } normalizeSnapState(); const profile = getLayoutProfile(); enforceVisibleLimit(profile); renderSnapAssistant(); const snappedLayout = buildSnappedLayout(profile); const layoutItems = snappedLayout || buildDefaultLayout(getVisibleWidgetNodes(), profile); applyLayout(layoutItems); if (!workspaceState.activeWidgetId && layoutItems.length) { setActiveWidget(layoutItems[layoutItems.length - 1].node.id); } else if (workspaceState.activeWidgetId) { const activeNode = getWidgetNode(workspaceState.activeWidgetId); if (activeNode) { activeNode.classList.add("is-gia-active"); } } renderTaskbar(); } function tileWidget(widgetId) { if (!widgetId) { return; } if (workspaceState.snapLeftId === widgetId || workspaceState.snapRightId === widgetId) { workspaceState.snapLeftId = ""; workspaceState.snapRightId = ""; } if (workspaceState.snapAssistantSourceId === widgetId) { clearSnapAssistant(); } setActiveWidget(widgetId); layoutWorkspace(); } function chooseSnapCandidate(excludeId) { const candidates = getVisibleWidgetNodes().filter(function (node) { return node.id !== excludeId; }); if (!candidates.length) { return ""; } const active = candidates.find(function (node) { return node.id === workspaceState.activeWidgetId; }); return active ? active.id : candidates[candidates.length - 1].id; } function snapWidgetLeft(widgetId) { const id = String(widgetId || "").trim(); if (!id) { return; } workspaceState.snapLeftId = id; workspaceState.snapRightId = ""; workspaceState.snapAssistantSourceId = id; setActiveWidget(id); layoutWorkspace(); } function snapWidgetRight(widgetId) { const id = String(widgetId || "").trim(); if (!id) { return; } if (!workspaceState.snapLeftId || workspaceState.snapLeftId === id) { workspaceState.snapLeftId = chooseSnapCandidate(id); } workspaceState.snapRightId = id; clearSnapAssistant(); setActiveWidget(id); layoutWorkspace(); } function chooseSnapRight(widgetId) { const id = String(widgetId || "").trim(); if (!id) { return; } workspaceState.snapRightId = id; clearSnapAssistant(); setActiveWidget(id); layoutWorkspace(); } function replaceExistingWidget(widgetNode) { if (!widgetNode || !widgetNode.id) { return -1; } const existingWidget = document.getElementById(widgetNode.id); if (!existingWidget) { return -1; } const previousIndex = workspaceState.order.indexOf(widgetNode.id); clearWidgetState(widgetNode.id); if ( window.grid && typeof window.grid.removeWidget === "function" && existingWidget.parentElement === getGridElement() ) { window.grid.removeWidget(existingWidget, true, false); } else { existingWidget.remove(); } return previousIndex; } function processWidgetShell(container) { if (!container || container.dataset.giaWidgetProcessed === "1") { return Promise.resolve(); } container.dataset.giaWidgetProcessed = "1"; const widgetNode = container.firstElementChild ? container.firstElementChild.cloneNode(true) : null; if (!widgetNode) { container.remove(); return Promise.resolve(); } const scripts = toArray(widgetNode.querySelectorAll("script")); const previousIndex = replaceExistingWidget(widgetNode); container.remove(); if (!window.grid || !getGridElement()) { return Promise.resolve(); } getGridElement().appendChild(widgetNode); if (typeof window.grid.makeWidget === "function") { window.grid.makeWidget(widgetNode); } else if (typeof window.grid.addWidget === "function") { window.grid.addWidget(widgetNode); } if (window.htmx && typeof window.htmx.process === "function") { window.htmx.process(widgetNode); } window.giaEnableWidgetSpawnButtons(widgetNode); const liveWidget = widgetNode.id ? document.getElementById(widgetNode.id) : widgetNode; registerWidget(liveWidget, previousIndex); return ensureContainerAssets(container).then(function () { scripts.forEach(executeWidgetScript); if (window.GIAComposePanel && typeof window.GIAComposePanel.initAll === "function") { window.GIAComposePanel.initAll(liveWidget); } layoutWorkspace(); }); } function processPendingWidgetShells(root) { const scope = withDocumentRoot(root); const containers = []; if ( scope !== document && typeof scope.matches === "function" && scope.matches(WIDGET_SHELL_SELECTOR) ) { containers.push(scope); } scope.querySelectorAll(WIDGET_SHELL_SELECTOR).forEach(function (node) { containers.push(node); }); Promise.all(containers.map(processWidgetShell)).finally(function () { layoutWorkspace(); window.giaEnableWidgetSpawnButtons(document); }); } function enableFloatingWindowInteractions(windowEl) { if (!windowEl || windowEl.dataset.giaWindowInteractive === "1") { return; } windowEl.dataset.giaWindowInteractive = "1"; windowEl.setAttribute("unmovable", ""); const heading = windowEl.querySelector(".panel-heading"); if (!heading) { return; } let dragging = false; let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; const onMove = function (event) { if (!dragging) { return; } const deltaX = event.clientX - startX; const deltaY = event.clientY - startY; windowEl.style.left = startLeft + deltaX + "px"; windowEl.style.top = startTop + deltaY + "px"; windowEl.style.right = "auto"; windowEl.style.bottom = "auto"; }; const stopDrag = function () { dragging = false; document.removeEventListener("pointermove", onMove); document.removeEventListener("pointerup", stopDrag); }; heading.addEventListener("pointerdown", function (event) { if (event.button !== 0) { return; } const interactive = event.target.closest( "button, a, input, textarea, select, label, .delete, .icon" ); if (interactive) { return; } const windowRect = windowEl.getBoundingClientRect(); windowEl.style.position = "fixed"; startLeft = windowRect.left; startTop = windowRect.top; startX = event.clientX; startY = event.clientY; dragging = true; document.addEventListener("pointermove", onMove); document.addEventListener("pointerup", stopDrag); event.preventDefault(); }); } function positionFloatingWindow(windowEl) { if (!windowEl) { return; } const margin = 12; const rect = windowEl.getBoundingClientRect(); const anchor = window.giaWindowAnchor || null; windowEl.style.position = "fixed"; if (!anchor || Date.now() - anchor.ts > 10000) { const fallbackLeft = Math.max(margin, Math.round((window.innerWidth - rect.width) / 2)); const fallbackTop = Math.max(margin, Math.round((window.innerHeight - rect.height) / 2)); windowEl.style.left = fallbackLeft + "px"; windowEl.style.top = fallbackTop + "px"; return; } const desiredLeftViewport = anchor.left; const desiredTopViewport = anchor.bottom + 6; const maxLeftViewport = window.innerWidth - rect.width - margin; const maxTopViewport = window.innerHeight - rect.height - margin; const boundedLeftViewport = Math.max( margin, Math.min(desiredLeftViewport, maxLeftViewport) ); const boundedTopViewport = Math.max( margin, Math.min(desiredTopViewport, maxTopViewport) ); windowEl.style.left = boundedLeftViewport + "px"; windowEl.style.top = boundedTopViewport + "px"; windowEl.style.right = "auto"; windowEl.style.bottom = "auto"; windowEl.style.transform = "none"; window.giaWindowAnchor = null; } function handleWidgetAction(action, widgetId) { if (!action || !widgetId) { return; } if (action === "close") { removeWidget(widgetId); return; } if (action === "minimize") { minimizeWidget(widgetId); return; } if (action === "tile") { tileWidget(widgetId); return; } if (action === "snap-left") { snapWidgetLeft(widgetId); return; } if (action === "snap-right") { snapWidgetRight(widgetId); } } function initWorkspaceShell() { if (window.giaWorkspaceShellInitialised) { processPendingWidgetShells(document); return; } const gridElement = document.getElementById("grid-stack-main"); if (!gridElement || !window.GridStack) { return; } window.giaWorkspaceShellInitialised = true; window.gridElement = gridElement; window.giaWindowAnchor = null; document.body.classList.add("gia-has-workspace"); document.documentElement.classList.add("gia-has-workspace-root"); window.giaPrepareWidgetTarget = function () { return document.getElementById("widgets-here"); }; window.giaCanSpawnWidgets = function () { return !!( window.grid && typeof window.grid.addWidget === "function" && document.getElementById("widgets-here") ); }; window.giaEnableWidgetSpawnButtons = function (root) { const scope = withDocumentRoot(root); const canSpawn = window.giaCanSpawnWidgets(); scope.querySelectorAll(WIDGET_SPAWN_SELECTOR).forEach(function (button) { const widgetUrl = String( button.getAttribute("data-widget-url") || button.getAttribute("hx-get") || "" ).trim(); const visible = canSpawn && !!widgetUrl; button.classList.toggle("is-hidden", !visible); button.setAttribute("aria-hidden", visible ? "false" : "true"); }); }; window.giaPrepareWindowAnchor = function (trigger) { if (!trigger || !trigger.getBoundingClientRect) { window.giaWindowAnchor = null; return; } const rect = trigger.getBoundingClientRect(); window.giaWindowAnchor = { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height, ts: Date.now(), }; }; window.giaPositionFloatingWindow = positionFloatingWindow; window.giaEnableFloatingWindowInteractions = enableFloatingWindowInteractions; window.giaCompactGrid = layoutWorkspace; window.giaRemoveWidget = removeWidget; window.giaMinimizeWidget = minimizeWidget; window.giaRestoreWidget = restoreWidget; window.giaProcessWidgetShells = processPendingWidgetShells; window.grid = window.GridStack.init( { animate: true, auto: false, cellHeight: 40, column: GRID_COLUMNS, float: false, margin: 8, removable: false, staticGrid: true, }, gridElement ); document.addEventListener("click", function (event) { const spawnTrigger = event.target.closest(WIDGET_SPAWN_SELECTOR); if (spawnTrigger) { window.giaPrepareWidgetTarget(); } const widgetAction = event.target.closest(WIDGET_ACTION_SELECTOR); if (widgetAction) { event.preventDefault(); event.stopPropagation(); handleWidgetAction( String(widgetAction.dataset.giaAction || ""), String(widgetAction.dataset.giaWidgetId || "") ); return; } const taskbarItem = event.target.closest(".js-gia-taskbar-item"); if (taskbarItem) { event.preventDefault(); const widgetId = String(taskbarItem.dataset.giaWidgetId || ""); if (workspaceState.minimized.has(widgetId)) { restoreWidget(widgetId); } else { minimizeWidget(widgetId); } return; } const snapTarget = event.target.closest(".js-gia-snap-target"); if (snapTarget) { event.preventDefault(); chooseSnapRight(String(snapTarget.dataset.giaWidgetId || "")); return; } if (event.target.closest(SNAP_ASSISTANT_CLOSE_SELECTOR)) { event.preventDefault(); clearSnapAssistant(); } }); document.body.addEventListener("htmx:afterSwap", function (event) { const target = (event && event.target) || document; window.giaEnableWidgetSpawnButtons(target); if (((target && target.id) || "") === "widgets-here") { processPendingWidgetShells(target); return; } if (((target && target.id) || "") !== "windows-here") { return; } target.querySelectorAll(".floating-window").forEach(function (floatingWindow) { window.setTimeout(function () { positionFloatingWindow(floatingWindow); enableFloatingWindowInteractions(floatingWindow); }, 0); }); }); document.addEventListener("load-widget", function (event) { processPendingWidgetShells(event && event.detail && event.detail.root); }); document.addEventListener("gia:load-widget", function (event) { processPendingWidgetShells(event && event.detail && event.detail.root); }); window.addEventListener("resize", function () { window.requestAnimationFrame(layoutWorkspace); }); window.giaEnableWidgetSpawnButtons(document); processPendingWidgetShells(document); window.requestAnimationFrame(layoutWorkspace); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initWorkspaceShell); } else { initWorkspaceShell(); } })();