diff --git a/workflowos/apps/WelcomeApp.js b/workflowos/apps/WelcomeApp.js new file mode 100644 index 0000000000..9ae944ff87 --- /dev/null +++ b/workflowos/apps/WelcomeApp.js @@ -0,0 +1,21 @@ + +export class WelcomeApp { + static title = "Welcome"; + + constructor(container) { + this.container = container; + this.render(); + } + + render() { + this.container.innerHTML = ` +

Welcome to Workflow OS!

+

This is the first micro-app.

+ `; + } + + destroy() { + // Cleanup logic for the app + console.log("WelcomeApp destroyed."); + } +} diff --git a/workflowos/index.html b/workflowos/index.html new file mode 100644 index 0000000000..f8d963f611 --- /dev/null +++ b/workflowos/index.html @@ -0,0 +1,26 @@ + + + + + + Workflow OS + + + +
+
+
+
+
+ Welcome +
+
+ + System Monitor +
+
+
+
+ + + \ No newline at end of file diff --git a/workflowos/main.js b/workflowos/main.js new file mode 100644 index 0000000000..1bd6378ff6 --- /dev/null +++ b/workflowos/main.js @@ -0,0 +1,180 @@ + +import { WelcomeApp } from './apps/WelcomeApp.js'; + +class WorkflowOS { + constructor() { + this.desktop = document.getElementById('desktop'); + this.taskbar = document.getElementById('taskbar'); + this.windows = new Map(); + this.appRegistry = new Map(); + this.nextZIndex = 100; + + this.init(); + } + + init() { + this.registerApp('welcome', WelcomeApp); + this.setupDesktopIcons(); + this.startSystemMonitorIcon(); + this.setupCursorEffects(); + } + + setupDesktopIcons() { + const icons = document.querySelectorAll('.desktop-icon'); + icons.forEach(icon => { + icon.addEventListener('dblclick', () => { + const appId = icon.dataset.appId; + this.launchApp(appId); + }); + }); + } + + createWindow(appId, title) { + const windowId = `window-${appId}-${Date.now()}`; + const win = document.createElement('div'); + win.className = 'window opening'; + win.style.zIndex = this.nextZIndex++; + win.dataset.windowId = windowId; + + win.innerHTML = ` +
+ ${title} +
+ +
+
+
+ `; + + win.addEventListener('animationend', () => { + win.classList.remove('opening'); + }, { once: true }); + + this.desktop.appendChild(win); + this.windows.set(windowId, { element: win, app: null }); + + this.setupWindowEvents(win); + return win; + } + + setupWindowEvents(win) { + const header = win.querySelector('.window-header'); + + win.addEventListener('mousedown', () => this.focusWindow(win)); + + const closeBtn = win.querySelector('.close-btn'); + closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.closeWindow(win.dataset.windowId); + }); + + let isDragging = false; + let offset = { x: 0, y: 0 }; + + header.addEventListener('mousedown', (e) => { + isDragging = true; + offset.x = e.clientX - win.offsetLeft; + offset.y = e.clientY - win.offsetTop; + header.style.cursor = 'grabbing'; + }); + + document.addEventListener('mousemove', (e) => { + if (!isDragging) return; + win.style.left = `${e.clientX - offset.x}px`; + win.style.top = `${e.clientY - offset.y}px`; + }); + + document.addEventListener('mouseup', () => { + isDragging = false; + header.style.cursor = 'grab'; + }); + } + + focusWindow(win) { + win.style.zIndex = this.nextZIndex++; + } + + closeWindow(windowId) { + const windowData = this.windows.get(windowId); + if (windowData) { + const win = windowData.element; + win.classList.add('closing'); + win.addEventListener('animationend', () => { + if (windowData.app && typeof windowData.app.destroy === 'function') { + windowData.app.destroy(); + } + win.remove(); + this.windows.delete(windowId); + }, { once: true }); + } + } + + registerApp(appId, appClass) { + this.appRegistry.set(appId, appClass); + } + + launchApp(appId) { + const AppClass = this.appRegistry.get(appId); + if (AppClass) { + const win = this.createWindow(appId, AppClass.title || 'Application'); + const content = win.querySelector('.window-content'); + const appInstance = new AppClass(content); + const windowId = win.dataset.windowId; + this.windows.get(windowId).app = appInstance; + } else { + console.error(`App ${appId} not found.`); + } + } + + startSystemMonitorIcon() { + const canvas = document.getElementById('systemMonitorIconCanvas'); + if (!canvas) return; + const ctx = canvas.getContext('2d'); + const width = canvas.width; + const height = canvas.height; + let data = Array(width).fill(height); + + function draw() { + data.shift(); + data.push(height - Math.random() * height * 0.8); + + ctx.clearRect(0, 0, width, height); + ctx.beginPath(); + ctx.moveTo(0, data[0]); + for (let i = 1; i < width; i++) { + ctx.lineTo(i, data[i]); + } + ctx.strokeStyle = '#00ff00'; + ctx.stroke(); + } + + setInterval(draw, 100); + } + + setupCursorEffects() { + const particleContainer = document.getElementById('particle-container'); + const rippleContainer = document.getElementById('ripple-container'); + + document.addEventListener('mousemove', e => { + const particle = document.createElement('div'); + particle.className = 'particle'; + particle.style.left = `${e.clientX}px`; + particle.style.top = `${e.clientY}px`; + particleContainer.appendChild(particle); + setTimeout(() => particle.remove(), 1000); + }); + + document.addEventListener('mousedown', e => { + const ripple = document.createElement('div'); + ripple.className = 'ripple'; + ripple.style.left = `${e.clientX - 10}px`; + ripple.style.top = `${e.clientY - 10}px`; + rippleContainer.appendChild(ripple); + setTimeout(() => ripple.remove(), 600); + }); + } +} + +window.addEventListener('load', () => { + window.os = new WorkflowOS(); +}); diff --git a/workflowos/style.css b/workflowos/style.css new file mode 100644 index 0000000000..ebf41b3aa7 --- /dev/null +++ b/workflowos/style.css @@ -0,0 +1,182 @@ +body, html { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + overflow: hidden; + font-family: sans-serif; + background-color: #333; + color: #fff; +} + +#os-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; +} + +#desktop { + flex-grow: 1; + position: relative; + padding: 20px; +} + +.desktop-icon { + width: 70px; + text-align: center; + padding: 10px; + border-radius: 5px; + margin: 10px; + cursor: pointer; + transition: background-color 0.2s ease-in-out; +} + +.desktop-icon:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.desktop-icon:active { + background-color: rgba(255, 255, 255, 0.2); +} + +.desktop-icon span { + display: block; + margin-top: 5px; + font-size: 12px; + color: #fff; + text-shadow: 1px 1px 2px rgba(0,0,0,0.5); +} + +#taskbar { + width: 100%; + height: 40px; + background-color: #222; + border-top: 1px solid #444; +} + +.window { + position: absolute; + top: 50px; + left: 50px; + width: 400px; + height: 300px; + background-color: #444; + border: 1px solid #555; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0,0,0,0.5); + display: flex; + flex-direction: column; +} + +.window-header { + height: 30px; + background-color: #555; + padding: 0 10px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: grab; +} + +.window-title { + font-weight: bold; +} + +.window-controls { + display: flex; +} + +.close-btn { + background: #ff5f56; + border: 1px solid #e0443e; + border-radius: 50%; + width: 12px; + height: 12px; + cursor: pointer; +} + +.window-content { + flex-grow: 1; + padding: 10px; +} + +@keyframes open-window { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes close-window { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.9); + } +} + +.window.opening { + animation: open-window 0.2s ease-in-out; +} + +.window.closing { + animation: close-window 0.2s ease-in-out; +} + +#particle-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 9999; +} + +.particle { + position: absolute; + width: 5px; + height: 5px; + background-color: #00ffcc; + border-radius: 50%; + opacity: 0; + animation: fade-out 1s forwards; +} + +#ripple-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 9998; +} + +.ripple { + position: absolute; + width: 20px; + height: 20px; + border: 2px solid #00ffcc; + border-radius: 50%; + transform: scale(0); + animation: ripple-effect 0.6s forwards; +} + +@keyframes fade-out { + from { opacity: 1; transform: scale(1); } + to { opacity: 0; transform: scale(0); } +} + +@keyframes ripple-effect { + from { transform: scale(0); opacity: 1; } + to { transform: scale(5); opacity: 0; } +}