flow: re-add FlowShell as Web Component
This commit is contained in:
parent
6c0c12c90a
commit
aa9c7a6567
|
@ -21,135 +21,8 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main_container %}
|
{% block main_container %}
|
||||||
<main class="pf-c-login__main" id="flow-body">
|
<flow-shell-card
|
||||||
<div class="pf-c-login__main-body pb-loading">
|
class="pf-c-login__main"
|
||||||
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
|
flowBodyUrl="{{ exec_url }}">
|
||||||
<span class="pf-c-spinner__clipper"></span>
|
</flow-shell-card>
|
||||||
<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>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -236,7 +236,6 @@ class FlowExecutorShellView(TemplateView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
||||||
kwargs["exec_url"] = reverse("passbook_flows:flow-executor", kwargs=self.kwargs)
|
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
|
self.request.session[SESSION_KEY_GET] = self.request.GET
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""passbook password stage"""
|
"""passbook password stage"""
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
from django.contrib.auth import _clean_credentials
|
from django.contrib.auth import _clean_credentials
|
||||||
from django.contrib.auth.backends import BaseBackend
|
from django.contrib.auth.backends import BaseBackend
|
||||||
|
@ -121,4 +122,5 @@ class PasswordStageView(FormView, StageView):
|
||||||
self.executor.plan.context[
|
self.executor.plan.context[
|
||||||
PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
PLAN_CONTEXT_AUTHENTICATION_BACKEND
|
||||||
] = user.backend
|
] = user.backend
|
||||||
|
messages.success(self.request, _("Successfully logged in!"))
|
||||||
return self.executor.stage_ok()
|
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 { getCookie } from "./utils.js";
|
||||||
|
import { updateMessages } from "./Messages.js";
|
||||||
|
|
||||||
const PRIMARY_CLASS = "pf-m-primary";
|
const PRIMARY_CLASS = "pf-m-primary";
|
||||||
const SUCCESS_CLASS = "pf-m-success";
|
const SUCCESS_CLASS = "pf-m-success";
|
||||||
|
@ -33,7 +34,7 @@ class ActionButton extends HTMLButtonElement {
|
||||||
this.innerText = this.oldBody;
|
this.innerText = this.oldBody;
|
||||||
this.classList.replace(PRIMARY_CLASS, statusClass);
|
this.classList.replace(PRIMARY_CLASS, statusClass);
|
||||||
// Trigger messages to update
|
// Trigger messages to update
|
||||||
document.querySelector("pb-messages").setAttribute("touch", Date.now());
|
updateMessages();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.classList.replace(statusClass, PRIMARY_CLASS);
|
this.classList.replace(statusClass, PRIMARY_CLASS);
|
||||||
}, 1000);
|
}, 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);
|
return prefix + Math.random().toString(36).substr(2, 9);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function updateMessages() {
|
||||||
|
document.querySelector("pb-messages").setAttribute("touch", Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
class Messages extends LitElement {
|
class Messages extends LitElement {
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import './FetchFillSlot.js';
|
import './FetchFillSlot.js';
|
||||||
import './ActionButton.js';
|
import './ActionButton.js';
|
||||||
import './Messages.js';
|
import './Messages.js';
|
||||||
|
import './FlowShellCard.js';
|
||||||
|
|
||||||
// Button Dropdowns
|
// Button Dropdowns
|
||||||
document.querySelectorAll("button.pf-c-dropdown__toggle").forEach((b) => {
|
document.querySelectorAll("button.pf-c-dropdown__toggle").forEach((b) => {
|
||||||
|
|
Reference in a new issue