diff --git a/package-lock.json b/package-lock.json index 0ee029d..524dc23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,9 @@ "express-session": "^1.18.2", "helmet": "^8.1.0", "node-pty": "^1.1.0", - "socket.io": "^4.8.3" + "socket.io": "^4.8.3", + "xterm": "^5.3.0", + "xterm-addon-fit": "^0.8.0" }, "engines": { "node": ">=20.0.0", @@ -1745,6 +1747,23 @@ "optional": true } } + }, + "node_modules/xterm": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz", + "integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==", + "deprecated": "This package is now deprecated. Move to @xterm/xterm instead.", + "license": "MIT" + }, + "node_modules/xterm-addon-fit": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz", + "integrity": "sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==", + "deprecated": "This package is now deprecated. Move to @xterm/addon-fit instead.", + "license": "MIT", + "peerDependencies": { + "xterm": "^5.0.0" + } } } } diff --git a/package.json b/package.json index 4bed9c7..3cbf462 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "express-session": "^1.18.2", "helmet": "^8.1.0", "node-pty": "^1.1.0", - "socket.io": "^4.8.3" + "socket.io": "^4.8.3", + "xterm": "^5.3.0", + "xterm-addon-fit": "^0.8.0" } } diff --git a/public/app.js b/public/app.js index f2ba763..e7a52ce 100644 --- a/public/app.js +++ b/public/app.js @@ -169,9 +169,15 @@ function openWindow(appName) { focusWindow(appName); updateTaskbar(); - // Initialize terminal if opening terminal window + // Initialize app-specific functionality if (appName === 'terminal' && !terminal) { setTimeout(initializeTerminal, 100); + } else if (appName === 'files') { + setTimeout(loadFilesList, 100); + } else if (appName === 'notes') { + setTimeout(loadNotes, 100); + } else if (appName === 'calculator') { + setTimeout(updateCalcDisplay, 100); } // Hide start menu @@ -234,6 +240,10 @@ function updateTaskbar() { const appNames = { terminal: { icon: '๐ป', name: 'Terminal' }, browser: { icon: '๐', name: 'Browser' }, + editor: { icon: '๐', name: 'Text Editor' }, + files: { icon: '๐', name: 'Files' }, + calculator: { icon: '๐ข', name: 'Calculator' }, + notes: { icon: '๐', name: 'Notes' }, store: { icon: '๐ฆ', name: 'App Store' }, apps: { icon: '๐ฑ', name: 'My Apps' }, activity: { icon: '๐', name: 'Activity' }, @@ -649,6 +659,298 @@ async function checkAuth() { } } +// ==================== TEXT EDITOR FUNCTIONALITY ==================== +let currentEditorFile = null; + +async function saveEditorFile() { + const filename = document.getElementById('editor-filename').value || 'untitled.txt'; + const content = document.getElementById('editor-content').value; + + try { + await fetch('/api/files', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ path: filename, content, type: 'text' }) + }); + showToast(`File "${filename}" saved successfully!`); + currentEditorFile = filename; + loadFilesList(); + } catch (error) { + showToast('Failed to save file', 'error'); + } +} + +function newEditorFile() { + document.getElementById('editor-filename').value = ''; + document.getElementById('editor-content').value = ''; + currentEditorFile = null; + showToast('New file created'); +} + +function clearEditor() { + if (confirm('Clear all content?')) { + document.getElementById('editor-content').value = ''; + } +} + +// ==================== FILE MANAGER FUNCTIONALITY ==================== +async function loadFilesList() { + try { + const response = await fetch('/api/files'); + const files = await response.json(); + + const filesList = document.getElementById('files-list'); + if (!filesList) return; + + if (files.length === 0) { + filesList.innerHTML = '
No files yet. Create files in the Text Editor.
'; + return; + } + + filesList.innerHTML = files.map(file => ` +Modified: ${new Date(file.updated_at).toLocaleString()}
+Failed to load files
'; + } + } +} + +async function openFile(filepath) { + try { + const response = await fetch(`/api/files?path=${encodeURIComponent(filepath)}`); + const file = await response.json(); + + if (file) { + // Open editor and load content + openWindow('editor'); + document.getElementById('editor-filename').value = file.file_path; + document.getElementById('editor-content').value = file.file_content || ''; + currentEditorFile = file.file_path; + showToast(`Opened "${file.file_path}"`); + } + } catch (error) { + showToast('Failed to open file', 'error'); + } +} + +async function deleteFile(filepath) { + if (!confirm(`Delete "${filepath}"?`)) return; + + try { + await fetch('/api/files', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ path: filepath }) + }); + showToast(`File "${filepath}" deleted`); + loadFilesList(); + } catch (error) { + showToast('Failed to delete file', 'error'); + } +} + +// ==================== CALCULATOR FUNCTIONALITY ==================== +let calcDisplay = '0'; +let calcPrevValue = null; +let calcOperator = null; +let calcWaitingForOperand = false; + +function updateCalcDisplay() { + document.getElementById('calc-display').textContent = calcDisplay; +} + +function handleCalcButton(action) { + if (!isNaN(action) || action === '.') { + // Number or decimal point + if (calcWaitingForOperand) { + calcDisplay = action; + calcWaitingForOperand = false; + } else { + calcDisplay = calcDisplay === '0' ? action : calcDisplay + action; + } + } else if (action === 'clear') { + calcDisplay = '0'; + calcPrevValue = null; + calcOperator = null; + calcWaitingForOperand = false; + } else if (action === 'backspace') { + calcDisplay = calcDisplay.slice(0, -1) || '0'; + } else if (action === '=') { + if (calcOperator && calcPrevValue !== null) { + const curr = parseFloat(calcDisplay); + const prev = parseFloat(calcPrevValue); + + switch (calcOperator) { + case '+': calcDisplay = String(prev + curr); break; + case '-': calcDisplay = String(prev - curr); break; + case '*': calcDisplay = String(prev * curr); break; + case '/': calcDisplay = curr !== 0 ? String(prev / curr) : 'Error'; break; + } + + calcPrevValue = null; + calcOperator = null; + calcWaitingForOperand = true; + } + } else { + // Operator + if (calcOperator && !calcWaitingForOperand) { + // Calculate first + const curr = parseFloat(calcDisplay); + const prev = parseFloat(calcPrevValue); + + switch (calcOperator) { + case '+': calcDisplay = String(prev + curr); break; + case '-': calcDisplay = String(prev - curr); break; + case '*': calcDisplay = String(prev * curr); break; + case '/': calcDisplay = curr !== 0 ? String(prev / curr) : 'Error'; break; + } + } + + calcPrevValue = calcDisplay; + calcOperator = action; + calcWaitingForOperand = true; + } + + updateCalcDisplay(); +} + +// ==================== NOTES FUNCTIONALITY ==================== +let notes = []; +let currentNoteId = null; + +async function loadNotes() { + try { + const response = await fetch('/api/userdata/notes'); + const data = await response.json(); + notes = Object.entries(data) + .map(([id, note]) => ({ id, ...note })) + .filter(note => note.title || note.content); // Filter out deleted notes + renderNotesList(); + } catch (error) { + notes = []; + renderNotesList(); + } +} + +function renderNotesList() { + const notesList = document.getElementById('notes-list'); + if (!notesList) return; + + if (notes.length === 0) { + notesList.innerHTML = 'No notes yet. Click "New Note" to create one.
'; + return; + } + + notesList.innerHTML = notes.map(note => ` +${new Date(note.updated || Date.now()).toLocaleDateString()}
+Your Virtual Linux Desktop in the Cloud
-Enter your credentials to continue
-Your Virtual Linux Desktop in the Cloud