Merge pull request #240 from HeyPuter/eric/gui-personalization

Puter Theme Color Setting
This commit is contained in:
Nariman Jelveh
2024-04-07 14:35:24 -07:00
committed by GitHub
9 changed files with 413 additions and 8 deletions

View File

@@ -19,7 +19,8 @@
"css_paths": [
"/css/normalize.css",
"/lib/jquery-ui-1.13.2/jquery-ui.min.css",
"/css/style.css"
"/css/style.css",
"/css/theme.css"
],
"js_paths": [
"/src/initgui.js",

View File

@@ -25,6 +25,7 @@ import UIWindowChangeUsername from '../UIWindowChangeUsername.js'
import changeLanguage from "../../i18n/i18nChangeLanguage.js"
import UIWindowConfirmUserDeletion from './UIWindowConfirmUserDeletion.js';
import UITabAbout from './UITabAbout.js';
import UIWindowThemeDialog from '../UIWindowThemeDialog.js';
async function UIWindowSettings(options){
return new Promise(async (resolve) => {
@@ -39,6 +40,7 @@ async function UIWindowSettings(options){
h += `<div class="settings-sidebar-item disable-user-select active" data-settings="about" style="background-image: url(${icons['logo-outline.svg']});">${i18n('about')}</div>`;
h += `<div class="settings-sidebar-item disable-user-select" data-settings="usage" style="background-image: url(${icons['speedometer-outline.svg']});">${i18n('usage')}</div>`;
h += `<div class="settings-sidebar-item disable-user-select" data-settings="account" style="background-image: url(${icons['user.svg']});">${i18n('account')}</div>`;
h += `<div class="settings-sidebar-item disable-user-select" data-settings="personalization" style="background-image: url(${icons['palette-outline.svg']});">${i18n('personalization')}</div>`;
h += `<div class="settings-sidebar-item disable-user-select" data-settings="language" style="background-image: url(${icons['language.svg']});">${i18n('language')}</div>`;
h += `<div class="settings-sidebar-item disable-user-select" data-settings="clock" style="background-image: url(${icons['clock.svg']});">${i18n('clock')}</div>`;
h += `</div>`;
@@ -111,6 +113,18 @@ async function UIWindowSettings(options){
h += `</div>`;
// Personalization
h += `<div class="settings-content" data-settings="personalization">`;
h += `<h1>${i18n('personalization')}</h1>`;
// change password button
h += `<div class="settings-card">`;
h += `<strong>${i18n('ui_colors')}</strong>`;
h += `<div style="flex-grow:1;">`;
h += `<button class="button change-ui-colors" style="float:right;">${i18n('change_ui_colors')}</button>`;
h += `</div>`;
h += `</div>`;
h += `</div>`;
// Language
h += `<div class="settings-content" data-settings="language">`;
h += `<h1>${i18n('language')}</h1>`;
@@ -306,6 +320,10 @@ async function UIWindowSettings(options){
UIWindowChangeUsername();
})
$(el_window).find('.change-ui-colors').on('click', function (e) {
UIWindowThemeDialog();
})
$(el_window).on('click', '.settings-sidebar-item', function(){
const $this = $(this);
const settings = $this.attr('data-settings');

View File

@@ -0,0 +1,145 @@
import UIWindow from "./UIWindow.js";
const UIWindowThemeDialog = async function UIWindowThemeDialog () {
const services = globalThis.services;
const svc_theme = services.get('theme');
const w = await UIWindow({
title: i18n('ui_colors'),
icon: null,
uid: null,
is_dir: false,
message: 'message',
// body_icon: options.body_icon,
// backdrop: options.backdrop ?? false,
is_resizable: false,
is_droppable: false,
has_head: true,
stay_on_top: true,
selectable_body: false,
draggable_body: true,
allow_context_menu: false,
show_in_taskbar: false,
window_class: 'window-alert',
dominant: true,
body_content: '',
width: 350,
// parent_uuid: options.parent_uuid,
// ...options.window_options,
window_css:{
height: 'initial',
},
body_css: {
width: 'initial',
padding: '20px',
// 'background-color': `hsla(
// var(--primary-hue),
// calc(max(var(--primary-saturation) - 15%, 0%)),
// calc(min(100%,var(--primary-lightness) + 20%)), .91)`,
'background-color': `hsla(
var(--primary-hue),
var(--primary-saturation),
var(--primary-lightness),
var(--primary-alpha))`,
'backdrop-filter': 'blur(3px)',
}
});
const w_body = w.querySelector('.window-body');
const Button = ({ label }) => {
const el = document.createElement('button');
el.textContent = label;
el.classList.add('button', 'button-block');
return {
appendTo (parent) {
parent.appendChild(el);
return this;
},
onPress (cb) {
el.addEventListener('click', cb);
return this;
},
};
}
const Slider = ({ name, label, min, max, initial, step }) => {
label = label ?? name;
const wrap = document.createElement('div');
const label_el = document.createElement('label');
label_el.textContent = label;
wrap.appendChild(label_el);
const el = document.createElement('input');
wrap.appendChild(el);
el.type = 'range';
el.min = min;
el.max = max;
el.defaultValue = initial ?? min;
el.step = step ?? 1;
el.classList.add('theme-dialog-slider');
return {
appendTo (parent) {
parent.appendChild(wrap);
return this;
},
onChange (cb) {
el.addEventListener('input', e => {
e.meta = { name, label };
cb(e);
});
return this;
},
};
};
const state = {};
const slider_ch = (e) => {
state[e.meta.name] = e.target.value;
svc_theme.apply(state);
};
Button({ label: i18n('reset_colors') })
.appendTo(w_body)
.onPress(() => {
svc_theme.reset();
})
;
Slider({
label: i18n('hue'),
name: 'hue', min: 0, max: 360,
initial: svc_theme.get('hue'),
})
.appendTo(w_body)
.onChange(slider_ch)
;
Slider({
label: i18n('saturation'),
name: 'sat', min: 0, max: 100,
initial: svc_theme.get('sat'),
})
.appendTo(w_body)
.onChange(slider_ch)
;
Slider({
label: i18n('lightness'),
name: 'lig', min: 0, max: 100,
initial: svc_theme.get('lig'),
})
.appendTo(w_body)
.onChange(slider_ch)
;
Slider({
label: i18n('transparency'),
name: 'alpha', min: 0, max: 1, step: 0.01,
initial: svc_theme.get('alpha'),
})
.appendTo(w_body)
.onChange(slider_ch)
;
return {};
}
export default UIWindowThemeDialog;

View File

@@ -22,6 +22,28 @@
user-select: none;
}
:root {
--primary-hue: 210;
--primary-saturation: 41.18%;
--primary-lightness: 93.33%;
--primary-alpha: 0.8;
--window-head-hue: var(--primary-hue);
--window-head-saturation: var(--primary-saturation);
--window-head-lightness: var(--primary-lightness);
--window-head-alpha: var(--primary-alpha);
--window-sidebar-hue: var(--primary-hue);
--window-sidebar-saturation: var(--primary-saturation);
--window-sidebar-lightness: var(--primary-lightness);
--window-sidebar-alpha: calc(min(1, 0.11 + var(--primary-alpha)));
--taskbar-hue: var(--primary-hue);
--taskbar-saturation: var(--primary-saturation);
--taskbar-lightness: var(--primary-lightness);
--taskbar-alpha: calc(0.73 * var(--primary-alpha));
}
html, body {
/* disables two fingers back/forward swipe */
overscroll-behavior-x: none;
@@ -827,7 +849,12 @@ span.header-sort-icon img {
.window-head {
overflow: hidden !important;
padding: 0;
background-color: rgba(231, 238, 245, .95);
background-color: hsla(
var(--window-head-hue),
var(--window-head-saturation),
var(--window-head-lightness),
calc(0.5 + 0.5 * var(--window-head-alpha))
);
filter: grayscale(80%);
box-shadow: inset 0px -4px 5px -7px rgb(0 0 0 / 64%);
display: flex;
@@ -1025,7 +1052,12 @@ span.header-sort-icon img {
border-right: 1px solid #CCC;
padding: 15px 10px;
box-sizing: border-box;
background-color: rgba(231, 238, 245, .95);
background-color: hsla(
var(--window-sidebar-hue),
var(--window-sidebar-saturation),
var(--window-sidebar-lightness),
calc(0.5 + 0.5*var(--window-sidebar-alpha))
);
overflow-y: scroll;
margin-top: 1px;
}
@@ -2019,7 +2051,12 @@ label {
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(231, 238, 245, .9);
background-color: hsla(
var(--taskbar-hue),
var(--taskbar-saturation),
var(--taskbar-lightness),
calc(0.5 + 0.5*var(--taskbar-alpha))
);
display: flex;
justify-content: center;
z-index: 99999;
@@ -2126,7 +2163,12 @@ label {
@supports ((backdrop-filter: blur())) {
.taskbar {
background-color: #ffffff94;
background-color: hsla(
var(--taskbar-hue),
var(--taskbar-saturation),
var(--taskbar-lightness),
var(--taskbar-alpha)
);
backdrop-filter: blur(10px);
}
@@ -2646,7 +2688,12 @@ label {
@supports ((backdrop-filter: blur())) {
.window-head {
background-color: rgba(231, 238, 245, .80);
background-color: hsla(
var(--window-head-hue),
var(--window-head-saturation),
var(--window-head-lightness),
var(--window-head-alpha)
);
backdrop-filter: blur(10px);
}
@@ -2656,7 +2703,13 @@ label {
}
.window-sidebar {
background-color: rgb(231 238 245 / 91%);
/* background-color: var(--puter-window-background); */
background-color: hsla(
var(--window-sidebar-hue),
var(--window-sidebar-saturation),
var(--window-sidebar-lightness),
var(--window-sidebar-alpha)
);
backdrop-filter: blur(10px);
}
@@ -3618,4 +3671,35 @@ label {
.confirm-user-deletion-password{
width: 100%;
margin-bottom: 20px;
}
}
.theme-dialog-slider {
--webkit-appearance: none;
width: 100%;
height: 25px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
--webkit-transition: .2s;
transition: opacity .2s;
}
.theme-dialog-slider:hover {
opacity: 1;
}
.theme-dialog-slider::-webkit-slider-thumb {
--webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
background: #04AA6D;
cursor: pointer;
}
.theme-dialog-slider::-moz-range-thumb {
width: 25px;
height: 25px;
background: #04AA6D;
cursor: pointer;
}

7
src/css/theme.css Normal file
View File

@@ -0,0 +1,7 @@
/* used for pseudo-stylesheet */
/*
hue = 320; ss.addRule('.taskbar, .window-head, .window-sidebar', `background-color: hsl(${hue}, 65.1%, 70.78%)`)
*/

3
src/definitions.js Normal file
View File

@@ -0,0 +1,3 @@
export class Service {
//
};

View File

@@ -37,11 +37,13 @@ const en = {
change_email: "Change Email",
change_language: "Change Language",
change_password: "Change Password",
change_ui_colors: "Change UI Colors",
change_username: "Change Username",
close_all_windows: "Close All Windows",
close_all_windows_and_log_out: 'Close Windows and Log Out',
change_always_open_with: "Do you want to always open this type of file with",
color: 'Color',
hue: 'Hue',
confirm_account_for_free_referral_storage_c2a: 'Create an account and confirm your email address to receive 1 GB of free storage. Your friend will get 1 GB of free storage too.',
confirm_delete_multiple_items: 'Are you sure you want to permanently delete these items?',
confirm_delete_single_item: 'Do you want to permanently delete this item?',
@@ -105,6 +107,7 @@ const en = {
keep_in_taskbar: 'Keep in Taskbar',
language: "Language",
license: "License",
lightness: 'Lightness',
loading: 'Loading',
log_in: "Log In",
log_into_another_account_anyway: 'Log into another account anyway',
@@ -137,6 +140,7 @@ const en = {
passwords_do_not_match: '`New Password` and `Confirm New Password` do not match.',
paste: 'Paste',
paste_into_folder: "Paste Into Folder",
personalization: "Personalization",
pick_name_for_website: "Pick a name for your website:",
picture: "Picture",
plural_suffix: 's',
@@ -163,7 +167,9 @@ const en = {
replace: 'Replace',
replace_all: 'Replace All',
resend_confirmation_code: "Re-send Confirmation Code",
reset_colors: "Reset Colors",
restore: "Restore",
saturation: 'Saturation',
save_account: 'Save account',
save_account_to_get_copy_link: "Please create an account to proceed.",
save_account_to_publish: 'Please create an account to proceed.',
@@ -194,9 +200,11 @@ const en = {
terms: "Terms",
text_document: 'Text document',
tos_fineprint: `By clicking 'Create Free Account' you agree to Puter's {{link=terms}}Terms of Service{{/link}} and {{link=privacy}}Privacy Policy{{/link}}.`,
transparency: "Transparency",
trash: 'Trash',
type: 'Type',
type_confirm_to_delete_account: "Type 'confirm' to delete your account.",
ui_colors: "UI Colors",
undo: 'Undo',
unlimited: 'Unlimited',
unzip: "Unzip",
@@ -211,6 +219,7 @@ const en = {
yes_release_it: 'Yes, Release It',
you_have_been_referred_to_puter_by_a_friend: "You have been referred to Puter by a friend!",
zip: "Zip",
storage_puter_used: "used by Puter",
}
};

View File

@@ -34,6 +34,27 @@ import update_last_touch_coordinates from './helpers/update_last_touch_coordinat
import update_title_based_on_uploads from './helpers/update_title_based_on_uploads.js';
import PuterDialog from './UI/PuterDialog.js';
import determine_active_container_parent from './helpers/determine_active_container_parent.js';
import { ThemeService } from './services/ThemeService.js';
import UIWindowThemeDialog from './UI/UIWindowThemeDialog.js';
const launch_services = async function () {
const services_l_ = [];
const services_m_ = {};
globalThis.services = {
get: (name) => services_m_[name],
};
const register = (name, instance) => {
services_l_.push([name, instance]);
services_m_[name] = instance;
}
register('theme', new ThemeService());
for (const [_, instance] of services_l_) {
await instance._init();
}
};
window.initgui = async function(){
let url = new URL(window.location);
@@ -1947,6 +1968,8 @@ window.initgui = async function(){
// go to home page
window.location.replace("/");
});
await launch_services();
}
function requestOpenerOrigin() {

View File

@@ -0,0 +1,115 @@
import UIAlert from "../UI/UIAlert.js";
import { Service } from "../definitions.js";
const PUTER_THEME_DATA_FILENAME = '~/.__puter_gui.json';
const SAVE_COOLDOWN_TIME = 1000;
const default_values = {
sat: 41.18,
hue: 210,
lig: 93.33,
alpha: 0.8,
};
export class ThemeService extends Service {
async _init () {
this.state = {
sat: 41.18,
hue: 210,
lig: 93.33,
alpha: 0.8,
};
this.root = document.querySelector(':root');
// this.ss = new CSSStyleSheet();
// document.adoptedStyleSheets.push(this.ss);
this.save_cooldown_ = undefined;
let data = undefined;
try {
data = await puter.fs.read(PUTER_THEME_DATA_FILENAME);
if ( typeof data === 'object' ) {
data = await data.text();
}
} catch (e) {
if ( e.code !== 'subject_does_not_exist' ) {
// TODO: once we have an event log,
// log this error to the event log
console.error(e);
// We don't show an alert becuase it's likely
// other things also aren't working.
}
}
if ( data ) try {
data = JSON.parse(data.toString());
} catch (e) {
data = undefined;
console.error(e);
UIAlert({
title: 'Error loading theme data',
message: `Could not parse "${PUTER_THEME_DATA_FILENAME}": ` +
e.message,
});
}
if ( data && data.colors ) {
this.state = {
...this.state,
...data.colors,
};
this.reload_();
}
}
reset () {
this.state = default_values;
this.reload_();
puter.fs.delete(PUTER_THEME_DATA_FILENAME);
}
apply (values) {
this.state = {
...this.state,
...values,
};
this.reload_();
this.save_();
}
get (key) { return this.state[key]; }
reload_ () {
// debugger;
const s = this.state;
// this.ss.replace(`
// .taskbar, .window-head, .window-sidebar {
// background-color: hsla(${s.hue}, ${s.sat}%, ${s.lig}%, ${s.alpha});
// }
// `)
// this.root.style.setProperty('--puter-window-background', `hsla(${s.hue}, ${s.sat}%, ${s.lig}%, ${s.alpha})`);
this.root.style.setProperty('--primary-hue', s.hue);
this.root.style.setProperty('--primary-saturation', s.sat + '%');
this.root.style.setProperty('--primary-lightness', s.lig + '%');
this.root.style.setProperty('--primary-alpha', s.alpha);
}
save_ () {
if ( this.save_cooldown_ ) {
clearTimeout(this.save_cooldown_);
}
this.save_cooldown_ = setTimeout(() => {
this.commit_save_();
}, SAVE_COOLDOWN_TIME);
}
commit_save_ () {
puter.fs.write(PUTER_THEME_DATA_FILENAME, JSON.stringify(
{ colors: this.state },
undefined,
4,
));
}
}