Better facebook button. Split account view.

This commit is contained in:
eraden 2022-07-09 21:52:48 +02:00
parent ad20d85012
commit d7f3f1c7f9
25 changed files with 947 additions and 750 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ uploads
client/dist
client/dist/app.js.map
client/dist/app.js
!/client/dist/

View File

@ -10,6 +10,8 @@
facebook-id="{{a.facebook_id.as_deref().unwrap_or_default()}}"
></ow-account>
{% when None %}
<ow-account></ow-account>
<ow-account
mode="form"
></ow-account>
{% endmatch %}
{% endblock %}

813
client/dist/app.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,13 @@
import "./business-items/business-item";
import { Component } from "./shared";
customElements.define('business-items', class extends HTMLElement {
#form;
customElements.define('business-items', class extends Component {
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
</style>
<slot></slot>
`;
`);
}
});

View File

@ -1,18 +1,13 @@
import "../shared/image-input";
import { Component } from "../shared";
customElements.define('business-item', class extends HTMLElement {
#form;
customElements.define('business-item', class extends Component {
static get observedAttributes() {
return ['item-id', 'name', 'price', 'picture-url', 'item-order']
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
section { display: flex; justify-content: space-between; }
@ -34,9 +29,9 @@ customElements.define('business-item', class extends HTMLElement {
<input name="picture_url" id="picture_url" />
</form>
</section>
`;
`);
const imageInput = shadow.querySelector('image-input');
const imageInput = this.shadowRoot.querySelector('image-input');
this.addEventListener('image-input:uploaded', ev => {
ev.preventDefault();
@ -44,7 +39,7 @@ customElements.define('business-item', class extends HTMLElement {
this.picture_url = imageInput.url;
const form = shadow.querySelector('form');
const form = this.shadowRoot.querySelector('form');
form.querySelector('#id').value = this.item_id;
form.querySelector('#name').value = this.name;
form.querySelector('#price').value = this.price;
@ -62,15 +57,18 @@ customElements.define('business-item', class extends HTMLElement {
}
attributeChangedCallback(name, oldV, newV) {
super.attributeChangedCallback(name, oldV, newV);
if (oldV === newV) return;
switch (name) {
case 'item-id': return this.item_id = newV;
case 'name': return this.name = newV;
case 'price': return this.price = newV / 100.0;
case 'picture-url': return this.picture_url = newV;
}
}
static get attr2FieldBlacklist() {
return ['price'];
}
get item_id() {
return this.getAttribute('item-id');
}
@ -93,16 +91,16 @@ customElements.define('business-item', class extends HTMLElement {
set name(v) {
this.setAttribute('name', v);
this.#form.querySelector('#name').textContent = v;
this.shadowRoot.querySelector('#name').textContent = v;
}
get price() {
return this.#form.querySelector('price-input').value;
return this.shadowRoot.querySelector('price-input').value;
}
set price(v) {
this.setAttribute('price', v);
this.#form.querySelector('price-input').value = v;
this.shadowRoot.querySelector('price-input').value = v;
}
get picture_url() {
@ -111,6 +109,6 @@ customElements.define('business-item', class extends HTMLElement {
set picture_url(v) {
this.setAttribute('picture-url', v);
this.#form.querySelector('image-input').url = v;
this.shadowRoot.querySelector('image-input').url = v;
}
});

View File

@ -1,17 +1,12 @@
import { FORM_STYLE } from "./shared";
customElements.define('form-navigation', class extends HTMLElement {
#form;
import { FORM_STYLE, Component } from "./shared";
customElements.define('form-navigation', class extends Component {
static get observedAttributes() {
return ['next', 'prev']
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -33,13 +28,14 @@ customElements.define('form-navigation', class extends HTMLElement {
<input id="next" type="submit" value="Następny" />
</div>
</form>
`;
shadow.querySelector('#prev').addEventListener('click', ev => {
`);
this.shadowRoot.querySelector('#prev').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
this.prev();
});
shadow.querySelector('#next').addEventListener('click', ev => {
this.shadowRoot.querySelector('#next').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
this.next();
@ -50,11 +46,11 @@ customElements.define('form-navigation', class extends HTMLElement {
if (oldV === newV) return;
switch (name) {
case 'next': {
this.#form.querySelector('#next').className = newV === 'hidden' ? 'hidden' : '';
this.shadowRoot.querySelector('#next').className = newV === 'hidden' ? 'hidden' : '';
break;
}
case 'prev': {
this.#form.querySelector('#prev').className = newV === 'hidden' ? 'hidden' : '';
this.shadowRoot.querySelector('#prev').className = newV === 'hidden' ? 'hidden' : '';
break;
}
}

View File

@ -1,21 +1,18 @@
import "./local-businesses/local-business-item";
import "./local-businesses/local-business";
import { Component } from "./shared";
customElements.define('local-businesses', class extends HTMLElement {
#form;
customElements.define('local-businesses', class extends Component {
static get observedAttributes() {
return ['filter']
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
::slotted(local-service[local-services-visible="invisible"]) {
::slotted(local-service[local-business-visible="invisible"]) {
display: none;
}
input {
@ -39,9 +36,9 @@ customElements.define('local-businesses', class extends HTMLElement {
<section id="items">
<slot name="services"></slot>
</section>
`;
`);
{
const filter = shadow.querySelector('#filter');
const filter = this.shadowRoot.querySelector('#filter');
let t = null;
filter.addEventListener('change', ev => {
ev.stopPropagation();
@ -65,11 +62,7 @@ customElements.define('local-businesses', class extends HTMLElement {
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'filter':
return this.filter = newV;
}
super.attributeChangedCallback(name, oldV, newV);
}
get filter() {
@ -79,17 +72,17 @@ customElements.define('local-businesses', class extends HTMLElement {
set filter(value) {
if (!value || value === '') {
this.removeAttribute('filter');
for (const el of this.querySelectorAll('local-service')) {
el.removeAttribute('local-services-visible');
for (const el of this.querySelectorAll('local-business')) {
el.removeAttribute('local-business-visible');
}
} else {
this.setAttribute('filter', value);
for (const el of this.querySelectorAll('local-service')) {
for (const el of this.querySelectorAll('local-business')) {
if (!el.name) continue;
if (el.name.includes(value)) {
el.setAttribute('local-services-visible', 'visible');
el.setAttribute('local-business-visible', 'visible');
} else {
el.setAttribute('local-services-visible', 'invisible');
el.setAttribute('local-business-visible', 'invisible');
}
}
}

View File

@ -1,14 +1,12 @@
customElements.define('local-business-item', class extends HTMLElement {
#form;
import { Component } from "../shared";
customElements.define('local-business-item', class extends Component {
static get observedAttributes() {
return ['name', 'price']
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -27,26 +25,34 @@ customElements.define('local-business-item', class extends HTMLElement {
<h3 id="name"></h3>
<price-view id="price"></price-view>
</section>
`;
`);
}
connectedCallback() {
this.#form.querySelector('#name').textContent = this.getAttribute('name');
this.#form.querySelector('#price').value = this.price();
this.name = this.name;
this.price = this.price;
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'name':
return this.#form.querySelector('#name').textContent = newV;
case 'price':
return this.#form.querySelector('#price').value = newV;
}
super.attributeChangedCallback(name, oldV, newV);
}
price(s) {
const n = parseInt(s || this.getAttribute('price'));
get price() {
const n = parseInt(this.getAttribute('price'));
return isNaN(n) ? 0 : n;
}
set price(v) {
this.setAttribute('price', v);
this.shadowRoot.querySelector('#price').value = v;
}
get name() {
return this.getAttribute('name');
}
set name(v) {
this.setAttribute('name', v);
this.shadowRoot.querySelector('#name').textContent = v;
}
});

View File

@ -1,16 +1,12 @@
import { S } from "../shared";
customElements.define('local-business', class extends HTMLElement {
#form;
import { Component, S } from "../shared";
customElements.define('local-business', class extends Component {
static get observedAttributes() {
return ['name', 'service-id', 'state']
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -23,22 +19,39 @@ customElements.define('local-business', class extends HTMLElement {
<section id="items">
<slot name="item"></slot>
</section>
`;
`);
}
connectedCallback() {
this.#form.querySelector('#name').textContent = this.getAttribute('name');
this.name = this.name;
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'name':
return this.#form.querySelector('#name').textContent = newV;
}
super.attributeChangedCallback(name, oldV, newV);
}
get name() {
return this.getAttribute('name') || ''
}
set name(v) {
this.setAttribute('name', v);
this.shadowRoot.querySelector('#name').textContent = v;
}
get state() {
return this.getAttribute('state');
}
set state(v) {
this.setAttribute('state', v);
}
get service_id() {
return this.getAttribute('service-id');
}
set service_id(v) {
this.setAttribute('service-id', v);
}
});

View File

@ -1,16 +1,8 @@
import { FORM_STYLE, S } from "./shared";
customElements.define('login-form', class extends HTMLElement {
#form;
static get observedAttributes() {
return []
}
import { Component, FORM_STYLE } from "./shared";
customElements.define('login-form', class extends Component {
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -29,15 +21,6 @@ customElements.define('login-form', class extends HTMLElement {
<input type="button" value="Zaloguj" />
</div>
</form>
`;
}
connectedCallback() {
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
}
`);
}
});

View File

@ -1,10 +1,8 @@
customElements.define('ow-nav', class extends HTMLElement {
#form;
import { Component } from "../shared";
customElements.define('ow-nav', class extends Component {
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -35,6 +33,6 @@ customElements.define('ow-nav', class extends HTMLElement {
<section>
<slot></slot>
</section>
`;
`);
}
});

View File

@ -1,14 +1,12 @@
customElements.define('ow-path', class extends HTMLElement {
#form;
import { Component } from "../shared";
customElements.define('ow-path', class extends Component {
static get observedAttributes() {
return ['selected', 'path'];
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -30,7 +28,7 @@ customElements.define('ow-path', class extends HTMLElement {
}
</style>
<a><slot></slot></a>
`;
`);
}
connectedCallback() {
@ -38,13 +36,7 @@ customElements.define('ow-path', class extends HTMLElement {
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'selected':
return this.selected = newV;
case 'path':
return this.path = newV;
}
super.attributeChangedCallback(name, oldV, newV);
}
get selected() {
@ -66,6 +58,6 @@ customElements.define('ow-path', class extends HTMLElement {
return;
}
this.setAttribute('path', value);
this.#form.querySelector('a').setAttribute('href', value);
this.shadowRoot.querySelector('a').setAttribute('href', value);
}
});

View File

@ -1,16 +1,14 @@
import { FORM_STYLE } from "./shared";
customElements.define('ow-account', class extends HTMLElement {
#form;
import { Component, FORM_STYLE } from "./shared";
import './shared/facebook-button';
import './ow-account/account-view';
customElements.define('ow-account', class extends Component {
static get observedAttributes() {
return ['mode', "id", "name", 'email', "facebook-id"]
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -23,15 +21,15 @@ customElements.define('ow-account', class extends HTMLElement {
:host([mode="register"]) #form > register-form, :host([mode="register"]) #switch-login {
display: block !important;
}
#display {
account-view {
display: none;
}
:host([mode="display"]) #display { display: block; }
:host([mode="display"]) account-view { display: block; }
:host([mode="display"]) #form { display: none; }
:host([mode="form"]) #form,
:host([mode="login"]) #form { display: block; }
a{
a {
display: block;
cursor: pointer;
}
@ -41,7 +39,7 @@ customElements.define('ow-account', class extends HTMLElement {
text-decoration: none;
}
${FORM_STYLE}
${ FORM_STYLE }
</style>
<article id="form">
<login-form></login-form>
@ -53,65 +51,43 @@ customElements.define('ow-account', class extends HTMLElement {
<a>Masz konta? Zaloguj się</a>
</section>
</article>
<article id="display">
<div>
<input id="id" name="id" readonly type="hidden" />
</div>
<div>
<label>Login</label>
<input id="name" name="name" readonly />
</div>
<div>
<label>E-Mail</label>
<input id="email" name="email" readonly />
</div>
<div>
<label>Powiązane konto Facebook</label>
<input id="facebook_id" name="facebook_id" readonly />
</div>
</article>
`;
shadow.querySelector('#switch-login > a').addEventListener('click', ev => {
<account-view></account-view>
`);
this.shadowRoot.querySelector('#switch-login > a').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
this.mode = 'login';
});
shadow.querySelector('#switch-register > a').addEventListener('click', ev => {
this.shadowRoot.querySelector('#switch-register > a').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
this.mode = 'register';
});
this.addEventListener('facebook:account', ev => {
ev.stopPropagation();
ev.preventDefault();
this.mode = 'facebook';
const { id, name, email } = ev.details;
console.info({ id, name, email })
// form.querySelector('#email').value = email;
// form.querySelector('#login').value = name;
// form.querySelector('#password').value = crypto.randomUUID();
// form.querySelector('#facebook_id').value = id;
// form.submit();
});
}
connectedCallback() {
if (this.mode === '') this.mode = 'login';
this.name = this.name;
this.email = this.email;
this.facebook_id = this.facebook_id;
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'mode': {
this.mode = newV;
break;
}
case 'id': {
this.id = newV;
break;
}
case 'name': {
this.name = newV;
break;
}
case 'email': {
this.email = newV;
break;
}
case 'facebook-id': {
this.facebook_id = newV;
break;
}
}
super.attributeChangedCallback(name, oldV, newV);
}
get mode() {
@ -129,7 +105,7 @@ customElements.define('ow-account', class extends HTMLElement {
set name(v) {
this.setAttribute('name', v);
this.#form.querySelector('#display #name').value = v;
this.shadowRoot.querySelector('account-view').name = v;
}
get email() {
@ -138,7 +114,7 @@ customElements.define('ow-account', class extends HTMLElement {
set email(v) {
this.setAttribute('email', v);
this.#form.querySelector('#display #email').value = v;
this.shadowRoot.querySelector('account-view').email = v;
}
get facebook_id() {
@ -147,6 +123,6 @@ customElements.define('ow-account', class extends HTMLElement {
set facebook_id(v) {
this.setAttribute('facebook-id', v);
this.#form.querySelector('#display #facebook_id').value = v;
this.shadowRoot.querySelector('account-view').facebook_id = v;
}
});

View File

@ -0,0 +1,85 @@
import { Component, FORM_STYLE } from "../shared";
customElements.define('account-view', class extends Component {
static get observedAttributes() {
return ['facebook-id', 'id', 'name', 'email']
}
constructor() {
super(`
<style>
:host {
display: block;
}
:host(:not([facebook-id = ""])) #fb-id {
display: block;
}
:host([facebook-id = ""]) #fb-id {
display: none;
}
:host([facebook-id = ""]) #fb-button {
display: flex;
padding: .375rem .75rem;
}
#fb-button {
display: none;
}
facebook-button {
margin-right: 16px;
}
${ FORM_STYLE }
</style>
<div>
<input id="id" name="id" readonly type="hidden" />
</div>
<div>
<label>Login</label>
<input id="name" name="name" readonly />
</div>
<div>
<label>E-Mail</label>
<input id="email" name="email" readonly />
</div>
<div id="fb-button">
<facebook-button width="100">
<p>Powiąż z kontem Facebook</p>
</facebook-button>
</div>
<div id="fb-id">
<label>Powiązane konto Facebook</label>
<input id="facebook_id" name="facebook_id" readonly />
</div>
`);
this.addEventListener('facebook:available', ev => {
ev.preventDefault();
ev.stopPropagation();
});
}
get name() {
return this.getAttribute('name') || '';
}
set name(v) {
this.setAttribute('name', v);
this.shadowRoot.querySelector('#name').value = v;
}
get email() {
return this.getAttribute('email') || '';
}
set email(v) {
this.setAttribute('email', v);
this.shadowRoot.querySelector('#email').value = v;
}
get facebook_id() {
return this.getAttribute('facebook-id');
}
set facebook_id(v) {
this.setAttribute('facebook-id', v);
this.shadowRoot.querySelector('#facebook_id').value = v;
}
});

View File

@ -1,4 +1,4 @@
import { S, FORM_STYLE } from "./shared";
import { S, FORM_STYLE, Component } from "./shared";
import "./register-form/register-basic-form";
import "./register-form/register-item-form-row.js";
@ -8,15 +8,13 @@ import "./register-form/register-submit-form";
import "./register-form/register-user-type";
import "./register-form/register-user-form";
customElements.define('register-form', class extends HTMLElement {
customElements.define('register-form', class extends Component {
static get observedAttributes() {
return ['step']
}
constructor() {
super();
const shadow = this[S] = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -54,27 +52,26 @@ customElements.define('register-form', class extends HTMLElement {
<register-submit-form id="step-4"></register-submit-form>
<register-user-form id="step-40"></register-user-form>
</article>
`;
const finalForm = shadow.querySelector('#step-4');
this[S].addEventListener('account:type:user', ev => {
`);
const finalForm = this.shadowRoot.querySelector('#step-4');
this.shadowRoot.addEventListener('account:type:user', ev => {
ev.stopPropagation();
finalForm.accountType = 'User';
this.step = 40;
});
this[S].addEventListener('account:type:local-service', ev => {
this.shadowRoot.addEventListener('account:type:local-service', ev => {
ev.stopPropagation();
finalForm.accountType = 'Business';
this.step = 1;
});
this[S].addEventListener('form:next', ev => {
this.shadowRoot.addEventListener('form:next', ev => {
ev.stopPropagation();
const form = shadow.querySelector(`#step-${ this.step }`);
const form = this.shadowRoot.querySelector(`#step-${ this.step }`);
if (this.#copyForm(form, finalForm)) {
this.step = this.step + 1;
}
});
this[S].addEventListener('form:prev', ev => {
this.shadowRoot.addEventListener('form:prev', ev => {
ev.stopPropagation();
this.step = this.step - 1;
});
@ -89,9 +86,7 @@ customElements.define('register-form', class extends HTMLElement {
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
}
super.attributeChangedCallback(name, oldV, newV);
}
get step() {

View File

@ -1,14 +1,8 @@
import { FORM_STYLE, PseudoForm } from "../shared";
customElements.define('register-basic-form', class extends PseudoForm {
#form;
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
super(`
<style>
:host {
display: block;
@ -30,13 +24,13 @@ customElements.define('register-basic-form', class extends PseudoForm {
</div>
<form-navigation></form-navigation>
</form>
`;
`);
const form = shadow.querySelector('form');
const form = this.shadowRoot.querySelector('form');
form.addEventListener('submit', ev => {
ev.preventDefault();
ev.stopPropagation();
shadow.querySelector('form-navigation').next();
this.shadowRoot.querySelector('form-navigation').next();
})
}
});

View File

@ -1,18 +1,12 @@
import { FORM_STYLE, PseudoForm } from "../shared";
customElements.define('register-business-form', class extends PseudoForm {
#form;
constructor() {
super();
const shadow = this.#form = this.attachShadow({mode: "closed"});
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
${FORM_STYLE}
${ FORM_STYLE }
</style>
<form id="step-2">
<div>
@ -24,12 +18,12 @@ customElements.define('register-business-form', class extends PseudoForm {
</div>
<form-navigation></form-navigation>
</form>
`;
`);
shadow.querySelector('form').addEventListener('submit', ev => {
this.shadowRoot.querySelector('form').addEventListener('submit', ev => {
ev.preventDefault();
ev.stopPropagation();
shadow.querySelector('form-navigation').next();
this.shadowRoot.querySelector('form-navigation').next();
});
}
})

View File

@ -1,15 +1,56 @@
import { FORM_STYLE, PseudoForm } from "../shared";
customElements.define('register-item-form-row', class extends PseudoForm {
#form;
const htmlContent = (idx) => {
return `
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
form {
display: flex;
justify-content: flex-start;
}
#fields > *:not(:last-child) {
margin-right: 16px;
}
#fields > div > label {
margin-right: 16px;
}
#fields {
display: flex;
justify-content: flex-start;
width: 100%;
max-width: 100%;
}
${ FORM_STYLE }
</style>
<form class="inline">
<div id="fields">
<image-input></image-input>
<input type="hidden" name="picture_url" id="picture_url" />
<div id="name" class="field">
<label>Nazwa</label>
<input class="item-name" name="items[${ idx }][name]" type="text" required>
</div>
<div id="price" class="field">
<label>Cena</label>
<price-input class="item-price" name="items[${ idx }][price]" required>
</price-input>
</div>
</div>
<div><input class="remove" type="button" value="Usuń"></div>
</form>
`;
}
customElements.define('register-item-form-row', class extends PseudoForm {
static get observedAttributes() {
return ['idx', 'name']
}
constructor() {
super();
this.#form = this.attachShadow({ mode: 'closed' });
super(htmlContent());
this.addEventListener('item:removed', () => {
this.setAttribute('removed', 'removed');
@ -20,62 +61,20 @@ customElements.define('register-item-form-row', class extends PseudoForm {
}
connectedCallback() {
const idx = this.idx;
this.#form.innerHTML = `
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
form {
display: flex;
justify-content: flex-start;
}
#fields > *:not(:last-child) {
margin-right: 16px;
}
#fields > div > label {
margin-right: 16px;
}
#fields {
display: flex;
justify-content: flex-start;
width: 100%;
max-width: 100%;
}
${ FORM_STYLE }
</style>
<form class="inline">
<div id="fields">
<image-input></image-input>
<input type="hidden" name="picture_url" id="picture_url" />
<div id="name" class="field">
<label>Nazwa</label>
<input class="item-name" name="items[${ idx }][name]" type="text" required>
</div>
<div id="price" class="field">
<label>Cena</label>
<price-input class="item-price" name="items[${ idx }][price]" required>
</price-input>
</div>
</div>
<div><input class="remove" type="button" value="Usuń"></div>
</form>
`;
const imageInput = this.#form.querySelector('image-input');
this.shadowRoot.innerHTML = htmlContent(this.idx);
const imageInput = this.shadowRoot.querySelector('image-input');
this.addEventListener('image-input:uploaded', ev => {
ev.preventDefault();
ev.stopPropagation();
this.picture_url = imageInput.url;
});
this.#form.querySelector('form').addEventListener('submit', ev => {
this.shadowRoot.querySelector('form').addEventListener('submit', ev => {
ev.preventDefault();
ev.stopPropagation();
this.reportValidity();
});
this.#form.querySelector('.remove').addEventListener('click', ev => {
this.shadowRoot.querySelector('.remove').addEventListener('click', ev => {
ev.preventDefault();
ev.stopPropagation();
this.dispatchEvent(new CustomEvent('item:removed', { bubbles: true, composed: false }));
@ -85,21 +84,23 @@ customElements.define('register-item-form-row', class extends PseudoForm {
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'idx':return this.updateNames();
case 'picture-url': return this.picture_url = newV;
case 'idx':
return this.updateNames();
case 'picture-url':
return this.picture_url = newV;
}
}
get inputs() {
return [
extract(this.#form.querySelector('.item-name')),
extract(this.#form.querySelector('.item-price')),
extract(this.shadowRoot.querySelector('.item-name')),
extract(this.shadowRoot.querySelector('.item-price')),
];
}
updateNames() {
const idx = this.getAttribute('idx');
for (const el of this.#form.querySelectorAll('.field')) {
for (const el of this.shadowRoot.querySelectorAll('.field')) {
const id = el.id;
el.querySelector('input, price-input').setAttribute('name', `items[${ idx }][${ id }]`);
}
@ -119,12 +120,12 @@ customElements.define('register-item-form-row', class extends PseudoForm {
set picture_url(v) {
this.setAttribute('picture-url', v);
this.#form.querySelector('image-input').url = v;
this.#form.querySelector('#picture_url').value = v;
this.shadowRoot.querySelector('image-input').url = v;
this.shadowRoot.querySelector('#picture_url').value = v;
}
reportValidity() {
return super.reportValidity() && this.#form.querySelector('price-input').reportValidity();
return super.reportValidity() && this.shadowRoot.querySelector('price-input').reportValidity();
}
});

View File

@ -11,24 +11,17 @@ const updateItems = (form) => {
}
customElements.define('register-items-form', class extends PseudoForm {
#form;
static get observedAttributes() {
return []
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
${ FORM_STYLE }
::slotted(section) {
display: flex;
}
${ FORM_STYLE }
</style>
<form>
<article id="items">
@ -39,7 +32,7 @@ customElements.define('register-items-form', class extends PseudoForm {
</div>
<form-navigation></form-navigation>
</form>
`;
`);
this.addEventListener('item:removed', ev => {
ev.stopPropagation();
updateItems(this)
@ -52,7 +45,7 @@ customElements.define('register-items-form', class extends PseudoForm {
}
}
});
shadow.querySelector('#add-item').addEventListener('click', ev => {
this.shadowRoot.querySelector('#add-item').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
this.appendChild(document.createElement('register-item-form-row'));

View File

@ -1,14 +1,8 @@
import { FORM_STYLE, PseudoForm } from "../shared";
customElements.define('register-submit-form', class extends PseudoForm {
#form;
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
@ -59,16 +53,16 @@ customElements.define('register-submit-form', class extends PseudoForm {
<input type="submit" value="Dodaj usługi/produkty" />
</div>
</form>
`;
`);
}
updateField(name, value) {
this.#form.querySelector(`[id="hidden-${ name }"]`).value = value;
this.#form.querySelector(`[id="preview-${ name }"]`).value = value;
this.shadowRoot.querySelector(`[id="hidden-${ name }"]`).value = value;
this.shadowRoot.querySelector(`[id="preview-${ name }"]`).value = value;
}
setItems(items) {
const host = this.#form.querySelector('#items');
const host = this.shadowRoot.querySelector('#items');
host.innerHTML = ``;
for (const row of items) {
const el = host.appendChild(document.createElement('div'));
@ -76,14 +70,14 @@ customElements.define('register-submit-form', class extends PseudoForm {
const [name, price] = row;
el.innerHTML = `
<input type="text" name="${name.name}" value="${name.value}" readonly />
<input type="hidden" name="${price.name}" value="${price.value}" readonly />
<price-view value="${price.value}"></price-view>
<input type="text" name="${ name.name }" value="${ name.value }" readonly />
<input type="hidden" name="${ price.name }" value="${ price.value }" readonly />
<price-view value="${ price.value }"></price-view>
`;
}
}
set accountType(v) {
this.#form.querySelector('#account_type').value = v;
this.shadowRoot.querySelector('#account_type').value = v;
}
});

View File

@ -1,21 +1,15 @@
import { FORM_STYLE } from "../shared";
customElements.define('register-user-form', class extends HTMLElement {
#form;
import { Component, FORM_STYLE } from "../shared";
import "../shared/facebook-button";
customElements.define('register-user-form', class extends Component {
static get observedAttributes() {
return ['mode']
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
super(`
<style>
:host { display: block; }
svg { width: 200px; }
* { font-family: 'Noto Sans', sans-serif; }
#icons {
display: flex;
@ -49,6 +43,7 @@ customElements.define('register-user-form', class extends HTMLElement {
</svg>
<form id="form" method="post" action="/register">
<input type="hidden" id="account_type" name="account_type">
<input type="hidden" id="facebook_id" name="facebook_id">
<div>
<label>E-Mail</label>
@ -68,34 +63,28 @@ customElements.define('register-user-form', class extends HTMLElement {
</form>
</div>
<div class="option">
<svg id="fb-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#3B5998" d="M448 512h-87.999c-22.094 0-39.999-17.922-39.999-40V360.001c0-22.094 17.905-39.999 39.999-39.999h12.922c8.32 0 16.844-6.656 19.062-15.031l8.484-31.953c2.484-9.312-2.812-17.023-11.852-17.023H360c-22.094 0-39.999-17.902-39.999-39.988v-16C320.001 177.905 337.906 160 360 160h24.008c8.82 0 16-7.156 16-16v-32.004c0-8.828-7.18-16.004-16-16.004H336c-44.187 0-79.991 35.828-79.991 80.011v40.004c0 22.085-17.922 39.988-40.003 39.988h-7.625c-9.039 0-16.379 7.711-16.379 17.023v31.953c0 8.375 6.746 15.031 15.07 15.031h8.934c22.082 0 40.003 17.905 40.003 39.999V472c0 22.078-17.922 40-40.003 40h-8c-8.844 0-16.004-7.172-16.004-16 0-8.844 7.16-16 16.004-16 8.824 0 16-7.172 16-16v-95.999c0-8.844-7.175-16-16-16H197.51c-20.719 0-37.511-16.578-37.511-37.577v-47.922c0-23.156 18.371-42.512 41.027-42.512h6.98c8.824 0 16-7.16 16-15.984v-40.004c0-57.441 46.559-103.995 103.995-103.995h64.008c22.086 0 39.984 17.902 39.984 39.988v48.004c0 22.085-17.898 40.003-39.984 40.003H352.001v16.004c0 8.824 7.156 15.984 16 15.984h29.969c22.672 0 37.398 19.355 33.062 42.512l-8.992 47.922c-3.93 20.999-23.82 37.577-44.539 37.577h-9.5c-8.844 0-16 7.156-16 16V464c0 8.828 7.156 16 16 16H448c17.672 0 32-14.328 32-32V64.007c0-17.688-14.328-32.003-32-32.003H64.007c-17.672 0-32.003 14.316-32.003 32.003V448c0 17.672 14.332 32 32.003 32h16c8.828 0 16.003 7.156 16.003 16 0 8.828-7.175 16-16.003 16h-16C28.668 512 0 483.344 0 448V64.007C0 28.648 28.668 0 64.007 0H448c35.359 0 64 28.648 64 64.007V448c0 35.344-28.641 64-64 64zm-304.001-32c8.844 0 16 7.156 16 16 0 8.828-7.156 16-16 16-8.828 0-16.004-7.172-16.004-16 0-8.844 7.176-16 16.004-16z"/>
</svg>
<facebook-button></facebook-button>
</div>
</section>
`;
`);
const form = shadow.querySelector('form');
const form = this.shadowRoot.querySelector('form');
shadow.querySelector('#fb-icon').addEventListener('click', ev => {
this.addEventListener('facebook:account', ev => {
ev.stopPropagation();
ev.preventDefault();
this.mode = 'facebook';
FB.login((res) => {
if (res.status === 'connected') {
FB.api("/me?fields=id,name,email", ({ id, name, email, ...rest }) => {
console.log(id, name, email, rest);
form.querySelector('#email').value = email;
form.querySelector('#login').value = name;
form.querySelector('#password').value = crypto.randomUUID();
form.querySelector('#facebook_id').value = id;
form.submit();
});
}
}, { scope: 'public_profile,email', return_scopes: true });
const { id, name, email } = ev.detail;
form.querySelector('#email').value = email;
form.querySelector('#login').value = name;
form.querySelector('#password').value = crypto.randomUUID();
form.querySelector('#facebook_id').value = id;
form.querySelector('#account_type').value = 'User';
form.submit();
});
shadow.querySelector('#email-icon').addEventListener('click', ev => {
this.shadowRoot.querySelector('#email-icon').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();

View File

@ -1,12 +1,8 @@
customElements.define('register-user-type', class extends HTMLElement {
#form;
import { Component } from "../shared";
customElements.define('register-user-type', class extends Component {
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
super(`
<style>
:host {
display: block;
@ -56,15 +52,15 @@ customElements.define('register-user-type', class extends HTMLElement {
</li>
</ul>
</article>
`;
`);
const user = shadow.querySelector('#user');
const user = this.shadowRoot.querySelector('#user');
user.addEventListener('click', ev => {
ev.preventDefault();
ev.stopPropagation();
this.dispatchEvent(new CustomEvent('account:type:user', { bubbles: true, composed: true }));
});
const service = shadow.querySelector('#local-service');
const service = this.shadowRoot.querySelector('#local-service');
service.addEventListener('click', ev => {
ev.preventDefault();
ev.stopPropagation();

View File

@ -104,17 +104,59 @@ label {
${BUTTON_STYLE}
`;
export class PseudoForm extends HTMLElement {
export class Component extends HTMLElement {
#shadow;
static get observedAttributes() {
return []
}
constructor(html) {
super();
this.#shadow = this.attachShadow({ mode: 'open' });
this.#shadow.innerHTML = html;
}
connectedCallback() {
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV)
return;
const observed = this.constructor.observedAttributes;
if (!Array.isArray(observed))
return;
if (!observed.includes(name))
return;
const field = this.constructor.attr2Field(name);
if (!field)
return;
this[field] = newV;
}
static attr2Field(name) {
if ((this.constructor.attr2FieldBlacklist || []).includes(name))
return;
return name.replace('-', '_');
}
static get attr2FieldBlacklist() {
return []
}
}
export class PseudoForm extends Component {
reportValidity() {
return this[S].querySelector('form').reportValidity();
return this.shadowRoot.querySelector('form').reportValidity();
}
checkValidity() {
return this[S].querySelector('form').checkValidity();
return this.shadowRoot.querySelector('form').checkValidity();
}
get elements() {
return this[S].querySelector('form').elements;
return this.shadowRoot.querySelector('form').elements;
}
}

View File

@ -0,0 +1,94 @@
import { Component, FORM_STYLE, runFbReady } from "../shared";
customElements.define('facebook-button', class extends Component {
#fb_sdk;
static get observedAttributes() {
return ['width', 'height', 'fb-sdk']
}
constructor() {
super(`
<style>
:host {
display: block;
}
#not-available {
display: block;
}
#svg {
display: none;
}
:host([fb-sdk="available"]) #not-available {
display: none;
}
:host([fb-sdk="available"]) #svg {
display: block;
}
${ FORM_STYLE }
</style>
<p id="not-available">Skrypty Facebook zablokowane przez rozszerzenie przeglądarki</p>
<div id="svg">
<svg id="fb-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#3B5998" d="M448 512h-87.999c-22.094 0-39.999-17.922-39.999-40V360.001c0-22.094 17.905-39.999 39.999-39.999h12.922c8.32 0 16.844-6.656 19.062-15.031l8.484-31.953c2.484-9.312-2.812-17.023-11.852-17.023H360c-22.094 0-39.999-17.902-39.999-39.988v-16C320.001 177.905 337.906 160 360 160h24.008c8.82 0 16-7.156 16-16v-32.004c0-8.828-7.18-16.004-16-16.004H336c-44.187 0-79.991 35.828-79.991 80.011v40.004c0 22.085-17.922 39.988-40.003 39.988h-7.625c-9.039 0-16.379 7.711-16.379 17.023v31.953c0 8.375 6.746 15.031 15.07 15.031h8.934c22.082 0 40.003 17.905 40.003 39.999V472c0 22.078-17.922 40-40.003 40h-8c-8.844 0-16.004-7.172-16.004-16 0-8.844 7.16-16 16.004-16 8.824 0 16-7.172 16-16v-95.999c0-8.844-7.175-16-16-16H197.51c-20.719 0-37.511-16.578-37.511-37.577v-47.922c0-23.156 18.371-42.512 41.027-42.512h6.98c8.824 0 16-7.16 16-15.984v-40.004c0-57.441 46.559-103.995 103.995-103.995h64.008c22.086 0 39.984 17.902 39.984 39.988v48.004c0 22.085-17.898 40.003-39.984 40.003H352.001v16.004c0 8.824 7.156 15.984 16 15.984h29.969c22.672 0 37.398 19.355 33.062 42.512l-8.992 47.922c-3.93 20.999-23.82 37.577-44.539 37.577h-9.5c-8.844 0-16 7.156-16 16V464c0 8.828 7.156 16 16 16H448c17.672 0 32-14.328 32-32V64.007c0-17.688-14.328-32.003-32-32.003H64.007c-17.672 0-32.003 14.316-32.003 32.003V448c0 17.672 14.332 32 32.003 32h16c8.828 0 16.003 7.156 16.003 16 0 8.828-7.175 16-16.003 16h-16C28.668 512 0 483.344 0 448V64.007C0 28.648 28.668 0 64.007 0H448c35.359 0 64 28.648 64 64.007V448c0 35.344-28.641 64-64 64zm-304.001-32c8.844 0 16 7.156 16 16 0 8.828-7.156 16-16 16-8.828 0-16.004-7.172-16.004-16 0-8.844 7.176-16 16.004-16z"/>
</svg>
<slot></slot>
</div>
`);
this.shadowRoot.querySelector('#fb-icon').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
if (!this.#fb_sdk)
return console.info("Facebook SDK is not available");
FB.login((res) => {
if (res.status === 'connected') {
FB.api("/me?fields=id,name,email", ({ id, name, email }) => {
console.info({ id, name, email });
this.dispatchEvent(new CustomEvent('facebook:account', {
bubbles: true, composed: true, detail: {
id, name, email
}
}));
});
}
}, { scope: 'public_profile,email', return_scopes: true });
});
}
connectedCallback() {
super.connectedCallback();
runFbReady(() => {
this.setAttribute('fb-sdk', 'available');
this.dispatchEvent(new CustomEvent('facebook:available', { bubbles: true, composed: true }));
});
}
get width() {
return this.getAttribute('width')
}
set width(v) {
this.setAttribute('width', v);
this.shadowRoot.querySelector('svg').setAttribute('width', v);
}
get height() {
return this.getAttribute('width')
}
set height(v) {
this.setAttribute('height', v);
this.shadowRoot.querySelector('svg').setAttribute('height', v);
}
get fb_sdk() {
return !!this.#fb_sdk;
}
set fb_sdk(v) {
this.setAttribute('fb-sdk', v);
this.#fb_sdk = v === 'available';
}
});

View File

@ -1,18 +1,12 @@
import { BUTTON_STYLE, S } from "../shared.js";
customElements.define('image-input', class extends HTMLElement {
#form;
import { BUTTON_STYLE, Component, S } from "../shared.js";
customElements.define('image-input', class extends Component {
static get observedAttributes() {
return ['width', 'height', "account-id", "url"]
}
constructor() {
super();
const shadow = this.#form = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
super(`
<style>
:host {
display: block;
@ -31,7 +25,7 @@ customElements.define('image-input', class extends HTMLElement {
border-bottom: none;
border-color: var(--border-light-gray-color);
}
${BUTTON_STYLE}
${ BUTTON_STYLE }
</style>
<article>
<section id="hidden">
@ -45,9 +39,9 @@ customElements.define('image-input', class extends HTMLElement {
<input id="save" type="button" value="Zapisz" />
</div>
</article>
`;
`);
shadow.querySelector('#save').addEventListener('click', ev => {
this.shadowRoot.querySelector('#save').addEventListener('click', ev => {
ev.preventDefault();
ev.stopPropagation();
const c = document.createElement('canvas');
@ -73,10 +67,10 @@ customElements.define('image-input', class extends HTMLElement {
});
const f = new FileReader();
const input = shadow.querySelector('#file');
const view = shadow.querySelector('#view');
const img = shadow.querySelector('img');
const canvas = shadow.querySelector('canvas');
const input = this.shadowRoot.querySelector('#file');
const view = this.shadowRoot.querySelector('#view');
const img = this.shadowRoot.querySelector('img');
const canvas = this.shadowRoot.querySelector('canvas');
const ctx = canvas.getContext('2d');
img.addEventListener('load', () => {
@ -119,13 +113,7 @@ customElements.define('image-input', class extends HTMLElement {
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'account-id':
return this.account_id = newV;
case 'url':
return this.url = newV;
}
super.attributeChangedCallback(name, oldV, newV);
}
get account_id() {
@ -141,17 +129,25 @@ customElements.define('image-input', class extends HTMLElement {
return isNaN(v) ? 0 : v;
}
set width(v) {
this.setAttribute('width', v);
}
get height() {
const v = parseInt(this.getAttribute('height'));
return isNaN(v) ? 0 : v;
}
set height(v) {
this.setAttribute('height', v);
}
get url() {
return this.getAttribute('url');
}
set url(v) {
this.setAttribute('url', v);
this.#form.querySelector('img').src = v;
this.shadowRoot.querySelector('img').src = v;
}
});