first commit
This commit is contained in:
BIN
clickin.wav
Normal file
BIN
clickin.wav
Normal file
Binary file not shown.
BIN
clickout.wav
Normal file
BIN
clickout.wav
Normal file
Binary file not shown.
49
immersion.js
Normal file
49
immersion.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// immersion.js
|
||||||
|
(function () {
|
||||||
|
// Preload audio
|
||||||
|
const clickIn = new Audio('clickin.wav');
|
||||||
|
const clickOut = new Audio('clickout.wav');
|
||||||
|
|
||||||
|
// Optional: make sure they can play multiple times quickly
|
||||||
|
clickIn.preload = 'auto';
|
||||||
|
clickOut.preload = 'auto';
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', () => {
|
||||||
|
// Reset currentTime so rapid clicks restart the sound
|
||||||
|
clickIn.currentTime = 0;
|
||||||
|
clickIn.play().catch(err => {
|
||||||
|
// Ignore if browser blocks autoplay until user interaction
|
||||||
|
console.warn('Click-in sound not played:', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
clickOut.currentTime = 0;
|
||||||
|
clickOut.play().catch(err => {
|
||||||
|
console.warn('Click-out sound not played:', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
windowHooks.push(function (e) {
|
||||||
|
if (e.event === "windowCreate") {
|
||||||
|
const iframe = e.window.iframe;
|
||||||
|
iframe.onload = function () {
|
||||||
|
try {
|
||||||
|
// Access the iframe's global scope
|
||||||
|
iframe.contentWindow.clickIn = clickIn;
|
||||||
|
iframe.contentWindow.clickOut = clickOut;
|
||||||
|
|
||||||
|
// Also attach event listeners inside iframe
|
||||||
|
const doc = iframe.contentDocument;
|
||||||
|
doc.addEventListener('mousedown', () => {
|
||||||
|
clickIn.currentTime = 0;
|
||||||
|
clickIn.play().catch(() => { });
|
||||||
|
});
|
||||||
|
doc.addEventListener('mouseup', () => {
|
||||||
|
clickOut.currentTime = 0;
|
||||||
|
clickOut.play().catch(() => { });
|
||||||
|
});
|
||||||
|
} catch { };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
23
index.html
Normal file
23
index.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>LAMINAX Windower</title>
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>The Laminax OS project needs javascript. Yes even the original terminal version needs it.</noscript>
|
||||||
|
<div id="desktop"></div>
|
||||||
|
<div id="background-layer" class="background-layer-windower"></div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
<!-- Load the shell -->
|
||||||
|
<script src="shell.js"></script>
|
||||||
|
<script>
|
||||||
|
window.addEventListener("DOMContentLoaded",shell.init);
|
||||||
|
</script>
|
||||||
|
<!-- Load OPTIONAL stuff -->
|
||||||
|
<script src="immersion.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
303
script.js
Normal file
303
script.js
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
// 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);
|
||||||
|
|
||||||
|
}
|
||||||
286
shell.js
Normal file
286
shell.js
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
var embeddedApps = {
|
||||||
|
calc: `data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+PGh0bWwgbGFuZz0iZW4iPjxoZWFkPjxtZXRhIGNoYXJzZXQ9IlVURi04Ij48dGl0bGU+Q2FsY3VsYXRvcjwvdGl0bGU+PHN0eWxlPmJvZHl7bWFyZ2luOjA7Zm9udC1mYW1pbHk6bW9ub3NwYWNlLG1vbm9zcGFjZTtkaXNwbGF5OmZsZXg7anVzdGlmeS1jb250ZW50OmNlbnRlcjthbGlnbi1pdGVtczpjZW50ZXI7aGVpZ2h0OjEwMHZoO2JhY2tncm91bmQ6IzIyMjtjb2xvcjojZWVlfSNjYWxjdWxhdG9ye2JhY2tncm91bmQ6IzMzMztwYWRkaW5nOjFyZW07Ym9yZGVyLXJhZGl1czo4cHg7d2lkdGg6MjIwcHg7Ym94LXNoYWRvdzowIDAgMTBweCAjMDAwfSNkaXNwbGF5e3dpZHRoOjEwMCU7aGVpZ2h0OjQwcHg7Zm9udC1zaXplOjEuMnJlbTtiYWNrZ3JvdW5kOiMxMTE7Ym9yZGVyOm5vbmU7Y29sb3I6I2VlZTtwYWRkaW5nOjAgMDttYXJnaW4tYm90dG9tOjEwcHg7dGV4dC1hbGlnbjpyaWdodH1idXR0b257d2lkdGg6NDhweDtoZWlnaHQ6NDhweDttYXJnaW46MnB4O2ZvbnQtc2l6ZToxLjJyZW07Ym9yZGVyOm5vbmU7Ym9yZGVyLXJhZGl1czo0cHg7Y3Vyc29yOnBvaW50ZXI7YmFja2dyb3VuZDojNTU1O2NvbG9yOiNlZWU7dHJhbnNpdGlvbjpiYWNrZ3JvdW5kIC4ycyBlYXNlfWJ1dHRvbjpob3ZlcntiYWNrZ3JvdW5kOiM3Nzd9Lm9wZXJhdG9ye2JhY2tncm91bmQ6I2Y1N2MwMDtjb2xvcjojZmZmfS5vcGVyYXRvcjpob3ZlcntiYWNrZ3JvdW5kOiNmYjhjMDB9LmVxdWFsc3tiYWNrZ3JvdW5kOiMxOTc2ZDI7Y29sb3I6I2ZmZjt3aWR0aDoxMDAlO21hcmdpbi10b3A6NnB4fS5lcXVhbHM6aG92ZXJ7YmFja2dyb3VuZDojMjE5NmYzfTwvc3R5bGU+PC9oZWFkPjxib2R5PjxkaXYgaWQ9ImNhbGN1bGF0b3IiPjxpbnB1dCBpZD0iZGlzcGxheSIgdHlwZT0idGV4dCIgcmVhZG9ubHk9InJlYWRvbmx5Ij48ZGl2PjxidXR0b24gZGF0YS12YWw9IjciPjc8L2J1dHRvbj48YnV0dG9uIGRhdGEtdmFsPSI4Ij44PC9idXR0b24+PGJ1dHRvbiBkYXRhLXZhbD0iOSI+OTwvYnV0dG9uPjxidXR0b24gZGF0YS12YWw9Ii8iIGNsYXNzPSJvcGVyYXRvciI+w7c8L2J1dHRvbj48L2Rpdj48ZGl2PjxidXR0b24gZGF0YS12YWw9IjQiPjQ8L2J1dHRvbj48YnV0dG9uIGRhdGEtdmFsPSI1Ij41PC9idXR0b24+PGJ1dHRvbiBkYXRhLXZhbD0iNiI+NjwvYnV0dG9uPjxidXR0b24gZGF0YS12YWw9IioiIGNsYXNzPSJvcGVyYXRvciI+w5c8L2J1dHRvbj48L2Rpdj48ZGl2PjxidXR0b24gZGF0YS12YWw9IjEiPjE8L2J1dHRvbj48YnV0dG9uIGRhdGEtdmFsPSIyIj4yPC9idXR0b24+PGJ1dHRvbiBkYXRhLXZhbD0iMyI+MzwvYnV0dG9uPjxidXR0b24gZGF0YS12YWw9Ii0iIGNsYXNzPSJvcGVyYXRvciI+4oiSPC9idXR0b24+PC9kaXY+PGRpdj48YnV0dG9uIGRhdGEtdmFsPSIwIj4wPC9idXR0b24+PGJ1dHRvbiBkYXRhLXZhbD0iLiI+LjwvYnV0dG9uPjxidXR0b24gaWQ9ImNsZWFyIj5DPC9idXR0b24+PGJ1dHRvbiBkYXRhLXZhbD0iKyIgY2xhc3M9Im9wZXJhdG9yIj4rPC9idXR0b24+PC9kaXY+PGJ1dHRvbiBpZD0iZXF1YWxzIiBjbGFzcz0iZXF1YWxzIj49PC9idXR0b24+PC9kaXY+PHNjcmlwdD5jb25zdCBkaXNwbGF5ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2Rpc3BsYXknKTsNCiAgY29uc3QgYnV0dG9ucyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJyNjYWxjdWxhdG9yIGJ1dHRvbltkYXRhLXZhbF0nKTsNCiAgY29uc3QgY2xlYXJCdG4gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnY2xlYXInKTsNCiAgY29uc3QgZXF1YWxzQnRuID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2VxdWFscycpOw0KDQogIGJ1dHRvbnMuZm9yRWFjaChidG4gPT4gew0KICAgIGJ0bi5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsICgpID0+IHsNCiAgICAgIGRpc3BsYXkudmFsdWUgKz0gYnRuLmdldEF0dHJpYnV0ZSgnZGF0YS12YWwnKTsNCiAgICB9KTsNCiAgfSk7DQoNCiAgY2xlYXJCdG4uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoKSA9PiB7DQogICAgZGlzcGxheS52YWx1ZSA9ICcnOw0KICB9KTsNCg0KICBlcXVhbHNCdG4uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCAoKSA9PiB7DQogICAgdHJ5IHsNCiAgICAgIC8vIEV2YWx1YXRlIGV4cHJlc3Npb24gc2FmZWx5DQogICAgICAvLyBSZXBsYWNlIG11bHRpcGxpY2F0aW9uIGFuZCBkaXZpc2lvbiBzeW1ib2xzIGZpcnN0IChpbiBjYXNlIHlvdSB3YW50IHRvIHN1cHBvcnQgw7cgYW5kIMOXIGluIGRpc3BsYXkpDQogICAgICBjb25zdCBleHByID0gZGlzcGxheS52YWx1ZS5yZXBsYWNlKC/Dty9nLCAnLycpLnJlcGxhY2UoL8OXL2csICcqJyk7DQogICAgICBjb25zdCByZXN1bHQgPSBGdW5jdGlvbignInVzZSBzdHJpY3QiO3JldHVybiAoJyArIGV4cHIgKyAnKScpKCk7DQogICAgICBkaXNwbGF5LnZhbHVlID0gcmVzdWx0Ow0KICAgIH0gY2F0Y2ggew0KICAgICAgZGlzcGxheS52YWx1ZSA9ICdFcnJvcic7DQogICAgfQ0KICB9KTs8L3NjcmlwdD48L2JvZHk+PC9odG1sPg==`,
|
||||||
|
notepad: "data:text/html;charset=utf-8;base64,PGh0bWw+PGhlYWQ+PHRpdGxlPm5vdGVwYWQ8L3RpdGxlPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Ym9keXttYXJnaW46MH10ZXh0YXJlYXtyZXNpemU6bm9uZTt3aWR0aDo5OCU7aGVpZ2h0Ojk4JTtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6MDtib3JkZXI6bm9uZTttYXJnaW46MSU7Zm9udC1mYW1pbHk6bW9ub3NwYWNlO2ZvbnQtc2l6ZToxZW07b3ZlcmZsb3c6aGlkZGVufXRleHRhcmVhOmZvY3Vze291dGxpbmU6MH08L3N0eWxlPjwvaGVhZD48Ym9keT48dGV4dGFyZWE+PC90ZXh0YXJlYT48L2JvZHk+PC9odG1sPg=="
|
||||||
|
}
|
||||||
|
|
||||||
|
var shell = {
|
||||||
|
taskbar_wind: null,
|
||||||
|
taskbar_size: 25,
|
||||||
|
startmenuWidth: 400,
|
||||||
|
startmenuHeight: 300
|
||||||
|
};
|
||||||
|
|
||||||
|
function doTheBullshitByShellBruh() {
|
||||||
|
const taskbarWind = shell.taskbar_wind;
|
||||||
|
const taskbarSize = shell.taskbar_size;
|
||||||
|
const desktopRect = desktop.getBoundingClientRect();
|
||||||
|
transformWindow(taskbarWind.windID,
|
||||||
|
desktopRect.width,
|
||||||
|
taskbarSize,
|
||||||
|
0,
|
||||||
|
desktopRect.height - taskbarSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.launch = function(app) {
|
||||||
|
if (app === "Notepad") {
|
||||||
|
createWindow(embeddedApps.notepad, {
|
||||||
|
resizable: true,
|
||||||
|
name: "Notepad"
|
||||||
|
}); // Launch notepad
|
||||||
|
} else if (app === "Calculator") {
|
||||||
|
const calc = createWindow(embeddedApps.calc, {
|
||||||
|
resizable: true,
|
||||||
|
name: "Calculator"
|
||||||
|
}); // Launch ZE CALCULATOR
|
||||||
|
calc.minSizeX = 500;
|
||||||
|
calc.minSizeY = 444;
|
||||||
|
updateWindowMin(calc.windID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
shell.updateTaskbar = doTheBullshitByShellBruh;
|
||||||
|
|
||||||
|
shell.init = function () {
|
||||||
|
// Create wind for shell using API
|
||||||
|
const taskbarWind = createWindow("about:blank", {
|
||||||
|
headerless: true,
|
||||||
|
resizable: false,
|
||||||
|
indent: false
|
||||||
|
});
|
||||||
|
|
||||||
|
shell.taskbar_wind = taskbarWind;
|
||||||
|
taskbarWind.minSizeX = 0;
|
||||||
|
taskbarWind.minSizeY = 0;
|
||||||
|
taskbarWind.alwaysOnTop = true;
|
||||||
|
taskbarWind.hideInTaskbar = true;
|
||||||
|
|
||||||
|
// Inject base HTML for taskbar
|
||||||
|
const tbDoc = taskbarWind.iframe.contentDocument || taskbarWind.iframe.contentWindow.document;
|
||||||
|
tbDoc.open();
|
||||||
|
tbDoc.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.start-btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: #555;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.start-btn:hover {
|
||||||
|
background: #777;
|
||||||
|
}
|
||||||
|
.task-list {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.task-item {
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: #444;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.task-item:hover {
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
.clock {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="start-btn" id="startBtn">Start</div>
|
||||||
|
<div class="task-list" id="taskList"></div>
|
||||||
|
<div class="clock" id="clock"></div>
|
||||||
|
<script>
|
||||||
|
function updateClock() {
|
||||||
|
const now = new Date();
|
||||||
|
document.getElementById('clock').textContent =
|
||||||
|
now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
}
|
||||||
|
setInterval(updateClock, 1000);
|
||||||
|
updateClock();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
tbDoc.close();
|
||||||
|
|
||||||
|
shell.updateTaskbar();
|
||||||
|
shell.updateTaskbarContents();
|
||||||
|
|
||||||
|
window.addEventListener('resize', function () {
|
||||||
|
shell.updateTaskbar();
|
||||||
|
}, true);
|
||||||
|
// Add hook
|
||||||
|
windowHooks.push(shell.updateTaskbarContents);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Create start menu window
|
||||||
|
const startMenu = createWindow("about:blank", {
|
||||||
|
headerless: true,
|
||||||
|
resizable: false,
|
||||||
|
indent: false,
|
||||||
|
layer: "foreground",
|
||||||
|
name: "Start Menu"
|
||||||
|
});
|
||||||
|
shell.startMenu = startMenu;
|
||||||
|
startMenu.hideInTaskbar = true;
|
||||||
|
shell.updateTaskbarContents();
|
||||||
|
|
||||||
|
// Inject start menu HTML into its iframe
|
||||||
|
const smDoc = startMenu.iframe.contentDocument || startMenu.iframe.contentWindow.document;
|
||||||
|
smDoc.open();
|
||||||
|
smDoc.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: #222;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.2em;
|
||||||
|
border-bottom: 1px solid #555;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
padding: 6px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
li:hover {
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Start Menu</h2>
|
||||||
|
<ul id="startItems">
|
||||||
|
<li onclick="parent.postMessage({type:'startItem', action:'openApp', app:'Calculator'}, '*')">Calculator</li>
|
||||||
|
<li onclick="parent.postMessage({type:'startItem', action:'openApp', app:'Notepad'}, '*')">Notepad</li>
|
||||||
|
<li onclick="parent.postMessage({type:'startItem', action:'openApp', app:'Settings'}, '*')">Settings</li>
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
smDoc.close();
|
||||||
|
|
||||||
|
// Initially hide the start menu by sizing it to zero and positioning it off-screen
|
||||||
|
transformWindow(startMenu.windID, 0, 0, 0, window.innerHeight);
|
||||||
|
|
||||||
|
// Helper function to show start menu
|
||||||
|
function showStartMenu() {
|
||||||
|
const desktopRect = desktop.getBoundingClientRect();
|
||||||
|
const taskbarHeight = shell.taskbar_size || 40;
|
||||||
|
const width = shell.startmenuWidth;
|
||||||
|
const height = shell.startmenuHeight;
|
||||||
|
const x = 0;
|
||||||
|
const y = desktopRect.height - taskbarHeight - height;
|
||||||
|
transformWindow(startMenu.windID, width, height, x, y,true);
|
||||||
|
focusWindow(startMenu.windID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to hide start menu
|
||||||
|
function hideStartMenu() {
|
||||||
|
transformWindow(startMenu.windID, 0, 0, 0, window.innerHeight + shell.startmenuHeight,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let startMenuVisible = false;
|
||||||
|
|
||||||
|
// Hook start button to toggle start menu
|
||||||
|
const startBtn = tbDoc.getElementById('startBtn');
|
||||||
|
startBtn.addEventListener('click', () => {
|
||||||
|
if (startMenuVisible) {
|
||||||
|
hideStartMenu();
|
||||||
|
startMenuVisible = false;
|
||||||
|
} else {
|
||||||
|
showStartMenu();
|
||||||
|
startMenuVisible = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close start menu when clicking outside (optional)
|
||||||
|
window.addEventListener('mousedown', (e) => {
|
||||||
|
if (!startMenuVisible) return;
|
||||||
|
const smElem = startMenu.windElems.windowDiv;
|
||||||
|
if (!smElem.contains(e.target)) {
|
||||||
|
hideStartMenu();
|
||||||
|
startMenuVisible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optionally, listen for start menu item clicks
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
if (event.data?.type === 'startItem' && event.data.action === 'openApp') {
|
||||||
|
console.log('Open app:', event.data.app);
|
||||||
|
// Close start menu on selection
|
||||||
|
hideStartMenu();
|
||||||
|
startMenuVisible = false;
|
||||||
|
shell.launch(event.data.app);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
hideStartMenu();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
shell.updateTaskbarContents = function () {
|
||||||
|
const taskbarWind = shell.taskbar_wind;
|
||||||
|
if (!taskbarWind) return;
|
||||||
|
|
||||||
|
const tbDoc = taskbarWind.iframe.contentDocument || taskbarWind.iframe.contentWindow.document;
|
||||||
|
const taskList = tbDoc.getElementById('taskList');
|
||||||
|
if (!taskList) return;
|
||||||
|
|
||||||
|
// Clear old tasks
|
||||||
|
taskList.innerHTML = '';
|
||||||
|
|
||||||
|
// Add a button for each open window except the taskbar
|
||||||
|
for (const id in windows) {
|
||||||
|
const winObj = windows[id];
|
||||||
|
if (!winObj || id === taskbarWind.windID) continue;
|
||||||
|
if (winObj.hideInTaskbar) continue;
|
||||||
|
|
||||||
|
const btn = tbDoc.createElement('div');
|
||||||
|
btn.className = 'task-item';
|
||||||
|
btn.textContent = winObj.windElems.title.textContent || 'Window';
|
||||||
|
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
// Focus the clicked window
|
||||||
|
winObj.windElems.windowDiv.style.zIndex = ++zIndexCounter;
|
||||||
|
});
|
||||||
|
|
||||||
|
taskList.appendChild(btn);
|
||||||
|
}
|
||||||
|
};
|
||||||
85
style.css
Normal file
85
style.css
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background: #282c34;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-window-btn {
|
||||||
|
position: fixed;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
z-index: 1000;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#desktop {
|
||||||
|
position: relative;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window {
|
||||||
|
position: absolute;
|
||||||
|
width: 400px;
|
||||||
|
height: 300px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.7);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-header {
|
||||||
|
background: #444;
|
||||||
|
color: white;
|
||||||
|
padding: 6px 10px;
|
||||||
|
cursor: grab;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-header:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-close-btn {
|
||||||
|
background: #ff4d4d;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-iframe {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-layer-windower {
|
||||||
|
z-index: -4;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user