mirror of
https://github.com/HeyPuter/puter
synced 2024-11-15 06:15:47 +00:00
Merge pull request #381 from AtkinsSJ/web-components
Convert UIWindowThemeDialog to components
This commit is contained in:
commit
65b489883d
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
export default class Button extends Component {
|
||||
static PROPERTIES = {
|
||||
@ -64,10 +64,4 @@ export default class Button extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_button ) {
|
||||
window.__component_button = true;
|
||||
|
||||
customElements.define('c-button', Button);
|
||||
}
|
||||
defineComponent('c-button', Button);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
export default class CodeEntryView extends Component {
|
||||
static PROPERTIES = {
|
||||
@ -215,10 +215,4 @@ export default class CodeEntryView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_codeEntryView ) {
|
||||
window.__component_codeEntryView = true;
|
||||
|
||||
customElements.define('c-code-entry-view', CodeEntryView);
|
||||
}
|
||||
defineComponent('c-code-entry-view', CodeEntryView);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
/**
|
||||
* Display a list of checkboxes for the user to confirm.
|
||||
@ -58,10 +58,4 @@ export default class ConfirmationsView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_confirmationsView ) {
|
||||
window.__component_confirmationsView = true;
|
||||
|
||||
customElements.define('c-confirmations-view', ConfirmationsView);
|
||||
}
|
||||
defineComponent('c-confirmations-view', ConfirmationsView);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
/**
|
||||
* Allows a flex layout of composed components to be
|
||||
@ -38,10 +38,4 @@ export default class Flexer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_flexer ) {
|
||||
window.__component_flexer = true;
|
||||
|
||||
customElements.define('c-flexer', Flexer);
|
||||
}
|
||||
defineComponent('c-flexer', Flexer);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
/**
|
||||
* Allows using an HTML string as a component.
|
||||
@ -15,10 +15,4 @@ export default class JustHTML extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_justHTML ) {
|
||||
window.__component_justHTML = true;
|
||||
|
||||
customElements.define('c-just-html', JustHTML);
|
||||
}
|
||||
defineComponent('c-just-html', JustHTML);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
import UIComponentWindow from "../UIComponentWindow.js";
|
||||
|
||||
export default class QRCodeView extends Component {
|
||||
@ -78,10 +78,4 @@ export default class QRCodeView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_qr_code ) {
|
||||
window.__component_qr_code = true;
|
||||
|
||||
customElements.define('c-qr-code', QRCodeView);
|
||||
}
|
||||
defineComponent('c-qr-code', QRCodeView);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
export default class RecoveryCodeEntryView extends Component {
|
||||
static PROPERTIES = {
|
||||
@ -84,4 +84,4 @@ export default class RecoveryCodeEntryView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('c-recovery-code-entry', RecoveryCodeEntryView);
|
||||
defineComponent('c-recovery-code-entry', RecoveryCodeEntryView);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
export default class RecoveryCodesView extends Component {
|
||||
static PROPERTIES = {
|
||||
@ -91,10 +91,4 @@ export default class RecoveryCodesView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_recoveryCodesView ) {
|
||||
window.__component_recoveryCodesView = true;
|
||||
|
||||
customElements.define('c-recovery-codes-view', RecoveryCodesView);
|
||||
}
|
||||
defineComponent('c-recovery-codes-view', RecoveryCodesView);
|
||||
|
108
src/UI/Components/Slider.js
Normal file
108
src/UI/Components/Slider.js
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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, defineComponent } 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
defineComponent('c-slider', Slider);
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
/**
|
||||
* StepHeading renders a heading with a leading symbol.
|
||||
@ -58,10 +58,4 @@ export default class StepHeading extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_stepHeading ) {
|
||||
window.__component_stepHeading = true;
|
||||
|
||||
customElements.define('c-step-heading', StepHeading);
|
||||
}
|
||||
defineComponent('c-step-heading', StepHeading);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
export default class StepView extends Component {
|
||||
static PROPERTIES = {
|
||||
@ -64,10 +64,4 @@ export default class StepView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_stepView ) {
|
||||
window.__component_stepView = true;
|
||||
|
||||
customElements.define('c-step-view', StepView);
|
||||
}
|
||||
defineComponent('c-step-view', StepView);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
/**
|
||||
* A simple component that displays a string in the
|
||||
@ -42,10 +42,4 @@ export default class StringView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_stringView ) {
|
||||
window.__component_stringView = true;
|
||||
|
||||
customElements.define('c-string-view', StringView);
|
||||
}
|
||||
defineComponent('c-string-view', StringView);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
import { Component, defineComponent } from "../../util/Component.js";
|
||||
|
||||
/**
|
||||
* A simple component when you just need to test something.
|
||||
@ -19,10 +19,4 @@ export default class TestView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// 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_testView ) {
|
||||
window.__component_testView = true;
|
||||
|
||||
customElements.define('c-test-view', TestView);
|
||||
}
|
||||
defineComponent('c-test-view', TestView);
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -136,3 +136,11 @@ export class Component extends HTMLElement {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const defineComponent = (name, component) => {
|
||||
// TODO: This is necessary because files can be loaded from
|
||||
// both `/src/UI` and `/UI` in the URL; we need to fix that
|
||||
if ( ! customElements.get(name) ) {
|
||||
customElements.define(name, component);
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user