303 lines
9.2 KiB
JavaScript
303 lines
9.2 KiB
JavaScript
// WINDOW MANAGER
|
||
const desktop = document.getElementById('desktop');
|
||
const bglayer = document.getElementById('background-layer');
|
||
const newWindowBtn = document.getElementById('new-window-btn');
|
||
let zIndexCounter = 1;
|
||
|
||
let focusedWindow = null;
|
||
let windows = {};
|
||
let windowHooks = [];
|
||
|
||
function uniqueid() {
|
||
// always start with a letter (for DOM friendlyness)
|
||
var idstr = String.fromCharCode(Math.floor((Math.random() * 25) + 65));
|
||
do {
|
||
// between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
|
||
var ascicode = Math.floor((Math.random() * 42) + 48);
|
||
if (ascicode < 58 || ascicode > 64) {
|
||
// exclude all chars between : (58) and @ (64)
|
||
idstr += String.fromCharCode(ascicode);
|
||
}
|
||
} while (idstr.length < 32);
|
||
|
||
return (idstr);
|
||
}
|
||
|
||
function fadeOut(fadeTarget, speed, callback) {
|
||
let eee = 0;
|
||
var fadeEffect = setInterval(function () {
|
||
if (!fadeTarget.style.opacity) {
|
||
fadeTarget.style.opacity = 1;
|
||
}
|
||
fadeTarget.style.transform = `rotate3d(1, 0, 0, ${eee}deg)`;
|
||
if (fadeTarget.style.opacity > 0) {
|
||
fadeTarget.style.opacity -= 0.1;
|
||
eee += 10;
|
||
} else {
|
||
clearInterval(fadeEffect);
|
||
callback();
|
||
}
|
||
}, speed);
|
||
}
|
||
|
||
function killWind(id) {
|
||
const windowDiv = windows[id]?.windElems?.windowDiv;
|
||
if (typeof (windowDiv) == "undefined") {
|
||
console.warn("Attempted to close undefined window..");
|
||
return;
|
||
};
|
||
fadeOut(windowDiv, 20, function () {
|
||
windowDiv.remove();
|
||
delete windows[id];
|
||
windowHooks.forEach(element => {
|
||
element({ event: "windowDestroy", id: id })
|
||
});
|
||
});
|
||
}
|
||
|
||
function focusWindow(id) {
|
||
const windData = windows[id];
|
||
const windowDiv = windData?.windElems?.windowDiv;
|
||
if (typeof (windData) == "undefined") {
|
||
console.warn("Attempted to focus undefined window..");
|
||
return;
|
||
};
|
||
windowDiv.style.zIndex = zIndexCounter++;
|
||
if (windData.layer == "background") windowDiv.style.zIndex = windowDiv.style.zIndex * -1;
|
||
windowHooks.forEach(element => {
|
||
element({ event: "windowFocused", id: id })
|
||
});
|
||
Object.keys(windows).forEach(key => {
|
||
const element = windows[key];
|
||
if (element.alwaysOnTop) {
|
||
element.windElems.windowDiv.style.zIndex = zIndexCounter + 5;
|
||
}
|
||
});
|
||
focusedWindow = id;
|
||
}
|
||
|
||
function createWindow(url, settings) {
|
||
const windID = uniqueid();
|
||
const name = settings.name;
|
||
const headerless = settings.headerless || false;
|
||
const resizable = settings.resizable !== undefined ? settings.resizable : true;
|
||
const shouldIndent = settings.indent !== undefined ? settings.indent : true;
|
||
const layer = settings.layer || "foreground";
|
||
|
||
const windowDiv = document.createElement('div');
|
||
windowDiv.classList.add('window');
|
||
windowDiv.style.top = '50px';
|
||
if (shouldIndent) windowDiv.style.left = '50px';
|
||
windowDiv.style.zIndex = zIndexCounter++;
|
||
windowDiv.id = windID;
|
||
|
||
// Header
|
||
const header = document.createElement('div');
|
||
header.classList.add('window-header');
|
||
|
||
if (headerless) header.classList.add("window-hidden");
|
||
|
||
const title = document.createElement('div');
|
||
title.classList.add('window-title');
|
||
title.textContent = name || url;
|
||
|
||
const closeBtn = document.createElement('button');
|
||
closeBtn.classList.add('window-close-btn');
|
||
closeBtn.textContent = '×';
|
||
|
||
header.appendChild(title);
|
||
header.appendChild(closeBtn);
|
||
windowDiv.appendChild(header);
|
||
|
||
// iframe
|
||
const iframe = document.createElement('iframe');
|
||
iframe.classList.add('window-iframe');
|
||
iframe.src = url;
|
||
windowDiv.appendChild(iframe);
|
||
iframe.onload = function () {
|
||
try {
|
||
iframe.contentDocument.oncontextmenu = function () {
|
||
return false;
|
||
};
|
||
} catch { };
|
||
};
|
||
|
||
// Add resize handle if resizable
|
||
let resizeHandle = null;
|
||
if (resizable) {
|
||
resizeHandle = document.createElement('div');
|
||
resizeHandle.style.width = '16px';
|
||
resizeHandle.style.height = '16px';
|
||
resizeHandle.style.position = 'absolute';
|
||
resizeHandle.style.right = '0';
|
||
resizeHandle.style.bottom = '0';
|
||
resizeHandle.style.cursor = 'se-resize';
|
||
// Optional: make it visible if you want
|
||
// resizeHandle.style.background = 'rgba(0,0,0,0.2)';
|
||
windowDiv.appendChild(resizeHandle);
|
||
}
|
||
|
||
if (layer == "foreground") desktop.appendChild(windowDiv);
|
||
else bglayer.appendChild(windowDiv);
|
||
|
||
// Dragging functionality
|
||
let isDragging = false;
|
||
let offsetX, offsetY;
|
||
|
||
header.addEventListener('mousedown', (e) => {
|
||
isDragging = true;
|
||
offsetX = e.clientX - windowDiv.getBoundingClientRect().left;
|
||
offsetY = e.clientY - windowDiv.getBoundingClientRect().top;
|
||
focusWindow(windID);
|
||
});
|
||
|
||
// Resizing variables
|
||
let isResizing = false;
|
||
let startWidth, startHeight, startX, startY;
|
||
|
||
window.addEventListener('mouseup', () => {
|
||
isDragging = false;
|
||
isResizing = false;
|
||
});
|
||
|
||
window.addEventListener('mousemove', (e) => {
|
||
if (isDragging) {
|
||
let newX = e.clientX - offsetX;
|
||
let newY = e.clientY - offsetY;
|
||
|
||
transformWindow(windID, null, null, newX, newY);
|
||
|
||
|
||
} else if (isResizing) {
|
||
const dx = e.clientX - startX;
|
||
const dy = e.clientY - startY;
|
||
|
||
const newWidth = startWidth + dx;
|
||
const newHeight = startHeight + dy;
|
||
|
||
transformWindow(windID, newWidth, newHeight);
|
||
}
|
||
});
|
||
|
||
// Resize handle mousedown
|
||
if (resizeHandle) {
|
||
resizeHandle.addEventListener('mousedown', (e) => {
|
||
e.stopPropagation();
|
||
isResizing = true;
|
||
startWidth = windowDiv.offsetWidth;
|
||
startHeight = windowDiv.offsetHeight;
|
||
startX = e.clientX;
|
||
startY = e.clientY;
|
||
focusWindow(windID);
|
||
});
|
||
}
|
||
|
||
// Focus window on click
|
||
windowDiv.addEventListener('mousedown', () => {
|
||
focusWindow(windID);
|
||
});
|
||
|
||
|
||
|
||
// Close window
|
||
closeBtn.addEventListener('click', () => {
|
||
killWind(windID);
|
||
});
|
||
|
||
const windObj = {
|
||
iframe: iframe,
|
||
windElems: {
|
||
closeBtn: closeBtn,
|
||
windowDiv: windowDiv,
|
||
header: header,
|
||
title: title,
|
||
resizeHandle: resizeHandle
|
||
},
|
||
windID: windID,
|
||
minSizeX: 250,
|
||
minSizeY: 150,
|
||
alwaysOnTop: false,
|
||
layer: layer,
|
||
hideInTaskbar: layer == "background"
|
||
};
|
||
|
||
windows[windID] = windObj;
|
||
windowHooks.forEach(element => {
|
||
element({ event: "windowCreate", id: windID, window: windObj })
|
||
});
|
||
return windObj;
|
||
}
|
||
|
||
|
||
function transformWindow(id, sizex, sizey, posx, posy, ignoreRects) {
|
||
const windData = windows[id];
|
||
const windowDiv = windData?.windElems?.windowDiv;
|
||
if (typeof (windData) == "undefined") {
|
||
console.warn("Attempted to transform undefined window..");
|
||
return;
|
||
};
|
||
if (sizex) windowDiv.style.width = Math.max(windData.minSizeX, sizex) + 'px';
|
||
if (sizey) windowDiv.style.height = Math.max(windData.minSizeY, sizey) + 'px';
|
||
const desktopRect = desktop.getBoundingClientRect();
|
||
const winRect = windowDiv.getBoundingClientRect();
|
||
if (posx) {
|
||
const newX = ignoreRects ? posx : Math.max(desktopRect.left, Math.min(posx, desktopRect.right - winRect.width));
|
||
windowDiv.style.left = newX - desktopRect.left + 'px';
|
||
}
|
||
if (posy) {
|
||
const newY = ignoreRects ? posy : Math.max(desktopRect.top, Math.min(posy, desktopRect.bottom - winRect.height));
|
||
windowDiv.style.top = newY - desktopRect.top + 'px';
|
||
}
|
||
|
||
}
|
||
|
||
function getTransform(id) {
|
||
const windData = windows[id];
|
||
const windowDiv = windData?.windElems?.windowDiv;
|
||
if (typeof windData === "undefined" || !windowDiv) {
|
||
console.warn("Attempted to transform undefined window..");
|
||
return;
|
||
}
|
||
|
||
const desktopRect = desktop.getBoundingClientRect();
|
||
|
||
// Extract numeric values from style or fallback to computed styles
|
||
const style = windowDiv.style;
|
||
|
||
// Helper to parse px values safely
|
||
function parsePx(val) {
|
||
if (!val) return 0;
|
||
return Math.floor(parseFloat(val));
|
||
}
|
||
|
||
// Use inline style if present, otherwise computed style
|
||
let topStr = style.top || window.getComputedStyle(windowDiv).top;
|
||
let leftStr = style.left || window.getComputedStyle(windowDiv).left;
|
||
let widthStr = style.width || window.getComputedStyle(windowDiv).width;
|
||
let heightStr = style.height || window.getComputedStyle(windowDiv).height;
|
||
|
||
// Parse numbers from strings like "50px"
|
||
const posy = parsePx(topStr);
|
||
const posx = parsePx(leftStr);
|
||
const sizex = parsePx(widthStr);
|
||
const sizey = parsePx(heightStr);
|
||
|
||
return {
|
||
x: posx,
|
||
y: posy,
|
||
sizex: sizex,
|
||
sizey: sizey
|
||
};
|
||
}
|
||
|
||
|
||
function updateWindowMin(id) {
|
||
const windData = windows[id];
|
||
if (typeof (windData) == "undefined") {
|
||
console.warn("Attempted to update undefined window..");
|
||
return;
|
||
};
|
||
const transform = getTransform(id);
|
||
transformWindow(id,transform.sizex,transform.sizey,transform.posx,transform.posy);
|
||
|
||
} |