mirror of
https://github.com/HeyPuter/puter
synced 2024-11-15 06:15:47 +00:00
feat: allow apps to add a menubar via puter.js
* Begin work on menubar and dropdowns * Improve menubar * Fix pointer event behavior * Fix labels * Fix active button * Eliminate flicker * Update _default.js --------- Co-authored-by: Nariman Jelveh <n.jelveh@gmail.com>
This commit is contained in:
parent
ec31007c4b
commit
331d9e7542
@ -176,7 +176,7 @@ router.all('*', async function(req, res, next) {
|
|||||||
const user = await get_user({uuid: req.query.user_uuid})
|
const user = await get_user({uuid: req.query.user_uuid})
|
||||||
|
|
||||||
// more validation
|
// more validation
|
||||||
if(user === undefined || user === null || user === false)
|
if(!user)
|
||||||
h += '<p style="text-align:center; color:red;">User not found.</p>';
|
h += '<p style="text-align:center; color:red;">User not found.</p>';
|
||||||
else if(user.unsubscribed === 1)
|
else if(user.unsubscribed === 1)
|
||||||
h += '<p style="text-align:center; color:green;">You are already unsubscribed.</p>';
|
h += '<p style="text-align:center; color:green;">You are already unsubscribed.</p>';
|
||||||
|
@ -9,6 +9,7 @@ import Auth from './modules/Auth.js';
|
|||||||
import FSItem from './modules/FSItem.js';
|
import FSItem from './modules/FSItem.js';
|
||||||
import * as utils from './lib/utils.js';
|
import * as utils from './lib/utils.js';
|
||||||
import path from './lib/path.js';
|
import path from './lib/path.js';
|
||||||
|
import Util from './modules/Util.js';
|
||||||
|
|
||||||
window.puter = (function() {
|
window.puter = (function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
@ -168,6 +169,9 @@ window.puter = (function() {
|
|||||||
|
|
||||||
// Initialize submodules
|
// Initialize submodules
|
||||||
initSubmodules = function(){
|
initSubmodules = function(){
|
||||||
|
// Util
|
||||||
|
this.util = new Util();
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
this.auth = new Auth(this.authToken, this.APIOrigin, this.appID, this.env);
|
this.auth = new Auth(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||||
// OS
|
// OS
|
||||||
@ -175,7 +179,7 @@ window.puter = (function() {
|
|||||||
// FileSystem
|
// FileSystem
|
||||||
this.fs = new FileSystem(this.authToken, this.APIOrigin, this.appID, this.env);
|
this.fs = new FileSystem(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||||
// UI
|
// UI
|
||||||
this.ui = new UI(this.appInstanceID, this.parentInstanceID, this.appID, this.env);
|
this.ui = new UI(this.appInstanceID, this.parentInstanceID, this.appID, this.env, this.util);
|
||||||
// Hosting
|
// Hosting
|
||||||
this.hosting = new Hosting(this.authToken, this.APIOrigin, this.appID, this.env);
|
this.hosting = new Hosting(this.authToken, this.APIOrigin, this.appID, this.env);
|
||||||
// Apps
|
// Apps
|
||||||
|
111
packages/puter-js/src/lib/xdrpc.js
Normal file
111
packages/puter-js/src/lib/xdrpc.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* This module provides a simple RPC mechanism for cross-document
|
||||||
|
* (iframe / window.postMessage) communication.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Since `Symbol` is not clonable, we use a UUID to identify RPCs.
|
||||||
|
const $SCOPE = '9a9c83a4-7897-43a0-93b9-53217b84fde6';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CallbackManager is used to manage callbacks for RPCs.
|
||||||
|
* It is used by the dehydrator and hydrator to store and retrieve
|
||||||
|
* the functions that are being called remotely.
|
||||||
|
*/
|
||||||
|
export class CallbackManager {
|
||||||
|
#messageId = 0;
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
this.callbacks = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
register_callback (callback) {
|
||||||
|
const id = this.#messageId++;
|
||||||
|
this.callbacks.set(id, callback);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
attach_to_source (source) {
|
||||||
|
source.addEventListener('message', event => {
|
||||||
|
const { data } = event;
|
||||||
|
console.log(
|
||||||
|
'test-app got message from window',
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
debugger;
|
||||||
|
if (data && typeof data === 'object' && data.$SCOPE === $SCOPE) {
|
||||||
|
const { id, args } = data;
|
||||||
|
const callback = this.callbacks.get(id);
|
||||||
|
if (callback) {
|
||||||
|
callback(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dehydrator replaces functions in an object with identifiers,
|
||||||
|
* so that hydrate() can be called on the other side of the frame
|
||||||
|
* to bind RPC stubs. The original functions are stored in a map
|
||||||
|
* so that they can be called when the RPC is invoked.
|
||||||
|
*/
|
||||||
|
export class Dehydrator {
|
||||||
|
constructor ({ callbackManager }) {
|
||||||
|
this.callbackManager = callbackManager;
|
||||||
|
}
|
||||||
|
dehydrate (value) {
|
||||||
|
return this.dehydrate_value_(value);
|
||||||
|
}
|
||||||
|
dehydrate_value_ (value) {
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
const id = this.callbackManager.register_callback(value);
|
||||||
|
return { $SCOPE, id };
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
return value.map(this.dehydrate_value_.bind(this));
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
const result = {};
|
||||||
|
for (const key in value) {
|
||||||
|
result[key] = this.dehydrate_value_(value[key]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hydrator binds RPC stubs to the functions that were
|
||||||
|
* previously dehydrated. This allows the RPC to be invoked
|
||||||
|
* on the other side of the frame.
|
||||||
|
*/
|
||||||
|
export class Hydrator {
|
||||||
|
constructor ({ target }) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
hydrate (value) {
|
||||||
|
return this.hydrate_value_(value);
|
||||||
|
}
|
||||||
|
hydrate_value_ (value) {
|
||||||
|
if (
|
||||||
|
value && typeof value === 'object' &&
|
||||||
|
value.$SCOPE === $SCOPE
|
||||||
|
) {
|
||||||
|
const { id } = value;
|
||||||
|
return (...args) => {
|
||||||
|
console.log('sending message', { $SCOPE, id, args });
|
||||||
|
console.log('target', this.target);
|
||||||
|
this.target.postMessage({ $SCOPE, id, args }, '*');
|
||||||
|
};
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
return value.map(this.hydrate_value_.bind(this));
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
const result = {};
|
||||||
|
for (const key in value) {
|
||||||
|
result[key] = this.hydrate_value_(value[key]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -149,7 +149,19 @@ class UI extends EventListener {
|
|||||||
this.#callbackFunctions[msg_id] = resolve;
|
this.#callbackFunctions[msg_id] = resolve;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (appInstanceID, parentInstanceID, appID, env) {
|
#postMessageWithObject = function(name, value) {
|
||||||
|
const dehydrator = this.util.rpc.getDehydrator({
|
||||||
|
target: this.messageTarget
|
||||||
|
});
|
||||||
|
this.messageTarget?.postMessage({
|
||||||
|
msg: name,
|
||||||
|
env: this.env,
|
||||||
|
appInstanceID: this.appInstanceID,
|
||||||
|
value: dehydrator.dehydrate(value),
|
||||||
|
}, '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (appInstanceID, parentInstanceID, appID, env, util) {
|
||||||
const eventNames = [
|
const eventNames = [
|
||||||
'localeChanged',
|
'localeChanged',
|
||||||
'themeChanged',
|
'themeChanged',
|
||||||
@ -160,6 +172,7 @@ class UI extends EventListener {
|
|||||||
this.parentInstanceID = parentInstanceID;
|
this.parentInstanceID = parentInstanceID;
|
||||||
this.appID = appID;
|
this.appID = appID;
|
||||||
this.env = env;
|
this.env = env;
|
||||||
|
this.util = util;
|
||||||
|
|
||||||
if(this.env === 'app'){
|
if(this.env === 'app'){
|
||||||
this.messageTarget = window.parent;
|
this.messageTarget = window.parent;
|
||||||
@ -641,6 +654,10 @@ class UI extends EventListener {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMenubar = function(spec) {
|
||||||
|
this.#postMessageWithObject('setMenubar', spec);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously extracts entries from DataTransferItems, like files and directories.
|
* Asynchronously extracts entries from DataTransferItems, like files and directories.
|
||||||
*
|
*
|
||||||
|
30
packages/puter-js/src/modules/Util.js
Normal file
30
packages/puter-js/src/modules/Util.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { CallbackManager, Dehydrator, Hydrator } from "../lib/xdrpc";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Util module exposes utilities within puter.js itself.
|
||||||
|
* These utilities may be used internally by other modules.
|
||||||
|
*/
|
||||||
|
export default class Util {
|
||||||
|
constructor () {
|
||||||
|
// This is in `puter.util.rpc` instead of `puter.rpc` because
|
||||||
|
// `puter.rpc` is reserved for an app-to-app RPC interface.
|
||||||
|
// This is a lower-level RPC interface used to communicate
|
||||||
|
// with iframes.
|
||||||
|
this.rpc = new UtilRPC();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UtilRPC {
|
||||||
|
constructor () {
|
||||||
|
this.callbackManager = new CallbackManager();
|
||||||
|
this.callbackManager.attach_to_source(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDehydrator () {
|
||||||
|
return new Dehydrator({ callbackManager: this.callbackManager });
|
||||||
|
}
|
||||||
|
|
||||||
|
getHydrator ({ target }) {
|
||||||
|
return new Hydrator({ target });
|
||||||
|
}
|
||||||
|
}
|
114
src/IPC.js
114
src/IPC.js
@ -27,6 +27,7 @@ import UIWindowColorPicker from './UI/UIWindowColorPicker.js';
|
|||||||
import UIPrompt from './UI/UIPrompt.js';
|
import UIPrompt from './UI/UIPrompt.js';
|
||||||
import download from './helpers/download.js';
|
import download from './helpers/download.js';
|
||||||
import path from "./lib/path.js";
|
import path from "./lib/path.js";
|
||||||
|
import UIContextMenu from './UI/UIContextMenu.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In Puter, apps are loaded in iframes and communicate with the graphical user interface (GUI) aand each other using the postMessage API.
|
* In Puter, apps are loaded in iframes and communicate with the graphical user interface (GUI) aand each other using the postMessage API.
|
||||||
@ -352,6 +353,119 @@ window.addEventListener('message', async (event) => {
|
|||||||
}, '*');
|
}, '*');
|
||||||
}
|
}
|
||||||
//--------------------------------------------------------
|
//--------------------------------------------------------
|
||||||
|
// setMenubar
|
||||||
|
//--------------------------------------------------------
|
||||||
|
else if(event.data.msg === 'setMenubar') {
|
||||||
|
const el_window = window_for_app_instance(event.data.appInstanceID);
|
||||||
|
|
||||||
|
console.error(`EXPERIMENTAL: setMenubar is a work-in-progress`);
|
||||||
|
const hydrator = puter.util.rpc.getHydrator({
|
||||||
|
target: target_iframe.contentWindow,
|
||||||
|
});
|
||||||
|
const value = hydrator.hydrate(event.data.value);
|
||||||
|
console.log('hydrated value', value);
|
||||||
|
|
||||||
|
// Show menubar
|
||||||
|
const $menubar = $(el_window).find('.window-menubar')
|
||||||
|
$menubar.show();
|
||||||
|
|
||||||
|
const sanitize_items = items => {
|
||||||
|
return items.map(item => {
|
||||||
|
return {
|
||||||
|
html: item.label,
|
||||||
|
action: item.action,
|
||||||
|
items: item.items && sanitize_items(item.items),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// This array will store the menubar button elements
|
||||||
|
const menubar_buttons = [];
|
||||||
|
|
||||||
|
// Add menubar items
|
||||||
|
let current = null;
|
||||||
|
let current_i = null;
|
||||||
|
let state_open = false;
|
||||||
|
const open_menu = ({ i, pos, parent_element, items }) => {
|
||||||
|
let delay = true;
|
||||||
|
if ( state_open ) {
|
||||||
|
if ( current_i === i ) return;
|
||||||
|
|
||||||
|
delay = false;
|
||||||
|
current && current.cancel({ meta: 'menubar', fade: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set this menubar button as active
|
||||||
|
menubar_buttons.forEach(el => el.removeClass('active'));
|
||||||
|
menubar_buttons[i].addClass('active');
|
||||||
|
|
||||||
|
// Open the context menu
|
||||||
|
const ctxMenu = UIContextMenu({
|
||||||
|
delay,
|
||||||
|
parent_element,
|
||||||
|
position: {top: pos.top + 28, left: pos.left},
|
||||||
|
items: sanitize_items(items),
|
||||||
|
});
|
||||||
|
|
||||||
|
state_open = true;
|
||||||
|
current = ctxMenu;
|
||||||
|
current_i = i;
|
||||||
|
|
||||||
|
ctxMenu.onClose = (cancel_options) => {
|
||||||
|
if ( cancel_options?.meta === 'menubar' ) return;
|
||||||
|
menubar_buttons.forEach(el => el.removeClass('active'));
|
||||||
|
ctxMenu.onClose = null;
|
||||||
|
current_i = null;
|
||||||
|
current = null;
|
||||||
|
state_open = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const add_items = (parent, items) => {
|
||||||
|
for (let i=0; i < items.length; i++) {
|
||||||
|
const I = i;
|
||||||
|
const item = items[i];
|
||||||
|
const label = html_encode(item.label);
|
||||||
|
const el_item = $(`<div class="window-menubar-item"><span>${label}</span></div>`);
|
||||||
|
const parent_element = el_item.parent()[0];
|
||||||
|
el_item.on('click', () => {
|
||||||
|
if ( state_open ) {
|
||||||
|
state_open = false;
|
||||||
|
current && current.cancel({ meta: 'menubar' });
|
||||||
|
current_i = null;
|
||||||
|
current = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item.action) {
|
||||||
|
item.action();
|
||||||
|
} else if (item.items) {
|
||||||
|
const pos = el_item[0].getBoundingClientRect();
|
||||||
|
open_menu({
|
||||||
|
i,
|
||||||
|
pos,
|
||||||
|
parent_element,
|
||||||
|
items: item.items,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
el_item.on('mouseover', () => {
|
||||||
|
if ( ! state_open ) return;
|
||||||
|
if ( ! item.items ) return;
|
||||||
|
|
||||||
|
const pos = el_item[0].getBoundingClientRect();
|
||||||
|
open_menu({
|
||||||
|
i,
|
||||||
|
pos,
|
||||||
|
parent_element,
|
||||||
|
items: item.items,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$menubar.append(el_item);
|
||||||
|
menubar_buttons.push(el_item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
add_items($menubar, value.items);
|
||||||
|
}
|
||||||
|
//--------------------------------------------------------
|
||||||
// setWindowWidth
|
// setWindowWidth
|
||||||
//--------------------------------------------------------
|
//--------------------------------------------------------
|
||||||
else if(event.data.msg === 'setWindowWidth' && event.data.width !== undefined){
|
else if(event.data.msg === 'setWindowWidth' && event.data.width !== undefined){
|
||||||
|
@ -119,17 +119,31 @@ function UIContextMenu(options){
|
|||||||
else
|
else
|
||||||
y_pos = start_y;
|
y_pos = start_y;
|
||||||
|
|
||||||
// Show ContextMenu
|
|
||||||
$(contextMenu).delay(100).show(0)
|
|
||||||
// In the right position (the mouse)
|
// In the right position (the mouse)
|
||||||
.css({
|
$(contextMenu).css({
|
||||||
top: y_pos + "px",
|
top: y_pos + "px",
|
||||||
left: x_pos + "px"
|
left: x_pos + "px"
|
||||||
});
|
});
|
||||||
|
// Show ContextMenu
|
||||||
|
if ( options?.delay === false ) {
|
||||||
|
$(contextMenu).show(0);
|
||||||
|
} else {
|
||||||
|
$(contextMenu).delay(100).show(0);
|
||||||
|
}
|
||||||
|
|
||||||
// mark other context menus as inactive
|
// mark other context menus as inactive
|
||||||
$('.context-menu').not(contextMenu).removeClass('context-menu-active');
|
$('.context-menu').not(contextMenu).removeClass('context-menu-active');
|
||||||
|
|
||||||
|
let cancel_options_ = null;
|
||||||
|
const fade_remove = () => {
|
||||||
|
$(`#context-menu-${menu_id}, .context-menu[data-element-id="${$(this).closest('.context-menu').attr('data-parent-id')}"]`).fadeOut(200, function(){
|
||||||
|
$(contextMenu).remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const remove = () => {
|
||||||
|
$(contextMenu).remove();
|
||||||
|
};
|
||||||
|
|
||||||
// An item is clicked
|
// An item is clicked
|
||||||
$(`#context-menu-${menu_id} > li:not(.context-menu-item-disabled)`).on('click', function (e) {
|
$(`#context-menu-${menu_id} > li:not(.context-menu-item-disabled)`).on('click', function (e) {
|
||||||
|
|
||||||
@ -139,11 +153,13 @@ function UIContextMenu(options){
|
|||||||
event.value = options.items[$(this).attr("data-action")]['val'] ?? undefined;
|
event.value = options.items[$(this).attr("data-action")]['val'] ?? undefined;
|
||||||
options.items[$(this).attr("data-action")].onClick(event);
|
options.items[$(this).attr("data-action")].onClick(event);
|
||||||
}
|
}
|
||||||
|
// "action" - onClick without un-clonable pointer event
|
||||||
|
else if(options.items[$(this).attr("data-action")].action && typeof options.items[$(this).attr("data-action")].action === 'function'){
|
||||||
|
options.items[$(this).attr("data-action")].action();
|
||||||
|
}
|
||||||
// close menu and, if exists, its parent
|
// close menu and, if exists, its parent
|
||||||
if(!$(this).hasClass('context-menu-item-submenu')){
|
if(!$(this).hasClass('context-menu-item-submenu')){
|
||||||
$(`#context-menu-${menu_id}, .context-menu[data-element-id="${$(this).closest('.context-menu').attr('data-parent-id')}"]`).fadeOut(200, function(){
|
fade_remove();
|
||||||
$(contextMenu).remove();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@ -233,6 +249,7 @@ function UIContextMenu(options){
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(contextMenu).on("remove", function () {
|
$(contextMenu).on("remove", function () {
|
||||||
|
if ( options.onClose ) options.onClose(cancel_options_);
|
||||||
// when removing, make parent scrollable again
|
// when removing, make parent scrollable again
|
||||||
if(options.parent_element){
|
if(options.parent_element){
|
||||||
$(options.parent_element).parent().removeClass('children-have-open-contextmenu');
|
$(options.parent_element).parent().removeClass('children-have-open-contextmenu');
|
||||||
@ -249,6 +266,20 @@ function UIContextMenu(options){
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
cancel: (cancel_options) => {
|
||||||
|
cancel_options_ = cancel_options;
|
||||||
|
if ( cancel_options.fade === false ) {
|
||||||
|
remove();
|
||||||
|
} else {
|
||||||
|
fade_remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set onClose (fn) {
|
||||||
|
options.onClose = fn;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
window.select_ctxmenu_item = function ($ctxmenu_item){
|
window.select_ctxmenu_item = function ($ctxmenu_item){
|
||||||
|
@ -265,6 +265,13 @@ async function UIWindow(options) {
|
|||||||
h += `<div draggable="false" title="Desktop" class="window-sidebar-item disable-user-select ${options.path === window.desktop_path ? 'window-sidebar-item-active' : ''}" data-path="${html_encode(window.desktop_path)}"><img draggable="false" class="window-sidebar-item-icon" src="${html_encode(window.icons['folder-desktop.svg'])}">Desktop</div>`;
|
h += `<div draggable="false" title="Desktop" class="window-sidebar-item disable-user-select ${options.path === window.desktop_path ? 'window-sidebar-item-active' : ''}" data-path="${html_encode(window.desktop_path)}"><img draggable="false" class="window-sidebar-item-icon" src="${html_encode(window.icons['folder-desktop.svg'])}">Desktop</div>`;
|
||||||
h += `<div draggable="false" title="Videos" class="window-sidebar-item disable-user-select ${options.path === window.videos_path ? 'window-sidebar-item-active' : ''}" data-path="${html_encode(window.videos_path)}"><img draggable="false" class="window-sidebar-item-icon" src="${html_encode(window.icons['folder-videos.svg'])}">Videos</div>`;
|
h += `<div draggable="false" title="Videos" class="window-sidebar-item disable-user-select ${options.path === window.videos_path ? 'window-sidebar-item-active' : ''}" data-path="${html_encode(window.videos_path)}"><img draggable="false" class="window-sidebar-item-icon" src="${html_encode(window.icons['folder-videos.svg'])}">Videos</div>`;
|
||||||
h += `</div>`;
|
h += `</div>`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menubar
|
||||||
|
{
|
||||||
|
h += `<div class="window-menubar">`;
|
||||||
|
h += `</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navbar
|
// Navbar
|
||||||
@ -462,6 +469,9 @@ async function UIWindow(options) {
|
|||||||
const el_openfiledialog_open_btn = document.querySelector(`#window-${win_id} .openfiledialog-open-btn`);
|
const el_openfiledialog_open_btn = document.querySelector(`#window-${win_id} .openfiledialog-open-btn`);
|
||||||
const el_directorypicker_select_btn = document.querySelector(`#window-${win_id} .directorypicker-select-btn`);
|
const el_directorypicker_select_btn = document.querySelector(`#window-${win_id} .directorypicker-select-btn`);
|
||||||
|
|
||||||
|
// disable menubar by default
|
||||||
|
$(el_window).find('.window-menubar').hide();
|
||||||
|
|
||||||
if(options.is_maximized){
|
if(options.is_maximized){
|
||||||
// save original size and position
|
// save original size and position
|
||||||
$(el_window).attr({
|
$(el_window).attr({
|
||||||
|
@ -46,6 +46,11 @@
|
|||||||
--taskbar-lightness: var(--primary-lightness);
|
--taskbar-lightness: var(--primary-lightness);
|
||||||
--taskbar-alpha: calc(0.73 * var(--primary-alpha));
|
--taskbar-alpha: calc(0.73 * var(--primary-alpha));
|
||||||
--taskbar-color: var(--primary-color);
|
--taskbar-color: var(--primary-color);
|
||||||
|
|
||||||
|
--select-hue: 213.05;
|
||||||
|
--select-saturation: 74.22%;
|
||||||
|
--select-lightness: 55.88%;
|
||||||
|
--select-color: hsl(var(--select-hue), var(--select-saturation), var(--select-lightness));
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
@ -739,6 +744,34 @@ span.header-sort-icon img {
|
|||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.window-menubar {
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
border-bottom: 1px solid #e3e3e3;
|
||||||
|
background-color: #fafafa;
|
||||||
|
--scale: 2pt;
|
||||||
|
padding: 0 2pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-menubar-item {
|
||||||
|
padding: calc(1.4 * var(--scale)) 0;
|
||||||
|
font-size: calc(5 * var(--scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-menubar-item span {
|
||||||
|
display: inline-block;
|
||||||
|
padding: calc(1.6 * var(--scale)) calc(4 * var(--scale));
|
||||||
|
font-size: calc(5 * var(--scale));
|
||||||
|
border-radius: calc(1.5 * var(--scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-menubar-item:hover > span,
|
||||||
|
.window-menubar-item.active > span {
|
||||||
|
background-color: var(--select-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.explorer-empty-message {
|
.explorer-empty-message {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
@ -1489,7 +1522,7 @@ span.header-sort-icon img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-menu .context-menu-item-active {
|
.context-menu .context-menu-item-active {
|
||||||
background-color: rgb(59 134 226);
|
background-color: var(--select-color);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user