mirror of
https://github.com/HeyPuter/puter.git
synced 2025-12-21 12:59:52 -06:00
Add Home tab with bento dashboard layout
This commit is contained in:
132
src/gui/src/UI/Dashboard/TabHome.js
Normal file
132
src/gui/src/UI/Dashboard/TabHome.js
Normal 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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user