From 37a15ea9e0ce88add589a1df7631167a655335bf Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Thu, 20 Nov 2025 18:29:55 -0300 Subject: [PATCH] simplifying the chat interface. --- web/desktop/drive/drive.css | 247 +++++++++++++++++++++++- web/desktop/drive/drive.html | 178 +++++++++++------ web/desktop/drive/drive.js | 96 ++++++++-- web/desktop/index.html | 4 + web/desktop/js/layout.js | 143 +++++++++++--- web/desktop/mail/mail.css | 358 ++++++++++++++++++++++++++++++++++- web/desktop/mail/mail.js | 99 +++++----- web/desktop/tasks/tasks.css | 289 +++++++++++++++++++++++++++- web/desktop/tasks/tasks.js | 64 +++---- 9 files changed, 1283 insertions(+), 195 deletions(-) diff --git a/web/desktop/drive/drive.css b/web/desktop/drive/drive.css index b978c1b59..a6cb5602f 100644 --- a/web/desktop/drive/drive.css +++ b/web/desktop/drive/drive.css @@ -1 +1,246 @@ -a {} \ No newline at end of file +/* Drive Layout */ +.drive-layout { + display: grid; + grid-template-columns: 250px 1fr 300px; + gap: 1rem; + padding: 1rem; + height: 100%; + width: 100%; + background: #ffffff; + color: #202124; +} + +[data-theme="dark"] .drive-layout { + background: #1a1a1a; + color: #e8eaed; +} + +.drive-sidebar, +.drive-main, +.drive-details { + background: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 12px; + overflow: hidden; +} + +[data-theme="dark"] .drive-sidebar, +[data-theme="dark"] .drive-main, +[data-theme="dark"] .drive-details { + background: #202124; + border-color: #3c4043; +} + +.drive-sidebar { + overflow-y: auto; +} + +.drive-main { + display: flex; + flex-direction: column; +} + +.drive-details { + overflow-y: auto; +} + +/* Navigation Items */ +.nav-item { + padding: 0.75rem 1rem; + display: flex; + align-items: center; + gap: 0.75rem; + cursor: pointer; + border-radius: 0.5rem; + margin: 0.25rem 0.5rem; + transition: all 0.2s; + color: #5f6368; +} + +[data-theme="dark"] .nav-item { + color: #9aa0a6; +} + +.nav-item:hover { + background: rgba(26, 115, 232, 0.08); + color: #1a73e8; +} + +[data-theme="dark"] .nav-item:hover { + background: rgba(138, 180, 248, 0.08); + color: #8ab4f8; +} + +.nav-item.active { + background: #e8f0fe; + color: #1a73e8; + font-weight: 500; +} + +[data-theme="dark"] .nav-item.active { + background: #1e3a5f; + color: #8ab4f8; +} + +/* File List */ +.file-list { + flex: 1; + overflow-y: auto; + padding: 0.5rem; +} + +.file-item { + padding: 0.75rem 1rem; + display: flex; + align-items: center; + gap: 1rem; + cursor: pointer; + border-radius: 0.5rem; + border: 1px solid transparent; + transition: all 0.2s; + margin-bottom: 0.25rem; +} + +.file-item:hover { + background: rgba(26, 115, 232, 0.08); + border-color: rgba(26, 115, 232, 0.2); +} + +[data-theme="dark"] .file-item:hover { + background: rgba(138, 180, 248, 0.08); + border-color: rgba(138, 180, 248, 0.2); +} + +.file-item.selected { + background: #e8f0fe; + border-color: #1a73e8; +} + +[data-theme="dark"] .file-item.selected { + background: #1e3a5f; + border-color: #8ab4f8; +} + +.file-icon { + font-size: 1.5rem; + flex-shrink: 0; +} + +/* Headers */ +h2, +h3 { + margin: 0; + padding: 0; + font-weight: 500; +} + +/* Text Styles */ +.text-xs { + font-size: 0.75rem; +} + +.text-sm { + font-size: 0.875rem; +} + +.text-gray { + color: #5f6368; +} + +[data-theme="dark"] .text-gray { + color: #9aa0a6; +} + +/* Inputs */ +input[type="text"] { + font-family: inherit; +} + +input[type="text"]:focus { + outline: none; + border-color: #1a73e8 !important; + box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2); +} + +[data-theme="dark"] input[type="text"]:focus { + border-color: #8ab4f8 !important; + box-shadow: 0 0 0 2px rgba(138, 180, 248, 0.2); +} + +/* Buttons */ +button { + font-family: inherit; + cursor: pointer; + transition: all 0.2s; +} + +button:hover { + opacity: 0.9; +} + +button:active { + transform: scale(0.98); +} + +/* Scrollbar Styles */ +.drive-sidebar::-webkit-scrollbar, +.file-list::-webkit-scrollbar, +.drive-details::-webkit-scrollbar { + width: 8px; +} + +.drive-sidebar::-webkit-scrollbar-track, +.file-list::-webkit-scrollbar-track, +.drive-details::-webkit-scrollbar-track { + background: transparent; +} + +.drive-sidebar::-webkit-scrollbar-thumb, +.file-list::-webkit-scrollbar-thumb, +.drive-details::-webkit-scrollbar-thumb { + background: rgba(128, 128, 128, 0.3); + border-radius: 4px; +} + +.drive-sidebar::-webkit-scrollbar-thumb:hover, +.file-list::-webkit-scrollbar-thumb:hover, +.drive-details::-webkit-scrollbar-thumb:hover { + background: rgba(128, 128, 128, 0.5); +} + +[data-theme="dark"] .drive-sidebar::-webkit-scrollbar-thumb, +[data-theme="dark"] .file-list::-webkit-scrollbar-thumb, +[data-theme="dark"] .drive-details::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); +} + +[data-theme="dark"] .drive-sidebar::-webkit-scrollbar-thumb:hover, +[data-theme="dark"] .file-list::-webkit-scrollbar-thumb:hover, +[data-theme="dark"] .drive-details::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} + +/* Responsive */ +@media (max-width: 1024px) { + .drive-layout { + grid-template-columns: 200px 1fr 250px; + gap: 0.5rem; + padding: 0.5rem; + } +} + +@media (max-width: 768px) { + .drive-layout { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + } + + .drive-details { + display: none; + } +} + +/* Alpine.js cloak */ +[x-cloak] { + display: none !important; +} diff --git a/web/desktop/drive/drive.html b/web/desktop/drive/drive.html index 820148c0b..a77cf95c8 100644 --- a/web/desktop/drive/drive.html +++ b/web/desktop/drive/drive.html @@ -1,67 +1,125 @@
-
-
-

General Bots Drive

+
+
+

General Bots Drive

+
+
- -
-
-
-

- -
-
-
-
-
- - -
+
+ + +
diff --git a/web/desktop/drive/drive.js b/web/desktop/drive/drive.js index ee877239a..ee0658a59 100644 --- a/web/desktop/drive/drive.js +++ b/web/desktop/drive/drive.js @@ -1,29 +1,85 @@ -function driveApp() { +window.driveApp = function driveApp() { return { - current: 'All Files', - search: '', + current: "All Files", + search: "", selectedFile: null, navItems: [ - { name: 'All Files', icon: 'πŸ“' }, - { name: 'Recent', icon: 'πŸ•' }, - { name: 'Starred', icon: '⭐' }, - { name: 'Shared', icon: 'πŸ‘₯' }, - { name: 'Trash', icon: 'πŸ—‘' } + { name: "All Files", icon: "πŸ“" }, + { name: "Recent", icon: "πŸ•" }, + { name: "Starred", icon: "⭐" }, + { name: "Shared", icon: "πŸ‘₯" }, + { name: "Trash", icon: "πŸ—‘" }, ], files: [ - { id: 1, name: 'Project Proposal.pdf', type: 'PDF', icon: 'πŸ“„', size: '2.4 MB', date: 'Nov 10, 2025' }, - { id: 2, name: 'Design Assets', type: 'Folder', icon: 'πŸ“', size: 'β€”', date: 'Nov 12, 2025' }, - { id: 3, name: 'Meeting Notes.docx', type: 'Document', icon: 'πŸ“', size: '156 KB', date: 'Nov 14, 2025' }, - { id: 4, name: 'Budget 2025.xlsx', type: 'Spreadsheet', icon: 'πŸ“Š', size: '892 KB', date: 'Nov 13, 2025' }, - { id: 5, name: 'Presentation.pptx', type: 'Presentation', icon: 'πŸ“½', size: '5.2 MB', date: 'Nov 11, 2025' }, - { id: 6, name: 'team-photo.jpg', type: 'Image', icon: 'πŸ–Ό', size: '3.1 MB', date: 'Nov 9, 2025' }, - { id: 7, name: 'source-code.zip', type: 'Archive', icon: 'πŸ“¦', size: '12.8 MB', date: 'Nov 8, 2025' }, - { id: 8, name: 'video-demo.mp4', type: 'Video', icon: '🎬', size: '45.2 MB', date: 'Nov 7, 2025' } + { + id: 1, + name: "Project Proposal.pdf", + type: "PDF", + icon: "πŸ“„", + size: "2.4 MB", + date: "Nov 10, 2025", + }, + { + id: 2, + name: "Design Assets", + type: "Folder", + icon: "πŸ“", + size: "β€”", + date: "Nov 12, 2025", + }, + { + id: 3, + name: "Meeting Notes.docx", + type: "Document", + icon: "πŸ“", + size: "156 KB", + date: "Nov 14, 2025", + }, + { + id: 4, + name: "Budget 2025.xlsx", + type: "Spreadsheet", + icon: "πŸ“Š", + size: "892 KB", + date: "Nov 13, 2025", + }, + { + id: 5, + name: "Presentation.pptx", + type: "Presentation", + icon: "πŸ“½", + size: "5.2 MB", + date: "Nov 11, 2025", + }, + { + id: 6, + name: "team-photo.jpg", + type: "Image", + icon: "πŸ–Ό", + size: "3.1 MB", + date: "Nov 9, 2025", + }, + { + id: 7, + name: "source-code.zip", + type: "Archive", + icon: "πŸ“¦", + size: "12.8 MB", + date: "Nov 8, 2025", + }, + { + id: 8, + name: "video-demo.mp4", + type: "Video", + icon: "🎬", + size: "45.2 MB", + date: "Nov 7, 2025", + }, ], get filteredFiles() { - return this.files.filter(file => - file.name.toLowerCase().includes(this.search.toLowerCase()) + return this.files.filter((file) => + file.name.toLowerCase().includes(this.search.toLowerCase()), ); - } + }, }; -} +}; diff --git a/web/desktop/index.html b/web/desktop/index.html index c211e9d6b..ad3716f3a 100644 --- a/web/desktop/index.html +++ b/web/desktop/index.html @@ -288,6 +288,10 @@ + diff --git a/web/desktop/js/layout.js b/web/desktop/js/layout.js index de7342ee4..a82cf01f3 100644 --- a/web/desktop/js/layout.js +++ b/web/desktop/js/layout.js @@ -31,6 +31,29 @@ async function loadSectionHTML(path) { return await response.text(); } +async function loadScript(jsPath) { + return new Promise((resolve, reject) => { + const existingScript = document.querySelector(`script[src="${jsPath}"]`); + if (existingScript) { + console.log(`Script already loaded: ${jsPath}`); + resolve(); + return; + } + + const script = document.createElement("script"); + script.src = jsPath; + script.onload = () => { + console.log(`βœ“ Script loaded: ${jsPath}`); + resolve(); + }; + script.onerror = (err) => { + console.error(`βœ— Script failed to load: ${jsPath}`, err); + reject(err); + }; + document.body.appendChild(script); + }); +} + async function switchSection(section) { const mainContent = document.getElementById("main-content"); @@ -51,8 +74,8 @@ async function switchSection(section) { try { const htmlPath = sections[section]; console.log("Loading section:", section, "from", htmlPath); - // Resolve CSS path relative to the base directory. const cssPath = getBasePath() + htmlPath.replace(".html", ".css"); + const jsPath = getBasePath() + htmlPath.replace(".html", ".js"); // Preload chat CSS if the target is chat if (section === "chat") { @@ -103,12 +126,44 @@ async function switchSection(section) { loadingDiv.textContent = "Loading…"; container.appendChild(loadingDiv); + // For Alpine sections, load JavaScript FIRST before HTML + const isAlpineSection = ["drive", "tasks", "mail"].includes(section); + + if (isAlpineSection) { + console.log(`Loading JS before HTML for Alpine section: ${section}`); + await loadScript(jsPath); + + // Wait for the component function to be registered + const appFunctionName = section + "App"; + let retries = 0; + while (typeof window[appFunctionName] !== "function" && retries < 50) { + await new Promise((resolve) => setTimeout(resolve, 50)); + retries++; + } + + if (typeof window[appFunctionName] !== "function") { + console.error(`${appFunctionName} function not found after waiting!`); + throw new Error( + `Component function ${appFunctionName} not available`, + ); + } + + console.log(`βœ“ Component function registered: ${appFunctionName}`); + } + // Load HTML const html = await loadSectionHTML(htmlPath); + // Create wrapper for the new section const wrapper = document.createElement("div"); wrapper.id = `section-${section}`; wrapper.className = "section"; + + // For Alpine sections, mark for manual initialization + if (isAlpineSection) { + wrapper.setAttribute("x-ignore", ""); + } + wrapper.innerHTML = html; // Hide any existing sections @@ -127,6 +182,28 @@ async function switchSection(section) { container.appendChild(wrapper); sectionCache[section] = wrapper; + // For Alpine sections, initialize after DOM insertion + if (isAlpineSection && window.Alpine) { + console.log(`Initializing Alpine for section: ${section}`); + + // Remove x-ignore to allow Alpine to process + wrapper.removeAttribute("x-ignore"); + + // Small delay to ensure DOM is ready + await new Promise((resolve) => setTimeout(resolve, 50)); + + try { + window.Alpine.initTree(wrapper); + console.log(`βœ“ Alpine initialized for ${section}`); + } catch (err) { + console.error(`Error initializing Alpine for ${section}:`, err); + } + } else if (!isAlpineSection) { + // For non-Alpine sections (like chat), load JS after HTML + await loadScript(jsPath); + await new Promise((resolve) => setTimeout(resolve, 100)); + } + // Dispatch a custom event to notify the section it's being shown wrapper.dispatchEvent(new CustomEvent("section-shown")); @@ -138,35 +215,8 @@ async function switchSection(section) { ); } - // Then load JS after HTML is inserted (skip if already loaded) - // Resolve JS path relative to the base directory. - const jsPath = getBasePath() + htmlPath.replace(".html", ".js"); - const existingScript = document.querySelector(`script[src="${jsPath}"]`); - - if (!existingScript) { - // Create script and wait for it to load before initializing Alpine - const script = document.createElement("script"); - script.src = jsPath; - script.defer = true; - - // Wait for script to load before initializing Alpine - await new Promise((resolve, reject) => { - script.onload = resolve; - script.onerror = reject; - document.body.appendChild(script); - }); - } - window.history.pushState({}, "", `#${section}`); - // Start Alpine on first load, then just init the tree for new sections - if (typeof window.startAlpine === "function") { - window.startAlpine(); - delete window.startAlpine; - } else if (window.Alpine) { - window.Alpine.initTree(mainContent); - } - const inputEl = document.getElementById("messageInput"); if (inputEl) { inputEl.focus(); @@ -201,18 +251,49 @@ function getInitialSection() { // Default to chat if nothing matches return section || "chat"; } + window.addEventListener("DOMContentLoaded", () => { - // Small delay to ensure all resources are loaded - setTimeout(() => { + console.log("DOM Content Loaded"); + + const initApp = () => { const section = getInitialSection(); + console.log(`Initializing app with section: ${section}`); + // Ensure valid section if (!sections[section]) { + console.warn(`Invalid section: ${section}, defaulting to chat`); window.location.hash = "#chat"; switchSection("chat"); } else { switchSection(section); } - }, 50); + }; + + // Check if Alpine sections might be needed and wait for Alpine + const hash = window.location.hash.substring(1); + if (["drive", "tasks", "mail"].includes(hash)) { + console.log(`Waiting for Alpine to load for section: ${hash}`); + + const waitForAlpine = () => { + if (window.Alpine) { + console.log("Alpine is ready"); + setTimeout(initApp, 100); + } else { + console.log("Waiting for Alpine..."); + setTimeout(waitForAlpine, 100); + } + }; + + // Also listen for alpine:init event + document.addEventListener("alpine:init", () => { + console.log("Alpine initialized via event"); + }); + + waitForAlpine(); + } else { + // For chat, don't need to wait for Alpine + setTimeout(initApp, 100); + } }); // Handle browser back/forward navigation diff --git a/web/desktop/mail/mail.css b/web/desktop/mail/mail.css index b978c1b59..246cd73f7 100644 --- a/web/desktop/mail/mail.css +++ b/web/desktop/mail/mail.css @@ -1 +1,357 @@ -a {} \ No newline at end of file +/* Mail Layout */ +.mail-layout { + display: grid; + grid-template-columns: 250px 350px 1fr; + gap: 1rem; + padding: 1rem; + height: 100%; + width: 100%; + background: #ffffff; + color: #202124; +} + +[data-theme="dark"] .mail-layout { + background: #1a1a1a; + color: #e8eaed; +} + +.mail-sidebar, +.mail-list, +.mail-content { + background: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 12px; + overflow: hidden; +} + +[data-theme="dark"] .mail-sidebar, +[data-theme="dark"] .mail-list, +[data-theme="dark"] .mail-content { + background: #202124; + border-color: #3c4043; +} + +.mail-sidebar { + overflow-y: auto; +} + +.mail-list { + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.mail-content { + overflow-y: auto; +} + +/* Folder Navigation */ +.nav-item { + padding: 0.75rem 1rem; + display: flex; + align-items: center; + gap: 0.75rem; + cursor: pointer; + border-radius: 0.5rem; + margin: 0.25rem 0.5rem; + transition: all 0.2s; + color: #5f6368; +} + +[data-theme="dark"] .nav-item { + color: #9aa0a6; +} + +.nav-item:hover { + background: rgba(26, 115, 232, 0.08); + color: #1a73e8; +} + +[data-theme="dark"] .nav-item:hover { + background: rgba(138, 180, 248, 0.08); + color: #8ab4f8; +} + +.nav-item.active { + background: #e8f0fe; + color: #1a73e8; + font-weight: 500; +} + +[data-theme="dark"] .nav-item.active { + background: #1e3a5f; + color: #8ab4f8; +} + +.nav-item .count { + margin-left: auto; + background: #1a73e8; + color: white; + padding: 0.125rem 0.5rem; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 500; +} + +[data-theme="dark"] .nav-item .count { + background: #8ab4f8; + color: #202124; +} + +/* Mail Items */ +.mail-item { + padding: 1rem; + cursor: pointer; + border-bottom: 1px solid #e0e0e0; + transition: all 0.2s; + position: relative; +} + +[data-theme="dark"] .mail-item { + border-bottom-color: #3c4043; +} + +.mail-item:hover { + background: rgba(26, 115, 232, 0.08); +} + +[data-theme="dark"] .mail-item:hover { + background: rgba(138, 180, 248, 0.08); +} + +.mail-item.unread { + background: #f8f9fa; + font-weight: 500; +} + +[data-theme="dark"] .mail-item.unread { + background: #292a2d; +} + +.mail-item.selected { + background: #e8f0fe; + border-left: 3px solid #1a73e8; +} + +[data-theme="dark"] .mail-item.selected { + background: #1e3a5f; + border-left-color: #8ab4f8; +} + +.mail-item-from { + font-size: 0.875rem; + margin-bottom: 0.25rem; + color: #202124; +} + +[data-theme="dark"] .mail-item-from { + color: #e8eaed; +} + +.mail-item.unread .mail-item-from { + font-weight: 600; +} + +.mail-item-subject { + font-size: 0.875rem; + margin-bottom: 0.25rem; + color: #202124; +} + +[data-theme="dark"] .mail-item-subject { + color: #e8eaed; +} + +.mail-item-preview { + font-size: 0.75rem; + color: #5f6368; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-bottom: 0.25rem; +} + +[data-theme="dark"] .mail-item-preview { + color: #9aa0a6; +} + +.mail-item-time { + font-size: 0.75rem; + color: #5f6368; +} + +[data-theme="dark"] .mail-item-time { + color: #9aa0a6; +} + +/* Mail Content View */ +.mail-content-view { + padding: 2rem; +} + +.mail-content-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: #5f6368; + text-align: center; +} + +[data-theme="dark"] .mail-content-empty { + color: #9aa0a6; +} + +.mail-content-empty .icon { + font-size: 4rem; + margin-bottom: 1rem; +} + +/* Mail Header */ +.mail-header { + margin-bottom: 2rem; + padding-bottom: 1rem; + border-bottom: 1px solid #e0e0e0; +} + +[data-theme="dark"] .mail-header { + border-bottom-color: #3c4043; +} + +.mail-subject { + font-size: 1.5rem; + font-weight: 500; + margin-bottom: 1rem; + color: #202124; +} + +[data-theme="dark"] .mail-subject { + color: #e8eaed; +} + +.mail-meta { + display: flex; + align-items: center; + gap: 1rem; + font-size: 0.875rem; + color: #5f6368; +} + +[data-theme="dark"] .mail-meta { + color: #9aa0a6; +} + +.mail-from { + font-weight: 500; + color: #202124; +} + +[data-theme="dark"] .mail-from { + color: #e8eaed; +} + +.mail-to { + font-size: 0.75rem; +} + +.mail-date { + margin-left: auto; +} + +/* Mail Body */ +.mail-body { + line-height: 1.7; + color: #202124; +} + +[data-theme="dark"] .mail-body { + color: #e8eaed; +} + +.mail-body p { + margin-bottom: 1rem; +} + +.mail-body p:last-child { + margin-bottom: 0; +} + +/* Headers */ +h2, +h3 { + margin: 0; + padding: 0; + font-weight: 500; +} + +/* Scrollbar Styles */ +.mail-sidebar::-webkit-scrollbar, +.mail-list::-webkit-scrollbar, +.mail-content::-webkit-scrollbar { + width: 8px; +} + +.mail-sidebar::-webkit-scrollbar-track, +.mail-list::-webkit-scrollbar-track, +.mail-content::-webkit-scrollbar-track { + background: transparent; +} + +.mail-sidebar::-webkit-scrollbar-thumb, +.mail-list::-webkit-scrollbar-thumb, +.mail-content::-webkit-scrollbar-thumb { + background: rgba(128, 128, 128, 0.3); + border-radius: 4px; +} + +.mail-sidebar::-webkit-scrollbar-thumb:hover, +.mail-list::-webkit-scrollbar-thumb:hover, +.mail-content::-webkit-scrollbar-thumb:hover { + background: rgba(128, 128, 128, 0.5); +} + +[data-theme="dark"] .mail-sidebar::-webkit-scrollbar-thumb, +[data-theme="dark"] .mail-list::-webkit-scrollbar-thumb, +[data-theme="dark"] .mail-content::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); +} + +[data-theme="dark"] .mail-sidebar::-webkit-scrollbar-thumb:hover, +[data-theme="dark"] .mail-list::-webkit-scrollbar-thumb:hover, +[data-theme="dark"] .mail-content::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} + +/* Alpine.js cloak */ +[x-cloak] { + display: none !important; +} + +/* Responsive */ +@media (max-width: 1024px) { + .mail-layout { + grid-template-columns: 200px 300px 1fr; + gap: 0.5rem; + padding: 0.5rem; + } +} + +@media (max-width: 768px) { + .mail-layout { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + } + + .mail-sidebar { + max-height: 200px; + } + + .mail-content { + display: none; + } + + .mail-item.selected + .mail-content { + display: block; + } +} diff --git a/web/desktop/mail/mail.js b/web/desktop/mail/mail.js index 9aeef1dfa..58d385925 100644 --- a/web/desktop/mail/mail.js +++ b/web/desktop/mail/mail.js @@ -1,78 +1,79 @@ -function mailApp() { +window.mailApp = function mailApp() { return { - currentFolder: 'Inbox', + currentFolder: "Inbox", selectedMail: null, - + folders: [ - { name: 'Inbox', icon: 'πŸ“₯', count: 4 }, - { name: 'Sent', icon: 'πŸ“€', count: 0 }, - { name: 'Drafts', icon: 'πŸ“', count: 2 }, - { name: 'Starred', icon: '⭐', count: 0 }, - { name: 'Trash', icon: 'πŸ—‘', count: 0 } + { name: "Inbox", icon: "πŸ“₯", count: 4 }, + { name: "Sent", icon: "πŸ“€", count: 0 }, + { name: "Drafts", icon: "πŸ“", count: 2 }, + { name: "Starred", icon: "⭐", count: 0 }, + { name: "Trash", icon: "πŸ—‘", count: 0 }, ], - + mails: [ { id: 1, - from: 'Sarah Johnson', - to: 'me@example.com', - subject: 'Q4 Project Update', - preview: 'Hi team, I wanted to share the latest updates on our Q4 projects...', - body: '

Hi team,

I wanted to share the latest updates on our Q4 projects. We\'ve made significant progress on the main deliverables and are on track to meet our goals.

Please review the attached documents and let me know if you have any questions.

Best regards,
Sarah

', - time: '10:30 AM', - date: 'Nov 15, 2025', - read: false + from: "Sarah Johnson", + to: "me@example.com", + subject: "Q4 Project Update", + preview: + "Hi team, I wanted to share the latest updates on our Q4 projects...", + body: "

Hi team,

I wanted to share the latest updates on our Q4 projects. We've made significant progress on the main deliverables and are on track to meet our goals.

Please review the attached documents and let me know if you have any questions.

Best regards,
Sarah

", + time: "10:30 AM", + date: "Nov 15, 2025", + read: false, }, { id: 2, - from: 'Mike Chen', - to: 'me@example.com', - subject: 'Meeting Tomorrow', - preview: 'Don\'t forget about our meeting tomorrow at 2 PM...', - body: '

Hi,

Don\'t forget about our meeting tomorrow at 2 PM to discuss the new features.

See you then!
Mike

', - time: '9:15 AM', - date: 'Nov 15, 2025', - read: false + from: "Mike Chen", + to: "me@example.com", + subject: "Meeting Tomorrow", + preview: "Don't forget about our meeting tomorrow at 2 PM...", + body: "

Hi,

Don't forget about our meeting tomorrow at 2 PM to discuss the new features.

See you then!
Mike

", + time: "9:15 AM", + date: "Nov 15, 2025", + read: false, }, { id: 3, - from: 'Emma Wilson', - to: 'me@example.com', - subject: 'Design Review Complete', - preview: 'The design review for the new dashboard is complete...', - body: '

Hi,

The design review for the new dashboard is complete. Overall, the team is happy with the direction.

I\'ve made the requested changes and updated the Figma file.

Thanks,
Emma

', - time: 'Yesterday', - date: 'Nov 14, 2025', - read: true + from: "Emma Wilson", + to: "me@example.com", + subject: "Design Review Complete", + preview: "The design review for the new dashboard is complete...", + body: "

Hi,

The design review for the new dashboard is complete. Overall, the team is happy with the direction.

I've made the requested changes and updated the Figma file.

Thanks,
Emma

", + time: "Yesterday", + date: "Nov 14, 2025", + read: true, }, { id: 4, - from: 'David Lee', - to: 'me@example.com', - subject: 'Budget Approval Needed', - preview: 'Could you please review and approve the Q1 budget?', - body: '

Hi,

Could you please review and approve the Q1 budget when you get a chance?

It\'s attached to this email.

Thanks,
David

', - time: 'Yesterday', - date: 'Nov 14, 2025', - read: false - } + from: "David Lee", + to: "me@example.com", + subject: "Budget Approval Needed", + preview: "Could you please review and approve the Q1 budget?", + body: "

Hi,

Could you please review and approve the Q1 budget when you get a chance?

It's attached to this email.

Thanks,
David

", + time: "Yesterday", + date: "Nov 14, 2025", + read: false, + }, ], - + get filteredMails() { return this.mails; }, - + selectMail(mail) { this.selectedMail = mail; mail.read = true; this.updateFolderCounts(); }, - + updateFolderCounts() { - const inbox = this.folders.find(f => f.name === 'Inbox'); + const inbox = this.folders.find((f) => f.name === "Inbox"); if (inbox) { - inbox.count = this.mails.filter(m => !m.read).length; + inbox.count = this.mails.filter((m) => !m.read).length; } - } + }, }; -} +}; diff --git a/web/desktop/tasks/tasks.css b/web/desktop/tasks/tasks.css index b978c1b59..5f2d8c43c 100644 --- a/web/desktop/tasks/tasks.css +++ b/web/desktop/tasks/tasks.css @@ -1 +1,288 @@ -a {} \ No newline at end of file +/* Tasks Container */ +.tasks-container { + max-width: 800px; + margin: 0 auto; + padding: 2rem; + height: 100%; + overflow-y: auto; + background: #ffffff; + color: #202124; +} + +[data-theme="dark"] .tasks-container { + background: #1a1a1a; + color: #e8eaed; +} + +/* Task Input */ +.task-input { + display: flex; + gap: 0.5rem; + margin-bottom: 2rem; +} + +.task-input input { + flex: 1; + padding: 0.875rem 1rem; + background: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 8px; + color: #202124; + font-size: 1rem; + font-family: inherit; + transition: all 0.2s; +} + +[data-theme="dark"] .task-input input { + background: #202124; + border-color: #3c4043; + color: #e8eaed; +} + +.task-input input:focus { + outline: none; + border-color: #1a73e8; + box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2); +} + +[data-theme="dark"] .task-input input:focus { + border-color: #8ab4f8; + box-shadow: 0 0 0 2px rgba(138, 180, 248, 0.2); +} + +.task-input input::placeholder { + color: #5f6368; +} + +[data-theme="dark"] .task-input input::placeholder { + color: #9aa0a6; +} + +.task-input button { + padding: 0.875rem 1.5rem; + background: #1a73e8; + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: 500; + font-size: 1rem; + transition: all 0.2s; + white-space: nowrap; +} + +.task-input button:hover { + background: #1557b0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.task-input button:active { + transform: scale(0.98); +} + +/* Task List */ +.task-list { + list-style: none; + padding: 0; + margin: 0; +} + +.task-item { + padding: 1rem; + display: flex; + align-items: center; + gap: 1rem; + background: #f8f9fa; + border: 1px solid #e0e0e0; + border-radius: 8px; + margin-bottom: 0.5rem; + transition: all 0.2s; +} + +[data-theme="dark"] .task-item { + background: #202124; + border-color: #3c4043; +} + +.task-item:hover { + border-color: #1a73e8; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +[data-theme="dark"] .task-item:hover { + border-color: #8ab4f8; +} + +.task-item.completed { + opacity: 0.6; +} + +.task-item.completed span { + text-decoration: line-through; +} + +.task-item input[type="checkbox"] { + width: 1.25rem; + height: 1.25rem; + cursor: pointer; + accent-color: #1a73e8; + flex-shrink: 0; +} + +.task-item span { + flex: 1; + font-size: 1rem; + line-height: 1.5; +} + +.task-item button { + background: #ea4335; + color: white; + border: none; + padding: 0.5rem 0.75rem; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + font-size: 0.875rem; + flex-shrink: 0; +} + +.task-item button:hover { + background: #c5221f; +} + +.task-item button:active { + transform: scale(0.95); +} + +/* Task Filters */ +.task-filters { + display: flex; + gap: 0.5rem; + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid #e0e0e0; + flex-wrap: wrap; +} + +[data-theme="dark"] .task-filters { + border-top-color: #3c4043; +} + +.task-filters button { + padding: 0.5rem 1rem; + background: #f8f9fa; + color: #5f6368; + border: 1px solid #e0e0e0; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + font-size: 0.875rem; + font-weight: 500; +} + +[data-theme="dark"] .task-filters button { + background: #202124; + color: #9aa0a6; + border-color: #3c4043; +} + +.task-filters button:hover { + background: #e8f0fe; + color: #1a73e8; + border-color: #1a73e8; +} + +[data-theme="dark"] .task-filters button:hover { + background: #1e3a5f; + color: #8ab4f8; + border-color: #8ab4f8; +} + +.task-filters button.active { + background: #1a73e8; + color: white; + border-color: #1a73e8; +} + +[data-theme="dark"] .task-filters button.active { + background: #8ab4f8; + color: #202124; + border-color: #8ab4f8; +} + +.task-filters button:active { + transform: scale(0.98); +} + +/* Stats */ +.task-stats { + display: flex; + gap: 1rem; + margin-top: 1rem; + font-size: 0.875rem; + color: #5f6368; +} + +[data-theme="dark"] .task-stats { + color: #9aa0a6; +} + +.task-stats span { + display: flex; + align-items: center; + gap: 0.25rem; +} + +/* Scrollbar */ +.tasks-container::-webkit-scrollbar { + width: 8px; +} + +.tasks-container::-webkit-scrollbar-track { + background: transparent; +} + +.tasks-container::-webkit-scrollbar-thumb { + background: rgba(128, 128, 128, 0.3); + border-radius: 4px; +} + +.tasks-container::-webkit-scrollbar-thumb:hover { + background: rgba(128, 128, 128, 0.5); +} + +[data-theme="dark"] .tasks-container::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); +} + +[data-theme="dark"] .tasks-container::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.3); +} + +/* Headers */ +h2 { + margin: 0 0 1.5rem 0; + font-size: 1.75rem; + font-weight: 500; +} + +/* Alpine.js cloak */ +[x-cloak] { + display: none !important; +} + +/* Responsive */ +@media (max-width: 768px) { + .tasks-container { + padding: 1rem; + } + + .task-input { + flex-direction: column; + } + + .task-input button { + width: 100%; + } +} diff --git a/web/desktop/tasks/tasks.js b/web/desktop/tasks/tasks.js index ae7328e5f..579d7830c 100644 --- a/web/desktop/tasks/tasks.js +++ b/web/desktop/tasks/tasks.js @@ -1,77 +1,77 @@ -function tasksApp() { +window.tasksApp = function tasksApp() { return { - newTask: '', - filter: 'all', + newTask: "", + filter: "all", tasks: [], - + init() { - const saved = localStorage.getItem('tasks'); + const saved = localStorage.getItem("tasks"); if (saved) { try { this.tasks = JSON.parse(saved); } catch (e) { - console.error('Failed to load tasks:', e); + console.error("Failed to load tasks:", e); this.tasks = []; } } }, - + addTask() { - if (this.newTask.trim() === '') return; - + if (this.newTask.trim() === "") return; + this.tasks.push({ id: Date.now(), text: this.newTask.trim(), completed: false, - createdAt: new Date().toISOString() + createdAt: new Date().toISOString(), }); - - this.newTask = ''; + + this.newTask = ""; this.save(); }, - + toggleTask(id) { - const task = this.tasks.find(t => t.id === id); + const task = this.tasks.find((t) => t.id === id); if (task) { task.completed = !task.completed; this.save(); } }, - + deleteTask(id) { - this.tasks = this.tasks.filter(t => t.id !== id); + this.tasks = this.tasks.filter((t) => t.id !== id); this.save(); }, - + clearCompleted() { - this.tasks = this.tasks.filter(t => !t.completed); + this.tasks = this.tasks.filter((t) => !t.completed); this.save(); }, - + save() { try { - localStorage.setItem('tasks', JSON.stringify(this.tasks)); + localStorage.setItem("tasks", JSON.stringify(this.tasks)); } catch (e) { - console.error('Failed to save tasks:', e); + console.error("Failed to save tasks:", e); } }, - + get filteredTasks() { - if (this.filter === 'active') { - return this.tasks.filter(t => !t.completed); + if (this.filter === "active") { + return this.tasks.filter((t) => !t.completed); } - if (this.filter === 'completed') { - return this.tasks.filter(t => t.completed); + if (this.filter === "completed") { + return this.tasks.filter((t) => t.completed); } return this.tasks; }, - + get activeTasks() { - return this.tasks.filter(t => !t.completed).length; + return this.tasks.filter((t) => !t.completed).length; }, - + get completedTasks() { - return this.tasks.filter(t => t.completed).length; - } + return this.tasks.filter((t) => t.completed).length; + }, }; -} +};