mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-26 16:07:13 +00:00
feat(gui): open dashboard apps maximized in-page with minimize + single instance
Apps launched from the dashboard's Apps tab now open as maximized Puter windows in the same page (via launch_app) instead of a new browser tab. External website shortcuts still open in a new tab since arbitrary sites can't reliably be iframed. Dashboard app windows keep their minimize button (fullpage mode normally hides it since there's no taskbar). With no taskbar item to animate toward, hideWindow now hides the window in place and flags it with data-minimized_in_place; showWindow un-hides it via that flag. An explicit flag is used because data-orig-* can't distinguish the two minimize paths - drag/maximize handlers set those attrs too. Tiles are single-instance: clicking a tile un-hides a minimized instance or focuses a visible one instead of launching a duplicate, and launches in flight swallow repeat clicks. Also fixes .window-body-app height in dashboard mode: fullpage mode sizes it to 100% assuming no titlebar, which clipped the bottom 29px of every app.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import UIContextMenu from '../UIContextMenu.js';
|
||||
import UIAlert from '../UIAlert.js';
|
||||
import launch_app from '../../helpers/launch_app.js';
|
||||
import { isTouchPrimaryDevice } from './ContextMenu/ContextMenu.js';
|
||||
import { reconcileAppOrder, serializeAppOrder, mergeSavedOrder, APPS_ORDER_KV_KEY } from './appOrder.js';
|
||||
|
||||
@@ -313,6 +314,7 @@ const TabApps = {
|
||||
_pendingLoad: null,
|
||||
_savedOrderNames: null,
|
||||
_orderSavedAtSeq: 0,
|
||||
_launchingApps: new Set(),
|
||||
|
||||
html () {
|
||||
let h = '<div class="dashboard-tab-content myapps-tab">';
|
||||
@@ -349,8 +351,9 @@ const TabApps = {
|
||||
});
|
||||
|
||||
// Handle app tile clicks. External apps carry a target link (their
|
||||
// index_url) and open the app's website directly; everything else
|
||||
// opens the Puter app page — matching the Home tab.
|
||||
// index_url) and open the app's website directly in a new browser tab
|
||||
// (an external site can't be reliably iframed); everything else
|
||||
// launches the app as a maximized window in this same page.
|
||||
$el_window.on('click', '.myapps-tile', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -364,7 +367,27 @@ const TabApps = {
|
||||
if ( targetLink && targetLink !== '' ) {
|
||||
window.open(targetLink, '_blank', 'noopener,noreferrer');
|
||||
} else if ( appName ) {
|
||||
window.open(`/app/${appName}`, '_blank', 'noopener,noreferrer');
|
||||
// One instance per app when launched from here: un-hide a
|
||||
// minimized instance / focus a visible one rather than
|
||||
// launching a duplicate.
|
||||
const $existing = $(`.window[data-app="${html_encode(appName)}"]`);
|
||||
if ( $existing.length ) {
|
||||
const $win = $existing.last();
|
||||
const minimized = $win.attr('data-is_minimized');
|
||||
if ( minimized === '1' || minimized === 'true' ) {
|
||||
$win.showWindow();
|
||||
} else {
|
||||
$win.focusWindow();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// A second click while the first launch's fetches are still in
|
||||
// flight has no window to find yet — swallow it instead of
|
||||
// spawning a duplicate instance.
|
||||
if ( self._launchingApps.has(appName) ) return;
|
||||
self._launchingApps.add(appName);
|
||||
launch_app({ name: appName, maximized: true })
|
||||
.finally(() => self._launchingApps.delete(appName));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -65,6 +65,10 @@ async function UIDashboard (options) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
options = options ?? {};
|
||||
|
||||
// Mark dashboard mode on <body> so window chrome can adapt — e.g. app
|
||||
// windows keep their minimize button even though fullpage-mode hides it.
|
||||
$('body').addClass('dashboard-mode');
|
||||
|
||||
// Create mutable tabs array from built-in tabs
|
||||
const tabs = [...builtinTabs];
|
||||
|
||||
|
||||
@@ -3821,6 +3821,25 @@ $.fn.showWindow = async function (options) {
|
||||
if ( $(this).hasClass('window') ) {
|
||||
// show window
|
||||
const el_window = this;
|
||||
|
||||
// A window minimized with no taskbar to animate toward (dashboard
|
||||
// mode) was simply hidden in place by hideWindow — its geometry
|
||||
// was never disturbed, so just un-hide it. (The explicit flag is
|
||||
// used because data-orig-* can't distinguish the two minimize
|
||||
// paths: drag/maximize handlers set it too.)
|
||||
if ( $(el_window).attr('data-minimized_in_place') === '1' ) {
|
||||
$(el_window).attr({
|
||||
'data-is_minimized': false,
|
||||
'data-minimized_in_place': '0',
|
||||
});
|
||||
$(el_window).fadeIn(150);
|
||||
$(el_window).css('z-index', ++window.last_window_zindex);
|
||||
setTimeout(() => {
|
||||
$(this).focusWindow();
|
||||
}, 80);
|
||||
return;
|
||||
}
|
||||
|
||||
$(el_window).css({
|
||||
'transition': 'top 0.2s, left 0.2s, bottom 0.2s, right 0.2s, width 0.2s, height 0.2s',
|
||||
top: `${$(el_window).attr('data-orig-top') }px`,
|
||||
@@ -3963,6 +3982,19 @@ $.fn.hideWindow = async function (options) {
|
||||
// get taskbar item location
|
||||
let taskbar_item_pos = $(`.taskbar .taskbar-item[data-app="${$(this).attr('data-app')}"]`).position();
|
||||
|
||||
// No taskbar item to animate toward (e.g. dashboard mode, which
|
||||
// has no taskbar): simply hide the window in place, geometry
|
||||
// untouched. showWindow() un-hides it via the
|
||||
// data-minimized_in_place flag.
|
||||
if ( ! taskbar_item_pos ) {
|
||||
$(this).attr({
|
||||
'data-is_minimized': true,
|
||||
'data-minimized_in_place': '1',
|
||||
});
|
||||
$(this).fadeOut(150);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate animation target based on taskbar position
|
||||
let animationTarget = {};
|
||||
const taskbarPosition = window.taskbar_position || 'bottom';
|
||||
|
||||
@@ -4198,3 +4198,17 @@ body.myapps-reordering .myapps-tile {
|
||||
.submenu-arrow {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Dashboard mode runs fullpage (which normally hides the minimize button
|
||||
because there's no taskbar to restore from), but here a minimized app can
|
||||
be brought back from its Apps-tab tile — so keep the button available. */
|
||||
body.dashboard-mode .window-minimize-btn {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Fullpage mode sizes .window-body-app to 100% because fullpage windows have
|
||||
no titlebar — but dashboard-mode app windows keep their 29px head, so
|
||||
without this the bottom of every app is clipped by that much. */
|
||||
body.dashboard-mode .window-body-app {
|
||||
height: calc(100% - 29px) !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user