Add Apps tab UI and installed apps fetch

This commit is contained in:
jelveh
2026-03-25 16:10:10 -07:00
parent ec766eecc2
commit 94395cd539
3 changed files with 184 additions and 48 deletions
+52 -46
View File
@@ -17,83 +17,89 @@
* 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>';
function buildAppsGrid (apps) {
if ( !apps || apps.length === 0 ) {
let h = '<div class="myapps-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" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/>';
h += '<rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/>';
h += '</svg>';
h += '<p>No apps installed yet</p>';
h += '</div>';
return h;
}
// 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>';
}
let h = '<div class="myapps-grid">';
for ( const app of apps ) {
const title = (app.title || app.name || '').trim();
const iconUrl = app.iconUrl || window.icons['app.svg'];
return apps_str;
h += `<div class="myapps-tile" data-app-name="${html_encode(app.name)}" title="${html_encode(title)}">`;
h += '<div class="myapps-tile-icon">';
h += `<img src="${html_encode(iconUrl)}" alt="" draggable="false">`;
h += '</div>';
h += `<span class="myapps-tile-label">${html_encode(title)}</span>`;
h += '</div>';
}
h += '</div>';
return h;
}
const TabApps = {
id: 'apps',
label: 'My Apps',
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><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>`,
label: 'Apps',
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/></svg>',
_apps: null,
html () {
return '<div class="dashboard-apps-container"></div>';
let h = '<div class="dashboard-tab-content myapps-tab">';
h += '<div class="myapps-container">';
h += '<div class="myapps-loading">Loading apps...</div>';
h += '</div>';
h += '</div>';
return h;
},
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) {
// Handle app tile clicks
$el_window.on('click', '.myapps-tile', function (e) {
e.preventDefault();
e.stopPropagation();
const appName = $(this).attr('data-app-name');
if ( appName ) {
const appUrl = `/app/${appName}`;
window.open(appUrl, '_blank');
window.open(`/app/${appName}`, '_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',
const $container = $el_window.find('.myapps-container');
try {
const res = await fetch(
`${window.api_origin}/installedApps?orderBy=name&limit=100`,
{
headers: {
'Authorization': `Bearer ${window.auth_token}`,
'Authorization': `Bearer ${puter.authToken}`,
},
});
} catch (e) {
console.error('Failed to load launch apps:', e);
}
method: 'GET',
},
);
const apps = await res.json();
this._apps = apps;
$container.html(buildAppsGrid(apps));
} catch (e) {
console.error('Failed to load installed apps:', e);
$container.html('<div class="myapps-empty"><p>Failed to load apps</p></div>');
}
// 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;
+2 -2
View File
@@ -54,7 +54,7 @@ import TabSecurity from './TabSecurity.js';
// Registry of built-in tabs
const builtinTabs = [
TabHome,
// TabApps,
TabApps,
TabFiles,
TabUsage,
TabAccount,
@@ -95,7 +95,7 @@ async function UIDashboard (options) {
for ( let i = 0; i < tabs.length; i++ ) {
const tab = tabs[i];
const isActive = i === 0 ? ' active' : '';
const isBeta = tab.label === 'Files';
const isBeta = tab.label === 'Apps';
h += `<div class="dashboard-sidebar-item${isActive} ${isBeta ? 'beta' : ''}" data-section="${tab.id}">`;
h += tab.icon;
h += tab.label;
+130
View File
@@ -433,6 +433,112 @@ body {
padding: 24px 0;
}
/* My Apps tab */
.myapps-tab {
max-width: 900px;
padding: 32px 40px;
}
.myapps-tab h2 {
font-size: 26px;
font-weight: 700;
color: var(--dashboard-text-heading);
margin: 0 0 24px 0;
}
.myapps-container {
min-height: 200px;
}
.myapps-loading {
color: var(--dashboard-text-tertiary);
font-size: 14px;
padding: 40px 0;
text-align: center;
}
.myapps-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 0;
color: var(--dashboard-text-tertiary);
}
.myapps-empty svg {
width: 48px;
height: 48px;
margin-bottom: 12px;
opacity: 0.4;
}
.myapps-empty p {
font-size: 15px;
margin: 0;
}
.myapps-grid {
display: grid;
grid-template-columns: repeat(auto-fill, 80px);
gap: 24px 20px;
justify-content: start;
}
.myapps-tile {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
width: 80px;
-webkit-user-select: none;
user-select: none;
}
.myapps-tile-icon {
width: 60px;
height: 60px;
border-radius: 14px;
overflow: hidden;
background: var(--dashboard-card-background);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
transition: transform 0.15s ease, box-shadow 0.15s ease;
display: flex;
align-items: center;
justify-content: center;
}
.myapps-tile-icon img {
width: 100%;
height: 100%;
object-fit: cover;
}
.myapps-tile:hover .myapps-tile-icon {
transform: scale(1.08);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.12), 0 0 0 0.5px rgba(0, 0, 0, 0.06);
}
.myapps-tile:active .myapps-tile-icon {
transform: scale(0.95);
}
.myapps-tile-label {
margin-top: 6px;
font-size: 12px;
color: var(--dashboard-text-heading);
text-align: center;
line-height: 1.3;
max-width: 80px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
}
/* Dashboard files */
.dashboard-content.files {
@@ -1548,6 +1654,30 @@ body {
width: 40px;
height: 40px;
}
.myapps-tab {
padding: 20px 16px;
}
.myapps-tab h2 {
font-size: 22px;
margin-bottom: 16px;
}
.myapps-grid {
grid-template-columns: repeat(auto-fill, 72px);
gap: 18px 14px;
}
.myapps-tile {
width: 72px;
}
.myapps-tile-icon {
width: 52px;
height: 52px;
border-radius: 12px;
}
}
/* Desktop: Make metadata wrapper transparent */