소스 검색

Re-use CodeEntryView for login

KernelDeimos 1 년 전
부모
커밋
60a561c84c
3개의 변경된 파일77개의 추가작업 그리고 221개의 파일을 삭제
  1. 24 0
      src/UI/Components/JustHTML.js
  2. 53 28
      src/UI/UIWindowLogin.js
  3. 0 193
      src/UI/UIWindowVerificationCode.js

+ 24 - 0
src/UI/Components/JustHTML.js

@@ -0,0 +1,24 @@
+import { Component } from "../../util/Component.js";
+
+/**
+ * Allows using an HTML string as a component.
+ */
+export default class JustHTML extends Component {
+    static PROPERTIES = { html: { value: '' } };
+    create_template ({ template }) {
+        $(template).html(`<span></span>`);
+    }
+    on_ready ({ listen }) {
+        listen('html', html => {
+            $(this.dom_).find('span').html(html);
+        });
+    }
+}
+
+// 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);
+}

+ 53 - 28
src/UI/UIWindowLogin.js

@@ -20,9 +20,12 @@
 import UIWindow from './UIWindow.js'
 import UIWindowSignup from './UIWindowSignup.js'
 import UIWindowRecoverPassword from './UIWindowRecoverPassword.js'
-import UIWindowVerificationCode from './UIWindowVerificationCode.js';
 import TeePromise from '../util/TeePromise.js';
 import UIAlert from './UIAlert.js';
