Convert UIWindowThemeDialog to components

This also fixes the "Reset Colors" button not adjusting the sliders.
This commit is contained in:
Sam Atkins 2024-05-08 13:06:08 +01:00
parent 5890b7e7bf
commit 9267b50666
3 changed files with 177 additions and 133 deletions

114
src/UI/Components/Slider.js Normal file
View File

@ -0,0 +1,114 @@
/**
* Copyright (C) 2024 Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Component } from "../../util/Component.js";
/**
* Slider: A labeled slider input.
*/
export default class Slider extends Component {
static PROPERTIES = {
name: { value: null },
label: { value: null },
min: { value: 0 },
max: { value: 100 },
value: { value: null },
step: { value: 1 },
on_change: { value: null },
};
static RENDER_MODE = Component.NO_SHADOW;
static CSS = /*css*/`
.slider-label {
color: var(--primary-color);
}
.slider-input {
--webkit-appearance: none;
width: 100%;
height: 25px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
--webkit-transition: .2s;
transition: opacity .2s;
}
.slider-input:hover {
opacity: 1;
}
.slider-input::-webkit-slider-thumb {
--webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
background: #04AA6D;
cursor: pointer;
}
.slider-input::-moz-range-thumb {
width: 25px;
height: 25px;
background: #04AA6D;
cursor: pointer;
}
`;
create_template ({ template }) {
const min = this.get('min');
const max = this.get('max');
const value = this.get('value') ?? min;
const step = this.get('step') ?? 1;
const label = this.get('label') ?? this.get('name');
$(template).html(/*html*/`
<div class="slider">
<label class="slider-label">${html_encode(label)}</label>
<input class="slider-input" type="range" min="${min}" max="${max}" value="${value}" step="${step}">
</div>
`);
}
on_ready ({ listen }) {
const input = this.dom_.querySelector('.slider-input');
input.addEventListener('input', e => {
const on_change = this.get('on_change');
if (on_change) {
const name = this.get('name');
const label = this.get('label') ?? name;
e.meta = { name, label };
on_change(e);
}
});
listen('value', value => {
input.value = value;
});
}
}
// TODO: This is necessary because files can be loaded from
// both `/src/UI` and `/UI` in the URL; we need to fix that
if ( ! window.__component_slider ) {
window.__component_slider = true;
customElements.define('c-slider', Slider);
}

View File

@ -1,13 +1,73 @@
import UIWindow from "./UIWindow.js";
import UIWindowColorPicker from "./UIWindowColorPicker.js";
import UIComponentWindow from './UIComponentWindow.js';
import Button from './Components/Button.js';
import Flexer from './Components/Flexer.js';
import Slider from './Components/Slider.js';
const UIWindowThemeDialog = async function UIWindowThemeDialog (options) {
options = options ?? {};
const services = globalThis.services;
const svc_theme = services.get('theme');
const w = await UIWindow({
let state = {};
const slider_ch = (e) => {
state[e.meta.name] = e.target.value;
if (e.meta.name === 'lig') {
state.light_text = e.target.value < 60 ? true : false;
}
svc_theme.apply(state);
};
const hue_slider = new Slider({
label: i18n('hue'),
name: 'hue', min: 0, max: 360,
value: svc_theme.get('hue'),
on_change: slider_ch,
});
const sat_slider = new Slider({
label: i18n('saturation'),
name: 'sat', min: 0, max: 100,
value: svc_theme.get('sat'),
on_change: slider_ch,
});
const lig_slider = new Slider({
label: i18n('lightness'),
name: 'lig', min: 0, max: 100,
value: svc_theme.get('lig'),
on_change: slider_ch,
});
const alpha_slider = new Slider({
label: i18n('transparency'),
name: 'alpha', min: 0, max: 1, step: 0.01,
value: svc_theme.get('alpha'),
on_change: slider_ch,
});
const component = new Flexer({
children: [
new Button({
label: i18n('reset_colors'),
style: 'secondary',
on_click: () => {
svc_theme.reset();
state = {};
hue_slider.set('value', svc_theme.get('hue'));
sat_slider.set('value', svc_theme.get('sat'));
lig_slider.set('value', svc_theme.get('lig'));
alpha_slider.set('value', svc_theme.get('alpha'));
},
}),
hue_slider,
sat_slider,
lig_slider,
alpha_slider,
],
gap: '10pt',
});
const w = await UIComponentWindow({
title: i18n('ui_colors'),
component,
icon: null,
uid: null,
is_dir: false,
@ -48,105 +108,6 @@ const UIWindowThemeDialog = async function UIWindowThemeDialog (options) {
},
...options.window_options,
});
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;
label_el.style = "color:var(--primary-color)";
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;
if (e.meta.name === 'lig') {
state.light_text = e.target.value < 60 ? true : false;
}
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 {};
}

View File

@ -3777,37 +3777,6 @@ fieldset[name=number-code] {
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;
}
.session-manager-list {
display: flex;
flex-direction: column;