Spaces:
Running
Running
Update index.html
Browse files- index.html +579 -503
index.html
CHANGED
@@ -3,13 +3,11 @@
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8" />
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
-
<title>
|
7 |
<style>
|
8 |
-
/* Basic Reset & Fonts */
|
9 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
10 |
body, html { width: 100%; height: 100%; overflow: hidden; font-family: Arial, sans-serif; }
|
11 |
|
12 |
-
/* Desktop */
|
13 |
#desktop {
|
14 |
position: absolute;
|
15 |
width: 100%;
|
@@ -39,7 +37,6 @@
|
|
39 |
font-size: 14px;
|
40 |
}
|
41 |
|
42 |
-
/* Taskbar */
|
43 |
#taskbar {
|
44 |
position: absolute;
|
45 |
bottom: 0;
|
@@ -49,7 +46,6 @@
|
|
49 |
background: rgba(0,0,0,0.8);
|
50 |
z-index: 1000;
|
51 |
}
|
52 |
-
/* Left side of Taskbar (Start button & pinned apps) */
|
53 |
#taskbar-left {
|
54 |
display: flex;
|
55 |
align-items: center;
|
@@ -64,7 +60,6 @@
|
|
64 |
cursor: pointer;
|
65 |
margin-right: 10px;
|
66 |
}
|
67 |
-
/* Pinned Apps on Taskbar */
|
68 |
#taskbar-pinned {
|
69 |
display: flex;
|
70 |
align-items: center;
|
@@ -77,7 +72,6 @@
|
|
77 |
font-size: 18px;
|
78 |
cursor: pointer;
|
79 |
}
|
80 |
-
/* Running Apps: प्रत्येक running window का अलग tab – केवल icon */
|
81 |
#taskbar-windows {
|
82 |
display: flex;
|
83 |
align-items: center;
|
@@ -98,7 +92,6 @@
|
|
98 |
display: inline-block;
|
99 |
}
|
100 |
|
101 |
-
/* Start Menu */
|
102 |
#start-menu {
|
103 |
position: absolute;
|
104 |
bottom: 45px;
|
@@ -117,7 +110,6 @@
|
|
117 |
}
|
118 |
#start-menu li:hover { background: #f0f0f0; }
|
119 |
|
120 |
-
/* Window Styles */
|
121 |
.window {
|
122 |
position: absolute;
|
123 |
width: 400px;
|
@@ -137,16 +129,13 @@
|
|
137 |
display: flex;
|
138 |
align-items: center;
|
139 |
justify-content: space-between;
|
140 |
-
position: relative;
|
141 |
}
|
142 |
.window-header .app-icon {
|
143 |
font-size: 16px;
|
144 |
margin-right: 5px;
|
145 |
-
display: inline-block;
|
146 |
}
|
147 |
.window-header .title {
|
148 |
font-weight: bold;
|
149 |
-
display: inline-block;
|
150 |
}
|
151 |
.window-header .controls button {
|
152 |
background: transparent;
|
@@ -162,7 +151,6 @@
|
|
162 |
max-height: calc(100% - 40px);
|
163 |
}
|
164 |
|
165 |
-
/* Resizer Styles */
|
166 |
.resizer {
|
167 |
position: absolute;
|
168 |
background: transparent;
|
@@ -177,8 +165,7 @@
|
|
177 |
.resizer.bottom-left { bottom: -5px; left: -5px; width: 10px; height: 10px; cursor: nesw-resize; }
|
178 |
.resizer.bottom-right { bottom: -5px; right: -5px; width: 10px; height: 10px; cursor: nwse-resize; }
|
179 |
|
180 |
-
|
181 |
-
#custom-context-menu {
|
182 |
position: absolute;
|
183 |
display: none;
|
184 |
background: #fff;
|
@@ -186,550 +173,592 @@
|
|
186 |
z-index: 5000;
|
187 |
box-shadow: 2px 2px 10px rgba(0,0,0,0.5);
|
188 |
}
|
189 |
-
#custom-context-menu ul { list-style: none; }
|
190 |
-
#custom-context-menu li {
|
191 |
padding: 8px 12px;
|
192 |
cursor: pointer;
|
193 |
}
|
194 |
-
#custom-context-menu li:hover {
|
195 |
background: #f0f0f0;
|
196 |
}
|
197 |
-
|
198 |
-
/* Customizer Form */
|
199 |
-
.customizer-form label {
|
200 |
-
display: block;
|
201 |
-
margin: 5px 0 2px;
|
202 |
-
}
|
203 |
-
.customizer-form input,
|
204 |
-
.customizer-form textarea {
|
205 |
-
width: 100%;
|
206 |
-
padding: 5px;
|
207 |
-
margin-bottom: 10px;
|
208 |
-
}
|
209 |
-
.customizer-form button {
|
210 |
-
padding: 5px 10px;
|
211 |
-
cursor: pointer;
|
212 |
-
}
|
213 |
</style>
|
214 |
</head>
|
215 |
<body>
|
216 |
-
<!-- A style element for custom CSS -->
|
217 |
<style id="custom-css"></style>
|
218 |
-
|
219 |
-
<!-- Desktop -->
|
220 |
<div id="desktop">
|
221 |
-
<div id="desktop-icons">
|
222 |
-
<!-- Built-In App Icons (updated via JS) -->
|
223 |
-
</div>
|
224 |
</div>
|
225 |
-
|
226 |
-
<!-- Taskbar -->
|
227 |
<div id="taskbar">
|
228 |
<div id="taskbar-left">
|
229 |
<button id="start-button">Start</button>
|
230 |
-
<div id="taskbar-pinned">
|
231 |
-
|
232 |
-
</div>
|
233 |
-
<div id="taskbar-windows">
|
234 |
-
<!-- Running windows: प्रत्येक window का एक अलग tab (केवल icon) -->
|
235 |
-
</div>
|
236 |
</div>
|
237 |
</div>
|
238 |
-
|
239 |
-
<!-- Start Menu -->
|
240 |
<div id="start-menu">
|
241 |
-
<ul id="start-menu-list">
|
242 |
-
<!-- Built-In Start Menu Items -->
|
243 |
-
</ul>
|
244 |
</div>
|
245 |
-
|
246 |
-
<!-- Custom Context Menu (Right-Click on Desktop) -->
|
247 |
<div id="custom-context-menu">
|
248 |
<ul>
|
249 |
<li data-custom="openCustomizer">Open Customizer</li>
|
250 |
<li data-custom="reset">Reset Customizations</li>
|
251 |
</ul>
|
252 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
253 |
|
254 |
<script>
|
255 |
-
/*****
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
}
|
276 |
-
}
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
let icon = document.createElement("div");
|
296 |
icon.className = "icon";
|
297 |
icon.setAttribute("data-app", app.id);
|
298 |
icon.innerHTML = `${app.icon || '🔳'}<br>${app.name}`;
|
299 |
-
icon.onclick = () =>
|
300 |
-
|
301 |
-
}
|
302 |
-
});
|
303 |
-
}
|
304 |
|
305 |
-
// Update Start Menu Items
|
306 |
-
function updateStartMenuApps() {
|
307 |
-
const list = document.getElementById("start-menu-list");
|
308 |
-
list.innerHTML = "";
|
309 |
-
builtInApps.forEach(app => {
|
310 |
-
let li = document.createElement("li");
|
311 |
-
li.setAttribute("data-app", app.id);
|
312 |
-
li.innerHTML = `${app.icon} ${app.name}`;
|
313 |
-
li.onclick = () => { openApp(app.id); hideStartMenu(); };
|
314 |
-
list.appendChild(li);
|
315 |
-
});
|
316 |
-
let apps = getInstalledApps();
|
317 |
-
apps.forEach(app => {
|
318 |
-
if (!builtInApps.find(b => b.id === app.id)) {
|
319 |
let li = document.createElement("li");
|
320 |
li.setAttribute("data-app", app.id);
|
321 |
li.innerHTML = `${app.icon || '🔳'} ${app.name}`;
|
322 |
-
li.onclick = () => {
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
builtInApps.forEach(app => {
|
333 |
-
let btn = document.createElement("button");
|
334 |
-
btn.setAttribute("data-app", app.id);
|
335 |
-
btn.title = app.name;
|
336 |
-
btn.innerHTML = `${app.icon}`;
|
337 |
-
btn.onclick = () => openApp(app.id);
|
338 |
-
container.appendChild(btn);
|
339 |
-
});
|
340 |
-
}
|
341 |
-
|
342 |
-
// Update all system apps UI
|
343 |
-
function updateSystemAppsUI() {
|
344 |
-
updateDesktopIcons();
|
345 |
-
updateStartMenuApps();
|
346 |
-
updateTaskbarPinned();
|
347 |
-
}
|
348 |
-
|
349 |
-
function hideStartMenu() {
|
350 |
-
document.getElementById("start-menu").style.display = "none";
|
351 |
-
}
|
352 |
-
|
353 |
-
/***** Start Menu Toggle *****/
|
354 |
-
const startButton = document.getElementById("start-button");
|
355 |
-
const startMenu = document.getElementById("start-menu");
|
356 |
-
startButton.addEventListener("click", (e) => {
|
357 |
-
e.stopPropagation();
|
358 |
-
startMenu.style.display = startMenu.style.display === "block" ? "none" : "block";
|
359 |
-
});
|
360 |
-
document.addEventListener("click", (e) => {
|
361 |
-
if (!startMenu.contains(e.target) && e.target !== startButton) {
|
362 |
-
hideStartMenu();
|
363 |
-
}
|
364 |
-
document.getElementById("custom-context-menu").style.display = "none";
|
365 |
-
});
|
366 |
-
|
367 |
-
/***** Window Creation, Resizing & Controls *****/
|
368 |
-
function createWindow(title, contentHTML = "", customControls = null) {
|
369 |
-
const win = document.createElement("div");
|
370 |
-
win.classList.add("window");
|
371 |
-
win.style.top = "100px";
|
372 |
-
win.style.left = "100px";
|
373 |
-
win.style.width = "400px";
|
374 |
-
win.style.height = "300px";
|
375 |
-
win.dataset.windowId = windowCounter++;
|
376 |
-
win.dataset.maximized = "false";
|
377 |
-
// appId and appIcon can be set later
|
378 |
-
|
379 |
-
const header = document.createElement("div");
|
380 |
-
header.classList.add("window-header");
|
381 |
-
let iconHTML = "";
|
382 |
-
if(win.dataset.appIcon) {
|
383 |
-
iconHTML = `<span class="app-icon">${win.dataset.appIcon}</span>`;
|
384 |
}
|
385 |
-
|
386 |
-
header.innerHTML = `${iconHTML}<span class="title">${title}</span><span class="controls">${controlsHTML}</span>`;
|
387 |
-
win.appendChild(header);
|
388 |
-
|
389 |
-
const content = document.createElement("div");
|
390 |
-
content.classList.add("window-content");
|
391 |
-
content.innerHTML = contentHTML;
|
392 |
-
win.appendChild(content);
|
393 |
-
|
394 |
-
// Add resizers for all edges/corners
|
395 |
-
makeResizable(win);
|
396 |
-
|
397 |
-
document.body.appendChild(win);
|
398 |
-
win.addEventListener("mousedown", () => bringToFront(win));
|
399 |
-
makeDraggable(win, header);
|
400 |
-
bringToFront(win);
|
401 |
-
|
402 |
-
const minimizeBtn = header.querySelector(".minimize-btn");
|
403 |
-
if(minimizeBtn) { minimizeBtn.addEventListener("click", () => minimizeWindow(win)); }
|
404 |
-
const maximizeBtn = header.querySelector(".maximize-btn");
|
405 |
-
if(maximizeBtn) { maximizeBtn.addEventListener("click", () => toggleMaximizeWindow(win, maximizeBtn)); }
|
406 |
-
const closeBtn = header.querySelector(".close");
|
407 |
-
if(closeBtn) { closeBtn.addEventListener("click", () => closeWindow(win)); }
|
408 |
-
|
409 |
-
// **Note:** हम createTaskbarWindowButton को बाद में call करेंगे, जब appIcon सेट हो जाए
|
410 |
-
runningWindows[win.dataset.windowId] = win;
|
411 |
-
return win;
|
412 |
-
}
|
413 |
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
function makeResizable(win) {
|
441 |
-
const directions = ["top", "right", "bottom", "left", "top-left", "top-right", "bottom-left", "bottom-right"];
|
442 |
-
directions.forEach(direction => {
|
443 |
-
const resizer = document.createElement("div");
|
444 |
-
resizer.classList.add("resizer", direction);
|
445 |
-
win.appendChild(resizer);
|
446 |
-
resizer.addEventListener("mousedown", initResize);
|
447 |
-
function initResize(e) {
|
448 |
-
e.preventDefault();
|
449 |
-
e.stopPropagation();
|
450 |
-
const startX = e.clientX;
|
451 |
-
const startY = e.clientY;
|
452 |
-
const startWidth = parseInt(document.defaultView.getComputedStyle(win).width, 10);
|
453 |
-
const startHeight = parseInt(document.defaultView.getComputedStyle(win).height, 10);
|
454 |
-
const startLeft = win.offsetLeft;
|
455 |
-
const startTop = win.offsetTop;
|
456 |
-
const desktopRect = document.getElementById("desktop").getBoundingClientRect();
|
457 |
-
function doResize(e) {
|
458 |
-
let dx = e.clientX - startX;
|
459 |
-
let dy = e.clientY - startY;
|
460 |
-
let newWidth = startWidth;
|
461 |
-
let newHeight = startHeight;
|
462 |
-
let newLeft = startLeft;
|
463 |
-
let newTop = startTop;
|
464 |
-
if(direction.includes("right")) {
|
465 |
-
newWidth = startWidth + dx;
|
466 |
-
}
|
467 |
-
if(direction.includes("left")) {
|
468 |
-
newWidth = startWidth - dx;
|
469 |
-
newLeft = startLeft + dx;
|
470 |
-
}
|
471 |
-
if(direction.includes("bottom")) {
|
472 |
-
newHeight = startHeight + dy;
|
473 |
-
}
|
474 |
-
if(direction.includes("top")) {
|
475 |
-
newHeight = startHeight - dy;
|
476 |
-
newTop = startTop + dy;
|
477 |
-
}
|
478 |
-
if(newLeft < 0) { newWidth += newLeft; newLeft = 0; }
|
479 |
-
if(newTop < 0) { newHeight += newTop; newTop = 0; }
|
480 |
-
if(newLeft + newWidth > desktopRect.width) newWidth = desktopRect.width - newLeft;
|
481 |
-
if(newTop + newHeight > desktopRect.height) newHeight = desktopRect.height - newTop;
|
482 |
-
const minWidth = 100, minHeight = 50;
|
483 |
-
if(newWidth < minWidth) {
|
484 |
-
newWidth = minWidth;
|
485 |
-
if(direction.includes("left")) { newLeft = startLeft + (startWidth - minWidth); }
|
486 |
-
}
|
487 |
-
if(newHeight < minHeight) {
|
488 |
-
newHeight = minHeight;
|
489 |
-
if(direction.includes("top")) { newTop = startTop + (startHeight - minHeight); }
|
490 |
}
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
}
|
496 |
-
function stopResize() {
|
497 |
-
document.removeEventListener("mousemove", doResize);
|
498 |
-
document.removeEventListener("mouseup", stopResize);
|
499 |
}
|
500 |
-
|
501 |
-
document.addEventListener("mouseup", stopResize);
|
502 |
}
|
503 |
-
}
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
514 |
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
|
|
|
|
|
|
|
|
519 |
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
win.dataset.originalTop = win.style.top;
|
529 |
-
win.dataset.originalLeft = win.style.left;
|
530 |
-
win.dataset.originalWidth = win.style.width;
|
531 |
-
win.dataset.originalHeight = win.style.height;
|
532 |
-
win.style.top = "0";
|
533 |
-
win.style.left = "0";
|
534 |
-
win.style.width = "100%";
|
535 |
-
win.style.height = "100%";
|
536 |
-
win.dataset.maximized = "true";
|
537 |
-
}
|
538 |
-
}
|
539 |
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
}
|
548 |
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
}
|
563 |
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
575 |
}
|
576 |
}
|
577 |
-
|
578 |
-
|
579 |
-
/***** Built-In Apps & Open Functions *****/
|
580 |
-
// openApp: if built-in, call respective function; else launch installed app
|
581 |
-
function openApp(appId) {
|
582 |
-
if(appId === "appInstaller") {
|
583 |
-
createAppInstaller();
|
584 |
-
} else if(appId === "customizer") {
|
585 |
-
createCustomizerApp();
|
586 |
-
} else {
|
587 |
-
launchInstalledApp(appId);
|
588 |
-
}
|
589 |
-
}
|
590 |
-
|
591 |
-
// Built-In App: App Installer
|
592 |
-
function createAppInstaller() {
|
593 |
-
const win = createWindow("App Installer", "");
|
594 |
-
win.dataset.appId = "appInstaller";
|
595 |
-
win.dataset.appIcon = "🛠";
|
596 |
-
updateWindowHeaderIcon(win);
|
597 |
-
// अब taskbar button create करें
|
598 |
-
createTaskbarWindowButton(win);
|
599 |
-
const content = document.createElement("div");
|
600 |
-
content.innerHTML = `
|
601 |
-
<h3>Install App</h3>
|
602 |
-
<textarea id="app-json" placeholder="Paste App JSON here"></textarea><br>
|
603 |
-
<button id="install-app-btn">Install</button>
|
604 |
-
<hr>
|
605 |
-
<h3>Installed Apps (LocalStorage)</h3>
|
606 |
-
<div id="installed-apps-list"></div>
|
607 |
-
`;
|
608 |
-
win.querySelector(".window-content").appendChild(content);
|
609 |
-
win.querySelector("#install-app-btn").addEventListener("click", () => {
|
610 |
-
installApp();
|
611 |
-
updateSystemAppsUI();
|
612 |
-
loadInstalledAppsList();
|
613 |
-
});
|
614 |
-
loadInstalledAppsList();
|
615 |
-
}
|
616 |
|
617 |
-
|
618 |
-
|
619 |
-
const win = createWindow("Customizer", "");
|
620 |
-
win.dataset.appId = "customizer";
|
621 |
-
win.dataset.appIcon = "🎨";
|
622 |
-
updateWindowHeaderIcon(win);
|
623 |
-
createTaskbarWindowButton(win);
|
624 |
-
const content = document.createElement("div");
|
625 |
-
content.innerHTML = `
|
626 |
-
<h3>Customize System</h3>
|
627 |
-
<div class="customizer-form">
|
628 |
-
<label for="desktop-bg-color">Desktop Background Color:</label>
|
629 |
-
<input type="color" id="desktop-bg-color" value="#ffffff">
|
630 |
-
<label for="desktop-bg-image">Desktop Background Image URL:</label>
|
631 |
-
<input type="text" id="desktop-bg-image" placeholder="Image URL">
|
632 |
-
<hr>
|
633 |
-
<label for="taskbar-bg-color">Taskbar Background Color:</label>
|
634 |
-
<input type="color" id="taskbar-bg-color" value="#0078d7">
|
635 |
-
<label for="taskbar-height">Taskbar Height (px):</label>
|
636 |
-
<input type="number" id="taskbar-height" value="40">
|
637 |
-
<hr>
|
638 |
-
<label for="startmenu-bg-color">Start Menu Background Color:</label>
|
639 |
-
<input type="color" id="startmenu-bg-color" value="#ffffff">
|
640 |
-
<label for="startmenu-text-color">Start Menu Text Color:</label>
|
641 |
-
<input type="color" id="startmenu-text-color" value="#000000">
|
642 |
-
<hr>
|
643 |
-
<label for="custom-css-input">Custom CSS:</label>
|
644 |
-
<textarea id="custom-css-input" placeholder="Enter your custom CSS here"></textarea><br>
|
645 |
-
<button onclick="applyAllCustomizations()">Apply Customizations</button>
|
646 |
-
</div>
|
647 |
-
`;
|
648 |
-
win.querySelector(".window-content").appendChild(content);
|
649 |
-
}
|
650 |
|
651 |
-
|
652 |
-
|
653 |
-
const
|
654 |
-
|
655 |
-
|
656 |
-
if(desktopBgImage.trim() !== "") { desktopBgStyle = `url('${desktopBgImage}')`; }
|
657 |
-
document.getElementById("desktop").style.background = desktopBgStyle;
|
658 |
-
document.getElementById("desktop").style.backgroundSize = "cover";
|
659 |
-
|
660 |
-
const taskbarBgColor = document.getElementById("taskbar-bg-color").value;
|
661 |
-
const taskbarHeight = document.getElementById("taskbar-height").value;
|
662 |
-
document.getElementById("taskbar").style.background = taskbarBgColor;
|
663 |
-
document.getElementById("taskbar").style.height = taskbarHeight + "px";
|
664 |
-
|
665 |
-
const startmenuBgColor = document.getElementById("startmenu-bg-color").value;
|
666 |
-
const startmenuTextColor = document.getElementById("startmenu-text-color").value;
|
667 |
-
document.getElementById("start-menu").style.background = startmenuBgColor;
|
668 |
-
document.getElementById("start-menu").style.color = startmenuTextColor;
|
669 |
-
|
670 |
-
const customCss = document.getElementById("custom-css-input").value;
|
671 |
-
document.getElementById("custom-css").innerHTML = customCss;
|
672 |
-
|
673 |
-
alert("Customizations applied!");
|
674 |
-
}
|
675 |
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
let appData = JSON.parse(localStorage.getItem(key));
|
680 |
-
if (!appData) return;
|
681 |
-
const win = createWindow(appData.name, appData.ui, appData.windowControls);
|
682 |
-
win.dataset.appId = appData.id;
|
683 |
-
win.dataset.appIcon = appData.icon || '🔳';
|
684 |
-
updateWindowHeaderIcon(win);
|
685 |
-
createTaskbarWindowButton(win);
|
686 |
-
if(appData.methods) {
|
687 |
-
let appMethods = {};
|
688 |
-
for(let method in appData.methods) {
|
689 |
-
try {
|
690 |
-
appMethods[method] = new Function("return " + appData.methods[method])();
|
691 |
-
} catch(e) {
|
692 |
-
console.error("Error in method", method, e);
|
693 |
-
}
|
694 |
-
}
|
695 |
-
window.app = { methods: appMethods };
|
696 |
}
|
697 |
-
|
698 |
-
|
699 |
-
// Load Installed Apps List in App Installer
|
700 |
-
function loadInstalledAppsList() {
|
701 |
-
const container = document.getElementById("installed-apps-list");
|
702 |
-
if(container) {
|
703 |
-
let apps = getInstalledApps();
|
704 |
-
container.innerHTML = apps.map(app => `<div>${app.icon || '🔳'} ${app.name}</div>`).join("");
|
705 |
}
|
706 |
-
|
707 |
-
|
708 |
-
// Install App from JSON input in App Installer
|
709 |
-
function installApp() {
|
710 |
-
let appData;
|
711 |
-
const jsonText = document.getElementById("app-json").value;
|
712 |
-
try { appData = JSON.parse(jsonText); }
|
713 |
-
catch (error) { alert("Invalid JSON. Please check your syntax."); return; }
|
714 |
-
if (!appData.id) { alert("App JSON must contain an 'id' property."); return; }
|
715 |
-
localStorage.setItem("app_" + appData.id, JSON.stringify(appData));
|
716 |
-
alert("App installed successfully!");
|
717 |
-
document.getElementById("app-json").value = "";
|
718 |
-
}
|
719 |
|
720 |
-
/***** Custom Context Menu & Shortcut *****/
|
721 |
-
const customContextMenu = document.getElementById("custom-context-menu");
|
722 |
document.getElementById("desktop").addEventListener("contextmenu", function(e) {
|
723 |
e.preventDefault();
|
|
|
724 |
customContextMenu.style.display = "block";
|
725 |
customContextMenu.style.top = e.clientY + "px";
|
726 |
customContextMenu.style.left = e.clientX + "px";
|
727 |
});
|
728 |
-
|
|
|
729 |
const option = e.target.getAttribute("data-custom");
|
730 |
-
if(option === "openCustomizer") {
|
731 |
-
|
732 |
-
} else if(option === "reset") {
|
733 |
document.getElementById("desktop").style.background = "url('background.jpg') no-repeat center center fixed";
|
734 |
document.getElementById("desktop").style.backgroundSize = "cover";
|
735 |
document.getElementById("taskbar").style.background = "rgba(0,0,0,0.8)";
|
@@ -739,13 +768,60 @@
|
|
739 |
document.getElementById("custom-css").innerHTML = "";
|
740 |
alert("Customizations reset to default!");
|
741 |
}
|
742 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
743 |
});
|
744 |
|
745 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
746 |
document.addEventListener("DOMContentLoaded", () => {
|
747 |
-
|
|
|
748 |
});
|
749 |
</script>
|
750 |
</body>
|
751 |
-
</html>
|
|
|
3 |
<head>
|
4 |
<meta charset="UTF-8" />
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>Custom OS with Fixed FileSystem CRUD</title>
|
7 |
<style>
|
|
|
8 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
9 |
body, html { width: 100%; height: 100%; overflow: hidden; font-family: Arial, sans-serif; }
|
10 |
|
|
|
11 |
#desktop {
|
12 |
position: absolute;
|
13 |
width: 100%;
|
|
|
37 |
font-size: 14px;
|
38 |
}
|
39 |
|
|
|
40 |
#taskbar {
|
41 |
position: absolute;
|
42 |
bottom: 0;
|
|
|
46 |
background: rgba(0,0,0,0.8);
|
47 |
z-index: 1000;
|
48 |
}
|
|
|
49 |
#taskbar-left {
|
50 |
display: flex;
|
51 |
align-items: center;
|
|
|
60 |
cursor: pointer;
|
61 |
margin-right: 10px;
|
62 |
}
|
|
|
63 |
#taskbar-pinned {
|
64 |
display: flex;
|
65 |
align-items: center;
|
|
|
72 |
font-size: 18px;
|
73 |
cursor: pointer;
|
74 |
}
|
|
|
75 |
#taskbar-windows {
|
76 |
display: flex;
|
77 |
align-items: center;
|
|
|
92 |
display: inline-block;
|
93 |
}
|
94 |
|
|
|
95 |
#start-menu {
|
96 |
position: absolute;
|
97 |
bottom: 45px;
|
|
|
110 |
}
|
111 |
#start-menu li:hover { background: #f0f0f0; }
|
112 |
|
|
|
113 |
.window {
|
114 |
position: absolute;
|
115 |
width: 400px;
|
|
|
129 |
display: flex;
|
130 |
align-items: center;
|
131 |
justify-content: space-between;
|
|
|
132 |
}
|
133 |
.window-header .app-icon {
|
134 |
font-size: 16px;
|
135 |
margin-right: 5px;
|
|
|
136 |
}
|
137 |
.window-header .title {
|
138 |
font-weight: bold;
|
|
|
139 |
}
|
140 |
.window-header .controls button {
|
141 |
background: transparent;
|
|
|
151 |
max-height: calc(100% - 40px);
|
152 |
}
|
153 |
|
|
|
154 |
.resizer {
|
155 |
position: absolute;
|
156 |
background: transparent;
|
|
|
165 |
.resizer.bottom-left { bottom: -5px; left: -5px; width: 10px; height: 10px; cursor: nesw-resize; }
|
166 |
.resizer.bottom-right { bottom: -5px; right: -5px; width: 10px; height: 10px; cursor: nwse-resize; }
|
167 |
|
168 |
+
#custom-context-menu, #file-context-menu {
|
|
|
169 |
position: absolute;
|
170 |
display: none;
|
171 |
background: #fff;
|
|
|
173 |
z-index: 5000;
|
174 |
box-shadow: 2px 2px 10px rgba(0,0,0,0.5);
|
175 |
}
|
176 |
+
#custom-context-menu ul, #file-context-menu ul { list-style: none; }
|
177 |
+
#custom-context-menu li, #file-context-menu li {
|
178 |
padding: 8px 12px;
|
179 |
cursor: pointer;
|
180 |
}
|
181 |
+
#custom-context-menu li:hover, #file-context-menu li:hover {
|
182 |
background: #f0f0f0;
|
183 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
</style>
|
185 |
</head>
|
186 |
<body>
|
|
|
187 |
<style id="custom-css"></style>
|
|
|
|
|
188 |
<div id="desktop">
|
189 |
+
<div id="desktop-icons"></div>
|
|
|
|
|
190 |
</div>
|
|
|
|
|
191 |
<div id="taskbar">
|
192 |
<div id="taskbar-left">
|
193 |
<button id="start-button">Start</button>
|
194 |
+
<div id="taskbar-pinned"></div>
|
195 |
+
<div id="taskbar-windows"></div>
|
|
|
|
|
|
|
|
|
196 |
</div>
|
197 |
</div>
|
|
|
|
|
198 |
<div id="start-menu">
|
199 |
+
<ul id="start-menu-list"></ul>
|
|
|
|
|
200 |
</div>
|
|
|
|
|
201 |
<div id="custom-context-menu">
|
202 |
<ul>
|
203 |
<li data-custom="openCustomizer">Open Customizer</li>
|
204 |
<li data-custom="reset">Reset Customizations</li>
|
205 |
</ul>
|
206 |
</div>
|
207 |
+
<div id="file-context-menu">
|
208 |
+
<ul>
|
209 |
+
<li data-action="open">Open</li>
|
210 |
+
<li data-action="rename">Rename</li>
|
211 |
+
<li data-action="delete">Delete</li>
|
212 |
+
</ul>
|
213 |
+
</div>
|
214 |
|
215 |
<script>
|
216 |
+
/***** Core OS Object *****/
|
217 |
+
const OS = {
|
218 |
+
windowCounter: 1,
|
219 |
+
runningWindows: {},
|
220 |
+
zIndexCounter: 300,
|
221 |
+
eventListeners: {},
|
222 |
+
|
223 |
+
// File System API
|
224 |
+
FileSystem: {
|
225 |
+
init: function() {
|
226 |
+
if (!localStorage.getItem('fs_root')) {
|
227 |
+
localStorage.setItem('fs_root', JSON.stringify({
|
228 |
+
name: "Root",
|
229 |
+
type: "directory",
|
230 |
+
contents: []
|
231 |
+
}));
|
232 |
+
}
|
233 |
+
},
|
234 |
+
getRoot: function() {
|
235 |
+
return JSON.parse(localStorage.getItem('fs_root'));
|
236 |
+
},
|
237 |
+
saveRoot: function(root) {
|
238 |
+
console.log("Saving root:", JSON.stringify(root));
|
239 |
+
localStorage.setItem('fs_root', JSON.stringify(root));
|
240 |
+
OS.triggerEvent('fileSystemChanged', root);
|
241 |
+
},
|
242 |
+
addItem: function(name, type, content = "", appId = null, parentPath = "/") {
|
243 |
+
console.log(`Adding ${type}: ${name} to ${parentPath}`);
|
244 |
+
const root = this.getRoot();
|
245 |
+
let parent = parentPath === "/" ? root : this.findItem(parentPath);
|
246 |
+
if (!parent || parent.type !== "directory") {
|
247 |
+
console.error(`Invalid parent directory: ${parentPath}`);
|
248 |
+
return false;
|
249 |
+
}
|
250 |
+
if (parent.contents.some(item => item.name === name)) {
|
251 |
+
console.error(`Item ${name} already exists in ${parentPath}`);
|
252 |
+
return false;
|
253 |
+
}
|
254 |
+
const newItem = {
|
255 |
+
name: name,
|
256 |
+
type: type,
|
257 |
+
created: new Date().toISOString()
|
258 |
+
};
|
259 |
+
if (type === "file") {
|
260 |
+
newItem.content = content;
|
261 |
+
newItem.fileType = "text";
|
262 |
+
newItem.associatedAppId = appId;
|
263 |
+
} else if (type === "directory") {
|
264 |
+
newItem.contents = [];
|
265 |
+
}
|
266 |
+
parent.contents.push(newItem);
|
267 |
+
this.saveRoot(root);
|
268 |
+
OS.triggerEvent('itemAdded', { path: `${parentPath}${name}${type === 'directory' ? '/' : ''}`, item: newItem });
|
269 |
+
return true;
|
270 |
+
},
|
271 |
+
findItem: function(path) {
|
272 |
+
console.log(`Finding item at: ${path}`);
|
273 |
+
if (path === "/") return this.getRoot();
|
274 |
+
const parts = path.split('/').filter(p => p);
|
275 |
+
let current = this.getRoot();
|
276 |
+
for (let part of parts) {
|
277 |
+
current = current.contents.find(item => item.name === part);
|
278 |
+
if (!current) {
|
279 |
+
console.error(`Item not found: ${path}`);
|
280 |
+
return null;
|
281 |
+
}
|
282 |
+
}
|
283 |
+
return current;
|
284 |
+
},
|
285 |
+
removeItem: function(path) {
|
286 |
+
console.log(`Removing item: ${path}`);
|
287 |
+
const parent = this.getParent(path);
|
288 |
+
if (!parent) {
|
289 |
+
console.error(`Parent not found for: ${path}`);
|
290 |
+
return false;
|
291 |
+
}
|
292 |
+
const itemName = path.split('/').filter(p => p).pop();
|
293 |
+
const index = parent.contents.findIndex(item => item.name === itemName);
|
294 |
+
if (index === -1) {
|
295 |
+
console.error(`Item ${itemName} not found in parent`);
|
296 |
+
return false;
|
297 |
+
}
|
298 |
+
parent.contents.splice(index, 1);
|
299 |
+
this.saveRoot(this.getRoot());
|
300 |
+
OS.triggerEvent('itemRemoved', { path });
|
301 |
+
return true;
|
302 |
+
},
|
303 |
+
renameItem: function(oldPath, newName) {
|
304 |
+
console.log(`Renaming ${oldPath} to ${newName}`);
|
305 |
+
const item = this.findItem(oldPath);
|
306 |
+
if (!item) {
|
307 |
+
console.error(`Item not found: ${oldPath}`);
|
308 |
+
return false;
|
309 |
+
}
|
310 |
+
const parent = this.getParent(oldPath);
|
311 |
+
if (!parent) {
|
312 |
+
console.error(`Parent not found for: ${oldPath}`);
|
313 |
+
return false;
|
314 |
+
}
|
315 |
+
if (parent.contents.some(i => i.name === newName && i !== item)) {
|
316 |
+
console.error(`Name ${newName} already exists in directory`);
|
317 |
+
return false;
|
318 |
+
}
|
319 |
+
item.name = newName;
|
320 |
+
this.saveRoot(this.getRoot());
|
321 |
+
const newPath = `${parentPath(oldPath)}${newName}${item.type === 'directory' ? '/' : ''}`;
|
322 |
+
OS.triggerEvent('itemRenamed', { oldPath, newPath });
|
323 |
+
return true;
|
324 |
+
},
|
325 |
+
getParent: function(path) {
|
326 |
+
console.log(`Getting parent of: ${path}`);
|
327 |
+
if (path === "/") return null;
|
328 |
+
const parts = path.split('/').filter(p => p);
|
329 |
+
parts.pop();
|
330 |
+
return parts.length === 0 ? this.getRoot() : this.findItem("/" + parts.join('/'));
|
331 |
+
},
|
332 |
+
readFile: function(path) {
|
333 |
+
console.log(`Reading file: ${path}`);
|
334 |
+
const file = this.findItem(path);
|
335 |
+
if (file && file.type === "file") {
|
336 |
+
return file.content;
|
337 |
+
}
|
338 |
+
console.error(`File not found or not a file: ${path}`);
|
339 |
+
return null;
|
340 |
+
},
|
341 |
+
writeFile: function(path, content) {
|
342 |
+
console.log(`Writing to file: ${path}`);
|
343 |
+
const file = this.findItem(path);
|
344 |
+
if (!file || file.type !== "file") {
|
345 |
+
console.error(`File not found or not a file: ${path}`);
|
346 |
+
return false;
|
347 |
+
}
|
348 |
+
file.content = content;
|
349 |
+
this.saveRoot(this.getRoot());
|
350 |
+
OS.triggerEvent('fileWritten', { path, content });
|
351 |
+
return true;
|
352 |
}
|
353 |
+
},
|
354 |
+
|
355 |
+
// Window Management API (unchanged for brevity, assumed working)
|
356 |
+
WindowManager: {
|
357 |
+
createWindow: function(title, contentHTML = "", options = {}) {
|
358 |
+
const win = document.createElement("div");
|
359 |
+
win.classList.add("window");
|
360 |
+
win.style.top = options.top || "100px";
|
361 |
+
win.style.left = options.left || "100px";
|
362 |
+
win.style.width = options.width || "400px";
|
363 |
+
win.style.height = options.height || "300px";
|
364 |
+
win.dataset.windowId = OS.windowCounter++;
|
365 |
+
win.dataset.maximized = "false";
|
366 |
+
win.dataset.appId = options.appId || '';
|
367 |
+
|
368 |
+
const header = document.createElement("div");
|
369 |
+
header.classList.add("window-header");
|
370 |
+
const controlsHTML = options.controls || "<button class='minimize-btn'>_</button><button class='maximize-btn'>[ ]</button><button class='close'>X</button>";
|
371 |
+
header.innerHTML = `<span class="app-icon">${options.icon || ''}</span><span class="title">${title}</span><span class="controls">${controlsHTML}</span>`;
|
372 |
+
win.appendChild(header);
|
373 |
+
|
374 |
+
const content = document.createElement("div");
|
375 |
+
content.classList.add("window-content");
|
376 |
+
content.innerHTML = contentHTML;
|
377 |
+
win.appendChild(content);
|
378 |
+
|
379 |
+
OS.makeResizable(win);
|
380 |
+
document.body.appendChild(win);
|
381 |
+
win.addEventListener("mousedown", () => OS.WindowManager.bringToFront(win));
|
382 |
+
OS.makeDraggable(win, header);
|
383 |
+
OS.WindowManager.bringToFront(win);
|
384 |
+
|
385 |
+
const minimizeBtn = header.querySelector(".minimize-btn");
|
386 |
+
if(minimizeBtn) minimizeBtn.addEventListener("click", () => OS.WindowManager.minimizeWindow(win));
|
387 |
+
const maximizeBtn = header.querySelector(".maximize-btn");
|
388 |
+
if(maximizeBtn) maximizeBtn.addEventListener("click", () => OS.WindowManager.toggleMaximizeWindow(win));
|
389 |
+
const closeBtn = header.querySelector(".close");
|
390 |
+
if(closeBtn) closeBtn.addEventListener("click", () => OS.WindowManager.closeWindow(win));
|
391 |
+
|
392 |
+
OS.runningWindows[win.dataset.windowId] = win;
|
393 |
+
OS.createTaskbarWindowButton(win);
|
394 |
+
OS.triggerEvent('windowCreated', { windowId: win.dataset.windowId });
|
395 |
+
return win;
|
396 |
+
},
|
397 |
+
minimizeWindow: function(win) { win.style.display = "none"; OS.triggerEvent('windowMinimized', { windowId: win.dataset.windowId }); },
|
398 |
+
maximizeWindow: function(win) {
|
399 |
+
if (win.dataset.maximized === "true") {
|
400 |
+
win.style.top = win.dataset.originalTop;
|
401 |
+
win.style.left = win.dataset.originalLeft;
|
402 |
+
win.style.width = win.dataset.originalWidth;
|
403 |
+
win.style.height = win.dataset.originalHeight;
|
404 |
+
win.dataset.maximized = "false";
|
405 |
+
} else {
|
406 |
+
win.dataset.originalTop = win.style.top;
|
407 |
+
win.dataset.originalLeft = win.style.left;
|
408 |
+
win.dataset.originalWidth = win.style.width;
|
409 |
+
win.dataset.originalHeight = win.style.height;
|
410 |
+
win.style.top = "0";
|
411 |
+
win.style.left = "0";
|
412 |
+
win.style.width = "100%";
|
413 |
+
win.style.height = "100%";
|
414 |
+
win.dataset.maximized = "true";
|
415 |
+
}
|
416 |
+
OS.triggerEvent('windowMaximized', { windowId: win.dataset.windowId, maximized: win.dataset.maximized === "true" });
|
417 |
+
},
|
418 |
+
toggleMaximizeWindow: function(win) { this.maximizeWindow(win); },
|
419 |
+
closeWindow: function(win) {
|
420 |
+
const winId = win.dataset.windowId;
|
421 |
+
const taskbarWindows = document.getElementById("taskbar-windows");
|
422 |
+
const btn = taskbarWindows.querySelector(`button[data-window-id="${winId}"]`);
|
423 |
+
if(btn) btn.remove();
|
424 |
+
delete OS.runningWindows[winId];
|
425 |
+
win.remove();
|
426 |
+
OS.triggerEvent('windowClosed', { windowId: winId });
|
427 |
+
},
|
428 |
+
bringToFront: function(win) {
|
429 |
+
OS.zIndexCounter++;
|
430 |
+
win.style.zIndex = OS.zIndexCounter;
|
431 |
+
OS.triggerEvent('windowFocused', { windowId: win.dataset.windowId });
|
432 |
+
},
|
433 |
+
getWindow: function(windowId) { return OS.runningWindows[windowId]; }
|
434 |
+
},
|
435 |
+
|
436 |
+
// App Management API (unchanged for brevity)
|
437 |
+
AppManager: {
|
438 |
+
apps: {},
|
439 |
+
registerApp: function(appId, appDef) {
|
440 |
+
this.apps[appId] = appDef;
|
441 |
+
localStorage.setItem(`app_${appId}`, JSON.stringify(appDef));
|
442 |
+
OS.triggerEvent('appRegistered', { appId });
|
443 |
+
OS.updateSystemAppsUI();
|
444 |
+
},
|
445 |
+
launchApp: function(appId, options = {}) {
|
446 |
+
const appDef = this.apps[appId] || JSON.parse(localStorage.getItem(`app_${appId}`));
|
447 |
+
if (!appDef) return null;
|
448 |
+
const win = OS.WindowManager.createWindow(appDef.name, appDef.ui || '', {
|
449 |
+
appId: appId,
|
450 |
+
icon: appDef.icon || '🔳',
|
451 |
+
controls: appDef.windowControls
|
452 |
+
});
|
453 |
+
if (appDef.init) appDef.init(win, options);
|
454 |
+
OS.triggerEvent('appLaunched', { appId, windowId: win.dataset.windowId });
|
455 |
+
return win;
|
456 |
+
}
|
457 |
+
},
|
458 |
+
|
459 |
+
// Event System
|
460 |
+
on: function(eventName, callback) {
|
461 |
+
if (!this.eventListeners[eventName]) this.eventListeners[eventName] = [];
|
462 |
+
this.eventListeners[eventName].push(callback);
|
463 |
+
},
|
464 |
+
triggerEvent: function(eventName, data) {
|
465 |
+
if (this.eventListeners[eventName]) {
|
466 |
+
this.eventListeners[eventName].forEach(callback => callback(data));
|
467 |
+
}
|
468 |
+
},
|
469 |
+
|
470 |
+
// Utility Functions (unchanged for brevity)
|
471 |
+
makeDraggable: function(win, handle) {
|
472 |
+
let offsetX = 0, offsetY = 0;
|
473 |
+
handle.addEventListener("mousedown", function(e) {
|
474 |
+
if (win.dataset.maximized === "true") return;
|
475 |
+
offsetX = e.clientX - win.offsetLeft;
|
476 |
+
offsetY = e.clientY - win.offsetTop;
|
477 |
+
document.addEventListener("mousemove", mouseMove);
|
478 |
+
document.addEventListener("mouseup", mouseUp);
|
479 |
+
});
|
480 |
+
function mouseMove(e) {
|
481 |
+
let newLeft = e.clientX - offsetX;
|
482 |
+
let newTop = e.clientY - offsetY;
|
483 |
+
const desktopRect = document.getElementById("desktop").getBoundingClientRect();
|
484 |
+
newLeft = Math.max(0, Math.min(newLeft, desktopRect.width - win.offsetWidth));
|
485 |
+
newTop = Math.max(0, Math.min(newTop, desktopRect.height - win.offsetHeight));
|
486 |
+
win.style.left = newLeft + "px";
|
487 |
+
win.style.top = newTop + "px";
|
488 |
+
}
|
489 |
+
function mouseUp() {
|
490 |
+
document.removeEventListener("mousemove", mouseMove);
|
491 |
+
document.removeEventListener("mouseup", mouseUp);
|
492 |
+
}
|
493 |
+
},
|
494 |
+
makeResizable: function(win) {
|
495 |
+
const directions = ["top", "right", "bottom", "left", "top-left", "top-right", "bottom-left", "bottom-right"];
|
496 |
+
directions.forEach(direction => {
|
497 |
+
const resizer = document.createElement("div");
|
498 |
+
resizer.classList.add("resizer", direction);
|
499 |
+
win.appendChild(resizer);
|
500 |
+
resizer.addEventListener("mousedown", initResize);
|
501 |
+
function initResize(e) {
|
502 |
+
e.preventDefault();
|
503 |
+
e.stopPropagation();
|
504 |
+
const startX = e.clientX;
|
505 |
+
const startY = e.clientY;
|
506 |
+
const startWidth = parseInt(document.defaultView.getComputedStyle(win).width, 10);
|
507 |
+
const startHeight = parseInt(document.defaultView.getComputedStyle(win).height, 10);
|
508 |
+
const startLeft = win.offsetLeft;
|
509 |
+
const startTop = win.offsetTop;
|
510 |
+
const desktopRect = document.getElementById("desktop").getBoundingClientRect();
|
511 |
+
function doResize(e) {
|
512 |
+
let dx = e.clientX - startX;
|
513 |
+
let dy = e.clientY - startY;
|
514 |
+
let newWidth = startWidth;
|
515 |
+
let newHeight = startHeight;
|
516 |
+
let newLeft = startLeft;
|
517 |
+
let newTop = startTop;
|
518 |
+
if(direction.includes("right")) newWidth = startWidth + dx;
|
519 |
+
if(direction.includes("left")) { newWidth = startWidth - dx; newLeft = startLeft + dx; }
|
520 |
+
if(direction.includes("bottom")) newHeight = startHeight + dy;
|
521 |
+
if(direction.includes("top")) { newHeight = startHeight - dy; newTop = startTop + dy; }
|
522 |
+
if(newLeft < 0) { newWidth += newLeft; newLeft = 0; }
|
523 |
+
if(newTop < 0) { newHeight += newTop; newTop = 0; }
|
524 |
+
if(newLeft + newWidth > desktopRect.width) newWidth = desktopRect.width - newLeft;
|
525 |
+
if(newTop + newHeight > desktopRect.height) newHeight = desktopRect.height - newTop;
|
526 |
+
const minWidth = 100, minHeight = 50;
|
527 |
+
newWidth = Math.max(minWidth, newWidth);
|
528 |
+
newHeight = Math.max(minHeight, newHeight);
|
529 |
+
win.style.width = newWidth + "px";
|
530 |
+
win.style.height = newHeight + "px";
|
531 |
+
win.style.left = newLeft + "px";
|
532 |
+
win.style.top = newTop + "px";
|
533 |
+
}
|
534 |
+
function stopResize() {
|
535 |
+
document.removeEventListener("mousemove", doResize);
|
536 |
+
document.removeEventListener("mouseup", stopResize);
|
537 |
+
}
|
538 |
+
document.addEventListener("mousemove", doResize);
|
539 |
+
document.addEventListener("mouseup", stopResize);
|
540 |
+
}
|
541 |
+
});
|
542 |
+
},
|
543 |
+
createTaskbarWindowButton: function(win) {
|
544 |
+
const taskbarWindows = document.getElementById("taskbar-windows");
|
545 |
+
let btn = document.createElement("button");
|
546 |
+
btn.classList.add("taskbar-window-btn");
|
547 |
+
btn.setAttribute("data-window-id", win.dataset.windowId);
|
548 |
+
let icon = win.querySelector('.app-icon')?.innerText || '🔳';
|
549 |
+
btn.innerHTML = `<span class="taskbar-icon">${icon}</span>`;
|
550 |
+
btn.onclick = () => {
|
551 |
+
if(win.style.display === "none") {
|
552 |
+
win.style.display = "block";
|
553 |
+
OS.WindowManager.bringToFront(win);
|
554 |
+
} else {
|
555 |
+
OS.WindowManager.bringToFront(win);
|
556 |
+
}
|
557 |
+
};
|
558 |
+
taskbarWindows.appendChild(btn);
|
559 |
+
},
|
560 |
+
updateSystemAppsUI: function() {
|
561 |
+
const desktopIcons = document.getElementById("desktop-icons");
|
562 |
+
const startMenuList = document.getElementById("start-menu-list");
|
563 |
+
const taskbarPinned = document.getElementById("taskbar-pinned");
|
564 |
+
desktopIcons.innerHTML = startMenuList.innerHTML = taskbarPinned.innerHTML = "";
|
565 |
+
|
566 |
+
const allApps = { ...builtInApps.reduce((acc, app) => ({ ...acc, [app.id]: app }), {}), ...OS.AppManager.apps };
|
567 |
+
Object.values(allApps).forEach(app => {
|
568 |
let icon = document.createElement("div");
|
569 |
icon.className = "icon";
|
570 |
icon.setAttribute("data-app", app.id);
|
571 |
icon.innerHTML = `${app.icon || '🔳'}<br>${app.name}`;
|
572 |
+
icon.onclick = () => OS.AppManager.launchApp(app.id);
|
573 |
+
desktopIcons.appendChild(icon);
|
|
|
|
|
|
|
574 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
575 |
let li = document.createElement("li");
|
576 |
li.setAttribute("data-app", app.id);
|
577 |
li.innerHTML = `${app.icon || '🔳'} ${app.name}`;
|
578 |
+
li.onclick = () => { OS.AppManager.launchApp(app.id); hideStartMenu(); };
|
579 |
+
startMenuList.appendChild(li);
|
580 |
+
|
581 |
+
let btn = document.createElement("button");
|
582 |
+
btn.setAttribute("data-app", app.id);
|
583 |
+
btn.title = app.name;
|
584 |
+
btn.innerHTML = `${app.icon || '🔳'}`;
|
585 |
+
btn.onclick = () => OS.AppManager.launchApp(app.id);
|
586 |
+
taskbarPinned.appendChild(btn);
|
587 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
588 |
}
|
589 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
590 |
|
591 |
+
/***** Built-in Apps *****/
|
592 |
+
const builtInApps = [
|
593 |
+
{
|
594 |
+
id: "appInstaller",
|
595 |
+
name: "App Installer",
|
596 |
+
icon: "🛠",
|
597 |
+
init: function(win) {
|
598 |
+
win.querySelector(".window-content").innerHTML = `
|
599 |
+
<h3>Install App</h3>
|
600 |
+
<textarea id="app-json" placeholder="Paste App JSON here"></textarea><br>
|
601 |
+
<button id="install-app-btn">Install</button>
|
602 |
+
<hr>
|
603 |
+
<h3>Installed Apps</h3>
|
604 |
+
<div id="installed-apps-list"></div>
|
605 |
+
`;
|
606 |
+
win.querySelector("#install-app-btn").addEventListener("click", () => {
|
607 |
+
const jsonText = win.querySelector("#app-json").value;
|
608 |
+
try {
|
609 |
+
const appData = JSON.parse(jsonText);
|
610 |
+
if (!appData.id) throw new Error("App JSON must contain an 'id'");
|
611 |
+
OS.AppManager.registerApp(appData.id, appData);
|
612 |
+
win.querySelector("#app-json").value = "";
|
613 |
+
updateInstalledAppsList();
|
614 |
+
} catch (e) {
|
615 |
+
alert("Invalid JSON: " + e.message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
616 |
}
|
617 |
+
});
|
618 |
+
function updateInstalledAppsList() {
|
619 |
+
const list = win.querySelector("#installed-apps-list");
|
620 |
+
list.innerHTML = Object.values(OS.AppManager.apps).map(app => `<div>${app.icon || '🔳'} ${app.name}</div>`).join("");
|
|
|
|
|
|
|
|
|
621 |
}
|
622 |
+
updateInstalledAppsList();
|
|
|
623 |
}
|
624 |
+
},
|
625 |
+
{
|
626 |
+
id: "fileExplorer",
|
627 |
+
name: "File Explorer",
|
628 |
+
icon: "📁",
|
629 |
+
init: function(win) {
|
630 |
+
win.dataset.currentPath = "/";
|
631 |
+
win.querySelector(".window-content").innerHTML = `
|
632 |
+
<div style="margin-bottom: 10px;">
|
633 |
+
<button id="new-file-btn">New File</button>
|
634 |
+
<button id="new-folder-btn">New Folder</button>
|
635 |
+
<button id="up-btn">Up</button>
|
636 |
+
<span id="current-path" style="margin-left: 10px;">/</span>
|
637 |
+
</div>
|
638 |
+
<div id="file-list"></div>
|
639 |
+
`;
|
640 |
+
win.querySelector("#new-file-btn").addEventListener("click", () => createNewFile());
|
641 |
+
win.querySelector("#new-folder-btn").addEventListener("click", () => createNewFolder());
|
642 |
+
win.querySelector("#up-btn").addEventListener("click", () => navigateUp());
|
643 |
+
win.querySelector("#file-list").addEventListener("contextmenu", showContextMenu);
|
644 |
+
updateFileList();
|
645 |
+
|
646 |
+
function updateFileList() {
|
647 |
+
const currentPath = win.dataset.currentPath;
|
648 |
+
console.log(`Updating file list for: ${currentPath}`);
|
649 |
+
const currentDir = OS.FileSystem.findItem(currentPath) || OS.FileSystem.getRoot();
|
650 |
+
win.querySelector("#current-path").textContent = currentPath;
|
651 |
+
const fileList = win.querySelector("#file-list");
|
652 |
+
fileList.innerHTML = currentDir.contents.map(item => `
|
653 |
+
<div style="padding: 5px; cursor: pointer;" data-path="${currentPath}${item.name}${item.type === 'directory' ? '/' : ''}">
|
654 |
+
${item.type === "file" ? "📄" : "📁"} ${item.name}
|
655 |
+
${item.type === "file" ? `<button class="open-btn" data-path="${currentPath}${item.name}">Open</button>` :
|
656 |
+
`<button class="enter-btn" data-path="${currentPath}${item.name}/">Enter</button>`}
|
657 |
+
</div>
|
658 |
+
`).join("");
|
659 |
+
fileList.querySelectorAll(".open-btn").forEach(btn => btn.addEventListener("click", () => OS.AppManager.launchApp("textViewer", { filePath: btn.dataset.path })));
|
660 |
+
fileList.querySelectorAll(".enter-btn").forEach(btn => btn.addEventListener("click", () => { win.dataset.currentPath = btn.dataset.path; updateFileList(); }));
|
661 |
+
}
|
662 |
|
663 |
+
function createNewFile() {
|
664 |
+
const filename = prompt("Enter file name:");
|
665 |
+
if (filename && OS.FileSystem.addItem(filename, "file", "New file content", null, win.dataset.currentPath)) {
|
666 |
+
updateFileList();
|
667 |
+
} else {
|
668 |
+
alert("Failed to create file!");
|
669 |
+
}
|
670 |
+
}
|
671 |
|
672 |
+
function createNewFolder() {
|
673 |
+
const foldername = prompt("Enter folder name:");
|
674 |
+
if (foldername && OS.FileSystem.addItem(foldername, "directory", "", null, win.dataset.currentPath)) {
|
675 |
+
updateFileList();
|
676 |
+
} else {
|
677 |
+
alert("Failed to create folder!");
|
678 |
+
}
|
679 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
680 |
|
681 |
+
function navigateUp() {
|
682 |
+
if (win.dataset.currentPath === "/") return;
|
683 |
+
const parts = win.dataset.currentPath.split('/').filter(p => p);
|
684 |
+
parts.pop();
|
685 |
+
win.dataset.currentPath = parts.length ? `/${parts.join('/')}/` : "/";
|
686 |
+
updateFileList();
|
687 |
+
}
|
|
|
688 |
|
689 |
+
function showContextMenu(e) {
|
690 |
+
e.preventDefault();
|
691 |
+
const target = e.target.closest("[data-path]");
|
692 |
+
if (target) {
|
693 |
+
const path = target.dataset.path;
|
694 |
+
const contextMenu = document.getElementById("file-context-menu");
|
695 |
+
contextMenu.style.top = e.clientY + "px";
|
696 |
+
contextMenu.style.left = e.clientX + "px";
|
697 |
+
contextMenu.style.display = "block";
|
698 |
+
contextMenu.querySelector("ul").dataset.path = path;
|
699 |
+
contextMenu.querySelector("[data-action='open']").style.display = OS.FileSystem.findItem(path)?.type === "file" ? "block" : "none";
|
700 |
+
}
|
701 |
+
}
|
|
|
702 |
|
703 |
+
OS.on('fileSystemChanged', updateFileList);
|
704 |
+
OS.on('itemAdded', updateFileList);
|
705 |
+
OS.on('itemRemoved', updateFileList);
|
706 |
+
OS.on('itemRenamed', updateFileList);
|
707 |
+
OS.on('fileWritten', updateFileList);
|
708 |
+
}
|
709 |
+
},
|
710 |
+
{
|
711 |
+
id: "textViewer",
|
712 |
+
name: "Text Viewer",
|
713 |
+
icon: "📄",
|
714 |
+
init: function(win, options) {
|
715 |
+
const filePath = options.filePath;
|
716 |
+
const content = OS.FileSystem.readFile(filePath) || "";
|
717 |
+
win.querySelector(".window-content").innerHTML = `<textarea style="width: 100%; height: 100%;">${content}</textarea>`;
|
718 |
+
win.querySelector("textarea").addEventListener("change", () => {
|
719 |
+
const newContent = win.querySelector("textarea").value;
|
720 |
+
if (OS.FileSystem.writeFile(filePath, newContent)) {
|
721 |
+
console.log(`Content updated for ${filePath}`);
|
722 |
+
} else {
|
723 |
+
alert("Failed to save file content!");
|
724 |
+
}
|
725 |
+
});
|
726 |
}
|
727 |
}
|
728 |
+
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
729 |
|
730 |
+
/***** System Initialization *****/
|
731 |
+
builtInApps.forEach(app => OS.AppManager.registerApp(app.id, app));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
732 |
|
733 |
+
document.getElementById("start-button").addEventListener("click", (e) => {
|
734 |
+
e.stopPropagation();
|
735 |
+
const startMenu = document.getElementById("start-menu");
|
736 |
+
startMenu.style.display = startMenu.style.display === "block" ? "none" : "block";
|
737 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
738 |
|
739 |
+
document.addEventListener("click", (e) => {
|
740 |
+
if (!document.getElementById("start-menu").contains(e.target) && e.target !== document.getElementById("start-button")) {
|
741 |
+
hideStartMenu();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
742 |
}
|
743 |
+
if (!e.target.closest('#file-context-menu')) {
|
744 |
+
document.getElementById("file-context-menu").style.display = "none";
|
|
|
|
|
|
|
|
|
|
|
|
|
745 |
}
|
746 |
+
document.getElementById("custom-context-menu").style.display = "none";
|
747 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
748 |
|
|
|
|
|
749 |
document.getElementById("desktop").addEventListener("contextmenu", function(e) {
|
750 |
e.preventDefault();
|
751 |
+
const customContextMenu = document.getElementById("custom-context-menu");
|
752 |
customContextMenu.style.display = "block";
|
753 |
customContextMenu.style.top = e.clientY + "px";
|
754 |
customContextMenu.style.left = e.clientX + "px";
|
755 |
});
|
756 |
+
|
757 |
+
document.getElementById("custom-context-menu").addEventListener("click", function(e) {
|
758 |
const option = e.target.getAttribute("data-custom");
|
759 |
+
if (option === "openCustomizer") {
|
760 |
+
// Placeholder for customizer app
|
761 |
+
} else if (option === "reset") {
|
762 |
document.getElementById("desktop").style.background = "url('background.jpg') no-repeat center center fixed";
|
763 |
document.getElementById("desktop").style.backgroundSize = "cover";
|
764 |
document.getElementById("taskbar").style.background = "rgba(0,0,0,0.8)";
|
|
|
768 |
document.getElementById("custom-css").innerHTML = "";
|
769 |
alert("Customizations reset to default!");
|
770 |
}
|
771 |
+
document.getElementById("custom-context-menu").style.display = "none";
|
772 |
+
});
|
773 |
+
|
774 |
+
document.getElementById("file-context-menu").addEventListener("click", (e) => {
|
775 |
+
const action = e.target.dataset.action;
|
776 |
+
const path = e.target.closest("ul").dataset.path;
|
777 |
+
const fileExplorerWin = OS.WindowManager.getWindow(Object.keys(OS.runningWindows).find(id => OS.runningWindows[id].dataset.appId === "fileExplorer"));
|
778 |
+
|
779 |
+
if (action === "open") {
|
780 |
+
OS.AppManager.launchApp("textViewer", { filePath: path });
|
781 |
+
} else if (action === "rename") {
|
782 |
+
const newName = prompt("Enter new name:");
|
783 |
+
if (newName && OS.FileSystem.renameItem(path, newName)) {
|
784 |
+
if (fileExplorerWin) updateFileExplorer(fileExplorerWin);
|
785 |
+
} else {
|
786 |
+
alert("Failed to rename item!");
|
787 |
+
}
|
788 |
+
} else if (action === "delete") {
|
789 |
+
if (confirm("Are you sure you want to delete this item?") && OS.FileSystem.removeItem(path)) {
|
790 |
+
if (fileExplorerWin) updateFileExplorer(fileExplorerWin);
|
791 |
+
} else {
|
792 |
+
alert("Failed to delete item!");
|
793 |
+
}
|
794 |
+
}
|
795 |
+
document.getElementById("file-context-menu").style.display = "none";
|
796 |
});
|
797 |
|
798 |
+
function updateFileExplorer(win) {
|
799 |
+
const updateFileList = () => {
|
800 |
+
const currentPath = win.dataset.currentPath;
|
801 |
+
const currentDir = OS.FileSystem.findItem(currentPath) || OS.FileSystem.getRoot();
|
802 |
+
win.querySelector("#current-path").textContent = currentPath;
|
803 |
+
const fileList = win.querySelector("#file-list");
|
804 |
+
fileList.innerHTML = currentDir.contents.map(item => `
|
805 |
+
<div style="padding: 5px; cursor: pointer;" data-path="${currentPath}${item.name}${item.type === 'directory' ? '/' : ''}">
|
806 |
+
${item.type === "file" ? "📄" : "📁"} ${item.name}
|
807 |
+
${item.type === "file" ? `<button class="open-btn" data-path="${currentPath}${item.name}">Open</button>` :
|
808 |
+
`<button class="enter-btn" data-path="${currentPath}${item.name}/">Enter</button>`}
|
809 |
+
</div>
|
810 |
+
`).join("");
|
811 |
+
fileList.querySelectorAll(".open-btn").forEach(btn => btn.addEventListener("click", () => OS.AppManager.launchApp("textViewer", { filePath: btn.dataset.path })));
|
812 |
+
fileList.querySelectorAll(".enter-btn").forEach(btn => btn.addEventListener("click", () => { win.dataset.currentPath = btn.dataset.path; updateFileList(); }));
|
813 |
+
};
|
814 |
+
updateFileList();
|
815 |
+
}
|
816 |
+
|
817 |
+
function hideStartMenu() {
|
818 |
+
document.getElementById("start-menu").style.display = "none";
|
819 |
+
}
|
820 |
+
|
821 |
document.addEventListener("DOMContentLoaded", () => {
|
822 |
+
OS.FileSystem.init();
|
823 |
+
OS.updateSystemAppsUI();
|
824 |
});
|
825 |
</script>
|
826 |
</body>
|
827 |
+
</html>
|