+import UIComponentWindow from './UIComponentWindow.js';
+import Flexer from './Components/Flexer.js';
+import CodeEntryView from './Components/CodeEntryView.js';
+import JustHTML from './Components/JustHTML.js';
 
 async function UIWindowLogin(options){
     options = options ?? {};
@@ -170,38 +173,60 @@ async function UIWindowLogin(options){
                     let p = Promise.resolve();
                     if ( data.next_step === 'otp' ) {
                         p = new TeePromise();
-                        UIWindowVerificationCode({
-                            title_key: 'confirm_code_2fa_title',
-                            instruction_key: 'confirm_code_2fa_instruction',
-                            submit_btn_key: 'confirm_code_2fa_submit_btn',
-                            on_value: async ({ actions, win, value }) => {
-                                try {
-                                    const resp = await fetch(`${api_origin}/login/otp`, {
-                                        method: 'POST',
-                                        headers: {
-                                            'Content-Type': 'application/json',
-                                        },
-                                        body: JSON.stringify({
-                                            token: data.otp_jwt_token,
-                                            code: value,
-                                        }),
-                                    });
+                        let code_entry;
+                        let win;
+                        const component = new Flexer({
+                            children: [
+                                new JustHTML({
+                                    html: /*html*/`
+                                        <h3 style="text-align:center; font-weight: 500; font-size: 20px;">Enter 2FA Code</h3>
+                                        <p style="text-align:center; padding: 0 20px;">Enter the 6-digit code from your authenticator app.</p>
+                                    `
+                                }),
+                                new CodeEntryView({
+                                    _ref: me => code_entry = me,
+                                    async [`property.value`] (value, { component }) {
+                                        const resp = await fetch(`${api_origin}/login/otp`, {
+                                            method: 'POST',
+                                            headers: {
+                                                'Content-Type': 'application/json',
+                                            },
+                                            body: JSON.stringify({
+                                                token: data.otp_jwt_token,
+                                                code: value,
+                                            }),
+                                        });
 
-                                    data = await resp.json();
+                                        data = await resp.json();
 
-                                    if ( ! data.proceed ) {
-                                        actions.clear();
-                                        actions.show_error(i18n('confirm_code_generic_incorrect'));
-                                        return;
-                                    }
+                                        if ( ! data.proceed ) {
+                                            actions.clear();
+                                            actions.show_error(i18n('confirm_code_generic_incorrect'));
+                                            return;
+                                        }
 
-                                    $(win).close();
-                                    p.resolve();
-                                } catch (e) {
-                                    actions.show_error(e.message ?? i18n('error_unknown_cause'));
-                                }
+                                        $(win).close();
+                                        p.resolve();
+                                    }
+                                }),
+                            ],
+                            ['event.focus'] () {
+                                code_entry.focus();
+                            }
+                        });
+                        win = await UIComponentWindow({
+                            component,
+                            width: 500,
+                            backdrop: true,
+                            body_css: {
+                                width: 'initial',
+                                height: '100%',
+                                'background-color': 'rgb(245 247 249)',
+                                'backdrop-filter': 'blur(3px)',
+                                padding: '20px',
                             },
                         });
+                        component.focus();
                     }
 
                     await p;

+ 0 - 193
src/UI/UIWindowVerificationCode.js

@@ -1,193 +0,0 @@
-import TeePromise from "../util/TeePromise.js";
-import UIWindow from "./UIWindow.js";
-
-const UIWindowVerificationCode = async function UIWindowVerificationCode ( options )  {
-    options = options ?? {};
-    let final_code = '';
-    let is_checking_code = false;
-
-    const html_title = i18n(options.title_key || 'confirm_code_generic_title');
-    const html_instruction = i18n(options.instruction_key || 'confirm_code_generic_instruction');
-    const submit_btn_txt = i18n(options.submit_btn_key || 'confirm_code_generic_submit');
-
-    let h = '';
-    h += `<div class="qr-code-window-close-btn generic-close-window-button"> &times; </div>`;
-    h += `<div style="-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #3e5362;">`;
-        h += `<h3 style="text-align:center; font-weight: 500; font-size: 20px;">${ html_title }</h3>`;
-        h += `<form>`;
-            h += `<p style="text-align:center; padding: 0 20px;">${ html_instruction }</p>`;
-            h += `<div class="error"></div>`;
-            h += `  <fieldset name="number-code" style="border: none; padding:0;" data-number-code-form>
-            <input class="digit-input" type="number" min='0' max='9' name='number-code-0' data-number-code-input='0' required />
-            <input class="digit-input" type="number" min='0' max='9' name='number-code-1' data-number-code-input='1' required />
-            <input class="digit-input" type="number" min='0' max='9' name='number-code-2' data-number-code-input='2' required />
-            <span class="confirm-code-hyphen">-</span>
-            <input class="digit-input" type="number" min='0' max='9' name='number-code-3' data-number-code-input='3' required />
-            <input class="digit-input" type="number" min='0' max='9' name='number-code-4' data-number-code-input='4' required />
-            <input class="digit-input" type="number" min='0' max='9' name='number-code-5' data-number-code-input='5' required />
-            </fieldset>`;
-            h += `<button type="submit" class="button button-block button-primary code-confirm-btn" style="margin-top:10px;" disabled>${submit_btn_txt}</button>`;
-        h += `</form>`;
-    h += `</div>`;
-
-    const el_window = await UIWindow({
-        title: null,
-        icon: null,
-        uid: null,
-        is_dir: false,
-        body_content: h,
-        has_head: false,
-        selectable_body: false,
-        draggable_body: true,
-        allow_context_menu: false,
-        is_draggable: options.is_draggable ?? true,
-        is_droppable: false,
-        is_resizable: false,
-        stay_on_top: options.stay_on_top ?? false,
-        allow_native_ctxmenu: true,
-        allow_user_select: true,
-        backdrop: true,
-        width: 390,
-        dominant: true,
-        onAppend: function(el_window){
-            $(el_window).find('.digit-input').first().focus();
-        },
-        window_class: 'window-item-properties',
-        window_css:{
-            height: 'initial',
-        },
-        body_css: {
-            padding: '30px',
-            width: 'initial',
-            height: 'initial',
-            'background-color': 'rgb(247 251 255)',
-            'backdrop-filter': 'blur(3px)',
-        }
-    });
-
-    $(el_window).find('.digit-input').first().focus();
-
-    const actions = {
-        clear: () => {
-            final_code = '';
-            $(el_window).find('.code-confirm-btn').prop('disabled', false);
-            $(el_window).find('.code-confirm-btn').html(submit_btn_txt);
-            $(el_window).find('.digit-input').val('');
-            $(el_window).find('.digit-input').first().focus();
-            
-        },
-        show_error: (msg) => {
-            $(el_window).find('.error').html(html_encode(msg));
-            $(el_window).find('.error').fadeIn();
-        }
-    };
-
-    $(el_window).find('.code-confirm-btn').on('click submit', function(e){
-        e.preventDefault();
-        e.stopPropagation();
-
-        $(el_window).find('.code-confirm-btn').prop('disabled', true);
-        $(el_window).find('.error').hide();
-        
-        // Check if already checking code to prevent multiple requests
-        if(is_checking_code)
-            return;
-        // Confirm button
-        is_checking_code = true;
-
-        // set animation
-        $(el_window).find('.code-confirm-btn').html(`<svg style="width:20px; margin-top: 5px;" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"><title>circle anim</title><g fill="#fff" class="nc-icon-wrapper"><g class="nc-loop-circle-24-icon-f"><path d="M12 24a12 12 0 1 1 12-12 12.013 12.013 0 0 1-12 12zm0-22a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2z" fill="#eee" opacity=".4"></path><path d="M24 12h-2A10.011 10.011 0 0 0 12 2V0a12.013 12.013 0 0 1 12 12z" data-color="color-2"></path></g><style>.nc-loop-circle-24-icon-f{--animation-duration:0.5s;transform-origin:12px 12px;animation:nc-loop-circle-anim var(--animation-duration) infinite linear}@keyframes nc-loop-circle-anim{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}</style></g></svg>`);
-
-        setTimeout(() => {
-            console.log('final code', final_code);
-            options.on_value({
-                actions,
-                value: final_code,
-                win: el_window
-            });
-        }, 1000);
-    })
-
-    // Elements
-    const numberCodeForm = document.querySelector('[data-number-code-form]');
-    const numberCodeInputs = [...numberCodeForm.querySelectorAll('[data-number-code-input]')];
-
-    // Event listeners
-    numberCodeForm.addEventListener('input', ({ target }) => {
-        if(!target.value.length) { return target.value = null; }
-        const inputLength = target.value.length;
-        let currentIndex = Number(target.dataset.numberCodeInput);
-        if(inputLength === 2){
-            const inputValues = target.value.split('');
-            target.value = inputValues[0];
-        }
-        else if (inputLength > 1) {
-            const inputValues = target.value.split('');
-
-            inputValues.forEach((value, valueIndex) => {
-                const nextValueIndex = currentIndex + valueIndex;
-
-                if (nextValueIndex >= numberCodeInputs.length) { return; }
-
-                numberCodeInputs[nextValueIndex].value = value;
-            });
-            currentIndex += inputValues.length - 2;
-        }
-
-        const nextIndex = currentIndex + 1;
-
-        if (nextIndex < numberCodeInputs.length) {
-            numberCodeInputs[nextIndex].focus();
-        }
-
-        // Concatenate all inputs into one string to create the final code
-        final_code = '';
-        for(let i=0; i< numberCodeInputs.length; i++){
-            final_code += numberCodeInputs[i].value;
-        }
-        // Automatically submit if 6 digits entered
-        if(final_code.length === 6){
-            $(el_window).find('.code-confirm-btn').prop('disabled', false);
-            $(el_window).find('.code-confirm-btn').trigger('click');
-        }
-    });
-
-    numberCodeForm.addEventListener('keydown', (e) => {
-        const { code, target } = e;
-
-        const currentIndex = Number(target.dataset.numberCodeInput);
-        const previousIndex = currentIndex - 1;
-        const nextIndex = currentIndex + 1;
-
-        const hasPreviousIndex = previousIndex >= 0;
-        const hasNextIndex = nextIndex <= numberCodeInputs.length - 1
-
-        switch (code) {
-            case 'ArrowLeft':
-            case 'ArrowUp':
-                if (hasPreviousIndex) {
-                    numberCodeInputs[previousIndex].focus();
-                }
-                e.preventDefault();
-                break;
-
-            case 'ArrowRight':
-            case 'ArrowDown':
-                if (hasNextIndex) {
-                    numberCodeInputs[nextIndex].focus();
-                }
-                e.preventDefault();
-                break;
-            case 'Backspace':
-                if (!e.target.value.length && hasPreviousIndex) {
-                    numberCodeInputs[previousIndex].value = null;
-                    numberCodeInputs[previousIndex].focus();
-                }
-                break;
-            default:
-                break;
-        }
-    });
-}
-
-export default UIWindowVerificationCode;