mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 16:40:41 +00:00
Add Apps tab UI and installed apps fetch
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user