mirror of
https://github.com/HeyPuter/puter
synced 2024-11-15 06:15:47 +00:00
Make using web components easier
This commit is contained in:
parent
2681a78501
commit
c99747d7f2
47
src/UI/Components/QRCode.js
Normal file
47
src/UI/Components/QRCode.js
Normal file
@ -0,0 +1,47 @@
|
||||
import { Component } from "../../util/Component.js";
|
||||
|
||||
export default class QRCodeView extends Component {
|
||||
static PROPERTIES = {
|
||||
value: {
|
||||
description: 'The text to encode in the QR code',
|
||||
}
|
||||
}
|
||||
|
||||
static CSS = /*css*/`
|
||||
.qr-code {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
|
||||
create_template ({ template }) {
|
||||
// TODO: The way we handle loading assets doesn't work well
|
||||
// with web components, so for now it goes in the template.
|
||||
$(template).html(`
|
||||
<div class="qr-code opt-qr-code">
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
on_ready ({ listen }) {
|
||||
listen('value', value => {
|
||||
console.log('got value', value);
|
||||
// $(this.dom_).find('.qr-code').empty();
|
||||
new QRCode($(this.dom_).find('.qr-code').get(0), {
|
||||
text: value,
|
||||
currectLevel: QRCode.CorrectLevel.H,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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('qr-code', QRCodeView);
|
||||
}
|
@ -59,7 +59,7 @@ export default {
|
||||
const confirmation = await UIWindowQR({
|
||||
message_i18n_key: 'scan_qr_2fa',
|
||||
text: data.url,
|
||||
text_below: data.secret,
|
||||
text_alternative: data.secret,
|
||||
confirmations: [
|
||||
i18n('confirm_2fa_setup'),
|
||||
i18n('confirm_2fa_recovery'),
|
||||
|
@ -17,7 +17,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Placeholder from '../util/Placeholder.js';
|
||||
import TeePromise from '../util/TeePromise.js';
|
||||
import QRCodeView from './Components/QRCode.js';
|
||||
import UIWindow from './UIWindow.js'
|
||||
|
||||
let checkbox_id_ = 0;
|
||||
@ -29,6 +31,8 @@ async function UIWindowQR(options){
|
||||
|
||||
options = options ?? {};
|
||||
|
||||
const placeholder_qr = Placeholder();
|
||||
|
||||
let h = '';
|
||||
// close button containing the multiplication sign
|
||||
// h += `<div class="qr-code-window-close-btn generic-close-window-button"> × </div>`;
|
||||
@ -38,6 +42,16 @@ async function UIWindowQR(options){
|
||||
}</h1>`;
|
||||
h += `</div>`;
|
||||
|
||||
h += placeholder_qr.html;
|
||||
|
||||
if ( options.text_alternative ) {
|
||||
h += `<div class="otp-as-text">`;
|
||||
h += `<p style="text-align: center; font-size: 16px; padding: 10px; font-weight: 400; margin: -10px 10px 20px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">${
|
||||
html_encode(options.text_alternative)
|
||||
}</p>`;
|
||||
h += `</div>`;
|
||||
}
|
||||
|
||||
if ( options.recovery_codes ) {
|
||||
h += `<div class="recovery-codes">`;
|
||||
h += `<h2 style="text-align: center; font-size: 16px; padding: 10px; font-weight: 400; margin: -10px 10px 20px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">${
|
||||
@ -112,6 +126,14 @@ async function UIWindowQR(options){
|
||||
},
|
||||
})
|
||||
|
||||
const component_qr = new QRCodeView({
|
||||
value: options.text
|
||||
});
|
||||
console.log('test', component_qr);
|
||||
component_qr.attach(placeholder_qr);
|
||||
// placeholder_qr.replaceWith($(`<h1>test</h1>`).get(0));
|
||||
|
||||
if ( false ) {
|
||||
// generate auth token QR code
|
||||
new QRCode($(el_window).find('.otp-qr-code').get(0), {
|
||||
text: options.text,
|
||||
@ -121,6 +143,7 @@ async function UIWindowQR(options){
|
||||
colorLight : "#ffffff",
|
||||
correctLevel : QRCode.CorrectLevel.H
|
||||
});
|
||||
}
|
||||
|
||||
if ( confirmations.length > 0 ) {
|
||||
$(el_window).find('.code-confirm-btn').prop('disabled', true);
|
||||
|
@ -2613,6 +2613,10 @@ label {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.otp-as-text {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.recovery-codes {
|
||||
border: 1px solid #ccc;
|
||||
padding: 20px;
|
||||
|
63
src/util/Component.js
Normal file
63
src/util/Component.js
Normal file
@ -0,0 +1,63 @@
|
||||
import ValueHolder from "./ValueHolder.js";
|
||||
|
||||
export class Component extends HTMLElement {
|
||||
constructor (property_values) {
|
||||
super();
|
||||
|
||||
this.dom_ = this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.values_ = {};
|
||||
|
||||
if ( this.constructor.template ) {
|
||||
const template = document.querySelector(this.constructor.template);
|
||||
this.dom_.appendChild(template.content.cloneNode(true));
|
||||
}
|
||||
|
||||
for ( const key in this.constructor.PROPERTIES ) {
|
||||
let initial_value;
|
||||
if ( property_values && key in property_values ) {
|
||||
initial_value = property_values[key];
|
||||
}
|
||||
this.values_[key] = ValueHolder.adapt(initial_value);
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback () {
|
||||
console.log('connectedCallback called')
|
||||
this.on_ready && this.on_ready(this.get_api_());
|
||||
}
|
||||
|
||||
attach (placeholder) {
|
||||
const el = this.create_element_();
|
||||
this.dom_.appendChild(el);
|
||||
placeholder.replaceWith(this);
|
||||
}
|
||||
|
||||
place (slot_name, child_node) {
|
||||
child_node.setAttribute('slot', slot_name);
|
||||
this.appendChild(child_node);
|
||||
}
|
||||
|
||||
create_element_ () {
|
||||
const template = document.createElement('template');
|
||||
if ( this.constructor.CSS ) {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = this.constructor.CSS;
|
||||
this.dom_.appendChild(style);
|
||||
}
|
||||
if ( this.create_template ) {
|
||||
this.create_template({ template });
|
||||
}
|
||||
const el = template.content.cloneNode(true);
|
||||
return el;
|
||||
}
|
||||
|
||||
get_api_ () {
|
||||
return {
|
||||
listen: (name, callback) => {
|
||||
this.values_[name].sub(callback);
|
||||
callback(this.values_[name].get());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
37
src/util/Placeholder.js
Normal file
37
src/util/Placeholder.js
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @typedef {Object} PlaceholderReturn
|
||||
* @property {String} html: An html string that represents the placeholder
|
||||
* @property {String} id: The unique ID of the placeholder
|
||||
* @property {Function} replaceWith: A function that takes a DOM element
|
||||
* as an argument and replaces the placeholder with it
|
||||
*/
|
||||
|
||||
/**
|
||||
* Placeholder creates a simple element with a unique ID
|
||||
* as an HTML string.
|
||||
*
|
||||
* This can be useful where string concatenation is used
|
||||
* to build element trees.
|
||||
*
|
||||
* The `replaceWith` method can be used to replace the
|
||||
* placeholder with a real element.
|
||||
*
|
||||
* @returns {PlaceholderReturn}
|
||||
*/
|
||||
const Placeholder = () => {
|
||||
const id = Placeholder.get_next_id_();
|
||||
return {
|
||||
html: `<div id="${id}"></div>`,
|
||||
id,
|
||||
replaceWith: (el) => {
|
||||
const place = document.getElementById(id);
|
||||
place.replaceWith(el);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const anti_collision = `94d2cb6b85a1`; // Arbitrary random string
|
||||
Placeholder.next_id_ = 0;
|
||||
Placeholder.get_next_id_ = () => `${anti_collision}_${Placeholder.next_id_++}`;
|
||||
|
||||
export default Placeholder;
|
58
src/util/ValueHolder.js
Normal file
58
src/util/ValueHolder.js
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Holds an observable value.
|
||||
*/
|
||||
export default class ValueHolder {
|
||||
constructor (initial_value) {
|
||||
this.value_ = null;
|
||||
this.listeners_ = [];
|
||||
|
||||
Object.defineProperty(this, 'value', {
|
||||
set: this.set_.bind(this),
|
||||
get: this.get_.bind(this),
|
||||
});
|
||||
|
||||
if (initial_value !== undefined) {
|
||||
this.set(initial_value);
|
||||
}
|
||||
}
|
||||
|
||||
static adapt (value) {
|
||||
if (value instanceof ValueHolder) {
|
||||
return value;
|
||||
} else {
|
||||
return new ValueHolder(value);
|
||||
}
|
||||
}
|
||||
|
||||
set (value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
get () {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
sub (listener) {
|
||||
this.listeners_.push(listener);
|
||||
}
|
||||
|
||||
set_ (value) {
|
||||
const old_value = this.value_;
|
||||
this.value_ = value;
|
||||
const more = {
|
||||
holder: this,
|
||||
old_value,
|
||||
};
|
||||
this.listeners_.forEach(listener => listener(value, more));
|
||||
}
|
||||
|
||||
get_ () {
|
||||
return this.value_;
|
||||
}
|
||||
|
||||
map (fn) {
|
||||
const holder = new ValueHolder();
|
||||
this.sub((value, more) => holder.set(fn(value, more)));
|
||||
return holder;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user