* Add dashboard UI and routing support

Introduces a new Dashboard UI component with sidebar navigation and user options. Updates backend routing to redirect /dashboard to the root path. Integrates dashboard mode detection and initialization in the GUI, including responsive styles and logic to open the dashboard or desktop as appropriate.

* Improve dashboard user menu and UI behavior

Added support for multiple logged-in users, session saving for temporary users, and improved context menu options in the dashboard. Updated CSS for user button state and adjusted font size for signup terms. Changed dashboard initialization to use UIDashboard instead of UIWindow.

* Add dynamic apps section to dashboard UI

* Add specific class for dashboard apps section

* Refactor dashboard tabs into modular components

* Update style.css

* Add Developers menu and improve signup window behavior

Added a 'Developers' menu item to the dashboard linking to developer.puter.com. Updated login and signup window logic to ensure the signup window opens centered and dominant, improving user experience when transitioning from login to signup.

* Update UIWindowLogin.js

* Refactor apps tab UI and improve app card interaction
This commit is contained in:
Nariman Jelveh
2025-12-06 17:06:20 -08:00
committed by GitHub
parent 277ee6ca5a
commit ed509ad286
7 changed files with 745 additions and 6 deletions

View File

@@ -346,6 +346,10 @@ router.all('*', async function (req, res, next) {
else if ( path.startsWith('/settings') ) {
path = '/';
}
// /dashboard
else if ( path === '/dashboard' || path === '/dashboard/' ) {
path = '/';
}
// /app/
else if ( path.startsWith('/app/') ) {
app_name = path.replace('/app/', '');

View File

@@ -0,0 +1,99 @@
/**
* 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 buildAppsSection () {
let apps_str = '';
if ( window.launch_apps?.recommended?.length > 0 ) {
apps_str += '<div class="dashboard-apps-grid">';
for ( let index = 0; index < window.launch_apps.recommended.length; index++ ) {
const app_info = window.launch_apps.recommended[index];
apps_str += `<div title="${html_encode(app_info.title)}" data-name="${html_encode(app_info.name)}" class="dashboard-app-card start-app-card">`;
apps_str += `<div class="start-app" data-app-name="${html_encode(app_info.name)}" data-app-uuid="${html_encode(app_info.uuid)}" data-app-icon="${html_encode(app_info.icon)}" data-app-title="${html_encode(app_info.title)}">`;
apps_str += `<img class="dashboard-app-icon" src="${html_encode(app_info.icon ? app_info.icon : window.icons['app.svg'])}">`;
apps_str += `<span class="dashboard-app-title">${html_encode(app_info.title)}</span>`;
apps_str += '</div>';
apps_str += '</div>';
}
apps_str += '</div>';
}
// No apps message
if ( (!window.launch_apps?.recent || window.launch_apps.recent.length === 0) &&
(!window.launch_apps?.recommended || window.launch_apps.recommended.length === 0) ) {
apps_str += '<p class="dashboard-no-apps">No apps available yet.</p>';
}
return apps_str;
}
const TabApps = {
id: 'apps',
label: 'My Apps',
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>`,
html () {
return '<div class="dashboard-apps-container"></div>';
},
init ($el_window) {
// Load apps initially
this.loadApps($el_window);
// Handle app clicks - open in new browser tab
$el_window.on('click', '.dashboard-apps-container .start-app', function (e) {
e.preventDefault();
e.stopPropagation();
const appName = $(this).attr('data-app-name');
if ( appName ) {
const appUrl = `/app/${appName}`;
window.open(appUrl, '_blank');
}
});
},
async loadApps ($el_window) {
// If launch_apps is not populated yet, fetch from server
if ( !window.launch_apps || !window.launch_apps.recent || window.launch_apps.recent.length === 0 ) {
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);
}
}
// Populate the apps container
$el_window.find('.dashboard-apps-container').html(buildAppsSection());
},
onActivate ($el_window) {
// Refresh apps when navigating to apps section
this.loadApps($el_window);
},
};
export default TabApps;

View File

@@ -0,0 +1,38 @@
/**
* 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/>.
*/
const TabFiles = {
id: 'files',
label: 'My Files',
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>`,
html () {
let h = '';
h += '<h2>My Files</h2>';
h += '<p>Your files will appear here.</p>';
return h;
},
init ($el_window) {
// Files tab initialization logic can go here
},
};
export default TabFiles;

View File

@@ -0,0 +1,281 @@
/**
* 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/>.
*/
import UIWindow from '../UIWindow.js';
import UIContextMenu from '../UIContextMenu.js';
import UIWindowSettings from '../Settings/UIWindowSettings.js';
import UIAlert from '../UIAlert.js';
import UIWindowSaveAccount from '../UIWindowSaveAccount.js';
import UIWindowLogin from '../UIWindowLogin.js';
import UIWindowFeedback from '../UIWindowFeedback.js';
// Import tab modules
import TabFiles from './TabFiles.js';
import TabApps from './TabApps.js';
// Registry of all available tabs
const tabs = [
TabFiles,
TabApps,
];
async function UIDashboard (options) {
options = options ?? {};
let h = '';
h += '<div class="dashboard">';
// Mobile sidebar toggle
h += '<button class="dashboard-sidebar-toggle">';
h += '<span></span><span></span><span></span>';
h += '</button>';
// Sidebar
h += '<div class="dashboard-sidebar">';
// Navigation items container
h += '<div class="dashboard-sidebar-nav">';
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
h += '<div class="dashboard-user-options">';
h += `<div class="dashboard-user-btn">`;
h += `<div class="dashboard-user-avatar" style="background-image: url(${window.user?.profile?.picture || window.icons['profile.svg']})"></div>`;
h += `<span class="dashboard-user-name">${html_encode(window.user?.username || 'User')}</span>`;
h += `<svg class="dashboard-user-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>`;
h += `</div>`;
h += '</div>';
h += '</div>';
// Main content area
h += '<div class="dashboard-content">';
for ( let i = 0; i < tabs.length; i++ ) {
const tab = tabs[i];
const isActive = i === 0 ? ' active' : '';
h += `<div class="dashboard-section dashboard-section-${tab.id}${isActive}" data-section="${tab.id}">`;
h += tab.html();
h += '</div>';
}
h += '</div>';
h += '</div>';
const el_window = await UIWindow({
title: 'Dashboard',
app: 'dashboard',
single_instance: true,
is_fullpage: true,
is_resizable: false,
is_maximized: true,
has_head: false,
body_content: h,
window_class: 'window-dashboard',
body_css: {
height: '100%',
overflow: 'hidden',
},
});
const $el_window = $(el_window);
// Initialize all tabs
for ( const tab of tabs ) {
if ( tab.init ) {
tab.init($el_window);
}
}
// Sidebar item click handler
$el_window.on('click', '.dashboard-sidebar-item', function () {
const $this = $(this);
const section = $this.attr('data-section');
// Update active sidebar item
$el_window.find('.dashboard-sidebar-item').removeClass('active');
$this.addClass('active');
// Update active content section
$el_window.find('.dashboard-section').removeClass('active');
$el_window.find(`.dashboard-section[data-section="${section}"]`).addClass('active');
// Call onActivate for the tab if it exists
const tab = tabs.find(t => t.id === section);
if ( tab && tab.onActivate ) {
tab.onActivate($el_window);
}
// Close sidebar on mobile after selection
$el_window.find('.dashboard-sidebar').removeClass('open');
$el_window.find('.dashboard-sidebar-toggle').removeClass('open');
});
// Mobile toggle handler
$el_window.on('click', '.dashboard-sidebar-toggle', function () {
$(this).toggleClass('open');
$el_window.find('.dashboard-sidebar').toggleClass('open');
});
// User options button click handler
$el_window.on('click', '.dashboard-user-btn', function (e) {
const $btn = $(this);
const pos = this.getBoundingClientRect();
// Don't open if already open
if ($('.context-menu[data-id="dashboard-user-menu"]').length > 0) {
return;
}
let items = [];
// Save Session (if temp user)
if (window.user.is_temp) {
items.push({
html: i18n('save_session'),
icon: '<svg style="margin-bottom: -4px; width: 16px; height: 16px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M45.521,39.04L27.527,5.134c-1.021-1.948-3.427-2.699-5.375-1.679-.717,.376-1.303,.961-1.679,1.679L2.479,39.04c-.676,1.264-.635,2.791,.108,4.017,.716,1.207,2.017,1.946,3.42,1.943H41.993c1.403,.003,2.704-.736,3.42-1.943,.743-1.226,.784-2.753,.108-4.017ZM23.032,15h1.937c.565,0,1.017,.467,1,1.031l-.438,14c-.017,.54-.459,.969-1,.969h-1.062c-.54,0-.983-.429-1-.969l-.438-14c-.018-.564,.435-1.031,1-1.031Zm.968,25c-1.657,0-3-1.343-3-3s1.343-3,3-3,3,1.343,3,3-1.343,3-3,3Z" fill="#ffbb00"/></svg>',
onClick: async function () {
UIWindowSaveAccount({
send_confirmation_code: false,
default_username: window.user.username,
});
},
});
items.push('-');
}
// Logged in users
if (window.logged_in_users.length > 0) {
let users_arr = window.logged_in_users;
// bring logged in user's item to top
users_arr.sort(function (x, y) {
return x.uuid === window.user.uuid ? -1 : y.uuid == window.user.uuid ? 1 : 0;
});
// create menu items for each user
users_arr.forEach(l_user => {
items.push({
html: l_user.username,
icon: l_user.username === window.user.username ? '✓' : '',
onClick: async function () {
if (l_user.username === window.user.username) {
return;
}
window.update_auth_data(l_user.auth_token, l_user);
location.reload();
},
});
});
items.push('-');
items.push({
html: i18n('add_existing_account'),
onClick: async function () {
await UIWindowLogin({
reload_on_success: true,
send_confirmation_code: false,
window_options: {
has_head: true,
stay_on_top: true,
},
});
},
});
items.push('-');
}
// Build final menu items
const menuItems = [
...items,
// Settings
{
html: i18n('settings'),
onClick: async function () {
UIWindowSettings();
},
},
// Developer
{
html: 'Developers<svg style="width: 11px; height: 11px; margin-left:2px; margin-bottom:-1px;" height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m26 28h-20a2.0027 2.0027 0 0 1 -2-2v-20a2.0027 2.0027 0 0 1 2-2h10v2h-10v20h20v-10h2v10a2.0027 2.0027 0 0 1 -2 2z"/><path d="m20 2v2h6.586l-8.586 8.586 1.414 1.414 8.586-8.586v6.586h2v-10z"/><path d="m0 0h32v32h-32z" fill="none"/></svg>',
html_active: 'Developers<svg style="width: 11px; height: 11px; margin-left:2px; margin-bottom:-1px;" height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m26 28h-20a2.0027 2.0027 0 0 1 -2-2v-20a2.0027 2.0027 0 0 1 2-2h10v2h-10v20h20v-10h2v10a2.0027 2.0027 0 0 1 -2 2z" style="fill: rgb(255, 255, 255);"/><path d="m20 2v2h6.586l-8.586 8.586 1.414 1.414 8.586-8.586v6.586h2v-10z" style="fill: rgb(255, 255, 255);"/><path d="m0 0h32v32h-32z" fill="none"/></svg>',
onClick: function () {
window.open('https://developer.puter.com', '_blank');
},
},
// Contact Us
{
html: i18n('contact_us'),
onClick: async function () {
UIWindowFeedback();
},
},
'-',
// Log out
{
html: i18n('log_out'),
onClick: async function () {
// Check for open windows
if ($('.window-app').length > 0) {
const alert_resp = await UIAlert({
message: `<p>${i18n('confirm_open_apps_log_out')}</p>`,
buttons: [
{
label: i18n('close_all_windows_and_log_out'),
value: 'close_and_log_out',
type: 'primary',
},
{
label: i18n('cancel'),
},
],
});
if (alert_resp === 'close_and_log_out') {
window.logout();
}
} else {
window.logout();
}
},
},
];
UIContextMenu({
id: 'dashboard-user-menu',
parent_element: $btn[0],
position: {
top: pos.top - 8,
left: pos.left
},
items: menuItems
});
});
return el_window;
}
export default UIDashboard;

View File

@@ -44,6 +44,7 @@ function UIWindowSignup (options) {
// Form
h += '<div style="padding: 20px; border-bottom: 1px solid #ced7e1;">';
// title
h += `<h1 class="signup-form-title">${i18n('create_free_account')}</h1>`;
// signup form
@@ -122,7 +123,7 @@ function UIWindowSignup (options) {
allow_native_ctxmenu: true,
allow_user_select: true,
...options.window_options,
dominant: false,
dominant: true,
center: true,
onAppend: function (el_window) {
$(el_window).find('.username').get(0).focus({ preventScroll: true });

View File

@@ -4017,7 +4017,7 @@ fieldset[name=number-code] {
}
.signup-terms {
font-size: 11px;
font-size: 10px;
color: #666;
margin-top: 10px;
bottom: 10px;
@@ -5436,4 +5436,300 @@ fieldset[name=number-code] {
.update-usage-details svg{
width: 20px;
height: 20px;
}
/* ======================================
Dashboard
====================================== */
.dashboard {
display: flex;
height: 100%;
background: #fff;
}
.dashboard-sidebar {
width: 200px;
min-width: 200px;
background: #f5f5f5;
border-right: 1px solid #e0e0e0;
padding: 16px 12px;
display: flex;
flex-direction: column;
}
.dashboard-sidebar-nav {
flex: 1;
}
.dashboard-sidebar-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
margin-bottom: 4px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
color: #444;
transition: background-color 0.15s;
}
.dashboard-sidebar-item svg {
width: 18px;
height: 18px;
flex-shrink: 0;
}
.dashboard-sidebar-item:hover {
background: #e8e8e8;
}
.dashboard-sidebar-item.active {
background: #e0e0e0;
font-weight: 500;
}
/* User options button at bottom of sidebar */
.dashboard-user-options {
border-top: 1px solid #e0e0e0;
padding-top: 12px;
margin-top: 8px;
}
.dashboard-user-btn {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.15s;
}
.dashboard-user-btn:hover {
background: #e8e8e8;
}
.dashboard-user-btn.has-open-contextmenu {
background: #e8e8e8;
}
.dashboard-user-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background-size: cover;
background-position: center;
background-color: #ddd;
flex-shrink: 0;
}
.dashboard-user-name {
flex: 1;
font-size: 14px;
color: #333;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dashboard-user-chevron {
width: 16px;
height: 16px;
color: #888;
flex-shrink: 0;
}
.dashboard-content {
flex: 1;
padding: 24px 32px;
overflow-y: auto;
}
.dashboard-section {
display: none;
}
.dashboard-section.active {
display: block;
}
.dashboard-section h2 {
margin: 0 0 16px 0;
font-size: 20px;
font-weight: 600;
color: #333;
}
.dashboard-section p {
color: #666;
font-size: 14px;
}
.dashboard-section-apps {
max-width: 600px;
margin: 0 auto;
}
/* Dashboard Apps */
.dashboard-apps-container {
margin-top: 8px;
}
.dashboard-apps-heading {
font-size: 14px;
font-weight: 600;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 0 0 16px 0;
}
.dashboard-apps-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 8px;
margin-bottom: 8px;
}
.dashboard-app-card {
width: 100%;
transition: background-color 0.15s, transform 0.1s;
transition: transform 0.15s ease;
}
.dashboard-app-card .start-app {
cursor: pointer;
}
.dashboard-app-card:hover {
background: none !important;
}
.dashboard-app-card:hover .start-app, .dashboard-app-card .start-app:hover {
background: none !important;
}
.dashboard-app-card:active {
transform: scale(0.97);
}
.dashboard-app-card .start-app {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
width: 100%;
}
.dashboard-app-icon {
width: 48px;
height: 48px;
margin-bottom: 8px;
filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.1));
}
.dashboard-app-card .start-app{
transition: transform 0.15s ease;
}
.dashboard-app-card .start-app:hover {
transform: scale(1.1);
}
.dashboard-app-title {
font-size: 12px;
color: #333;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
line-height: 1.3;
}
.dashboard-no-apps {
color: #888;
font-size: 14px;
padding: 24px 0;
}
/* Mobile sidebar toggle */
.dashboard-sidebar-toggle {
display: none;
position: fixed;
top: 12px;
left: 12px;
z-index: 100;
width: 40px;
height: 40px;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 6px;
cursor: pointer;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
}
.dashboard-sidebar-toggle span {
display: block;
width: 18px;
height: 2px;
background: #444;
border-radius: 1px;
transition: transform 0.2s, opacity 0.2s;
}
.dashboard-sidebar-toggle.open span:nth-child(1) {
transform: rotate(45deg) translate(4px, 4px);
}
.dashboard-sidebar-toggle.open span:nth-child(2) {
opacity: 0;
}
.dashboard-sidebar-toggle.open span:nth-child(3) {
transform: rotate(-45deg) translate(4px, -4px);
}
/* Responsive: tablet and below */
@media (max-width: 768px) {
.dashboard-sidebar-toggle {
display: flex;
}
.dashboard-sidebar {
position: fixed;
left: 0;
top: 0;
height: 100%;
z-index: 99;
transform: translateX(-100%);
transition: transform 0.2s ease;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
}
.dashboard-sidebar.open {
transform: translateX(0);
}
.dashboard-content {
padding: 64px 16px 24px;
}
.dashboard-apps-grid {
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 4px;
}
.dashboard-app-card {
padding: 10px 6px;
}
.dashboard-app-icon {
width: 40px;
height: 40px;
}
}

View File

@@ -47,6 +47,7 @@ import { ProcessService } from './services/ProcessService.js';
import { SettingsService } from './services/SettingsService.js';
import { ThemeService } from './services/ThemeService.js';
import { privacy_aware_path } from './util/desktop.js';
import UIDashboard from './UI/Dashboard/UIDashboard.js';
const launch_services = async function (options) {
// === Services Data Structures ===
@@ -150,6 +151,11 @@ if ( jQuery ) {
};
}
// are we in dashboard mode?
if(window.location.pathname === '/dashboard' || window.location.pathname === '/dashboard/'){
window.is_dashboard_mode = true;
}
/**
* Shows a Turnstile challenge modal for first-time temp user creation
* @param {Object} options - Configuration options
@@ -348,6 +354,8 @@ window.initgui = async function (options) {
// Puter is in fullpage mode.
window.is_fullpage_mode = true;
} else if (window.is_dashboard_mode) {
window.is_fullpage_mode = true;
}
// Launch services before any UI is rendered
@@ -601,15 +609,21 @@ window.initgui = async function (options) {
window.update_auth_data(whoami.token || window.auth_token, whoami);
// -------------------------------------------------------------------------------------
// Load desktop, only if we're not embedded in a popup
// Load desktop, only if we're not embedded in a popup and not on the dashboard page
// -------------------------------------------------------------------------------------
if ( ! window.embedded_in_popup ) {
if ( ! window.embedded_in_popup && ! window.is_dashboard_mode ) {
await window.get_auto_arrange_data();
puter.fs.stat({ path: window.desktop_path, consistency: 'eventual' }).then(desktop_fsentry => {
UIDesktop({ desktop_fsentry: desktop_fsentry });
});
}
// -------------------------------------------------------------------------------------
// Dashboard mode
// -------------------------------------------------------------------------------------
else if ( window.is_dashboard_mode ) {
UIDashboard();
}
// -------------------------------------------------------------------------------------
// If embedded in a popup, send the token to the opener and close the popup
// -------------------------------------------------------------------------------------
else {
@@ -1107,15 +1121,21 @@ window.initgui = async function (options) {
$('.window').close();
// -------------------------------------------------------------------------------------
// Load desktop, if not embedded in a popup
// Load desktop, if not embedded in a popup and not on the dashboard page
// -------------------------------------------------------------------------------------
if ( ! window.embedded_in_popup ) {
if ( ! window.embedded_in_popup && ! window.is_dashboard_mode ) {
await window.get_auto_arrange_data();
puter.fs.stat({ path: window.desktop_path, consistency: 'eventual' }).then(desktop_fsentry => {
UIDesktop({ desktop_fsentry: desktop_fsentry });
});
}
// -------------------------------------------------------------------------------------
// Dashboard mode: open explorer pointing to home directory
// -------------------------------------------------------------------------------------
else if ( window.is_dashboard_mode ) {
UIDashboard();
}
// -------------------------------------------------------------------------------------
// If embedded in a popup, send the 'ready' event to referrer and close the popup
// -------------------------------------------------------------------------------------
else {