Add Home tab with bento dashboard layout

This commit is contained in:
jelveh
2025-12-06 19:40:07 -08:00
parent 622f53468b
commit c2bcd3092d
3 changed files with 378 additions and 15 deletions

View File

@@ -0,0 +1,132 @@
/**
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
function getTimeGreeting () {
const hour = new Date().getHours();
if ( hour < 12 ) return 'Good morning';
if ( hour < 17 ) return 'Good afternoon';
return 'Good evening';
}
function buildRecentAppsHTML () {
let h = '';
if ( window.launch_apps?.recent?.length > 0 ) {
h += '<div class="bento-recent-apps-grid">';
// Show up to 6 recent apps
const recentApps = window.launch_apps.recent.slice(0, 6);
for ( const app_info of recentApps ) {
h += `<div class="bento-recent-app" data-app-name="${html_encode(app_info.name)}">`;
h += `<img class="bento-recent-app-icon" src="${html_encode(app_info.icon || window.icons['app.svg'])}">`;
h += `<span class="bento-recent-app-title">${html_encode(app_info.title)}</span>`;
h += '</div>';
}
h += '</div>';
} else {
h += '<div class="bento-recent-apps-empty">';
h += '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">';
h += '<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>';
h += '<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>';
h += '</svg>';
h += '<p>No recent apps yet</p>';
h += '<span>Apps you use will appear here</span>';
h += '</div>';
}
return h;
}
const TabHome = {
id: 'home',
label: 'Home',
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>`,
html () {
const username = window.user?.username || 'User';
const greeting = getTimeGreeting();
let h = '';
h += '<div class="bento-container">';
// Welcome card (square)
h += '<div class="bento-card bento-welcome">';
h += '<div class="bento-welcome-inner">';
h += '<div class="bento-welcome-pattern"></div>';
h += `<div class="bento-welcome-content">`;
h += `<span class="bento-greeting">${greeting},</span>`;
h += `<h1 class="bento-username">${html_encode(username)}</h1>`;
h += '<p class="bento-tagline">Your personal cloud computer</p>';
h += '</div>';
h += '</div>';
h += '</div>';
// Recent apps card (rectangle)
h += '<div class="bento-card bento-recent">';
h += '<div class="bento-card-header">';
h += '<h2>Recent Apps</h2>';
h += '</div>';
h += '<div class="bento-recent-apps-container">';
h += buildRecentAppsHTML();
h += '</div>';
h += '</div>';
h += '</div>';
return h;
},
init ($el_window) {
this.loadRecentApps($el_window);
// Handle app clicks
$el_window.on('click', '.bento-recent-app', function (e) {
e.preventDefault();
e.stopPropagation();
const appName = $(this).attr('data-app-name');
if ( appName ) {
window.open(`/app/${appName}`, '_blank');
}
});
},
async loadRecentApps ($el_window) {
if ( !window.launch_apps?.recent?.length ) {
try {
window.launch_apps = await $.ajax({
url: `${window.api_origin}/get-launch-apps?icon_size=64`,
type: 'GET',
async: true,
contentType: 'application/json',
headers: {
'Authorization': `Bearer ${window.auth_token}`,
},
});
} catch (e) {
console.error('Failed to load launch apps:', e);
}
}
$el_window.find('.bento-recent-apps-container').html(buildRecentAppsHTML());
},
onActivate ($el_window) {
this.loadRecentApps($el_window);
},
};
export default TabHome;

View File

@@ -26,14 +26,16 @@ import UIWindowLogin from '../UIWindowLogin.js';
import UIWindowFeedback from '../UIWindowFeedback.js';
// Import tab modules
import TabHome from './TabHome.js';
import TabFiles from './TabFiles.js';
import TabApps from './TabApps.js';
import TabUsage from './TabUsage.js';
// Registry of all available tabs
const tabs = [
TabFiles,
TabHome,
TabApps,
TabFiles,
TabUsage,
];
@@ -53,20 +55,14 @@ async function UIDashboard (options) {
h += '<div class="dashboard-sidebar">';
// Navigation items container
h += '<div class="dashboard-sidebar-nav">';
h += `<div class="dashboard-sidebar-item active" data-section="${TabFiles.id}">`;
h += TabFiles.icon;
h += TabFiles.label;
h += '</div>';
h += `<div class="dashboard-sidebar-item" data-section="${TabApps.id}">`;
h += TabApps.icon;
h += TabApps.label;
h += '</div>';
h += `<div class="dashboard-sidebar-item" data-section="${TabUsage.id}">`;
h += TabUsage.icon;
h += TabUsage.label;
for ( let i = 0; i < tabs.length; i++ ) {
const tab = tabs[i];
const isActive = i === 0 ? ' active' : '';
h += `<div class="dashboard-sidebar-item${isActive}" data-section="${tab.id}">`;
h += tab.icon;
h += tab.label;
h += '</div>';
}
h += '</div>';
// User options button at bottom

View File

@@ -5589,7 +5589,7 @@ fieldset[name=number-code] {
}
.dashboard-section p {
color: #666;
color: #ffffff;
font-size: 14px;
}
@@ -5767,4 +5767,239 @@ fieldset[name=number-code] {
width: 40px;
height: 40px;
}
}
/* ============================================== */
/* Bento Box Home Dashboard */
/* ============================================== */
.bento-container {
display: grid;
grid-template-columns: 280px 1fr;
gap: 20px;
max-width: 900px;
margin: 0 auto;
padding: 8px 0;
align-items: stretch;
}
.bento-card {
background: #fff;
border-radius: 20px;
overflow: hidden;
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.04),
0 4px 12px rgba(0, 0, 0, 0.03);
border: 1px solid rgba(0, 0, 0, 0.06);
}
/* Welcome Card */
.bento-welcome {
position: relative;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
color: #fff;
min-height: 280px;
}
.bento-welcome-inner {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 24px;
box-sizing: border-box;
}
.bento-welcome-pattern {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.08) 0%, transparent 50%),
radial-gradient(circle at 80% 70%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
radial-gradient(circle at 60% 20%, rgba(236, 72, 153, 0.1) 0%, transparent 35%);
pointer-events: none;
}
.bento-welcome-content {
position: relative;
z-index: 1;
}
.bento-greeting {
font-size: 14px;
opacity: 0.7;
font-weight: 400;
letter-spacing: 0.02em;
display: block;
margin-bottom: 4px;
}
.bento-username {
font-size: 28px;
font-weight: 700;
margin: 0 0 8px 0;
line-height: 1.1;
letter-spacing: -0.02em;
background: linear-gradient(135deg, #fff 0%, rgba(255, 255, 255, 0.85) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bento-tagline {
font-size: 13px;
opacity: 0.5;
margin: 0;
font-weight: 400;
}
/* Recent Apps Card - Rectangle */
.bento-recent {
min-height: 280px;
display: flex;
flex-direction: column;
}
.bento-card-header {
padding: 20px 24px 0;
}
.bento-card-header h2 {
margin: 0;
font-size: 15px;
font-weight: 600;
color: #1a1a1a;
letter-spacing: -0.01em;
}
.bento-recent-apps-container {
flex: 1;
padding: 16px 24px 24px;
}
.bento-recent-apps-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.bento-recent-app {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 12px;
border-radius: 14px;
cursor: pointer;
transition: all 0.15s ease;
background: #f8f9fa;
border: 1px solid transparent;
}
.bento-recent-app:hover {
background: #f0f1f3;
transform: translateY(-2px);
border-color: rgba(0, 0, 0, 0.04);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.bento-recent-app:active {
transform: scale(0.97);
}
.bento-recent-app-icon {
width: 44px;
height: 44px;
margin-bottom: 10px;
border-radius: 10px;
}
.bento-recent-app-title {
font-size: 12px;
color: #333;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
font-weight: 500;
}
/* Empty state for recent apps */
.bento-recent-apps-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 200px;
text-align: center;
color: #888;
}
.bento-recent-apps-empty svg {
width: 48px;
height: 48px;
stroke: #ccc;
margin-bottom: 16px;
}
.bento-recent-apps-empty p {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 500;
color: #666;
}
.bento-recent-apps-empty span {
font-size: 13px;
color: #999;
}
/* Responsive bento layout */
@media (max-width: 768px) {
.bento-container {
grid-template-columns: 1fr;
gap: 16px;
padding: 0;
}
.bento-welcome {
aspect-ratio: auto;
min-height: 180px;
}
.bento-welcome-inner {
padding: 20px;
}
.bento-username {
font-size: 24px;
}
.bento-recent-apps-grid {
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.bento-recent-app {
padding: 12px 8px;
}
.bento-recent-app-icon {
width: 36px;
height: 36px;
margin-bottom: 8px;
}
.bento-recent-app-title {
font-size: 11px;
}
}