Files
GIA/core/static/js/workspace-shell.js

1891 lines
57 KiB
JavaScript

(function () {
const WIDGET_SHELL_SELECTOR = ".js-gia-widget-shell";
const WIDGET_SPAWN_SELECTOR = ".js-widget-spawn-trigger";
const WIDGET_LOAD_TARGET_SELECTOR = '[hx-target="#widgets-here"], [data-widget-url]';
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: {},
};
let widgetShellProcessingChain = Promise.resolve();
const workspaceState = {
order: [],
minimized: new Map(),
pendingWidgetIds: new Set(),
activeWidgetId: "",
anchorWidgetId: "",
pendingSpawnSourceId: "",
pendingSpawnTs: 0,
lastSpawnedId: "",
snapLeftId: "",
snapRightId: "",
snapTopId: "",
snapBottomId: "",
snapAssistantSourceId: "",
snapAssistantMode: "",
};
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 writeGridPositionAttrs(widgetNode, layoutItem) {
if (!widgetNode || !layoutItem) {
return;
}
widgetNode.setAttribute("gs-id", String(widgetNode.id || ""));
["x", "y", "w", "h"].forEach(function (key) {
const value = Number(layoutItem[key]);
if (!Number.isFinite(value)) {
return;
}
const attr = "gs-" + key;
if ((key === "w" || key === "h") && value <= 1) {
widgetNode.removeAttribute(attr);
return;
}
widgetNode.setAttribute(attr, String(value));
});
}
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 getAnchorWidgetId(visibleIds) {
const ids = Array.isArray(visibleIds) && visibleIds.length
? visibleIds.map(function (value) {
return String(value || "").trim();
})
: getVisibleWidgetIds();
const explicitAnchorId = String(workspaceState.anchorWidgetId || "").trim();
if (
explicitAnchorId
&& ids.includes(explicitAnchorId)
&& !workspaceState.minimized.has(explicitAnchorId)
) {
return explicitAnchorId;
}
return "";
}
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 getWidgetMeta(widgetId) {
const node = getWidgetNode(widgetId);
const minimizedMeta = workspaceState.minimized.get(widgetId) || null;
return {
id: widgetId,
node: node,
minimized: workspaceState.minimized.has(widgetId),
title: minimizedMeta && minimizedMeta.title
? String(minimizedMeta.title)
: getWidgetTitle(node),
iconClass: minimizedMeta && minimizedMeta.iconClass
? String(minimizedMeta.iconClass)
: getWidgetIconClass(node),
};
}
function normalizeComposeWidgetSegment(value) {
const cleaned = String(value || "")
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return cleaned || "none";
}
function buildComposeWidgetIdFromUrl(urlValue) {
const raw = String(urlValue || "").trim();
if (!raw) {
return "";
}
try {
const parsed = new URL(raw, window.location.origin);
if (!/\/compose\/widget\/?$/.test(parsed.pathname)) {
return "";
}
const service = normalizeComposeWidgetSegment(parsed.searchParams.get("service"));
const identifier = normalizeComposeWidgetSegment(
String(parsed.searchParams.get("identifier") || "").split("@", 1)[0]
);
const person = normalizeComposeWidgetSegment(parsed.searchParams.get("person"));
return ["widget", "compose-widget", service, identifier, person].join("-");
} catch (_err) {
return "";
}
}
function getWidgetUrlFromTrigger(trigger) {
if (!trigger || typeof trigger.getAttribute !== "function") {
return "";
}
return String(
trigger.getAttribute("data-widget-url")
|| trigger.getAttribute("hx-get")
|| ""
).trim();
}
function getRequestedWidgetIdFromTrigger(trigger) {
if (!trigger || typeof trigger.getAttribute !== "function") {
return "";
}
const explicitId = String(trigger.getAttribute("data-gia-widget-id") || "").trim();
if (explicitId) {
return explicitId;
}
return buildComposeWidgetIdFromUrl(getWidgetUrlFromTrigger(trigger));
}
function clearPendingWidgetRequest(trigger) {
if (!trigger || !trigger.dataset) {
return;
}
const widgetId = String(trigger.dataset.giaPendingWidgetId || "").trim();
if (widgetId) {
workspaceState.pendingWidgetIds.delete(widgetId);
}
delete trigger.dataset.giaPendingWidgetId;
}
function activateExistingWidget(widgetId) {
const id = String(widgetId || "").trim();
if (!id || !hasWidget(id)) {
return false;
}
if (workspaceState.minimized.has(id)) {
restoreWidget(id);
} else {
setActiveWidget(id);
syncWidgetChrome();
renderTaskbar();
}
if (window.GIAComposePanel && typeof window.GIAComposePanel.scrollWidgetToLatest === "function") {
window.GIAComposePanel.scrollWidgetToLatest(id);
}
return true;
}
function clearSnapAssistant() {
workspaceState.snapAssistantSourceId = "";
workspaceState.snapAssistantMode = "";
const assistant = getSnapAssistant();
if (assistant) {
assistant.classList.add("is-hidden");
}
const options = getSnapAssistantOptions();
if (options) {
options.innerHTML = "";
}
}
function normalizeSnapState() {
if (
workspaceState.anchorWidgetId
&& (
!hasWidget(workspaceState.anchorWidgetId)
|| workspaceState.minimized.has(workspaceState.anchorWidgetId)
)
) {
workspaceState.anchorWidgetId = "";
}
if (!hasWidget(workspaceState.snapLeftId)) {
workspaceState.snapLeftId = "";
}
if (!hasWidget(workspaceState.snapRightId)) {
workspaceState.snapRightId = "";
}
if (!hasWidget(workspaceState.snapTopId)) {
workspaceState.snapTopId = "";
}
if (!hasWidget(workspaceState.snapBottomId)) {
workspaceState.snapBottomId = "";
}
if (workspaceState.snapLeftId && workspaceState.snapLeftId === workspaceState.snapRightId) {
workspaceState.snapRightId = "";
}
if (workspaceState.snapTopId && workspaceState.snapTopId === workspaceState.snapBottomId) {
workspaceState.snapBottomId = "";
}
if (workspaceState.snapTopId || workspaceState.snapBottomId) {
workspaceState.snapLeftId = "";
workspaceState.snapRightId = "";
}
if (workspaceState.snapLeftId || workspaceState.snapRightId) {
workspaceState.snapTopId = "";
workspaceState.snapBottomId = "";
}
if (
workspaceState.snapAssistantSourceId
&& workspaceState.snapAssistantSourceId !== workspaceState.snapLeftId
&& workspaceState.snapAssistantSourceId !== workspaceState.snapTopId
) {
workspaceState.snapAssistantSourceId = "";
workspaceState.snapAssistantMode = "";
}
}
function getSnapAssistantMeta() {
if (workspaceState.snapAssistantMode === "vertical") {
return {
title: "Snap Bottom",
message: "Choose a second window for the bottom side.",
};
}
return {
title: "Snap Right",
message: "Choose a second window for the right side.",
};
}
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");
}
syncWidgetChrome();
renderTaskbar();
}
function syncWidgetChrome() {
const anchorId = getAnchorWidgetId(syncKnownWidgetOrder());
toArray(document.querySelectorAll(".grid-stack-item[id]")).forEach(function (node) {
const widgetId = String(node.id || "").trim();
const spawned = widgetId && widgetId === workspaceState.lastSpawnedId;
const gridNode = node.gridstackNode || null;
const atLeftEdge = !!(
gridNode
&& Number(gridNode.x || 0) === 0
&& Number(gridNode.w || GRID_COLUMNS) < GRID_COLUMNS
);
const atRightEdge = !!(
gridNode
&& Number((gridNode.x || 0) + (gridNode.w || GRID_COLUMNS)) === GRID_COLUMNS
&& Number(gridNode.w || GRID_COLUMNS) < GRID_COLUMNS
);
node.classList.toggle("is-gia-anchor", widgetId === anchorId);
node.classList.toggle("is-gia-spawned", spawned);
node.querySelectorAll('.js-gia-widget-action[data-gia-action="snap-left"]').forEach(
function (button) {
const icon = button.querySelector("i");
const label = atLeftEdge ? "Choose window for right side" : "Snap window left";
button.setAttribute("aria-label", label);
button.setAttribute("title", label);
button.classList.toggle("is-link", atLeftEdge);
if (icon) {
icon.className = atLeftEdge
? "fa-solid fa-table-columns"
: "fa-solid fa-arrow-left";
}
}
);
node.querySelectorAll('.js-gia-widget-action[data-gia-action="snap-right"]').forEach(
function (button) {
const icon = button.querySelector("i");
const label = atRightEdge ? "Choose window for left side" : "Snap window right";
button.setAttribute("aria-label", label);
button.setAttribute("title", label);
button.classList.toggle("is-link", atRightEdge);
if (icon) {
icon.className = atRightEdge
? "fa-solid fa-table-columns"
: "fa-solid fa-arrow-right";
}
}
);
});
}
function flashSpawnedWidget(widgetNode) {
if (!widgetNode || !widgetNode.id) {
return;
}
workspaceState.lastSpawnedId = widgetNode.id;
syncWidgetChrome();
window.setTimeout(function () {
if (workspaceState.lastSpawnedId === widgetNode.id) {
workspaceState.lastSpawnedId = "";
syncWidgetChrome();
}
}, 1800);
}
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 rememberPendingSpawn(trigger) {
const sourceWidget = trigger && trigger.closest
? trigger.closest(".grid-stack-item[id]")
: null;
const sourceId = String(sourceWidget && sourceWidget.id ? sourceWidget.id : "").trim();
workspaceState.pendingSpawnSourceId = sourceId;
workspaceState.pendingSpawnTs = Date.now();
if (sourceId) {
workspaceState.anchorWidgetId = sourceId;
syncWidgetChrome();
renderTaskbar();
}
}
function consumePendingSpawnContext() {
const sourceId = String(workspaceState.pendingSpawnSourceId || "").trim();
const spawnTs = Number(workspaceState.pendingSpawnTs || 0);
workspaceState.pendingSpawnSourceId = "";
workspaceState.pendingSpawnTs = 0;
if (!sourceId || (spawnTs > 0 && Date.now() - spawnTs > 15000)) {
return { sourceId: "" };
}
return { sourceId: sourceId };
}
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);
});
syncWidgetChrome();
taskbar.classList.remove("is-hidden");
}
function buildSnapAssistantOption(widgetMeta) {
const button = document.createElement("button");
button.type = "button";
button.className = "button is-light js-gia-snap-target";
button.dataset.giaWidgetId = widgetMeta.id;
if (widgetMeta.minimized) {
button.dataset.giaWidgetState = "minimized";
}
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 = widgetMeta.iconClass;
iconWrap.appendChild(icon);
const label = document.createElement("span");
label.textContent = widgetMeta.title;
leftGroup.appendChild(iconWrap);
leftGroup.appendChild(label);
const actionLabel = document.createElement("span");
actionLabel.textContent = widgetMeta.minimized ? "Restore" : "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 = syncKnownWidgetOrder().filter(function (widgetId) {
return widgetId !== sourceId && hasWidget(widgetId);
});
if (!candidates.length) {
clearSnapAssistant();
return;
}
options.innerHTML = "";
candidates.forEach(function (widgetId) {
options.appendChild(buildSnapAssistantOption(getWidgetMeta(widgetId)));
});
const meta = getSnapAssistantMeta();
const headingText = assistant.querySelector(".gia-snap-assistant-title");
if (headingText) {
headingText.textContent = meta.title;
}
const bodyText = assistant.querySelector(".gia-snap-assistant-message");
if (bodyText) {
bodyText.textContent = meta.message;
}
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.setAnimation === "function") {
window.grid.setAnimation(!window.matchMedia(MOBILE_MEDIA_QUERY).matches);
}
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,
};
});
}
if (widgetNodes.length === 3) {
const dominantNode = widgetNodes.find(function (node) {
return node.id === workspaceState.activeWidgetId;
}) || widgetNodes[widgetNodes.length - 1];
const secondaryNodes = widgetNodes.filter(function (node) {
return node !== dominantNode;
});
return [
{
node: dominantNode,
x: 0,
y: 0,
w: GRID_COLUMNS / 2,
h: GRID_ROWS,
},
{
node: secondaryNodes[0],
x: GRID_COLUMNS / 2,
y: 0,
w: GRID_COLUMNS / 2,
h: GRID_ROWS / 2,
},
{
node: secondaryNodes[1],
x: GRID_COLUMNS / 2,
y: GRID_ROWS / 2,
w: GRID_COLUMNS / 2,
h: GRID_ROWS / 2,
},
];
}
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 buildWorkspaceLayoutItems(widgetNodes, profile) {
const snappedLayout = buildSnappedLayout(profile);
const anchoredLayout = snappedLayout ? null : buildAnchoredLayout(widgetNodes, profile);
return snappedLayout || anchoredLayout || buildDefaultLayout(widgetNodes, profile);
}
function buildIncomingLayout(widgetNode, spawnSourceId) {
if (!widgetNode) {
return null;
}
const sourceId = String(spawnSourceId || "").trim();
const profile = getLayoutProfile();
const existingNodes = getVisibleWidgetNodes().filter(function (node) {
return node.id !== widgetNode.id;
});
let widgetNodes = existingNodes.slice();
if (sourceId) {
const sourceIndex = widgetNodes.findIndex(function (node) {
return node.id === sourceId;
});
if (sourceIndex >= 0) {
widgetNodes.splice(sourceIndex + 1, 0, widgetNode);
} else {
widgetNodes.push(widgetNode);
}
} else {
widgetNodes.push(widgetNode);
}
const previousSpawnedId = workspaceState.lastSpawnedId;
if (sourceId && sourceId !== widgetNode.id) {
workspaceState.lastSpawnedId = widgetNode.id;
}
const layoutItems = buildWorkspaceLayoutItems(widgetNodes, profile);
workspaceState.lastSpawnedId = previousSpawnedId;
return layoutItems.find(function (item) {
return item && item.node === widgetNode;
}) || null;
}
function buildAnchoredLayout(widgetNodes, profile) {
if (!widgetNodes.length || widgetNodes.length < 2) {
return null;
}
const anchorId = getAnchorWidgetId(
widgetNodes.map(function (node) {
return node.id;
})
);
if (!anchorId) {
return null;
}
const anchorNode = widgetNodes.find(function (node) {
return node.id === anchorId;
});
if (!anchorNode) {
return null;
}
let secondaryNodes = widgetNodes.filter(function (node) {
return node.id !== anchorId;
});
const spawnedIndex = secondaryNodes.findIndex(function (node) {
return node.id === workspaceState.lastSpawnedId;
});
if (spawnedIndex > 0) {
secondaryNodes = [secondaryNodes[spawnedIndex]].concat(
secondaryNodes.filter(function (_node, index) {
return index !== spawnedIndex;
})
);
}
if (!secondaryNodes.length) {
return null;
}
if (profile.isMobile || !profile.isDesktop) {
return [
{
node: anchorNode,
x: 0,
y: 0,
w: GRID_COLUMNS,
h: GRID_ROWS / 2,
},
{
node: secondaryNodes[0],
x: 0,
y: GRID_ROWS / 2,
w: GRID_COLUMNS,
h: GRID_ROWS / 2,
},
];
}
const stackedNodes = secondaryNodes.slice(0, 3);
const layoutItems = [
{
node: anchorNode,
x: 0,
y: 0,
w: GRID_COLUMNS / 2,
h: GRID_ROWS,
},
];
const baseHeight = Math.floor(GRID_ROWS / stackedNodes.length);
let offsetY = 0;
stackedNodes.forEach(function (node, index) {
const height = index === stackedNodes.length - 1
? GRID_ROWS - offsetY
: baseHeight;
layoutItems.push({
node: node,
x: GRID_COLUMNS / 2,
y: offsetY,
w: GRID_COLUMNS / 2,
h: height,
});
offsetY += height;
});
return layoutItems;
}
function buildSnappedLayout(profile) {
const gridElement = getGridElement();
const leftNode = getWidgetNode(workspaceState.snapLeftId);
const rightNode = getWidgetNode(workspaceState.snapRightId);
if (
leftNode
&& rightNode
&& leftNode.parentElement === gridElement
&& rightNode.parentElement === gridElement
&& !profile.isMobile
) {
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,
},
];
}
const topNode = getWidgetNode(workspaceState.snapTopId);
const bottomNode = getWidgetNode(workspaceState.snapBottomId);
if (
!topNode
|| !bottomNode
|| topNode.parentElement !== gridElement
|| bottomNode.parentElement !== gridElement
) {
return null;
}
return [
{
node: topNode,
x: 0,
y: 0,
w: GRID_COLUMNS,
h: GRID_ROWS / 2,
},
{
node: bottomNode,
x: 0,
y: GRID_ROWS / 2,
w: GRID_COLUMNS,
h: GRID_ROWS / 2,
},
];
}
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;
}
const anchorId = getAnchorWidgetId(visibleIds);
workspaceState.activeWidgetId = anchorId || 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.lastSpawnedId === widgetId) {
workspaceState.lastSpawnedId = "";
}
if (workspaceState.snapLeftId === widgetId) {
workspaceState.snapLeftId = "";
workspaceState.snapRightId = "";
clearSnapAssistant();
}
if (workspaceState.snapRightId === widgetId) {
workspaceState.snapRightId = "";
clearSnapAssistant();
}
if (workspaceState.snapTopId === widgetId) {
workspaceState.snapTopId = "";
workspaceState.snapBottomId = "";
clearSnapAssistant();
}
if (workspaceState.snapBottomId === widgetId) {
workspaceState.snapBottomId = "";
clearSnapAssistant();
}
if (workspaceState.anchorWidgetId === widgetId) {
workspaceState.anchorWidgetId = "";
}
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.anchorWidgetId === widgetId) {
workspaceState.anchorWidgetId = "";
}
if (workspaceState.lastSpawnedId === widgetId) {
workspaceState.lastSpawnedId = "";
}
if (workspaceState.snapLeftId === widgetId) {
workspaceState.snapLeftId = "";
}
if (workspaceState.snapRightId === widgetId) {
workspaceState.snapRightId = "";
}
if (workspaceState.snapTopId === widgetId) {
workspaceState.snapTopId = "";
}
if (workspaceState.snapBottomId === widgetId) {
workspaceState.snapBottomId = "";
}
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 anchorId = getAnchorWidgetId(syncKnownWidgetOrder());
const visibleIds = getVisibleWidgetIds();
const hardProtectedIds = new Set([anchorId].filter(Boolean));
const softProtectedIds = new Set(
[
workspaceState.activeWidgetId,
workspaceState.snapLeftId,
workspaceState.snapRightId,
workspaceState.snapTopId,
workspaceState.snapBottomId,
].filter(Boolean)
);
const anchoredMobileSplit = !snappedLayout && !!anchorId && profile.isMobile;
const maxVisible = snappedLayout ? 2 : anchoredMobileSplit ? 2 : profile.maxVisible;
if (visibleIds.length <= maxVisible) {
return;
}
const removableIds = visibleIds.filter(function (widgetId) {
return !hardProtectedIds.has(widgetId) && !softProtectedIds.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 !hardProtectedIds.has(widgetId) && !softProtectedIds.has(widgetId);
}) || remainingIds.find(function (widgetId) {
return !hardProtectedIds.has(widgetId);
});
if (!candidate) {
break;
}
minimizeWidget(candidate, { skipLayout: true });
remainingIds.splice(remainingIds.indexOf(candidate), 1);
}
}
function applyLayout(layoutItems) {
if (!window.grid || !layoutItems.length) {
return;
}
syncGridMetrics();
layoutItems.forEach(function (item) {
writeGridPositionAttrs(item.node, item);
});
const canLoadAtomically = typeof window.grid.load === "function"
&& layoutItems.every(function (item) {
return !!(
item
&& item.node
&& item.node.id
&& item.node.gridstackNode
&& String(item.node.gridstackNode.id || "") === String(item.node.id || "")
);
});
if (canLoadAtomically) {
window.grid.load(
layoutItems.map(function (item) {
return {
id: item.node.id,
x: item.x,
y: item.y,
w: item.w,
h: item.h,
};
}),
false
);
return;
}
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 visibleNodes = getVisibleWidgetNodes();
const layoutItems = buildWorkspaceLayoutItems(visibleNodes, 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.snapTopId === widgetId || workspaceState.snapBottomId === widgetId) {
workspaceState.snapTopId = "";
workspaceState.snapBottomId = "";
}
if (workspaceState.snapAssistantSourceId === widgetId) {
clearSnapAssistant();
}
setActiveWidget(widgetId);
layoutWorkspace();
}
function getSnapCandidateIds(excludeId, options) {
const config = options || {};
const candidates = [];
const pushCandidate = function (widgetId) {
const id = String(widgetId || "").trim();
if (!id || id === excludeId || candidates.includes(id) || !hasWidget(id)) {
return;
}
candidates.push(id);
};
const visibleIds = getVisibleWidgetIds().filter(function (widgetId) {
return widgetId !== excludeId;
});
pushCandidate(getAnchorWidgetId(visibleIds));
pushCandidate(workspaceState.activeWidgetId);
visibleIds.forEach(pushCandidate);
if (config.includeMinimized) {
getLockedWidgetIds().forEach(pushCandidate);
syncKnownWidgetOrder().forEach(function (widgetId) {
if (workspaceState.minimized.has(widgetId)) {
pushCandidate(widgetId);
}
});
}
return candidates;
}
function chooseSnapCandidate(excludeId, options) {
const candidates = getSnapCandidateIds(excludeId, options);
return candidates.length ? candidates[0] : "";
}
function ensureWidgetVisible(widgetId) {
const id = String(widgetId || "").trim();
if (!id) {
return "";
}
if (workspaceState.minimized.has(id)) {
restoreWidget(id, { skipLayout: true });
}
return id;
}
function snapWidgetLeft(widgetId) {
const id = String(widgetId || "").trim();
if (!id) {
return;
}
const candidates = getSnapCandidateIds(id, { includeMinimized: true });
workspaceState.snapTopId = "";
workspaceState.snapBottomId = "";
workspaceState.snapLeftId = id;
if (candidates.length === 1) {
workspaceState.snapRightId = ensureWidgetVisible(candidates[0]);
clearSnapAssistant();
} else {
workspaceState.snapRightId = "";
workspaceState.snapAssistantSourceId = id;
workspaceState.snapAssistantMode = "horizontal";
}
setActiveWidget(id);
layoutWorkspace();
}
function snapWidgetRight(widgetId) {
const id = String(widgetId || "").trim();
if (!id) {
return;
}
if (!workspaceState.snapLeftId || workspaceState.snapLeftId === id) {
workspaceState.snapLeftId = ensureWidgetVisible(
chooseSnapCandidate(id, { includeMinimized: true })
);
}
workspaceState.snapTopId = "";
workspaceState.snapBottomId = "";
workspaceState.snapRightId = id;
clearSnapAssistant();
setActiveWidget(id);
layoutWorkspace();
}
function chooseSnapRight(widgetId) {
const id = String(widgetId || "").trim();
if (!id) {
return;
}
ensureWidgetVisible(id);
workspaceState.snapTopId = "";
workspaceState.snapBottomId = "";
workspaceState.snapRightId = id;
clearSnapAssistant();
setActiveWidget(id);
layoutWorkspace();
}
function snapWidgetTop(widgetId) {
const id = String(widgetId || "").trim();
if (!id) {
return;
}
const partnerId = ensureWidgetVisible(
chooseSnapCandidate(id, { includeMinimized: true })
);
workspaceState.snapLeftId = "";
workspaceState.snapRightId = "";
workspaceState.snapTopId = id;
workspaceState.snapBottomId = partnerId;
clearSnapAssistant();
setActiveWidget(id);
layoutWorkspace();
}
function snapWidgetBottom(widgetId) {
const id = String(widgetId || "").trim();
if (!id) {
return;
}
const partnerId = ensureWidgetVisible(
chooseSnapCandidate(id, { includeMinimized: true })
);
workspaceState.snapLeftId = "";
workspaceState.snapRightId = "";
workspaceState.snapTopId = partnerId;
workspaceState.snapBottomId = id;
clearSnapAssistant();
setActiveWidget(id);
layoutWorkspace();
}
function chooseSnapBottom(widgetId) {
const id = String(widgetId || "").trim();
if (!id) {
return;
}
ensureWidgetVisible(id);
workspaceState.snapLeftId = "";
workspaceState.snapRightId = "";
workspaceState.snapBottomId = 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 spawnContext = consumePendingSpawnContext();
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();
}
const initialLayout = buildIncomingLayout(widgetNode, spawnContext.sourceId);
if (initialLayout) {
writeGridPositionAttrs(widgetNode, initialLayout);
}
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;
if (liveWidget && liveWidget.id) {
workspaceState.pendingWidgetIds.delete(liveWidget.id);
}
let insertIndex = previousIndex;
if (spawnContext.sourceId && spawnContext.sourceId !== liveWidget.id) {
const sourceIndex = workspaceState.order.indexOf(spawnContext.sourceId);
if (sourceIndex >= 0) {
insertIndex = sourceIndex + 1;
}
workspaceState.anchorWidgetId = spawnContext.sourceId;
liveWidget.dataset.giaSpawnSourceId = spawnContext.sourceId;
} else {
delete liveWidget.dataset.giaSpawnSourceId;
}
registerWidget(liveWidget, insertIndex);
return ensureContainerAssets(container).then(function () {
scripts.forEach(executeWidgetScript);
if (window.GIAComposePanel && typeof window.GIAComposePanel.initAll === "function") {
window.GIAComposePanel.initAll(liveWidget);
}
if (spawnContext.sourceId && spawnContext.sourceId !== liveWidget.id) {
flashSpawnedWidget(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);
});
widgetShellProcessingChain = widgetShellProcessingChain
.catch(function () {
return null;
})
.then(function () {
return containers.reduce(function (promise, containerNode) {
return promise.then(function () {
return processWidgetShell(containerNode);
});
}, Promise.resolve());
})
.finally(function () {
layoutWorkspace();
window.giaEnableWidgetSpawnButtons(document);
});
return widgetShellProcessingChain;
}
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-top") {
snapWidgetTop(widgetId);
return;
}
if (action === "snap-right") {
snapWidgetRight(widgetId);
return;
}
if (action === "snap-bottom") {
snapWidgetBottom(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)
|| event.target.closest(WIDGET_LOAD_TARGET_SELECTOR);
if (spawnTrigger) {
rememberPendingSpawn(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();
if (workspaceState.snapAssistantMode === "vertical") {
chooseSnapBottom(String(snapTarget.dataset.giaWidgetId || ""));
} else {
chooseSnapRight(String(snapTarget.dataset.giaWidgetId || ""));
}
return;
}
if (event.target.closest(SNAP_ASSISTANT_CLOSE_SELECTOR)) {
event.preventDefault();
clearSnapAssistant();
}
});
document.body.addEventListener("htmx:beforeRequest", function (event) {
const trigger = event && event.detail ? event.detail.elt : null;
if (!trigger || typeof trigger.getAttribute !== "function") {
return;
}
const target = event.detail ? event.detail.target : null;
const targetId = String((target && target.id) || trigger.getAttribute("hx-target") || "")
.replace(/^#/, "")
.trim();
if (targetId !== "widgets-here") {
return;
}
const widgetId = getRequestedWidgetIdFromTrigger(trigger);
if (!widgetId) {
return;
}
if (workspaceState.pendingWidgetIds.has(widgetId) || hasWidget(widgetId)) {
event.preventDefault();
activateExistingWidget(widgetId);
return;
}
workspaceState.pendingWidgetIds.add(widgetId);
trigger.dataset.giaPendingWidgetId = widgetId;
});
document.body.addEventListener("htmx:afterRequest", function (event) {
const trigger = event && event.detail ? event.detail.elt : null;
if (!trigger || !trigger.dataset || !trigger.dataset.giaPendingWidgetId) {
return;
}
if (event.detail && event.detail.successful) {
return;
}
clearPendingWidgetRequest(trigger);
});
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();
}
})();