flow: re-add FlowShell as Web Component
This commit is contained in:
		@ -21,135 +21,8 @@
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block main_container %}
 | 
			
		||||
<main class="pf-c-login__main" id="flow-body">
 | 
			
		||||
    <div class="pf-c-login__main-body pb-loading">
 | 
			
		||||
        <span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
 | 
			
		||||
            <span class="pf-c-spinner__clipper"></span>
 | 
			
		||||
            <span class="pf-c-spinner__lead-ball"></span>
 | 
			
		||||
            <span class="pf-c-spinner__tail-ball"></span>
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
</main>
 | 
			
		||||
<script>
 | 
			
		||||
const flowBodyUrl = "{{ exec_url }}";
 | 
			
		||||
const messagesUrl = "{{ msg_url }}";
 | 
			
		||||
const flowBody = document.querySelector("#flow-body");
 | 
			
		||||
const spinner = document.querySelector(".pb-loading");
 | 
			
		||||
 | 
			
		||||
const updateMessages = () => {
 | 
			
		||||
    let messageContainer = document.querySelector(".pf-c-alert-group");
 | 
			
		||||
    fetch(messagesUrl).then(response => {
 | 
			
		||||
        messageContainer.innerHTML = "";
 | 
			
		||||
        response.json().then(data => {
 | 
			
		||||
            data.forEach(msg => {
 | 
			
		||||
                let icon = "";
 | 
			
		||||
                switch (msg.level_tag) {
 | 
			
		||||
                    case 'error':
 | 
			
		||||
                        icon = 'fas fa-exclamation-circle'
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'warning':
 | 
			
		||||
                        icon = 'fas fa-exclamation-triangle'
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'success':
 | 
			
		||||
                        icon = 'fas fa-check-circle'
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'info':
 | 
			
		||||
                        icon = 'fas fa-info'
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
                if (msg.level_tag === "error") {
 | 
			
		||||
                    msg.extra_tags = "pf-m-danger";
 | 
			
		||||
                }
 | 
			
		||||
                let item = `<li class="pf-c-alert-group__item">
 | 
			
		||||
                    <div class="pf-c-alert pf-m-${msg.level_tag} ${msg.extra_tags}">
 | 
			
		||||
                        <div class="pf-c-alert__icon">
 | 
			
		||||
                            <i class="${icon}"></i>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <h4 class="pf-c-alert__title">
 | 
			
		||||
                            ${msg.message}
 | 
			
		||||
                        </h4>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </li>`;
 | 
			
		||||
                var template = document.createElement('template');
 | 
			
		||||
                template.innerHTML = item;
 | 
			
		||||
                messageContainer.appendChild(template.content.firstChild);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
const updateCard = (data) => {
 | 
			
		||||
    switch (data.type) {
 | 
			
		||||
        case "redirect":
 | 
			
		||||
            window.location = data.to
 | 
			
		||||
            break;
 | 
			
		||||
        case "template":
 | 
			
		||||
            flowBody.innerHTML = data.body;
 | 
			
		||||
            checkAutofocus();
 | 
			
		||||
            updateMessages();
 | 
			
		||||
            loadFormCode();
 | 
			
		||||
            setFormSubmitHandlers();
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
const showSpinner = () => {
 | 
			
		||||
    flowBody.innerHTML = "";
 | 
			
		||||
    flowBody.appendChild(spinner);
 | 
			
		||||
};
 | 
			
		||||
const loadFormCode = () => {
 | 
			
		||||
    document.querySelectorAll("#flow-body script").forEach(script => {
 | 
			
		||||
        let newScript = document.createElement("script");
 | 
			
		||||
        newScript.src = script.src;
 | 
			
		||||
        document.head.appendChild(newScript);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
const checkAutofocus = () => {
 | 
			
		||||
    const autofocusElement = document.querySelector("#flow-body [autofocus]");
 | 
			
		||||
    if (autofocusElement !== null) {
 | 
			
		||||
        autofocusElement.focus();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
const updateFormAction = (form) => {
 | 
			
		||||
    for (let index = 0; index < form.elements.length; index++) {
 | 
			
		||||
        const element = form.elements[index];
 | 
			
		||||
        if (element.value === form.action) {
 | 
			
		||||
            console.log("pb-flow: Found Form action URL in form elements, not changing form action.");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    form.action = flowBodyUrl;
 | 
			
		||||
    console.log(`pb-flow: updated form.action ${flowBodyUrl}`);
 | 
			
		||||
    return true;
 | 
			
		||||
};
 | 
			
		||||
const checkAutosubmit = (form) => {
 | 
			
		||||
    if ("autosubmit" in form.attributes) {
 | 
			
		||||
        return form.submit();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
const setFormSubmitHandlers = () => {
 | 
			
		||||
    document.querySelectorAll("#flow-body form").forEach(form => {
 | 
			
		||||
        console.log(`pb-flow: Checking for autosubmit attribute ${form}`);
 | 
			
		||||
        checkAutosubmit(form);
 | 
			
		||||
        console.log(`pb-flow: Setting action for form ${form}`);
 | 
			
		||||
        updateFormAction(form);
 | 
			
		||||
        console.log(`pb-flow: Adding handler for form ${form}`);
 | 
			
		||||
        form.addEventListener('submit', (e) => {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            let formData = new FormData(form);
 | 
			
		||||
            showSpinner();
 | 
			
		||||
            fetch(flowBodyUrl, {
 | 
			
		||||
                method: 'post',
 | 
			
		||||
                body: formData,
 | 
			
		||||
            }).then(response => response.json()).then(data => {
 | 
			
		||||
                updateCard(data);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        form.classList.add("pb-flow-wrapped");
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fetch(flowBodyUrl).then(response => response.json()).then(data => updateCard(data));
 | 
			
		||||
</script>
 | 
			
		||||
<flow-shell-card
 | 
			
		||||
    class="pf-c-login__main"
 | 
			
		||||
    flowBodyUrl="{{ exec_url }}">
 | 
			
		||||
</flow-shell-card>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
@ -236,7 +236,6 @@ class FlowExecutorShellView(TemplateView):
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs) -> Dict[str, Any]:
 | 
			
		||||
        kwargs["exec_url"] = reverse("passbook_flows:flow-executor", kwargs=self.kwargs)
 | 
			
		||||
        kwargs["msg_url"] = reverse("passbook_api:messages-list")
 | 
			
		||||
        self.request.session[SESSION_KEY_GET] = self.request.GET
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
"""passbook password stage"""
 | 
			
		||||
from typing import Any, Dict, List, Optional
 | 
			
		||||
from django.contrib import messages
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth import _clean_credentials
 | 
			
		||||
from django.contrib.auth.backends import BaseBackend
 | 
			
		||||
@ -121,4 +122,5 @@ class PasswordStageView(FormView, StageView):
 | 
			
		||||
            self.executor.plan.context[
 | 
			
		||||
                PLAN_CONTEXT_AUTHENTICATION_BACKEND
 | 
			
		||||
            ] = user.backend
 | 
			
		||||
            messages.success(self.request, _("Successfully logged in!"))
 | 
			
		||||
            return self.executor.stage_ok()
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -1,4 +1,5 @@
 | 
			
		||||
import { getCookie } from "./utils.js";
 | 
			
		||||
import { updateMessages } from "./Messages.js";
 | 
			
		||||
 | 
			
		||||
const PRIMARY_CLASS = "pf-m-primary";
 | 
			
		||||
const SUCCESS_CLASS = "pf-m-success";
 | 
			
		||||
@ -33,7 +34,7 @@ class ActionButton extends HTMLButtonElement {
 | 
			
		||||
        this.innerText = this.oldBody;
 | 
			
		||||
        this.classList.replace(PRIMARY_CLASS, statusClass);
 | 
			
		||||
        // Trigger messages to update
 | 
			
		||||
        document.querySelector("pb-messages").setAttribute("touch", Date.now());
 | 
			
		||||
        updateMessages();
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            this.classList.replace(statusClass, PRIMARY_CLASS);
 | 
			
		||||
        }, 1000);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										113
									
								
								passbook/static/static/src/FlowShellCard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								passbook/static/static/src/FlowShellCard.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,113 @@
 | 
			
		||||
import { LitElement, html } from 'lit-element';
 | 
			
		||||
import { updateMessages } from "./Messages.js";
 | 
			
		||||
 | 
			
		||||
class FetchFillSlot extends LitElement {
 | 
			
		||||
 | 
			
		||||
    static get properties() {
 | 
			
		||||
        return {
 | 
			
		||||
            flowBodyUrl: { type: String },
 | 
			
		||||
            flowBody: { type: String },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createRenderRoot() {
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    firstUpdated() {
 | 
			
		||||
        fetch(this.flowBodyUrl).then(r => r.json()).then(r => this.updateCard(r));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async updateCard(data) {
 | 
			
		||||
        switch (data.type) {
 | 
			
		||||
            case "redirect":
 | 
			
		||||
                window.location = data.to
 | 
			
		||||
                break;
 | 
			
		||||
            case "template":
 | 
			
		||||
                this.flowBody = data.body;
 | 
			
		||||
                await this.requestUpdate();
 | 
			
		||||
                this.checkAutofocus();
 | 
			
		||||
                updateMessages();
 | 
			
		||||
                this.loadFormCode();
 | 
			
		||||
                this.setFormSubmitHandlers();
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    loadFormCode() {
 | 
			
		||||
        this.querySelectorAll("script").forEach(script => {
 | 
			
		||||
            let newScript = document.createElement("script");
 | 
			
		||||
            newScript.src = script.src;
 | 
			
		||||
            document.head.appendChild(newScript);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    checkAutofocus() {
 | 
			
		||||
        const autofocusElement = this.querySelector("[autofocus]");
 | 
			
		||||
        if (autofocusElement !== null) {
 | 
			
		||||
            autofocusElement.focus();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateFormAction(form) {
 | 
			
		||||
        for (let index = 0; index < form.elements.length; index++) {
 | 
			
		||||
            const element = form.elements[index];
 | 
			
		||||
            if (element.value === form.action) {
 | 
			
		||||
                console.log("pb-flow: Found Form action URL in form elements, not changing form action.");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        form.action = this.flowBodyUrl;
 | 
			
		||||
        console.log(`pb-flow: updated form.action ${this.flowBodyUrl}`);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    checkAutosubmit(form) {
 | 
			
		||||
        if ("autosubmit" in form.attributes) {
 | 
			
		||||
            return form.submit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setFormSubmitHandlers() {
 | 
			
		||||
        this.querySelectorAll("form").forEach(form => {
 | 
			
		||||
            console.log(`pb-flow: Checking for autosubmit attribute ${form}`);
 | 
			
		||||
            this.checkAutosubmit(form);
 | 
			
		||||
            console.log(`pb-flow: Setting action for form ${form}`);
 | 
			
		||||
            this.updateFormAction(form);
 | 
			
		||||
            console.log(`pb-flow: Adding handler for form ${form}`);
 | 
			
		||||
            form.addEventListener('submit', (e) => {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                let formData = new FormData(form);
 | 
			
		||||
                this.flowBody = undefined;
 | 
			
		||||
                fetch(this.flowBodyUrl, {
 | 
			
		||||
                    method: 'post',
 | 
			
		||||
                    body: formData,
 | 
			
		||||
                }).then(response => response.json()).then(data => {
 | 
			
		||||
                    this.updateCard(data);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            form.classList.add("pb-flow-wrapped");
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    loading() {
 | 
			
		||||
        return html`
 | 
			
		||||
            <div class="pf-c-login__main-body pb-loading">
 | 
			
		||||
                <span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
 | 
			
		||||
                    <span class="pf-c-spinner__clipper"></span>
 | 
			
		||||
                    <span class="pf-c-spinner__lead-ball"></span>
 | 
			
		||||
                    <span class="pf-c-spinner__tail-ball"></span>
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        if (this.flowBody !== undefined) {
 | 
			
		||||
            return html([this.flowBody]);
 | 
			
		||||
        }
 | 
			
		||||
        return this.loading();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
customElements.define('flow-shell-card', FetchFillSlot);
 | 
			
		||||
@ -11,6 +11,10 @@ let ID = function (prefix) {
 | 
			
		||||
    return prefix + Math.random().toString(36).substr(2, 9);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function updateMessages() {
 | 
			
		||||
    document.querySelector("pb-messages").setAttribute("touch", Date.now());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Messages extends LitElement {
 | 
			
		||||
 | 
			
		||||
    static get properties() {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import './FetchFillSlot.js';
 | 
			
		||||
import './ActionButton.js';
 | 
			
		||||
import './Messages.js';
 | 
			
		||||
import './FlowShellCard.js';
 | 
			
		||||
 | 
			
		||||
// Button Dropdowns
 | 
			
		||||
document.querySelectorAll("button.pf-c-dropdown__toggle").forEach((b) => {
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user