Compare commits
137 Commits
master
...
new-design
Author | SHA1 | Date | |
---|---|---|---|
f14d27b93d | |||
de03009257 | |||
bd61bee720 | |||
9329c57970 | |||
d4135d425f | |||
1f18ba54d3 | |||
4799f92961 | |||
f27ed3e90e | |||
2489fed697 | |||
ad976119af | |||
34b31f6677 | |||
4ed2eaa1ad | |||
0c60ec4b1c | |||
24751a3a17 | |||
9158987469 | |||
5e25d81308 | |||
1fc74988cf | |||
6a3378a6b5 | |||
a5954e6966 | |||
b9f94d0fd4 | |||
a7a02a227e | |||
cacd56b31b | |||
0b145bec5d | |||
6d721886d0 | |||
96ddd2c51b | |||
0666c74068 | |||
e10c903b52 | |||
88fecb793c | |||
12c11c9a27 | |||
e32046ffec | |||
095bd30f62 | |||
f21040d507 | |||
5f2bc7b5fa | |||
aa10241d57 | |||
7fe4b285ce | |||
7fa7fcda2b | |||
58a0239a05 | |||
877dcae1a9 | |||
38026a6f42 | |||
2e688b7c49 | |||
7a3471ec0a | |||
4edccc7661 | |||
5e806454f9 | |||
e0525b3d82 | |||
ecf3c3a344 | |||
2852f89895 | |||
6792a71820 | |||
a50cff494b | |||
f20dbfc14b | |||
7069e58f1d | |||
9a05b64eaf | |||
4d0e589a44 | |||
dcb2276ed3 | |||
97c8383313 | |||
1c8a536f92 | |||
13c64f58fd | |||
77cbb84885 | |||
2901da51f5 | |||
29e6e75e95 | |||
c9fd7128f3 | |||
749ce35ab9 | |||
da10be1553 | |||
f265d22b87 | |||
5560f068b1 | |||
e34a306668 | |||
932665a767 | |||
8526a45e13 | |||
7ec783651f | |||
f498846c53 | |||
6d42bb477a | |||
bb1f1e4a3e | |||
dfb881f2ba | |||
6692df9aeb | |||
26dea34054 | |||
335a84838f | |||
c111d2f668 | |||
a90c47bed3 | |||
530980e2b7 | |||
1c6dabcd77 | |||
36325a2de2 | |||
5e7ef00a99 | |||
6564757800 | |||
16ffc5a46d | |||
0da5817d9c | |||
beb3902cc5 | |||
ad918232c2 | |||
9384aa540f | |||
2c4f5c3b02 | |||
54ea6030e8 | |||
e9c086a466 | |||
71df209a4f | |||
da4e57d7ce | |||
e48e8ffc9b | |||
96c49299f6 | |||
f07e5e3bd5 | |||
11d7432b07 | |||
354856e222 | |||
21ceab8861 | |||
cd044e810c | |||
05c1a9abb0 | |||
d48b39c5c3 | |||
29f8a421c4 | |||
87f4ee8c45 | |||
c1b98e536e | |||
d6cd82bf8e | |||
62dc015b99 | |||
0d6627d86a | |||
c31776e46d | |||
76090d4266 | |||
daf472fd3e | |||
1317863cec | |||
08bdbbfded | |||
a330b6ddd2 | |||
3daaaca521 | |||
f634ac4891 | |||
86d4b2c6de | |||
7b3014b44d | |||
9ee4a35cab | |||
243b971539 | |||
bd73c2e9ea | |||
3fcb854ff9 | |||
0e541c2f52 | |||
8fe911c9dd | |||
b984caef76 | |||
0989a48065 | |||
f314c4d63d | |||
b3a56558f2 | |||
99a7f1c362 | |||
2a9d2f812d | |||
a00602192a | |||
e8d63c3b84 | |||
8d9855d249 | |||
42bf3c58e3 | |||
07e0d95f12 | |||
9e30d854e8 | |||
5a155c0cd7 | |||
7a9f26c18f |
9
.gitignore
vendored
9
.gitignore
vendored
@ -2,13 +2,14 @@
|
||||
node_modules
|
||||
uploads
|
||||
client/dist
|
||||
client/dist/app.js.map
|
||||
client/dist/app.js
|
||||
client/dist/admin.js.map
|
||||
client/dist/admin.js
|
||||
web/tmp
|
||||
web/build
|
||||
**/*.wasm
|
||||
tmp
|
||||
web/node_modules
|
||||
web/dist
|
||||
node_modules
|
||||
/assets/build.js
|
||||
/assets/build.js.map
|
||||
/assets/style.css
|
||||
config
|
||||
|
4898
Cargo.lock
generated
4898
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@ -1,6 +1,10 @@
|
||||
[workspace]
|
||||
members = [
|
||||
'crates/server',
|
||||
'crates/web',
|
||||
'crates/contract',
|
||||
'./crates/oswilno-server',
|
||||
'./crates/oswilno-contract',
|
||||
'./crates/web-assets',
|
||||
'./crates/oswilno-parking-space',
|
||||
'./crates/migration',
|
||||
'./crates/oswilno-actix-admin',
|
||||
]
|
||||
resolver = "2"
|
||||
|
7
assets/build.js
Normal file
7
assets/build.js
Normal file
@ -0,0 +1,7 @@
|
||||
(()=>{var m=Object.create;var l=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var h=Object.getPrototypeOf,u=Object.prototype.hasOwnProperty;var E=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,s)=>(typeof require<"u"?require:t)[s]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var g=(e,t,s,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of f(t))!u.call(e,n)&&n!==s&&l(e,n,{get:()=>t[n],enumerable:!(o=p(t,n))||o.enumerable});return e};var S=(e,t,s)=>(s=e!=null?m(h(e)):{},g(t||!e||!e.__esModule?l(s,"default",{value:e,enumerable:!0}):s,e));customElements.define("oswilno-price",class extends HTMLElement{});var a=e=>{let t="";for(let o=0;o<document.styleSheets.length;o++){let n=document.styleSheets[o];for(let r=0;r<n.rules.length;r++)t+=n.rules[r].cssText}let s=new CSSStyleSheet;s.replaceSync(t),e.adoptedStyleSheets=[s]};customElements.define("oswilno-error",class extends HTMLElement{constructor(){super();let e=this.attachShadow({mode:"open"});e.innerHTML=`
|
||||
<style>:host{display:block;}</style>
|
||||
<div class="flex bg-red-100 rounded-lg p-4 mb-4 text-sm text-red-700">
|
||||
<slot></slot>
|
||||
<div>
|
||||
`,a(e)}});customElements.define("oswilno-parking-space-rents",class extends HTMLElement{});customElements.define("oswilno-parking-space-rent",class extends HTMLElement{});customElements.define("oswilno-parking-space",class extends HTMLElement{});customElements.define("oswilno-parking-space-location",class extends HTMLElement{});import("https://unpkg.com/htmx.org@1.9.4/dist/htmx.min.js");var c="Authorization",w="ACX-Authorization",i="ACX-Refresh",d=document.body;d.addEventListener("htmx:beforeOnLoad",function(e){let t=e.detail,s=t.xhr,o=s.status,n=t.successful;if(o===200){let r=s.getResponseHeader(c);r&&(console.log(s),localStorage.setItem("jwt",r.replace(/^Bearer /i,""))),s.getResponseHeader(i)&&localStorage.setItem("refresh",r.replace(/^Bearer /i,""))}else o===401&&localStorage.removeItem("jwt");(o===422||o===400)&&(t.shouldSwap=!0,t.isError=!1)});d.addEventListener("htmx:configRequest",function(e){localStorage.getItem("jwt")&&(e.detail.headers[w]="Bearer "+(localStorage.getItem("jwt")||""),e.detail.headers[c]="Bearer "+(localStorage.getItem("jwt")||""),e.detail.headers[i]=localStorage.getItem("refresh")||"")});})();
|
||||
//# sourceMappingURL=build.js.map
|
7
assets/build.js.map
Normal file
7
assets/build.js.map
Normal file
File diff suppressed because one or more lines are too long
1670
assets/style.css
Normal file
1670
assets/style.css
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,69 +0,0 @@
|
||||
{
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "ecmascript",
|
||||
"jsx": false
|
||||
},
|
||||
"target": "es2022",
|
||||
"loose": true,
|
||||
"minify": {
|
||||
"compress": {
|
||||
"arguments": false,
|
||||
"arrows": true,
|
||||
"booleans": true,
|
||||
"booleans_as_integers": false,
|
||||
"collapse_vars": true,
|
||||
"comparisons": true,
|
||||
"computed_props": false,
|
||||
"conditionals": false,
|
||||
"dead_code": false,
|
||||
"directives": false,
|
||||
"drop_console": false,
|
||||
"drop_debugger": true,
|
||||
"evaluate": true,
|
||||
"expression": false,
|
||||
"hoist_funs": false,
|
||||
"hoist_props": true,
|
||||
"hoist_vars": false,
|
||||
"if_return": true,
|
||||
"join_vars": true,
|
||||
"keep_classnames": false,
|
||||
"keep_fargs": true,
|
||||
"keep_fnames": false,
|
||||
"keep_infinity": false,
|
||||
"loops": true,
|
||||
"negate_iife": true,
|
||||
"properties": true,
|
||||
"reduce_funcs": false,
|
||||
"reduce_vars": false,
|
||||
"side_effects": true,
|
||||
"switches": false,
|
||||
"typeofs": true,
|
||||
"unsafe": false,
|
||||
"unsafe_arrows": false,
|
||||
"unsafe_comps": false,
|
||||
"unsafe_Function": false,
|
||||
"unsafe_math": false,
|
||||
"unsafe_symbols": false,
|
||||
"unsafe_methods": false,
|
||||
"unsafe_proto": false,
|
||||
"unsafe_regexp": false,
|
||||
"unsafe_undefined": false,
|
||||
"unused": true
|
||||
},
|
||||
"mangle": {
|
||||
"toplevel": false,
|
||||
"keep_classnames": false,
|
||||
"keep_fnames": false,
|
||||
"keep_private_props": false,
|
||||
"ie8": false,
|
||||
"safari10": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "es6"
|
||||
},
|
||||
"minify": true,
|
||||
"isModule": true
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@swc/cli": "^0.1.57",
|
||||
"@swc/core": "^1.2.218",
|
||||
"@swc/core-linux-musl": "^1.2.42",
|
||||
"@swc/helpers": "^0.4.3",
|
||||
"@swc/jest": "^0.2.22",
|
||||
"@swc/wasm-web": "^1.2.212",
|
||||
"babel-minify": "^0.5.2",
|
||||
"browserslist": "^4.21.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"terser": "npm:@swc/core"
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
const { config } = require("@swc/core/spack");
|
||||
|
||||
module.exports = config({
|
||||
entry: {
|
||||
app: `${__dirname}/src/app.js`,
|
||||
admin: `${__dirname}/src/admin.js`,
|
||||
},
|
||||
output: {
|
||||
path: `${__dirname}/dist`,
|
||||
},
|
||||
mode: 'production',
|
||||
target: "browser",
|
||||
options: {
|
||||
swcrc: true,
|
||||
swcrcRoots: __dirname,
|
||||
configFile: `${__dirname}/.swcrc`,
|
||||
plugin: (program) => {
|
||||
console.error(program);
|
||||
return program;
|
||||
}
|
||||
},
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
import "./admin/ow-admin";
|
||||
|
||||
import "./admin/news/article-form";
|
||||
import "./admin/news/edit-news-article";
|
||||
|
||||
import "./admin/businesses/admin-business";
|
||||
import "./admin/businesses/admin-businesses";
|
||||
import "./admin/businesses/admin-edit-business";
|
||||
|
||||
import "./admin/offers/admin-edit-offer";
|
||||
import "./admin/offers/ow-admin-offers";
|
@ -1,47 +0,0 @@
|
||||
import { Component } from "../../shared";
|
||||
|
||||
customElements.define('admin-business', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['business-id', "name"];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
::slotted(local-business-item) {
|
||||
width: 100%;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
</style>
|
||||
<article>
|
||||
<h1 id="name"></h1>
|
||||
</article>
|
||||
<article>
|
||||
<slot name="description"></slot>
|
||||
</article>
|
||||
<article>
|
||||
<slot name="item"></slot>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
|
||||
get business_id() {
|
||||
return this.getAttribute('business-id');
|
||||
}
|
||||
|
||||
set business_id(v) {
|
||||
this.setAttribute('business-id', v);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.getAttribute('name');
|
||||
}
|
||||
|
||||
set name(v) {
|
||||
this.setAttribute('name', v);
|
||||
this.shadowRoot.querySelector('#name').textContent = v;
|
||||
}
|
||||
});
|
@ -1,66 +0,0 @@
|
||||
import { Component, FORM_STYLE } from "../../shared";
|
||||
|
||||
customElements.define('admin-businesses', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['state-filter'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
::slotted([state]) {
|
||||
display: none;
|
||||
}
|
||||
:host([state-filter="Pending"]) ::slotted([state="Pending"]) {
|
||||
display: block;
|
||||
}
|
||||
:host([state-filter="Approved"]) ::slotted([state="Approved"]) {
|
||||
display: block;
|
||||
}
|
||||
:host([state-filter="Banned"]) ::slotted([state="Banned"]) {
|
||||
display: block;
|
||||
}
|
||||
:host([state-filter="Pinned"]) ::slotted([state="Pinned"]) {
|
||||
display: block;
|
||||
}
|
||||
:host([state-filter="Internal"]) ::slotted([state="Internal"]) {
|
||||
display: block;
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section>
|
||||
<select id="state">
|
||||
<option value="Pending">Pending</option>
|
||||
<option value="Approved">Approved</option>
|
||||
<option value="Banned">Banned</option>
|
||||
<option value="Pinned">Pinned</option>
|
||||
<option value="Internal">Internal</option>
|
||||
</select>
|
||||
</section>
|
||||
<section>
|
||||
<slot></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
this.shadowRoot.querySelector('#state').addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
this.state_filter = ev.target.value;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.state_filter = 'Pending';
|
||||
}
|
||||
|
||||
get state_filter() {
|
||||
return this.getAttribute('state-filter');
|
||||
}
|
||||
|
||||
set state_filter(v) {
|
||||
this.setAttribute('state-filter', v);
|
||||
this.shadowRoot.querySelector('#state').value = v;
|
||||
}
|
||||
});
|
@ -1,90 +0,0 @@
|
||||
import { Component, BUTTON_STYLE, INPUT_STYLE } from "../../shared";
|
||||
|
||||
customElements.define('admin-edit-business', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['business-id', 'state'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
border-bottom: 2px solid var(--border-slim-color);
|
||||
padding: 8px 0;
|
||||
}
|
||||
:host(:first-child) {
|
||||
border-top: 2px solid var(--border-slim-color);
|
||||
}
|
||||
article {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#state {
|
||||
min-width: 200px;
|
||||
}
|
||||
#view {
|
||||
width: calc(100% - 220px);
|
||||
}
|
||||
#actions {
|
||||
width: 200px
|
||||
}
|
||||
#actions > input:not(:last-child) {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
#actions > input {
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
#actions select {
|
||||
width: 100%;
|
||||
}
|
||||
::slotted(admin-business) {
|
||||
width: 100%;
|
||||
}
|
||||
${ BUTTON_STYLE }${ INPUT_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section id="view">
|
||||
<slot></slot>
|
||||
</section>
|
||||
<section id="actions">
|
||||
<input value="Usuń" type="button" />
|
||||
<form id="change-state" action="/admin/businesses/set-state" method="post">
|
||||
<input name="id" id="id" type="hidden" />
|
||||
<select id="state" name="state">
|
||||
<option value="Pending">Pending</option>
|
||||
<option value="Approved">Approved</option>
|
||||
<option value="Banned">Banned</option>
|
||||
<option value="Pinned">Pinned</option>
|
||||
<option value="Internal">Internal</option>
|
||||
</select>
|
||||
</form>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
|
||||
const form = this.shadowRoot.querySelector('#change-state');
|
||||
this.shadowRoot.querySelector('#state').addEventListener('change', ev => {
|
||||
form.submit();
|
||||
});
|
||||
}
|
||||
|
||||
get business_id() {
|
||||
return this.getAttribute('business-id');
|
||||
}
|
||||
|
||||
set business_id(v) {
|
||||
this.setAttribute('business-id', v);
|
||||
this.shadowRoot.querySelector('#id').value = v;
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.getAttribute('state');
|
||||
}
|
||||
|
||||
set state(v) {
|
||||
this.setAttribute('state', v);
|
||||
this.shadowRoot.querySelector('#state').value = v;
|
||||
}
|
||||
});
|
@ -1,110 +0,0 @@
|
||||
import { Component, FORM_STYLE } from "../../shared";
|
||||
|
||||
customElements.define('article-form', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['article-id', 'article-title', 'status'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
rich-text-editor {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<form action="/admin/news/create" method="post">
|
||||
<div>
|
||||
<label>Tytuł</label>
|
||||
<input placeholder="Tytuł" name="title" id="title" />
|
||||
</div>
|
||||
<div id="body-view">
|
||||
<rich-text-editor id="body-rte" upload-url="/admin/news/upload">
|
||||
</rich-text-editor>
|
||||
<input type="hidden" name="body" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Status</label>
|
||||
<select id="status" name="status">
|
||||
<option value="pending">Oczekujący</option>
|
||||
<option value="published">Opublikowany</option>
|
||||
<option value="hidden">Ukryty</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type="submit" value="Zapisz" />
|
||||
</form>
|
||||
`);
|
||||
|
||||
const bodyInput = this.shadowRoot.querySelector('[name="body"]');
|
||||
this.shadowRoot.querySelector('rich-text-editor').addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
bodyInput.value = ev.target.value;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.body = this.innerHTML;
|
||||
}
|
||||
|
||||
get article_id() {
|
||||
const id = parseInt(this.getAttribute('article-id'));
|
||||
return isNaN(id) ? null : id;
|
||||
}
|
||||
|
||||
set article_id(v) {
|
||||
this.setAttribute('article-id', v);
|
||||
const form = this.shadowRoot.querySelector('form');
|
||||
if (this.article_id === null) {
|
||||
this.#removeIdInput();
|
||||
form.action = "/admin/news/create";
|
||||
} else {
|
||||
this.#removeIdInput();
|
||||
this.#createIdInput(v);
|
||||
form.action = "/admin/news/update";
|
||||
}
|
||||
}
|
||||
|
||||
#createIdInput(v) {
|
||||
const el = this.shadowRoot.querySelector('form').appendChild(document.createElement('input'));
|
||||
el.id = 'id';
|
||||
el.name = 'id';
|
||||
el.value = v;
|
||||
el.type = 'hidden';
|
||||
}
|
||||
|
||||
#removeIdInput() {
|
||||
const el = this.shadowRoot.querySelector('#id');
|
||||
el && el.remove();
|
||||
}
|
||||
|
||||
get article_title() {
|
||||
return this.getAttribute('article-title');
|
||||
}
|
||||
|
||||
set article_title(v) {
|
||||
this.setAttribute('article-title', v);
|
||||
this.shadowRoot.querySelector('#title').value = v;
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this.getAttribute('status');
|
||||
}
|
||||
|
||||
set status(v) {
|
||||
this.setAttribute('status', v);
|
||||
this.shadowRoot.querySelector('#status').value = (v || '').toLocaleLowerCase();
|
||||
}
|
||||
|
||||
get body() {
|
||||
return this.getAttribute('status');
|
||||
}
|
||||
|
||||
set body(v) {
|
||||
v = (v || '').trim();
|
||||
this.shadowRoot.querySelector('#body-rte').value = v;
|
||||
this.shadowRoot.querySelector('[name="body"]').value = v;
|
||||
}
|
||||
});
|
@ -1,50 +0,0 @@
|
||||
import { Component, BUTTON_STYLE } from "../../shared";
|
||||
|
||||
customElements.define('edit-news-article', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['article-id'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
article { display: flex; }
|
||||
#edit {
|
||||
margin-right: 8px;
|
||||
}
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<slot></slot>
|
||||
</article>
|
||||
<article>
|
||||
<ow-path id="edit" path="/">
|
||||
Edytuj
|
||||
</ow-path>
|
||||
<form method="post" action="/admin/news/delete">
|
||||
<input name="id" id="id" type="hidden" />
|
||||
<input class="link" type="submit" id="delete" value="Usuń" />
|
||||
</form>
|
||||
</article>
|
||||
`);
|
||||
|
||||
this.shadowRoot.querySelector('#edit').addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
get article_id() {
|
||||
const v = parseInt(this.getAttribute('article-id'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set article_id(v) {
|
||||
this.setAttribute('article-id', v);
|
||||
const id = this.article_id;
|
||||
if (id === null) return;
|
||||
this.shadowRoot.querySelector('ow-path').path = `/admin/news/${id}`;
|
||||
this.shadowRoot.querySelector('#id').value = id;
|
||||
}
|
||||
});
|
@ -1,82 +0,0 @@
|
||||
import { Component, BUTTON_STYLE } from "../../shared";
|
||||
|
||||
customElements.define('admin-edit-offer', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['offer-id', 'state'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
border-bottom: 2px solid var(--border-slim-color);
|
||||
padding: 8px 0;
|
||||
}
|
||||
#view { display: block; }
|
||||
#actions form {
|
||||
width: 120px;
|
||||
}
|
||||
#actions form input {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
#state {
|
||||
font-weight: bold;
|
||||
margin: 8px;
|
||||
}
|
||||
:host([state="approved"]) #state { color: green; }
|
||||
:host([state="banned"]) #state { color: darkred; }
|
||||
:host([state="pending"]) #state { color: orange; }
|
||||
|
||||
:host([state="approved"]) #approve { display: none; }
|
||||
:host([state="banned"]) #ban { display: none; }
|
||||
|
||||
@media only screen and (min-device-width: 1200px) {
|
||||
#view { display: flex; }
|
||||
#actions {
|
||||
margin-left: 16px;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
${BUTTON_STYLE}
|
||||
</style>
|
||||
<section id="view">
|
||||
<slot></slot>
|
||||
<div id="actions">
|
||||
<div id="state"></div>
|
||||
<form action="/admin/offers/approve" method="post">
|
||||
<input type="hidden" name="id" class="offer_id" />
|
||||
<input type="submit" id="approve" value="Zaakceptuj" />
|
||||
</form>
|
||||
<form action="/admin/offers/ban" method="post">
|
||||
<input type="hidden" name="id" class="offer_id" />
|
||||
<input type="submit" id="ban" value="Zbanuj" />
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
`);
|
||||
}
|
||||
|
||||
get offer_id() {
|
||||
const v = parseInt(this.getAttribute('offer-id'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set offer_id(v) {
|
||||
v = parseInt(v);
|
||||
this.setAttribute('offer-id', v);
|
||||
for (const el of this.shadowRoot.querySelectorAll('.offer_id')) {
|
||||
el.value = v;
|
||||
}
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.getAttribute('state');
|
||||
}
|
||||
|
||||
set state(v) {
|
||||
this.setAttribute('state', v);
|
||||
this.shadowRoot.querySelector('#state').textContent = v;
|
||||
}
|
||||
});
|
@ -1,64 +0,0 @@
|
||||
import { Component, FORM_STYLE } from "../../shared.js";
|
||||
|
||||
customElements.define('ow-admin-offers', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['state-filter'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
::slotted([state]) {
|
||||
display: none;
|
||||
}
|
||||
:host([state-filter='Pending']) ::slotted([state="Pending"]) {
|
||||
display: block;
|
||||
}
|
||||
:host([state-filter='Approved']) ::slotted([state="Approved"]) {
|
||||
display: block;
|
||||
}
|
||||
:host([state-filter='Banned']) ::slotted([state="Banned"]) {
|
||||
display: block;
|
||||
}
|
||||
:host([state-filter='Finished']) ::slotted([state="Finished"]) {
|
||||
display: block;
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section>
|
||||
<select id="state">
|
||||
<option value="Pending">Pending</option>
|
||||
<option value="Approved">Approved</option>
|
||||
<option value="Banned">Banned</option>
|
||||
<option value="Finished">Finished</option>
|
||||
</select>
|
||||
</section>
|
||||
<section>
|
||||
<slot></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
this.shadowRoot.querySelector('#state').addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
this.state_filter = ev.target.value;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.state_filter = 'Pending';
|
||||
}
|
||||
|
||||
get state_filter() {
|
||||
return this.getAttribute('state-filter');
|
||||
}
|
||||
|
||||
set state_filter(v) {
|
||||
this.setAttribute('state-filter', v);
|
||||
this.shadowRoot.querySelector('#state').value = v;
|
||||
}
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('ow-admin', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
</style>
|
||||
<slot></slot>
|
||||
`);
|
||||
}
|
||||
});
|
@ -1,48 +0,0 @@
|
||||
const JSON_HEADER = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
export const accountContacts = () =>
|
||||
fetch('/contacts/list.json', {
|
||||
headers: {
|
||||
...JSON_HEADER,
|
||||
},
|
||||
method: 'GET',
|
||||
}).then(res => res.json())
|
||||
|
||||
export const createContact = (body) =>
|
||||
fetch('/contacts/create', {
|
||||
headers: {
|
||||
...JSON_HEADER,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
}).then(res => res.json())
|
||||
|
||||
export const updateContact = (body) =>
|
||||
fetch('/contacts/update', {
|
||||
headers: {
|
||||
...JSON_HEADER,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
}).then(res => res.json())
|
||||
|
||||
export const deleteContact = async (body) =>
|
||||
fetch('/contacts/delete', {
|
||||
headers: {
|
||||
...JSON_HEADER,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
}).then(res => res.json())
|
||||
|
||||
export const register = (body) =>
|
||||
fetch('/register', {
|
||||
headers: {
|
||||
...JSON_HEADER,
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
}).then(res => res.json())
|
@ -1,76 +0,0 @@
|
||||
import "./poly.js";
|
||||
|
||||
import "./shared/error-message.js";
|
||||
|
||||
import "./shared/rich-text-editor.js";
|
||||
import "./shared/form-navigation.js";
|
||||
import "./shared/image-popup.js";
|
||||
import "./shared/facebook-button.js";
|
||||
import "./shared/search-input.js";
|
||||
import "./shared/nav/ow-nav.js";
|
||||
import "./shared/nav/ow-path.js";
|
||||
import "./shared/price/price-input.js";
|
||||
import "./shared/price/price-view.js";
|
||||
|
||||
import "./ow-account/ow-account.js";
|
||||
import "./ow-account/account-view.js";
|
||||
|
||||
import "./local-businesses/local-businesses.js";
|
||||
import "./local-businesses/local-business-item.js";
|
||||
import "./local-businesses/local-business.js";
|
||||
import "./local-businesses/single-local-business.js";
|
||||
|
||||
import "./login-form.js";
|
||||
|
||||
import "./register-form.js";
|
||||
import "./register-form/register-business-account-form";
|
||||
import "./register-form/register-business-item-form.js";
|
||||
import "./register-form/register-business-items-form.js";
|
||||
import "./register-form/register-business-details-form.js";
|
||||
import "./register-form/register-account-type.js";
|
||||
import "./register-form/register-user-account-form.js";
|
||||
import "./register-form/register-business-contacts-form.js";
|
||||
import "./register-form/register-business-submit-form.js";
|
||||
|
||||
import "./business-editor.js";
|
||||
import "./business-items/business-item.js";
|
||||
import "./business-items/business-item-editor.js";
|
||||
|
||||
import "./news/ow-articles.js";
|
||||
import "./news/news-article.js";
|
||||
|
||||
import "./contacts/contact-type-icon.js";
|
||||
import "./contacts/contact-info.js";
|
||||
import "./contacts/contact-info-editor.js";
|
||||
import "./contacts/edit-contact-info.js";
|
||||
import "./contacts/contact-info-list.js";
|
||||
|
||||
import "./marketplace/marketplace-offer.js";
|
||||
import "./marketplace/marketplace-offers.js";
|
||||
import "./marketplace/offer-form.js";
|
||||
import "./marketplace/user-edit-offer.js";
|
||||
import "./marketplace/marketplace-editor.js";
|
||||
|
||||
import "./terms_and_conditions/terms-and-conditions.js";
|
||||
import "./terms_and_conditions/privacy-policy.js";
|
||||
import "./terms_and_conditions/privacy-policy-bar.js"
|
||||
|
||||
import { fireFbReady } from "./shared.js";
|
||||
|
||||
if (!document.querySelector('#facebook-jssdk')) {
|
||||
window.fbAsyncInit = () => {
|
||||
FB.init({
|
||||
appId: '1293538251053124',
|
||||
cookie: true,
|
||||
xfbml: true,
|
||||
version: 'v14.0'
|
||||
});
|
||||
FB.AppEvents.logPageView();
|
||||
fireFbReady();
|
||||
};
|
||||
|
||||
const js = document.createElement('script');
|
||||
js.id = 'facebook-jssdk';
|
||||
js.src = "https://connect.facebook.net/en_US/sdk.js";
|
||||
document.head.appendChild(js);
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
import { Component } from "./shared.js";
|
||||
import * as api from "./api";
|
||||
|
||||
customElements.define('business-editor', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
</style>
|
||||
<article>
|
||||
<section>
|
||||
<slot name="items"></slot>
|
||||
</section>
|
||||
<section>
|
||||
<slot name="contacts"></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
|
||||
this.addEventListener('contact:create', async ({ detail }) => {
|
||||
await this.#createContact(detail);
|
||||
});
|
||||
this.addEventListener('contact:update', async ({ detail }) => {
|
||||
await this.#updateContact(detail);
|
||||
});
|
||||
this.addEventListener('contact:delete', async ({ detail }) => {
|
||||
await this.#deleteContact(detail)
|
||||
});
|
||||
}
|
||||
|
||||
async #createContact({ content, type }) {
|
||||
await api.createContact({ content, type });
|
||||
const { contacts } = await api.accountContacts();
|
||||
this.#contacts = this.#formatContacts(contacts);
|
||||
}
|
||||
|
||||
async #updateContact({ id, content, type }) {
|
||||
console.info(1);
|
||||
await api.updateContact({ id, content, type });
|
||||
console.info(2);
|
||||
const { contacts } = await api.accountContacts();
|
||||
console.info(3, contacts);
|
||||
this.#contacts = this.#formatContacts(contacts);
|
||||
}
|
||||
|
||||
async #deleteContact({ id }) {
|
||||
await api.deleteContact({ id });
|
||||
const { contacts } = await api.accountContacts();
|
||||
this.#contacts = this.#formatContacts(contacts);
|
||||
}
|
||||
|
||||
#formatContacts(contacts) {
|
||||
return contacts.map(({ id, content, contact_type }) => `
|
||||
<edit-contact-info
|
||||
contact-id="${ id }"
|
||||
type="${ contact_type }"
|
||||
content="${ content }"
|
||||
mode="view"
|
||||
>
|
||||
<contact-info
|
||||
contact-id="${ id }"
|
||||
type="${ contact_type }"
|
||||
content="${ content }"
|
||||
></contact-info>
|
||||
</edit-contact-info>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
set #contacts(html) {
|
||||
this.querySelector('contact-info-editor')
|
||||
.innerHTML = html;
|
||||
}
|
||||
});
|
@ -1,179 +0,0 @@
|
||||
import { Component, FORM_STYLE } from "../shared";
|
||||
|
||||
import "../register-form/register-business-item-form";
|
||||
|
||||
customElements.define('business-item-editor', class extends Component {
|
||||
#idx;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['business-id', 'name', 'description']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
::slotted(business-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
register-item-form-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#shadow {
|
||||
width: 33px;
|
||||
}
|
||||
#description {
|
||||
min-height: 200px;
|
||||
}
|
||||
article {
|
||||
margin: 8px;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
article {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<article id="business-details">
|
||||
<h2>Edycja konta przedsiębiorcy</h2>
|
||||
<form method="post" action="/business-item/atomic-update">
|
||||
<input type="hidden" name="id" id="id">
|
||||
<div>
|
||||
<label>Nazwa</label>
|
||||
<input name="name" id="name" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Opis</label>
|
||||
<textarea name="description" id="description"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Zapisz" />
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
<article id="list">
|
||||
<slot></slot>
|
||||
</article>
|
||||
|
||||
<article id="form">
|
||||
<register-business-item-form remove="hidden" save="show" idx="0">
|
||||
<div id="shadow" slot="head"> </div>
|
||||
</register-business-item-form>
|
||||
</article>
|
||||
|
||||
<article id="new-business-item-form">
|
||||
<form id="create-new-item-form" method="post" action="/business-item/new">
|
||||
<input type="hidden" name="name" id="name" />
|
||||
<input type="hidden" name="price" id="price" />
|
||||
<input type="hidden" name="picture_url" id="picture_url" />
|
||||
<input type="hidden" name="item_order" id="item_order" />
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<form id="moveForm" action="/business-item/move" method="post">
|
||||
<input name="id" type="hidden" />
|
||||
<input name="item_order" type="hidden" />
|
||||
</form>
|
||||
`);
|
||||
const form = this.shadowRoot.querySelector('#create-new-item-form');
|
||||
this.addEventListener('item:submit', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
// console.info(this);
|
||||
// if (this.shadowRoot.querySelector('register-item-form-row').reportValidity()) {
|
||||
if (form.reportValidity()) {
|
||||
const {
|
||||
name,
|
||||
price,
|
||||
picture_url,
|
||||
item_order,
|
||||
} = ev.detail;
|
||||
|
||||
form.querySelector('#name').value = name;
|
||||
form.querySelector('#price').value = price;
|
||||
form.querySelector('#picture_url').value = picture_url;
|
||||
form.querySelector('#item_order').value = item_order;
|
||||
form.submit();
|
||||
}
|
||||
// }
|
||||
});
|
||||
const moveForm = this.shadowRoot.querySelector('#moveForm');
|
||||
this.addEventListener('item:up', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const item_id = ev.detail.id;
|
||||
const current = this.querySelector(`business-item[item-id="${ item_id }"]`);
|
||||
if (!current) return console.warn(`business-item[item-id="${ item_id }"] not found`);
|
||||
let prev = current.previousElementSibling;
|
||||
if (!prev) return console.warn(`prev of business-item[item-id="${ item_id }"] not found`);
|
||||
moveForm.querySelector('[name=id]').value = item_id;
|
||||
moveForm.querySelector('[name=item_order]').value = prev.item_order;
|
||||
moveForm.submit();
|
||||
});
|
||||
this.addEventListener('item:down', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const item_id = ev.detail.id;
|
||||
const current = this.querySelector(`business-item[item-id="${ item_id }"]`);
|
||||
if (!current) return;
|
||||
let next = current.nextElementSibling;
|
||||
if (!next) return;
|
||||
moveForm.querySelector('[name=id]').value = item_id;
|
||||
moveForm.querySelector('[name=item_order]').value = next.item_order;
|
||||
moveForm.submit();
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.#updateForm();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
super.attributeChangedCallback(name, oldV, newV);
|
||||
this.#updateForm();
|
||||
}
|
||||
|
||||
#updateForm() {
|
||||
const items = [...this.querySelectorAll('business-item')];
|
||||
items.sort((a, b) => a.item_order - b.item_order);
|
||||
for (const item of items) {
|
||||
this.#idx = item.item_order;
|
||||
}
|
||||
|
||||
const el = this.shadowRoot.querySelector('register-item-form-row');
|
||||
if (!el) return;
|
||||
el.idx = this.#idx + 1;
|
||||
}
|
||||
|
||||
get business_id() {
|
||||
return this.getAttribute('business-id');
|
||||
}
|
||||
|
||||
set business_id(v) {
|
||||
this.setAttribute('business-id', v);
|
||||
this.shadowRoot.querySelector('#id').value = v;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.getAttribute('name');
|
||||
}
|
||||
|
||||
set name(v) {
|
||||
this.setAttribute('name', v);
|
||||
this.shadowRoot.querySelector('#name').value = v;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.getAttribute('description');
|
||||
}
|
||||
|
||||
set description(v) {
|
||||
this.setAttribute('description', v);
|
||||
this.shadowRoot.querySelector('#description').textContent = (v || '').trim();
|
||||
}
|
||||
});
|
@ -1,212 +0,0 @@
|
||||
import "../shared/image-input";
|
||||
import { Component, FORM_STYLE } from "../shared";
|
||||
|
||||
customElements.define('business-item', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['item-id', 'name', 'price', 'picture-url', 'item-order']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
:host(:first-child) #move-up {
|
||||
display: none;
|
||||
}
|
||||
:host(:last-child) #move-down {
|
||||
display: none;
|
||||
}
|
||||
#move svg {
|
||||
cursor: pointer;
|
||||
}
|
||||
form > #fields > div, form > #fields > div > label, form > #fields > div > .input {
|
||||
width: 90% !important;
|
||||
}
|
||||
@media(min-width: 1280px) {
|
||||
section > form {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#actions {
|
||||
width: 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
form > #fields {
|
||||
width: 50%;
|
||||
}
|
||||
#move {
|
||||
width: 33px;
|
||||
}
|
||||
form > #fields > div, form > #fields > div > label, form > #fields > div > .input {
|
||||
width: 90% !important;
|
||||
}
|
||||
}
|
||||
#move-up, #move-down {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<section>
|
||||
<form id="updateForm" action="/business-item/update" method="post">
|
||||
<div id="move">
|
||||
<button id="move-up">
|
||||
<svg width="32" height="32" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" class="icon">
|
||||
<path d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 0 0 140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"/>
|
||||
</svg>
|
||||
<button id="move-down">
|
||||
<svg width="32" height="32" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" class="icon">
|
||||
<path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<image-input send-original="true"></image-input>
|
||||
|
||||
<div id="fields">
|
||||
<div>
|
||||
<label>Nazwa</label>
|
||||
<input name="name" id="name" class="input" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Cena</label>
|
||||
<price-input class="input"></price-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="actions">
|
||||
<input type="submit" id="save" value="Zapisz" />
|
||||
<input type="button" id="delete" value="Usuń" />
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="id" id="id" />
|
||||
<input type="hidden" name="price" id="price" />
|
||||
<input type="hidden" name="item_order" id="item_order" />
|
||||
<input type="hidden" name="picture_url" id="picture_url" />
|
||||
</form>
|
||||
<form id="deleteForm" action="/business-item/delete" method="post">
|
||||
<input id="delete-id" name="id" type="hidden" />
|
||||
</form>
|
||||
</section>
|
||||
`);
|
||||
|
||||
const imageInput = this.shadowRoot.querySelector('image-input');
|
||||
|
||||
this.addEventListener('image-input:uploaded', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.picture_url = imageInput.url;
|
||||
|
||||
const updateForm = this.shadowRoot.querySelector('#updateForm');
|
||||
updateForm.querySelector('#id').value = this.item_id;
|
||||
updateForm.querySelector('#name').value = this.name;
|
||||
updateForm.querySelector('#price').value = this.price;
|
||||
updateForm.querySelector('#picture_url').value = this.picture_url;
|
||||
updateForm.querySelector('#item_order').value = this.item_order;
|
||||
// updateForm.submit();
|
||||
});
|
||||
this.shadowRoot.querySelector('#delete').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
const form = this.shadowRoot.querySelector('#deleteForm');
|
||||
form.querySelector('#delete-id').value = this.item_id;
|
||||
form.submit();
|
||||
});
|
||||
|
||||
const priceInput = this.shadowRoot.querySelector('price-input');
|
||||
const price = this.shadowRoot.querySelector('#price');
|
||||
priceInput.addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
price.value = ev.detail.price;
|
||||
});
|
||||
|
||||
{
|
||||
const button = this.shadowRoot.querySelector('#move-up');
|
||||
button.addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('item:up', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { id: this.item_id, item_order: this.item_order }
|
||||
}));
|
||||
});
|
||||
}
|
||||
{
|
||||
const button = this.shadowRoot.querySelector('#move-down');
|
||||
button.addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('item:down', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { id: this.item_id }
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
super.attributeChangedCallback(name, oldV, newV);
|
||||
|
||||
if (oldV === newV) return;
|
||||
switch (name) {
|
||||
case 'price':
|
||||
return this.price = newV;
|
||||
}
|
||||
}
|
||||
|
||||
static get attr2FieldBlacklist() {
|
||||
return ['price'];
|
||||
}
|
||||
|
||||
get item_id() {
|
||||
return this.getAttribute('item-id');
|
||||
}
|
||||
|
||||
set item_id(v) {
|
||||
this.setAttribute('item-id', v);
|
||||
this.shadowRoot.querySelector('#id').value = v;
|
||||
}
|
||||
|
||||
get item_order() {
|
||||
const v = parseInt(this.getAttribute('item-order'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set item_order(v) {
|
||||
this.setAttribute('item-order', v);
|
||||
this.shadowRoot.querySelector('#item_order').value = v;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.getAttribute('name');
|
||||
}
|
||||
|
||||
set name(v) {
|
||||
this.setAttribute('name', v);
|
||||
this.shadowRoot.querySelector('#name').value = v;
|
||||
}
|
||||
|
||||
get price() {
|
||||
return this.shadowRoot.querySelector('price-input').value;
|
||||
}
|
||||
|
||||
set price(v) {
|
||||
this.setAttribute('price', v);
|
||||
this.shadowRoot.querySelector('price-input').value = v / 100.0;
|
||||
this.shadowRoot.querySelector('#price').value = v;
|
||||
}
|
||||
|
||||
get picture_url() {
|
||||
return this.getAttribute('picture-url');
|
||||
}
|
||||
|
||||
set picture_url(v) {
|
||||
if (!v.startsWith("/")) v = "";
|
||||
this.setAttribute('picture-url', v);
|
||||
this.shadowRoot.querySelector('image-input').url = v;
|
||||
this.shadowRoot.querySelector('#picture_url').value = v;
|
||||
}
|
||||
});
|
@ -1,168 +0,0 @@
|
||||
import { Component, FORM_STYLE, onKeyDown } from "../shared.js";
|
||||
|
||||
customElements.define('contact-info-editor', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['type', "contact-id", "content", 'save'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
#contact-wrapper contact-type-icon {
|
||||
width: 24px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
#contact-wrapper #input {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
#icon {
|
||||
align-self: flex-end;
|
||||
position: relative;
|
||||
}
|
||||
article {
|
||||
margin: 8px;
|
||||
}
|
||||
:host([save="false"]) #submit {
|
||||
display: none;
|
||||
}
|
||||
input[type=submit] {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
article {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section>
|
||||
<form method="post" action="/contacts/create">
|
||||
<div id="contact-wrapper">
|
||||
<label>E-Mail</label>
|
||||
<div id="input">
|
||||
<div id="icon">
|
||||
<contact-type-icon type="email"></contact-type-icon>
|
||||
</div>
|
||||
|
||||
<input type="text" id="content" name="content" />
|
||||
<input type="hidden" id="type" name="type" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="submit">
|
||||
<input type="submit" value="Dodaj" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<section>
|
||||
<slot></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
|
||||
onKeyDown(this.shadowRoot.querySelector('#content'), (ev, input) => {
|
||||
this.#updateContactType(input.value, null);
|
||||
this.content = input.value;
|
||||
});
|
||||
|
||||
this.shadowRoot.querySelector('#type').addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#updateContactType(null, ev.target.value);
|
||||
});
|
||||
|
||||
this.shadowRoot.querySelector('form').addEventListener('submit', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
this.contact_id
|
||||
? 'contact:update'
|
||||
: 'contact:create',
|
||||
{
|
||||
composed: true,
|
||||
bubbles: true,
|
||||
detail: { type: this.type, content: this.content, id: this.contact_id }
|
||||
}
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.getAttribute('type');
|
||||
}
|
||||
|
||||
set type(v) {
|
||||
this.#updateContactType(null, v);
|
||||
}
|
||||
|
||||
get content() {
|
||||
return this.getAttribute('content');
|
||||
}
|
||||
|
||||
set content(v) {
|
||||
this.setAttribute('content', v);
|
||||
this.shadowRoot.querySelector('#content').value = v;
|
||||
}
|
||||
|
||||
get contact_id() {
|
||||
const v = parseInt(this.getAttribute('contact-id'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set contact_id(v) {
|
||||
this.setAttribute('contact-id', v);
|
||||
const n = parseInt(v);
|
||||
if (isNaN(n))
|
||||
this.#removeId();
|
||||
else
|
||||
this.#addId(n);
|
||||
}
|
||||
|
||||
#removeId() {
|
||||
const form = this.shadowRoot.querySelector('form');
|
||||
const input = form.querySelector('#contact-id');
|
||||
input && input.remove();
|
||||
form.action = '/contacts/create';
|
||||
form.querySelector('input[type=submit]').value = 'Dodaj';
|
||||
}
|
||||
|
||||
#addId(v) {
|
||||
this.#removeId();
|
||||
const form = this.shadowRoot.querySelector('form');
|
||||
const input = form.appendChild(document.createElement('input'));
|
||||
input.setAttribute('type', 'hidden');
|
||||
input.setAttribute('name', 'id');
|
||||
input.setAttribute('id', 'contact-id');
|
||||
input.value = v;
|
||||
form.action = '/contacts/update';
|
||||
form.querySelector('input[type=submit]').value = 'Zmień';
|
||||
}
|
||||
|
||||
#updateContactType(value, type) {
|
||||
type = type || this.#resolveContactType(value);
|
||||
this.setAttribute('type', type);
|
||||
const icon = this.shadowRoot.querySelector('contact-type-icon');
|
||||
icon.type = type;
|
||||
this.shadowRoot.querySelector('#type').value = type;
|
||||
}
|
||||
|
||||
#resolveContactType(s) {
|
||||
s = s || '';
|
||||
if (s.match(/http(s)?:\/\/(www\.)?facebook\.com/)) {
|
||||
return 'facebook';
|
||||
}
|
||||
if (s.match(/(\+\d{2,3} )?\d{3}([ \-])?\d{3}([ \-])?\d{3}/)) {
|
||||
return 'mobile';
|
||||
}
|
||||
if (s.match(/^[a-zA-Z\d.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z\d-]+(?:\.[a-zA-Z\d-]+)*$/)) {
|
||||
return 'email';
|
||||
}
|
||||
if (s.match(/https?:\/\//)) {
|
||||
return 'link';
|
||||
}
|
||||
return 'other';
|
||||
}
|
||||
});
|
@ -1,19 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('contact-info-list', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
article {
|
||||
display: flex;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
}
|
||||
</style>
|
||||
<article>
|
||||
<slot></slot>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
});
|
@ -1,106 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
import "./contact-type-icon.js";
|
||||
|
||||
customElements.define('contact-info', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['type', 'content', 'contact-id', 'mode'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
section a {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
contact-type-icon {
|
||||
width: 24px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
:host([mode="icon"]) #content {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<slot></slot>
|
||||
<section>
|
||||
<a target="_blank">
|
||||
<contact-type-icon type="email"></contact-type-icon>
|
||||
<div id="content"></div>
|
||||
</a>
|
||||
</section>
|
||||
`);
|
||||
this.shadowRoot.querySelector('a').addEventListener('click', ev => {
|
||||
if (this.type === 'mobile') {
|
||||
ev.preventDefault();
|
||||
const decoded = atob(this.content);
|
||||
if (this.#isMobile()) {
|
||||
const link = `tel:${ decoded }`;
|
||||
window.open(link);
|
||||
} else {
|
||||
const match = decoded.match(/(\+\d+ ?)?(\d{3})([ \-])?(\d{3})([ \-])?(\d+)/);
|
||||
this.shadowRoot.querySelector('section').innerHTML = `<div>${ match[2] } ${ match[4] } ${ match[6] }</div>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
set type(v) {
|
||||
if (!v) return;
|
||||
this.setAttribute('type', v);
|
||||
this.shadowRoot.querySelector('contact-type-icon').type = v;
|
||||
|
||||
this.#setHref();
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.getAttribute('type');
|
||||
}
|
||||
|
||||
set content(v) {
|
||||
this.setAttribute('content', v);
|
||||
this.shadowRoot.querySelector('#content').textContent = v;
|
||||
|
||||
this.#setHref();
|
||||
}
|
||||
|
||||
get content() {
|
||||
return this.getAttribute('content')
|
||||
}
|
||||
|
||||
get contact_id() {
|
||||
return this.getAttribute('contact-id')
|
||||
}
|
||||
|
||||
set contact_id(v) {
|
||||
this.setAttribute('contact-id', v);
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this.getAttribute('mode');
|
||||
}
|
||||
|
||||
// full or icon
|
||||
set mode(v) {
|
||||
this.setAttribute('mode', (v || '').toLocaleLowerCase())
|
||||
}
|
||||
|
||||
#setHref() {
|
||||
this.shadowRoot.querySelector('a').href = this.#createLinkPath();
|
||||
}
|
||||
|
||||
#createLinkPath() {
|
||||
const s = this.shadowRoot.querySelector('#content').textContent || '';
|
||||
switch (this.type) {
|
||||
case 'email':
|
||||
return `mailto:${ s }`;
|
||||
default:
|
||||
return s;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#isMobile() {
|
||||
return !!navigator.userAgent.toLowerCase().match(/mobile/i);
|
||||
}
|
||||
});
|
@ -1,62 +0,0 @@
|
||||
import { Component } from "../shared.js";
|
||||
|
||||
customElements.define('contact-type-icon',
|
||||
class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['type'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
svg { display: none; }
|
||||
:host([type="email"]) #email-icon {
|
||||
display: block;
|
||||
}
|
||||
:host([type="facebook"]) #fb-icon {
|
||||
display: block;
|
||||
}
|
||||
:host([type="other"]) #other-icon {
|
||||
display: block;
|
||||
}
|
||||
:host([type="mobile"]) #mobile-icon {
|
||||
display: block;
|
||||
}
|
||||
:host([type='link']) #link-icon {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svg id="email-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75.294 75.294" xml:space="preserve">
|
||||
<path d="M66.097 12.089h-56.9C4.126 12.089 0 16.215 0 21.286v32.722c0 5.071 4.126 9.197 9.197 9.197h56.9c5.071 0 9.197-4.126 9.197-9.197V21.287c.001-5.072-4.125-9.198-9.197-9.198zm-4.494 6L37.647 33.523 13.691 18.089h47.912zm4.494 39.117h-56.9A3.201 3.201 0 0 1 6 54.009V21.457l29.796 19.16c.04.025.083.042.124.065.043.024.087.047.131.069.231.119.469.215.712.278.025.007.05.01.075.016.267.063.537.102.807.102h.006c.27 0 .54-.038.807-.102.025-.006.05-.009.075-.016.243-.063.48-.159.712-.278a3.27 3.27 0 0 0 .131-.069c.041-.023.084-.04.124-.065l29.796-19.16v32.551a3.204 3.204 0 0 1-3.199 3.198z"/>
|
||||
</svg>
|
||||
<svg id="fb-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 314.652 314.652" xml:space="preserve">
|
||||
<path d="M157.326 0C70.576 0 0 70.576 0 157.326s70.576 157.326 157.326 157.326 157.326-70.576 157.326-157.326S244.076 0 157.326 0zm0 296.652C80.501 296.652 18 234.15 18 157.326S80.501 18 157.326 18s139.326 62.502 139.326 139.326-62.501 139.326-139.326 139.326z"/><path d="M193.764 71.952H172.43c-17.461 0-31.667 14.206-31.667 31.667v24h-19.875c-4.971 0-9 4.029-9 9s4.029 9 9 9h19.875v83.333c0 4.971 4.029 9 9 9s9-4.029 9-9v-83.333h30.75c4.971 0 9-4.029 9-9s-4.029-9-9-9h-30.75v-24c0-7.536 6.131-13.667 13.667-13.667h21.333c4.971 0 9-4.029 9-9s-4.029-9-8.999-9z"/>
|
||||
</svg>
|
||||
<svg id="other-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="#000" stroke-width="2" d="M1 2h21v16h-8l-8 4v-4H1V2Zm5 8h1v1H6v-1Zm5 0h1v1h-1v-1Zm5 0h1v1h-1v-1Z"/>
|
||||
</svg>
|
||||
<svg id="mobile-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 557.389 557.389" xml:space="preserve">
|
||||
<path d="M368.318 0H189.061c-29.223 0-62.529 34.721-62.529 65.159V492.22c0 30.447 33.306 65.169 62.529 65.169h179.258c29.225 0 62.539-34.722 62.539-65.169V65.159C430.857 34.721 397.543 0 368.318 0zm48.196 492.229c0 23.275-26.125 50.825-48.195 50.825H189.061c-22.07 0-48.186-27.55-48.186-50.825V65.159c0-23.275 26.125-50.815 48.186-50.815h179.258c22.07 0 48.195 27.549 48.195 50.815v427.07z"/>
|
||||
<path d="M289.15 477.341a1.079 1.079 0 0 0-1.49.172c-.363.469-.287 1.139.172 1.492a14.682 14.682 0 0 1 5.594 11.561c0 8.129-6.598 14.736-14.726 14.736-8.119 0-14.727-6.607-14.727-14.736 0-4.561 2.065-8.797 5.671-11.618a1.034 1.034 0 0 0 .191-1.473c-.363-.469-1.033-.564-1.492-.191a16.767 16.767 0 0 0-6.493 13.282c0 9.305 7.564 16.868 16.859 16.868 9.294 0 16.859-7.563 16.859-16.868a16.862 16.862 0 0 0-6.418-13.225z"/>
|
||||
<path d="M278.69 491.627c.593 0 1.062-.469 1.062-1.062v-15.777c0-.594-.479-1.062-1.062-1.062s-1.062.468-1.062 1.062v15.777a1.06 1.06 0 0 0 1.062 1.062z"/>
|
||||
<path d="M278.69 458.866c-18.063 0-32.761 14.697-32.761 32.761s14.697 32.762 32.761 32.762c18.064 0 32.761-14.698 32.761-32.762s-14.687-32.761-32.761-32.761zm0 60.741c-15.425 0-27.979-12.546-27.979-27.98 0-15.424 12.546-27.979 27.979-27.979s27.98 12.546 27.98 27.979c0 15.425-12.547 27.98-27.98 27.98zM361.053 87.927H196.347c-12.527 0-22.711 10.174-22.711 22.711v304.804c0 12.518 10.184 22.711 22.711 22.711h164.706c12.525 0 22.711-10.193 22.711-22.711V110.638c0-12.527-10.196-22.711-22.711-22.711zm15.539 327.515c0 8.568-6.973 15.539-15.539 15.539H196.347c-8.568 0-15.539-6.971-15.539-15.539V110.638c0-8.568 6.971-15.539 15.539-15.539h164.706c8.566 0 15.539 6.971 15.539 15.539v304.804z"/>
|
||||
<circle cx="233.02" cy="55.769" r="5.977"/>
|
||||
<circle cx="272.723" cy="55.769" r="5.977"/>
|
||||
<circle cx="312.426" cy="55.769" r="5.977"/>
|
||||
</svg>
|
||||
<svg id="link-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 162.656 162.656" xml:space="preserve">
|
||||
<path d="M151.764 10.894c-14.522-14.522-38.152-14.525-52.676-.008l.003.003-22.979 22.983 10.607 10.605 22.983-22.988-.002-.002c8.678-8.663 22.785-8.658 31.457.014 8.673 8.672 8.672 22.786 0 31.461l-34.486 34.484a22.095 22.095 0 0 1-15.729 6.516 22.098 22.098 0 0 1-15.73-6.516L64.605 98.052c7.035 7.035 16.389 10.91 26.338 10.91 9.949 0 19.303-3.875 26.335-10.91l34.487-34.484c14.519-14.525 14.519-38.155-.001-52.674z"/><path d="M52.96 141.162c-8.675 8.67-22.788 8.668-31.461-.005-8.673-8.675-8.673-22.791-.001-31.465L55.98 75.21c8.675-8.674 22.789-8.674 31.462 0L98.05 64.604c-14.524-14.523-38.154-14.524-52.676 0L10.89 99.086c-14.519 14.523-14.519 38.154.001 52.678 7.263 7.262 16.801 10.893 26.341 10.892 9.536 0 19.074-3.629 26.333-10.887l.002-.001 22.984-22.99-10.608-10.606-22.983 22.99z"/>
|
||||
</svg>
|
||||
`);
|
||||
}
|
||||
|
||||
set type(v) {
|
||||
this.setAttribute('type', v || 'email');
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.getAttribute('type')
|
||||
}
|
||||
});
|
@ -1,133 +0,0 @@
|
||||
import { Component, BUTTON_STYLE } from "../shared";
|
||||
|
||||
customElements.define('edit-contact-info', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['contact-id', "mode", "delete", 'type'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
article {
|
||||
display: block;
|
||||
}
|
||||
#buttons {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
margin: 8px 0;
|
||||
}
|
||||
#actions input {
|
||||
margin-right: 8px;
|
||||
}
|
||||
#cancel, #edit {
|
||||
display: none;
|
||||
}
|
||||
:host([mode = 'view']) contact-info-editor {
|
||||
display: none;
|
||||
}
|
||||
:host([mode = 'edit']) #actions {
|
||||
width: 100%;
|
||||
}
|
||||
:host([mode = 'edit']) contact-info-editor {
|
||||
display: block;
|
||||
min-width: 50%;
|
||||
}
|
||||
:host([mode = 'edit']) #cancel {
|
||||
display: block;
|
||||
}
|
||||
:host([mode = 'view']) #edit {
|
||||
display: block;
|
||||
}
|
||||
:host([mode = 'view']) ::slotted(contact-info) {
|
||||
display: block;
|
||||
}
|
||||
:host([mode = 'edit']) ::slotted(contact-info) {
|
||||
display: none;
|
||||
}
|
||||
:host([delete = "false"]) #deleteButton {
|
||||
display: none;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
article {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
#actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<slot></slot>
|
||||
<section id="actions">
|
||||
<contact-info-editor></contact-info-editor>
|
||||
<div id="buttons">
|
||||
<input type="button" value="Edytuj" id="edit" />
|
||||
<input type="button" value="Anuluj" id="cancel" />
|
||||
<form id="deleteButton" action="/contacts/delete" method="post">
|
||||
<input type="hidden" name="id" id="remove-id" />
|
||||
<input type="submit" value="Usuń" id="remove" />
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
|
||||
const form = this.shadowRoot.querySelector('contact-info-editor');
|
||||
this.shadowRoot.querySelector('#edit').addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const info = this.querySelector('contact-info');
|
||||
if (!info) return;
|
||||
|
||||
form.contact_id = this.contact_id;
|
||||
form.type = info.type;
|
||||
form.content = info.content;
|
||||
|
||||
this.mode = 'edit';
|
||||
});
|
||||
this.shadowRoot.querySelector('#cancel').addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.mode = 'view';
|
||||
});
|
||||
const deleteForm = this.shadowRoot.querySelector('#deleteButton');
|
||||
deleteForm.addEventListener('submit', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('contact:delete', {
|
||||
composed: true,
|
||||
bubbles: true,
|
||||
detail: { id: this.contact_id }
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
get contact_id() {
|
||||
const v = parseInt(this.getAttribute('contact-id'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set contact_id(v) {
|
||||
this.setAttribute('contact-id', v);
|
||||
this.shadowRoot.querySelector('#remove-id').value = v;
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this.getAttribute('mode');
|
||||
}
|
||||
|
||||
set mode(v) {
|
||||
if (v !== 'edit' && v !== 'view') {
|
||||
console.warn('wrong edit contact info mode', v);
|
||||
this.setAttribute('mode', 'view');
|
||||
return
|
||||
}
|
||||
this.setAttribute('mode', v);
|
||||
}
|
||||
});
|
@ -1,116 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('local-business-item', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['name', 'price', 'picture-url']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
* {
|
||||
font-family: 'Cardo', sans-serif;
|
||||
--img-width: 128px;
|
||||
--price-width: 160px;
|
||||
--name-width: calc(100% - var(--price-width) - var(--img-width) - 20px);
|
||||
}
|
||||
:host([picture-url = '']) #img {
|
||||
display: none;
|
||||
}
|
||||
#item {
|
||||
display: grid;
|
||||
grid-template-areas: 'img name' 'img price';
|
||||
}
|
||||
h3#name {
|
||||
font-weight: normal;
|
||||
grid-area: name;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 16px;
|
||||
width: var(--name-width);
|
||||
}
|
||||
#price {
|
||||
grid-area: price;
|
||||
text-align: right;
|
||||
width: var(--price-width);
|
||||
}
|
||||
#img {
|
||||
width: var(--img-width);
|
||||
max-width: var(--img-width);
|
||||
grid-area: img;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
#item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
h3#name {
|
||||
font-weight: normal;
|
||||
line-height: 1.6;
|
||||
width: var(--name-width);
|
||||
text-align: left;
|
||||
}
|
||||
#price {
|
||||
font-weight: bold;
|
||||
width: var(--price-width);
|
||||
}
|
||||
#img {
|
||||
width: var(--img-width);
|
||||
max-width: 128px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<section id="item">
|
||||
<image-popup id="img"></image-popup>
|
||||
<h3 id="name"></h3>
|
||||
<price-view id="price"></price-view>
|
||||
</section>
|
||||
`);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
get picture_url() {
|
||||
const v = this.getAttribute('picture-url');
|
||||
if (!v || v === '') return;
|
||||
return v;
|
||||
}
|
||||
|
||||
set picture_url(v) {
|
||||
v = v || '';
|
||||
if (!(v || '').startsWith("/")) v = '';
|
||||
this.setAttribute('picture-url', v);
|
||||
const el = this.shadowRoot.querySelector('#img');
|
||||
el.src = v;
|
||||
}
|
||||
|
||||
matches(text) {
|
||||
return !!this.name.match(text)
|
||||
}
|
||||
});
|
@ -1,121 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('local-business', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['name', 'business-id', 'state']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
#items {
|
||||
margin-top: 16px;
|
||||
}
|
||||
#contacts {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
::slotted(local-business-item) {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
::slotted(contact-info-list) {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
<h2 id="name"></h2>
|
||||
<slot name="description"></slot>
|
||||
<section id="items">
|
||||
<slot name="item"></slot>
|
||||
</section>
|
||||
<section id="contacts">
|
||||
<span style="margin-right: 10px; font-weight: bold">Kontakt:</span>
|
||||
<slot name="contacts"></slot>
|
||||
</section>
|
||||
`);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.getAttribute('name') || ''
|
||||
}
|
||||
|
||||
set name(v) {
|
||||
this.setAttribute('name', v);
|
||||
this.#setNameHeader();
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.getAttribute('state');
|
||||
}
|
||||
|
||||
set state(v) {
|
||||
this.setAttribute('state', v);
|
||||
}
|
||||
|
||||
get business_id() {
|
||||
return this.getAttribute('business-id');
|
||||
}
|
||||
|
||||
set business_id(v) {
|
||||
this.setAttribute('business-id', v);
|
||||
this.#setNameHeader();
|
||||
}
|
||||
|
||||
get description() {
|
||||
return Array.from(this.querySelectorAll('[slot=description]')).map(el => el.textContent);
|
||||
}
|
||||
|
||||
matches(regex) {
|
||||
return this.name.match(regex) ||
|
||||
this.description.any(s => s.match(regex)) ||
|
||||
Array.from(this.querySelectorAll('local-business-item')).any(el => el.matches(regex));
|
||||
}
|
||||
|
||||
#setNameHeader() {
|
||||
this.shadowRoot.querySelector('#name').innerHTML = `<a href="/local-businesses/${ this.business_id }">${ this.name }</a>`;
|
||||
}
|
||||
});
|
||||
|
||||
customElements.define('business-description', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['truncate'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`<style>:host{display:block;}</style><p><article></article>`);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.#renderContent();
|
||||
}
|
||||
|
||||
get truncate() {
|
||||
const v = parseInt(this.getAttribute('truncate'));
|
||||
return isNaN(v) ? 0 : v;
|
||||
}
|
||||
|
||||
set truncate(v) {
|
||||
if (v === false || v === 'false' || v == undefined) {
|
||||
this.removeAttribute('truncate');
|
||||
} else {
|
||||
this.setAttribute('truncate', v);
|
||||
}
|
||||
this.#renderContent();
|
||||
}
|
||||
|
||||
#renderContent() {
|
||||
let text = this.textContent;
|
||||
const max = this.truncate;
|
||||
const view = this.shadowRoot.querySelector('article');
|
||||
const tail = text.length > max ? '...' : '';
|
||||
if (max > 0) text = text.substring(0, max);
|
||||
view.innerHTML = text
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(s => s && s.length)
|
||||
.map((s, idx, a) => `<p>${ idx + 1 === a.length ? `${s}${tail}` : s }</p>`)
|
||||
.join('')
|
||||
}
|
||||
});
|
@ -1,67 +0,0 @@
|
||||
import { Component } from "../shared.js";
|
||||
import "../shared/search-input.js";
|
||||
|
||||
customElements.define('local-business-list', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
::slotted([search-visible='invisible']) {
|
||||
display: none;
|
||||
}
|
||||
input {
|
||||
font-size: 1rem;
|
||||
line-height: 2.6em;
|
||||
height: 2.6em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
border:none;
|
||||
outline:none;
|
||||
display: block;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid #ccc;
|
||||
text-indent: 20px;
|
||||
}
|
||||
article {
|
||||
margin: 8px;
|
||||
}
|
||||
#items {
|
||||
display: block;
|
||||
padding: 8px;
|
||||
}
|
||||
::slotted(local-business) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
#search {
|
||||
margin-bottom: 16px;;
|
||||
margin-top: 16px;;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
article {
|
||||
margin: 0;
|
||||
}
|
||||
#items {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
justify-items: stretch;
|
||||
}
|
||||
::slotted(local-business) {
|
||||
width: calc(50% - 40px);
|
||||
margin: 0 20px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<article>
|
||||
<section id="search">
|
||||
<search-input target="local-business"></search-input>
|
||||
</section>
|
||||
<section id="items">
|
||||
<slot name="business"></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
import { Component } from '../shared.js';
|
||||
|
||||
customElements.define('single-local-business', class extends Component {
|
||||
constructor() {
|
||||
super(`<style>:host{display:block;margin:0 8px;}@media only screen and (min-device-width: 100px){:host{margin:0;}}</style><article><slot></slot></article>`);
|
||||
}
|
||||
});
|
@ -1,26 +0,0 @@
|
||||
import { Component, FORM_STYLE } from "./shared";
|
||||
|
||||
customElements.define('login-form', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<form action="/login" method="post">
|
||||
<div>
|
||||
<label>E-Mail</label>
|
||||
<input name="email" placeholder="E-Mail" type="email" required />
|
||||
</div>
|
||||
<div>
|
||||
<label>Hasło</label>
|
||||
<input name="password" placeholder="Hasło" type="password" required />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Zaloguj" />
|
||||
</div>
|
||||
</form>
|
||||
`);
|
||||
}
|
||||
});
|
@ -1,73 +0,0 @@
|
||||
import { Component } from "../shared.js";
|
||||
import * as api from "../api.js";
|
||||
|
||||
customElements.define('marketplace-editor', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
</style>
|
||||
<article>
|
||||
<section>
|
||||
<slot name="offers"></slot>
|
||||
</section>
|
||||
<section>
|
||||
<slot name="contacts"></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
|
||||
this.addEventListener('contact:create', async ({ detail }) => {
|
||||
await this.#createContact(detail);
|
||||
});
|
||||
this.addEventListener('contact:update', async ({ detail }) => {
|
||||
await this.#updateContact(detail);
|
||||
});
|
||||
this.addEventListener('contact:delete', async ({ detail }) => {
|
||||
await this.#deleteContact(detail)
|
||||
});
|
||||
}
|
||||
|
||||
async #createContact({ content, type }) {
|
||||
await api.createContact({ content, type });
|
||||
const { contacts } = await api.accountContacts();
|
||||
this.#contacts = this.#formatContacts(contacts);
|
||||
}
|
||||
|
||||
async #updateContact({ id, content, type }) {
|
||||
await api.updateContact({ id, content, type });
|
||||
const { contacts } = await api.accountContacts();
|
||||
this.#contacts = this.#formatContacts(contacts);
|
||||
}
|
||||
|
||||
async #deleteContact({ id }) {
|
||||
await api.deleteContact({ id });
|
||||
const { contacts } = await api.accountContacts();
|
||||
this.#contacts = this.#formatContacts(contacts);
|
||||
}
|
||||
|
||||
#formatContacts(contacts) {
|
||||
return contacts.map(({ id, content, contact_type }) => `
|
||||
<edit-contact-info
|
||||
contact-id="${ id }"
|
||||
type="${ contact_type }"
|
||||
content="${ content }"
|
||||
mode="view"
|
||||
>
|
||||
<contact-info
|
||||
contact-id="${ id }"
|
||||
type="${ contact_type }"
|
||||
content="${ content }"
|
||||
></contact-info>
|
||||
</edit-contact-info>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
set #contacts(html) {
|
||||
for (const el of this.querySelectorAll('edit-contact-info'))
|
||||
el.remove();
|
||||
const fragment = document.createElement('template');
|
||||
fragment.innerHTML = html;
|
||||
this.appendChild(fragment.content);
|
||||
}
|
||||
});
|
@ -1,249 +0,0 @@
|
||||
import { Component, INPUT_STYLE, PriceRange } from "../shared";
|
||||
|
||||
customElements.define('marketplace-offer', class extends Component {
|
||||
#price_range;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['offer-id', 'description', 'picture-url', "price-range", "price-range-min", "price-range-max"]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
// language=HTML
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--border-light-gray-color);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 16px;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
#preview {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
image-popup {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#sep {
|
||||
display: block;
|
||||
grid-area: sep;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:host([price-range-max="0"]) #sep, :host([price-range]) #sep,
|
||||
:host([price-range-max="0"]) #price-min, :host([price-range]) #price-min {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#details {
|
||||
display: grid;
|
||||
column-gap: 16px;
|
||||
grid-template-areas: "img img img"
|
||||
"desc desc desc"
|
||||
"min sep max";
|
||||
grid-template-columns: auto 10px auto;
|
||||
}
|
||||
|
||||
#preview {
|
||||
grid-area: img;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#description {
|
||||
grid-area: desc;
|
||||
text-align: justify;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#price-min {
|
||||
grid-area: min;
|
||||
justify-self: end;
|
||||
-webkit-transition: background .3s, border .3s, border-radius .3s, -webkit-box-shadow .3s;
|
||||
transition: background .3s, border .3s, border-radius .3s, -webkit-box-shadow .3s;
|
||||
-o-transition: background .3s, border .3s, border-radius .3s, box-shadow .3s;
|
||||
transition: background .3s, border .3s, border-radius .3s, box-shadow .3s;
|
||||
transition: background .3s, border .3s, border-radius .3s, box-shadow .3s, -webkit-box-shadow .3s;
|
||||
}
|
||||
|
||||
#price-max {
|
||||
grid-area: max;
|
||||
justify-self: start;
|
||||
-webkit-transition: background .3s, border .3s, border-radius .3s, -webkit-box-shadow .3s;
|
||||
transition: background .3s, border .3s, border-radius .3s, -webkit-box-shadow .3s;
|
||||
-o-transition: background .3s, border .3s, border-radius .3s, box-shadow .3s;
|
||||
transition: background .3s, border .3s, border-radius .3s, box-shadow .3s;
|
||||
transition: background .3s, border .3s, border-radius .3s, box-shadow .3s, -webkit-box-shadow .3s;
|
||||
}
|
||||
|
||||
#contacts {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
#details {
|
||||
display: grid;
|
||||
column-gap: 16px;
|
||||
grid-template-areas: "img img img" "desc desc desc" "min sep max";
|
||||
grid-template-columns: auto 10px auto;
|
||||
margin: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
image-popup {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#preview {
|
||||
grid-area: img;
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#description {
|
||||
grid-area: desc;
|
||||
justify-self: stretch;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#price-min {
|
||||
grid-area: min;
|
||||
justify-self: end;
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#price-max {
|
||||
grid-area: max;
|
||||
justify-self: start;
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
a {
|
||||
font-style: normal;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
${ INPUT_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section id="details">
|
||||
<div id="preview">
|
||||
<image-popup src="" id="picture">
|
||||
</image-popup>
|
||||
</div>
|
||||
<p id="description"></p>
|
||||
<span id="price-min"></span>
|
||||
<span id="sep">-</span>
|
||||
<span id="price-max"></span>
|
||||
</section>
|
||||
<section id="contacts">
|
||||
<span style="margin-right: 10px; font-weight: bold">Kontakt:</span>
|
||||
<slot name="contacts"></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
this.#price_range = new PriceRange(0, 0);
|
||||
}
|
||||
|
||||
get offer_id() {
|
||||
const v = parseInt(this.getAttribute('offer-id'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set offer_id(v) {
|
||||
v = parseInt(v);
|
||||
this.setAttribute('offer-id', v);
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.getAttribute('description');
|
||||
}
|
||||
|
||||
set description(v) {
|
||||
this.setAttribute('description', v);
|
||||
this.shadowRoot.querySelector('#description').textContent = v;
|
||||
}
|
||||
|
||||
get picture_url() {
|
||||
return this.getAttribute('picture-url');
|
||||
}
|
||||
|
||||
set picture_url(v) {
|
||||
this.setAttribute('picture-url', v);
|
||||
this.shadowRoot.querySelector('#picture').src = v;
|
||||
}
|
||||
|
||||
get price_range() {
|
||||
return this.#price_range
|
||||
}
|
||||
|
||||
set price_range(v) {
|
||||
if (v instanceof PriceRange) return;
|
||||
v = v + '';
|
||||
if (!v.match(/free|(\d+([,.]\d{2})?)(\|(\d+([,.]\d{2})?))?/i))
|
||||
return console.warn('malformed price range');
|
||||
|
||||
if (v === 'free')
|
||||
this.#price_range = new PriceRange(v, 0);
|
||||
else if (v.includes(',')) {
|
||||
const [min, max, ..._] = v.split(',');
|
||||
this.#price_range = new PriceRange(parseInt(min), parseInt(max));
|
||||
} else {
|
||||
this.#price_range.min = parseInt(v);
|
||||
}
|
||||
this.#displayPrice();
|
||||
}
|
||||
|
||||
get price_range_min() {
|
||||
return this.#price_range.min
|
||||
}
|
||||
|
||||
set price_range_min(v) {
|
||||
this.#price_range.min = v;
|
||||
this.#displayPrice();
|
||||
}
|
||||
|
||||
get price_range_max() {
|
||||
return this.#price_range.max
|
||||
}
|
||||
|
||||
set price_range_max(v) {
|
||||
this.#price_range.max = v;
|
||||
this.#displayPrice();
|
||||
}
|
||||
|
||||
#displayPrice() {
|
||||
const min = this.shadowRoot.querySelector('#price-min');
|
||||
const max = this.shadowRoot.querySelector('#price-max');
|
||||
|
||||
if (this.#price_range.isFree) {
|
||||
min.innerHTML = ``;
|
||||
max.innerHTML = `Za darmo`;
|
||||
}
|
||||
if (this.#price_range.isRange) {
|
||||
min.innerHTML = `<price-view value="${ this.#price_range.min }"></price-view>`
|
||||
max.innerHTML = `<price-view value="${ this.#price_range.max }"></price-view>`;
|
||||
}
|
||||
if (this.#price_range.isFixed) {
|
||||
min.innerHTML = ``;
|
||||
max.innerHTML = `<price-view value="${ this.#price_range.min }"></price-view>`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
matches(regex) {
|
||||
return !!this.description.match(regex)
|
||||
}
|
||||
});
|
@ -1,86 +0,0 @@
|
||||
import { Component, BUTTON_STYLE } from "../shared.js";
|
||||
import "../shared/search-input.js";
|
||||
|
||||
customElements.define('marketplace-offers', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['account-id'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
h1 {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
#offers {
|
||||
display: block;
|
||||
padding: 8px;
|
||||
}
|
||||
#publishSection {
|
||||
display: none;
|
||||
}
|
||||
:host([account-id]) #publishSection {
|
||||
display: block;
|
||||
}
|
||||
::slotted(a), ::slotted(marketplace-offer), ::slotted(user-edit-offer) {
|
||||
margin-bottom: 20px;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-style: normal;
|
||||
}
|
||||
::slotted([search-visible='invisible']) {
|
||||
display: none;
|
||||
}
|
||||
#search {
|
||||
margin-bottom: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
#offers {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
justify-items: stretch;
|
||||
}
|
||||
::slotted(a), ::slotted(marketplace-offer), ::slotted(user-edit-offer) {
|
||||
width: calc(33% - 40px);
|
||||
margin: 0 20px 20px;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-style: normal;
|
||||
}
|
||||
}
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section id="publishSection">
|
||||
<button id="publish" class="btn">Dodaj ogłoszenie</button>
|
||||
</section>
|
||||
<section><slot></slot></section>
|
||||
<section id="search">
|
||||
<search-input target="user-edit-offer, marketplace-offer"></search-input>
|
||||
</section>
|
||||
<section id="offers">
|
||||
<slot name="offer"></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
this.shadowRoot.querySelector('#publish').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
location.href = '/offers/new';
|
||||
});
|
||||
}
|
||||
|
||||
set account_id(v) {
|
||||
this.setAttribute('account-id', v)
|
||||
}
|
||||
|
||||
get account_id() {
|
||||
return this.getAttribute('account-id');
|
||||
}
|
||||
});
|
@ -1,189 +0,0 @@
|
||||
import { Component, FORM_STYLE, TIP_STYLE } from "../shared.js";
|
||||
|
||||
customElements.define('offer-form', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['offer-id', 'description', 'picture-url', 'price-range-min', 'price-range-max', 'free'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
#description {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
section {
|
||||
padding: 8px;
|
||||
}
|
||||
:host([free]) #priceMinSection {
|
||||
display: none;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
section > form {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-items: stretch;
|
||||
}
|
||||
#imageSection {
|
||||
margin-right: 16px;
|
||||
}
|
||||
#priceSection {
|
||||
width: 300px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
#descriptionSection {
|
||||
width: calc(100% - 550px);
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
${ FORM_STYLE }${ TIP_STYLE }
|
||||
</style>
|
||||
<section>
|
||||
<form action="/offers/create" method="post">
|
||||
<div id="imageSection">
|
||||
<image-input send-original="true" width="800" height="800"
|
||||
></image-input>
|
||||
<input name="picture_url" id="picture_url" type="hidden" />
|
||||
</div>
|
||||
<div id="descriptionSection">
|
||||
<label for="description">Opis</label>
|
||||
<textarea
|
||||
name="description"
|
||||
id="description"
|
||||
placeholder="Opisz przedmiot, który chcesz sprzedać"
|
||||
></textarea>
|
||||
</div>
|
||||
<div id="priceSection">
|
||||
<div id="priceMinSection">
|
||||
<label>Cena</label>
|
||||
<price-input id="priceMinUI" value="0"></price-input>
|
||||
<input name="price_min" id="priceMin" type="hidden" value="0" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Za darmo</label>
|
||||
<input type="checkbox" id="free" />
|
||||
<input name="price_max" id="priceMax" type="hidden" value="0" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<input id="submit" type="submit" value="Utwórz" />
|
||||
<div>
|
||||
<slot name="action"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
`);
|
||||
|
||||
this.shadowRoot.querySelector('#priceMinUI').addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
this.shadowRoot.querySelector('#priceMin').value = ev.target.value;
|
||||
});
|
||||
|
||||
// this.shadowRoot.querySelector('#priceMaxUI').addEventListener('change', ev => {
|
||||
// ev.stopPropagation();
|
||||
// this.shadowRoot.querySelector('#priceMax').value = ev.target.value;
|
||||
// });
|
||||
this.addEventListener('image-input:uploaded', ev => {
|
||||
this.picture_url = ev.detail;
|
||||
});
|
||||
this.shadowRoot.querySelector('#free').addEventListener('change', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.free = ev.target.checked;
|
||||
if (this.free) {
|
||||
this.price_range_min = 0;
|
||||
this.price_range_max = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get offer_id() {
|
||||
const v = parseInt(this.getAttribute('offer-id'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set offer_id(v) {
|
||||
v = parseInt(v);
|
||||
this.setAttribute('offer-id', v);
|
||||
if (isNaN(v)) {
|
||||
this.#removeId();
|
||||
} else {
|
||||
this.#addId(v)
|
||||
}
|
||||
}
|
||||
|
||||
get price_range_min() {
|
||||
this.getAttribute('price-range-min');
|
||||
}
|
||||
|
||||
set price_range_min(v) {
|
||||
this.setAttribute('price-range-min', v);
|
||||
this.shadowRoot.querySelector('#priceMinUI').value = v;
|
||||
this.shadowRoot.querySelector('#priceMin').value = v;
|
||||
}
|
||||
|
||||
get price_range_max() {
|
||||
this.getAttribute('price-range-max');
|
||||
}
|
||||
|
||||
set price_range_max(v) {
|
||||
this.setAttribute('price-range-max', v);
|
||||
// this.shadowRoot.querySelector('#priceMaxUI').value = v;
|
||||
this.shadowRoot.querySelector('#priceMax').value = v;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.getAttribute('description');
|
||||
}
|
||||
|
||||
set description(v) {
|
||||
this.setAttribute('description', v);
|
||||
this.shadowRoot.querySelector('#description').value = v;
|
||||
}
|
||||
|
||||
get picture_url() {
|
||||
return this.getAttribute('picture-url');
|
||||
}
|
||||
|
||||
set picture_url(v) {
|
||||
this.setAttribute('picture-url', v);
|
||||
this.shadowRoot.querySelector('#picture_url').value = v;
|
||||
this.shadowRoot.querySelector('image-input').url = v;
|
||||
}
|
||||
|
||||
get free() {
|
||||
return this.getAttribute('free') === 'true';
|
||||
}
|
||||
|
||||
set free(v) {
|
||||
if (v === true || v === 'true') {
|
||||
this.setAttribute('free', 'true');
|
||||
this.shadowRoot.querySelector('#free').checked = true;
|
||||
} else {
|
||||
this.removeAttribute('free');
|
||||
this.shadowRoot.querySelector('#free').checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
#removeId() {
|
||||
const form = this.shadowRoot.querySelector('form');
|
||||
const input = form.querySelector('#offer-id');
|
||||
input && input.remove();
|
||||
form.action = '/offers/create';
|
||||
form.querySelector('input[type=submit]').value = 'Utwórz';
|
||||
}
|
||||
|
||||
#addId(v) {
|
||||
this.#removeId();
|
||||
const form = this.shadowRoot.querySelector('form');
|
||||
const input = form.appendChild(document.createElement('input'));
|
||||
input.setAttribute('type', 'hidden');
|
||||
input.setAttribute('name', 'id');
|
||||
input.setAttribute('id', 'offer-id');
|
||||
input.value = v;
|
||||
form.action = '/offers/update';
|
||||
form.querySelector('input[type=submit]').value = 'Zmień';
|
||||
}
|
||||
});
|
@ -1,159 +0,0 @@
|
||||
import { Component, BUTTON_STYLE } from "../shared";
|
||||
|
||||
const MODES = { 'view': 'view', 'form': 'form' };
|
||||
|
||||
customElements.define('user-edit-offer', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['mode', 'state', 'offer-id', 'description', 'picture-url', 'price-range-min', 'price-range-max'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
#view, #form { display: none; }
|
||||
:host([mode='view']) #view { display: block; }
|
||||
:host([mode='form']) #form { display: block; }
|
||||
:host([state='Finished']) #finishForm { display: none; }
|
||||
section, a, ::slotted(a) {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-style: normal;
|
||||
}
|
||||
#actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#actions input {
|
||||
width: 140px;
|
||||
}
|
||||
#state {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
@media only screen and (min-device-width: 1200px) {
|
||||
:host([mode='view']) #view { display: flex; }
|
||||
}
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<section id="state"></section>
|
||||
<section id="view">
|
||||
<slot></slot>
|
||||
</section>
|
||||
<section id="actions">
|
||||
<input type="button" value="Edytuj" id="edit" />
|
||||
<form id="finishForm" action="/offers/finish" method="post">
|
||||
<input type="hidden" name="id" class="id" value="" />
|
||||
<input type="submit" slot="action" id="finish" value="Zakończ" />
|
||||
</form>
|
||||
</section>
|
||||
<section id="form">
|
||||
<offer-form>
|
||||
<input type="button" slot="action" id="cancel" value="Anuluj" />
|
||||
</offer-form>
|
||||
</section>
|
||||
`);
|
||||
|
||||
this.shadowRoot.querySelector('#edit').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
location.href = `/marketplace/${this.offer_id}/edit`
|
||||
});
|
||||
this.shadowRoot.querySelector('#cancel').addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.mode = 'view';
|
||||
});
|
||||
}
|
||||
|
||||
get mode() {
|
||||
const mode = this.getAttribute('mode');
|
||||
return MODES[mode] || 'view';
|
||||
}
|
||||
|
||||
set mode(mode) {
|
||||
this.setAttribute('mode', MODES[mode] || 'view');
|
||||
}
|
||||
|
||||
get offer_id() {
|
||||
const v = parseInt(this.getAttribute('offer-id'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set offer_id(v) {
|
||||
v = parseInt(v);
|
||||
this.setAttribute('offer-id', v);
|
||||
this.shadowRoot.querySelector('offer-form').offer_id = v;
|
||||
for (const el of this.shadowRoot.querySelectorAll('.id'))
|
||||
el.value = v;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.getAttribute('description');
|
||||
}
|
||||
|
||||
set description(v) {
|
||||
this.setAttribute('description', v);
|
||||
this.shadowRoot.querySelector('offer-form').description = v;
|
||||
}
|
||||
|
||||
get picture_url() {
|
||||
return this.getAttribute('picture-url');
|
||||
}
|
||||
|
||||
set picture_url(v) {
|
||||
this.setAttribute('picture-url', v);
|
||||
this.shadowRoot.querySelector('offer-form').picture_url = v;
|
||||
}
|
||||
|
||||
get price_range_min() {
|
||||
this.getAttribute('price-range-min');
|
||||
}
|
||||
|
||||
set price_range_min(v) {
|
||||
this.setAttribute('price-range-min', v);
|
||||
this.shadowRoot.querySelector('offer-form').price_range_min = v;
|
||||
}
|
||||
|
||||
get price_range_max() {
|
||||
this.getAttribute('price-range-max');
|
||||
}
|
||||
|
||||
set price_range_max(v) {
|
||||
this.setAttribute('price-range-max', v);
|
||||
this.shadowRoot.querySelector('offer-form').price_range_max = v;
|
||||
}
|
||||
|
||||
get state() {
|
||||
return this.getAttribute('state');
|
||||
}
|
||||
|
||||
set state(v) {
|
||||
this.setAttribute('state', v);
|
||||
const el = this.shadowRoot.querySelector('#state');
|
||||
switch (v) {
|
||||
case 'Pending': {
|
||||
el.textContent = 'Oczekuje';
|
||||
break;
|
||||
}
|
||||
case 'Approved': {
|
||||
el.textContent = 'Zaakceptowane';
|
||||
break;
|
||||
}
|
||||
case 'Banned': {
|
||||
el.textContent = 'Odrzucone';
|
||||
break;
|
||||
}
|
||||
case 'Finished': {
|
||||
el.textContent = 'Zakończone';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches(regex) {
|
||||
return !!this.description.match(regex)
|
||||
}
|
||||
});
|
@ -1,130 +0,0 @@
|
||||
import { BLOCK_QUOTE_STYLE, Component } from "../shared";
|
||||
import "../shared/date-time";
|
||||
|
||||
customElements.define('news-article', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ["article-id", "article-title", "status", "body", "created-at", "published-at", "hide-status"]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; width: 100%; }
|
||||
.time {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
h1 {
|
||||
display: flex;
|
||||
}
|
||||
h1 #status {
|
||||
font-size: 14px;
|
||||
}
|
||||
.time span:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
#title {
|
||||
margin-right: 16px;
|
||||
}
|
||||
#body {
|
||||
margin-bottom: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.time, date-time {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
:host([hide-status="true"]) #status {
|
||||
display: none;
|
||||
}
|
||||
article {
|
||||
margin: 8px;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
article {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
#time {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
${ BLOCK_QUOTE_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<h1>
|
||||
<span id="title"></span>
|
||||
<span id="status"></span>
|
||||
</h1>
|
||||
<section id="time">
|
||||
<div class="time">
|
||||
<span>Napisano:</span>
|
||||
<date-time id="created_at" hide-date="false" hide-time="false">
|
||||
</date-time>
|
||||
</div>
|
||||
<div class="time">
|
||||
<span>Opublikowano:</span>
|
||||
<date-time id="published_at" hide-date="false" hide-time="false">
|
||||
</date-time>
|
||||
</div>
|
||||
</section>
|
||||
<section id="body">
|
||||
<slot></slot>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
|
||||
get article_id() {
|
||||
const id = parseInt(this.getAttribute('article-id'));
|
||||
return isNaN(id) ? null : id;
|
||||
}
|
||||
|
||||
set article_id(v) {
|
||||
this.setAttribute('article-id', v);
|
||||
}
|
||||
|
||||
get article_title() {
|
||||
return this.getAttribute('article-title');
|
||||
}
|
||||
|
||||
set article_title(v) {
|
||||
this.setAttribute('article-title', v);
|
||||
this.shadowRoot.querySelector('#title').textContent = v;
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this.getAttribute('status');
|
||||
}
|
||||
|
||||
set status(v) {
|
||||
this.setAttribute('status', v);
|
||||
this.shadowRoot.querySelector('#status').textContent = v;
|
||||
}
|
||||
|
||||
get created_at() {
|
||||
return this.getAttribute('created-at');
|
||||
}
|
||||
|
||||
set created_at(v) {
|
||||
this.shadowRoot.querySelector('#created_at').datetime = v;
|
||||
}
|
||||
|
||||
get published_at() {
|
||||
return this.getAttribute('published-at');
|
||||
}
|
||||
|
||||
set published_at(v) {
|
||||
this.shadowRoot.querySelector('#published_at').datetime = v;
|
||||
}
|
||||
|
||||
get hide_status() {
|
||||
return this.getAttribute('hide-status') === 'true';
|
||||
}
|
||||
|
||||
set hide_status(v) {
|
||||
this.setAttribute('hide-status', v);
|
||||
}
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import { Component } from "../shared"
|
||||
|
||||
customElements.define('ow-articles', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
</style>
|
||||
<slot></slot>
|
||||
`);
|
||||
}
|
||||
});
|
@ -1,209 +0,0 @@
|
||||
import { Component, FORM_STYLE, BUTTON_STYLE } from "../shared";
|
||||
|
||||
customElements.define('account-view', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['facebook-id', 'id', 'name', 'email', 'register-success']
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
#register-success {
|
||||
display: none;
|
||||
text-align: center;
|
||||
color: darkgreen;
|
||||
}
|
||||
:host([register-success]) #register-success {
|
||||
display: block;
|
||||
}
|
||||
#fb-button {
|
||||
display: none;
|
||||
}
|
||||
facebook-button {
|
||||
margin-right: 16px;
|
||||
}
|
||||
article {
|
||||
margin: 8px;
|
||||
}
|
||||
#logout {
|
||||
border-color: var(--red-color);
|
||||
background: var(--red-color);
|
||||
color: white;
|
||||
}
|
||||
#logoutSection {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
section input[type="button"],
|
||||
section input[type="button"],
|
||||
section input[type="submit"] {
|
||||
width: 100%;
|
||||
}
|
||||
section input[type="submit"],
|
||||
section a {
|
||||
color: black;
|
||||
}
|
||||
::slotted(#editService) {
|
||||
padding-bottom: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
display: block !important;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
article {
|
||||
margin: 0;
|
||||
}
|
||||
#rules {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#rules a {
|
||||
display: block;
|
||||
min-width: calc(120px - 1.5rem);
|
||||
width: auto;
|
||||
}
|
||||
input {
|
||||
min-width: 120px;
|
||||
width: auto;
|
||||
}
|
||||
::slotted(#editService) {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
||||
::slotted(button) {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
height: calc(1.5em + 0.75rem + 2px);
|
||||
border: 1px solid black;
|
||||
color: white;
|
||||
background: black;
|
||||
padding: 10px 20px;
|
||||
font-family: "Cardo", Sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 1em;
|
||||
letter-spacing: 0;
|
||||
transition: all 0.2s;
|
||||
width: 100%;
|
||||
}
|
||||
#editServiceSection {
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
label {
|
||||
width: 120px;
|
||||
}
|
||||
.input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
${ FORM_STYLE }${ BUTTON_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<h3 id="register-success">Konto utworzone!</h3>
|
||||
<section>
|
||||
<input id="id" name="id" readonly type="hidden" />
|
||||
</section>
|
||||
<section class="input">
|
||||
<label>Login</label>
|
||||
<input id="name" name="name" readonly />
|
||||
</section>
|
||||
<section class="input">
|
||||
<label>E-Mail</label>
|
||||
<input id="email" name="email" readonly />
|
||||
</section>
|
||||
<section id="fb-button">
|
||||
<facebook-button width="100">
|
||||
<p>Powiąż z kontem Facebook</p>
|
||||
</facebook-button>
|
||||
</section>
|
||||
<section id="fb-id">
|
||||
<label>Powiązane konto Facebook</label>
|
||||
<input id="facebook_id" name="facebook_id" readonly />
|
||||
</section>
|
||||
<section id="editServiceSection">
|
||||
<slot name="editService"></slot>
|
||||
</section>
|
||||
<section id="logoutSection">
|
||||
<form action="/logout" method="post">
|
||||
<input id="logout" value="Wyloguj" type="submit" />
|
||||
</form>
|
||||
</section>
|
||||
<section id="rules">
|
||||
<a class="" href="/terms-and-condition" target="_blank">Regulamin</a>
|
||||
<a class="" href="/privacy-policy" target="_blank">Polityka prywatności</a>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
this.addEventListener('facebook:available', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
});
|
||||
{
|
||||
const input = this.querySelector('#editService');
|
||||
if (input) input.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
location.href = "/account/business-items";
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.register_success = (location.search || '').includes('success');
|
||||
localStorage.removeItem('register');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
get register_success() {
|
||||
return this.getAttribute('register-success') === 'true'
|
||||
}
|
||||
|
||||
set register_success(v) {
|
||||
if (v === true || v === 'true')
|
||||
this.setAttribute('register-success', 'true');
|
||||
else
|
||||
this.removeAttribute('register-success');
|
||||
}
|
||||
});
|
@ -1,101 +0,0 @@
|
||||
import { Component, BUTTON_STYLE, Router } from "../shared";
|
||||
|
||||
customElements.define('ow-account', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['mode']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
#switch-register, #switch-login {
|
||||
display: none;
|
||||
}
|
||||
:host([mode="login"]) #switch-register {
|
||||
display: block !important;
|
||||
}
|
||||
:host([mode="register"]) #switch-login {
|
||||
display: block !important;
|
||||
}
|
||||
input {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
section > input[type=button] {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
}
|
||||
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<slot></slot>
|
||||
<section id="switch-register">
|
||||
<input type="button" class="btn" value="Nie masz konta? Utwórz nowe" />
|
||||
</section>
|
||||
<section id="switch-login">
|
||||
<input type="button" class="btn" value="Posiadasz konto? Zaloguj się" />
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
|
||||
this.shadowRoot.querySelector('#switch-login > input').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
Router.goTo('/login');
|
||||
});
|
||||
this.shadowRoot.querySelector('#switch-register > input').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
Router.goTo('/register/account-type');
|
||||
});
|
||||
this.addEventListener('facebook:account', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.listenHistory(Router.historyDetails());
|
||||
}
|
||||
|
||||
listenHistory = ({ parts }) => {
|
||||
switch (parts.first) {
|
||||
case 'register': {
|
||||
if (this.mode === 'register')
|
||||
return;
|
||||
this.mode = 'register';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (this.mode === 'login')
|
||||
return;
|
||||
this.mode = 'login';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this.getAttribute('mode') || '';
|
||||
}
|
||||
|
||||
set mode(value) {
|
||||
value = ['login', 'register', 'display'].includes(value) ? value : 'login';
|
||||
this.setAttribute('mode', value);
|
||||
switch (value) {
|
||||
case 'register': {
|
||||
this.innerHTML = `<register-form></register-form>`;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
this.innerHTML = `<login-form></login-form>`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
@ -1,46 +0,0 @@
|
||||
Object.defineProperties(Object.prototype, {
|
||||
'entry': {
|
||||
value(key) {
|
||||
const owner = this;
|
||||
return {
|
||||
owner, orElse(v) {
|
||||
if (owner[key] === undefined) owner[key] = v;
|
||||
return owner[key];
|
||||
}, get() {
|
||||
return owner[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Object.defineProperties(Array.prototype, {
|
||||
'last': {
|
||||
get() {
|
||||
return this[this.length - 1]
|
||||
}
|
||||
},
|
||||
'tail': {
|
||||
get() {
|
||||
return this[this.length - 1]
|
||||
}
|
||||
},
|
||||
'first': {
|
||||
get() {
|
||||
return this[0]
|
||||
}
|
||||
},
|
||||
'head': {
|
||||
get() {
|
||||
return this[0]
|
||||
}
|
||||
},
|
||||
'any': {
|
||||
value(cb) {
|
||||
for (const el of this) {
|
||||
if (cb(el)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
})
|
@ -1,276 +0,0 @@
|
||||
import { FORM_STYLE, Component, Router } from "./shared.js";
|
||||
import { RegisterForm } from "./register-form/model.js";
|
||||
|
||||
customElements.define('register-form', class extends Component {
|
||||
#form = new RegisterForm;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['current']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.actions > input:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.actions > input:last-child {
|
||||
margin-left: 8px;
|
||||
}
|
||||
#step-4 > #copied {
|
||||
display: none;
|
||||
}
|
||||
article {
|
||||
margin: 8px;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
article {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<slot></slot>
|
||||
</article>
|
||||
`);
|
||||
this.shadowRoot.addEventListener('form:next', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#transfer('next');
|
||||
});
|
||||
this.shadowRoot.addEventListener('form:prev', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#transfer('prev');
|
||||
});
|
||||
|
||||
this.shadowRoot.addEventListener('account:type', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#copyDetail(ev);
|
||||
});
|
||||
this.addEventListener('account:basic', ev => {
|
||||
this.#copyDetail(ev);
|
||||
});
|
||||
this.addEventListener('account:business', ev => {
|
||||
this.#copyDetail(ev);
|
||||
});
|
||||
this.addEventListener('account:items', ev => {
|
||||
this.#copyDetail(ev);
|
||||
});
|
||||
this.addEventListener('account:contacts', ev => {
|
||||
this.#copyDetail(ev);
|
||||
});
|
||||
this.#loadCache();
|
||||
}
|
||||
|
||||
#loadCache() {
|
||||
let register = {};
|
||||
try {
|
||||
register = JSON.parse(localStorage.getItem('register') || '{}');
|
||||
} catch (e) {
|
||||
localStorage.removeItem('register');
|
||||
}
|
||||
this.#form.from(register);
|
||||
}
|
||||
|
||||
#copyDetail(ev) {
|
||||
ev.stopPropagation();
|
||||
for (const [key, value] of Object.entries(ev.detail)) {
|
||||
this.#form[key] = value;
|
||||
}
|
||||
localStorage.setItem('register', JSON.stringify(this.#form.payload));
|
||||
}
|
||||
|
||||
listenHistory = ({ parts }) => {
|
||||
this.current = parts.last;
|
||||
}
|
||||
|
||||
#host(body) {
|
||||
this.innerHTML = body;
|
||||
}
|
||||
|
||||
#showAccountTypeForm() {
|
||||
this.#host(`
|
||||
<register-account-type></register-account-type>
|
||||
`);
|
||||
}
|
||||
|
||||
#showUserAccountForm() {
|
||||
this.#host(`
|
||||
<register-user-account-form
|
||||
login="${ this.#form.login }"
|
||||
email="${ this.#form.email }"
|
||||
password="${ this.#form.password }"
|
||||
></register-user-account-form>
|
||||
`);
|
||||
}
|
||||
|
||||
#showBusinessAccountForm() {
|
||||
this.#host(`
|
||||
<register-business-account-form
|
||||
login="${ this.#form.login }"
|
||||
email="${ this.#form.email }"
|
||||
password="${ this.#form.password }"
|
||||
></register-business-account-form>
|
||||
`);
|
||||
}
|
||||
|
||||
#showBusinessDetailsForm() {
|
||||
this.#host(`
|
||||
<register-business-details-form
|
||||
name="${ this.#form.name || '' }"
|
||||
description="${ this.#form.description || '' }"
|
||||
></register-business-details-form>
|
||||
`);
|
||||
}
|
||||
|
||||
#showBusinessItemsForm() {
|
||||
this.#host(`
|
||||
<register-business-items-form>
|
||||
${ this.#form.items.map(
|
||||
({ name, price, picture_url }) => `
|
||||
<register-business-item-form
|
||||
name="${ name }"
|
||||
price="${ price }"
|
||||
picture-url="${ picture_url }"
|
||||
></register-business-item-form>
|
||||
`
|
||||
).join('') }
|
||||
</register-business-items-form>
|
||||
`);
|
||||
}
|
||||
|
||||
#showBusinessContactsForm() {
|
||||
this.#host(`
|
||||
<register-business-contacts-form>
|
||||
${ this.#form.contacts.map(
|
||||
({ type, content }) => `
|
||||
<edit-contact-info
|
||||
mode="view"
|
||||
delete="false"
|
||||
>
|
||||
<contact-info
|
||||
type="${ type }"
|
||||
content="${ content }"
|
||||
></contact-info>
|
||||
</edit-contact-info>
|
||||
`).join('') }
|
||||
</register-business-contacts-form>
|
||||
`);
|
||||
}
|
||||
|
||||
#showBusinessSubmitForm() {
|
||||
this.#host(`
|
||||
<register-business-submit-form
|
||||
name="${ this.#form.name }"
|
||||
description="${ this.#form.description }"
|
||||
login="${ this.#form.login }"
|
||||
email="${ this.#form.email }"
|
||||
password="${ this.#form.password }"
|
||||
account-type="${ this.#form.account_type }"
|
||||
>
|
||||
${ this.#form.items.map(
|
||||
({ name, price, picture_url }) => `
|
||||
<local-business-item
|
||||
slot="items"
|
||||
name="${ name }"
|
||||
price="${ price }"
|
||||
picture-url="${ picture_url }"
|
||||
></local-business-item>
|
||||
`
|
||||
).join('') }
|
||||
${ this.#form.contacts.map(
|
||||
({ type, content }) => `
|
||||
<contact-info
|
||||
slot="contacts"
|
||||
type="${ type }"
|
||||
content="${ content }"
|
||||
></contact-info>
|
||||
`).join('') }
|
||||
</register-business-submit-form>
|
||||
`);
|
||||
}
|
||||
|
||||
#transfer(direction) {
|
||||
const current = direction === 'next'
|
||||
? this.#nextPage()
|
||||
: this.#prevPage();
|
||||
this.current = current;
|
||||
Router.goTo(`/register/${ current }`);
|
||||
}
|
||||
|
||||
get current() {
|
||||
return this.getAttribute('current');
|
||||
}
|
||||
|
||||
set current(current) {
|
||||
if (!current) return;
|
||||
this.setAttribute('current', current);
|
||||
|
||||
switch (current) {
|
||||
case "account-type":
|
||||
return this.#showAccountTypeForm();
|
||||
case "user-account":
|
||||
return this.#showUserAccountForm();
|
||||
case "business-account":
|
||||
return this.#showBusinessAccountForm();
|
||||
case "business-details":
|
||||
return this.#showBusinessDetailsForm();
|
||||
case "business-items":
|
||||
return this.#showBusinessItemsForm();
|
||||
case "business-contacts":
|
||||
return this.#showBusinessContactsForm();
|
||||
case "business-submit":
|
||||
return this.#showBusinessSubmitForm();
|
||||
default:
|
||||
throw new Error(`Unknown page "${ current }"`);
|
||||
}
|
||||
}
|
||||
|
||||
#nextPage() {
|
||||
switch (this.current) {
|
||||
case "account-type":
|
||||
return this.#form.account_type === 'User'
|
||||
? 'user-account'
|
||||
: 'business-account';
|
||||
case "user-account":
|
||||
return null;
|
||||
case "business-account":
|
||||
return 'business-details';
|
||||
case "business-details":
|
||||
return 'business-items';
|
||||
case "business-items":
|
||||
return 'business-contacts';
|
||||
case "business-contacts":
|
||||
return 'business-submit';
|
||||
case "business-submit":
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#prevPage() {
|
||||
switch (this.current) {
|
||||
case "account-type":
|
||||
return null;
|
||||
case "user-account":
|
||||
return 'account-type';
|
||||
case "business-account":
|
||||
return 'account-type';
|
||||
case "business-details":
|
||||
return 'business-account';
|
||||
case "business-items":
|
||||
return 'business-details';
|
||||
case "business-contacts":
|
||||
return 'business-items';
|
||||
case "business-submit":
|
||||
return 'business-contacts';
|
||||
}
|
||||
}
|
||||
});
|
@ -1,183 +0,0 @@
|
||||
import { PseudoForm } from "../shared.js";
|
||||
|
||||
export class RegisterFormComponent extends PseudoForm {
|
||||
#mounted = false;
|
||||
|
||||
get submitEventName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
mountFormHandler(dispatchForm) {
|
||||
if (this.#mounted) return;
|
||||
this.#mounted = true;
|
||||
|
||||
if (!dispatchForm) dispatchForm = () => this.#dispatchForm();
|
||||
|
||||
const form = this.shadowRoot.querySelector('form');
|
||||
form.addEventListener('submit', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
this.shadowRoot.querySelector('form-navigation').next();
|
||||
});
|
||||
this.addEventListener('form:next', ev => {
|
||||
if (form.reportValidity()) {
|
||||
dispatchForm()
|
||||
} else {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#dispatchForm() {
|
||||
const detail = [...this.elements].filter(el => el.name && el.name.length).reduce((memo, el) => ({
|
||||
...memo,
|
||||
[el.name]: el.value,
|
||||
}), {});
|
||||
this.dispatchEvent(new CustomEvent(this.submitEventName, { composed: true, bubbles: true, detail }));
|
||||
}
|
||||
|
||||
get elements() {
|
||||
return this.shadowRoot.querySelector('form').elements;
|
||||
}
|
||||
}
|
||||
|
||||
export class RegisterForm {
|
||||
#email = ''; //: String,
|
||||
#login = ''; //: String,
|
||||
#password = ''; //: String,
|
||||
#facebook_id = null; //: Option<String>,
|
||||
#account_type = 'User'; //: db::AccountType,
|
||||
#items = null; //: Option<Vec<view::BusinessItemInput>>,
|
||||
#contacts = null; //: Option<Vec<view::CreateContactInfoInput>>,
|
||||
#name = null; //: Option<String>,
|
||||
#description = null; //: Option<String>,
|
||||
|
||||
get email() {
|
||||
return this.#email;
|
||||
}
|
||||
|
||||
set email(v) {
|
||||
this.#email = v;
|
||||
}
|
||||
|
||||
get login() {
|
||||
return this.#login;
|
||||
}
|
||||
|
||||
set login(v) {
|
||||
this.#login = v;
|
||||
}
|
||||
|
||||
get pass() {
|
||||
return this.#password;
|
||||
}
|
||||
|
||||
set pass(v) {
|
||||
this.#password = v;
|
||||
}
|
||||
|
||||
get password() {
|
||||
return this.#password;
|
||||
}
|
||||
|
||||
set password(v) {
|
||||
this.#password = v;
|
||||
}
|
||||
|
||||
get facebook_id() {
|
||||
return this.#facebook_id;
|
||||
}
|
||||
|
||||
set facebook_id(v) {
|
||||
this.#facebook_id = v;
|
||||
}
|
||||
|
||||
get account_type() {
|
||||
return this.#account_type;
|
||||
}
|
||||
|
||||
set account_type(v) {
|
||||
this.#account_type = v;
|
||||
switch (v) {
|
||||
case 'User' : {
|
||||
this.#items = null;
|
||||
this.#contacts = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this.#items || [];
|
||||
}
|
||||
|
||||
set items(a) {
|
||||
this.#items = a;
|
||||
}
|
||||
|
||||
get contacts() {
|
||||
return this.#contacts || [];
|
||||
}
|
||||
|
||||
set contacts(c) {
|
||||
this.#contacts = c;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
set name(v) {
|
||||
this.#name = v;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.#description;
|
||||
}
|
||||
|
||||
set description(v) {
|
||||
this.#description = v;
|
||||
}
|
||||
|
||||
get payload() {
|
||||
return [
|
||||
'email',
|
||||
'login',
|
||||
'password',
|
||||
'facebook_id',
|
||||
'account_type',
|
||||
'items',
|
||||
'contacts',
|
||||
'name',
|
||||
'description'
|
||||
].reduce((m, k) => {
|
||||
const v = this[k];
|
||||
if (v === undefined || v === null) {
|
||||
return m;
|
||||
}
|
||||
m[k] = v;
|
||||
return m;
|
||||
}, {})
|
||||
}
|
||||
|
||||
from(object = {}) {
|
||||
object = object || {};
|
||||
[
|
||||
'email',
|
||||
'login',
|
||||
'password',
|
||||
'facebook_id',
|
||||
'account_type',
|
||||
'items',
|
||||
'contacts',
|
||||
'name',
|
||||
'description'
|
||||
].forEach(key => {
|
||||
const value = object[key];
|
||||
if (!value) return;
|
||||
this[key] = value;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
import { Component, BUTTON_STYLE, TIP_STYLE } from "../shared";
|
||||
|
||||
customElements.define('register-account-type', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ul, li {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
ul > li {
|
||||
min-width: 48%;
|
||||
}
|
||||
a {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: var(--border-slim-color);
|
||||
margin: 8px;
|
||||
}
|
||||
svg, path {
|
||||
fill: var(--border-slim-color) !important;
|
||||
}
|
||||
svg {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
button#accept-terms {
|
||||
width: 80%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: auto auto 16px;
|
||||
}
|
||||
button#accept-terms > a {
|
||||
border: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
#rules {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
svg {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
#rules > a {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
${ BUTTON_STYLE }${ TIP_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section id="rules">
|
||||
<a target="_blank" href="/terms-and-condition">
|
||||
Regulamin
|
||||
</a>
|
||||
<a target="_blank" href="/privacy-policy">
|
||||
Polityka prywatności
|
||||
</a>
|
||||
</section>
|
||||
<button id="accept-terms">
|
||||
Zapoznałem się i zgadzam się
|
||||
</button>
|
||||
</article>
|
||||
`);
|
||||
|
||||
const article = this.shadowRoot.querySelector('article');
|
||||
article.querySelector('button').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
this.#termsAccepted();
|
||||
})
|
||||
}
|
||||
|
||||
#termsAccepted() {
|
||||
const article = this.shadowRoot.querySelector('article');
|
||||
|
||||
article.innerHTML = `
|
||||
<ul>
|
||||
<li>
|
||||
<a id="user">
|
||||
<svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" xml:space="preserve">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5.875a3.625 3.625 0 0 0-1.006 7.109c-1.194.145-2.218.567-2.99 1.328-.982.967-1.479 2.408-1.479 4.288a.475.475 0 1 0 .95 0c0-1.72.453-2.88 1.196-3.612.744-.733 1.856-1.113 3.329-1.113s2.585.38 3.33 1.113c.742.733 1.195 1.892 1.195 3.612a.475.475 0 1 0 .95 0c0-1.88-.497-3.32-1.48-4.288-.77-.76-1.795-1.183-2.989-1.328A3.627 3.627 0 0 0 7.5.875ZM4.825 4.5a2.675 2.675 0 1 1 5.35 0 2.675 2.675 0 0 1-5.35 0Z" fill="currentColor"/>
|
||||
</svg>
|
||||
<div>Użytkownik</div>
|
||||
<div class="tip">Zwykły użytkownik z opcją wystawiania niepotrzebnych prywatnych rzeczy na sprzedaż</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="local-service">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 489.4 489.4" xml:space="preserve">
|
||||
<path d="M347.7 263.75h-66.5c-18.2 0-33 14.8-33 33v51c0 18.2 14.8 33 33 33h66.5c18.2 0 33-14.8 33-33v-51c0-18.2-14.8-33-33-33zm9 84c0 5-4.1 9-9 9h-66.5c-5 0-9-4.1-9-9v-51c0-5 4.1-9 9-9h66.5c5 0 9 4.1 9 9v51z"/>
|
||||
<path d="M489.4 171.05c0-2.1-.5-4.1-1.6-5.9l-72.8-128c-2.1-3.7-6.1-6.1-10.4-6.1H84.7c-4.3 0-8.3 2.3-10.4 6.1l-72.7 128c-1 1.8-1.6 3.8-1.6 5.9 0 28.7 17.3 53.3 42 64.2v211.1c0 6.6 5.4 12 12 12h381.3c6.6 0 12-5.4 12-12v-209.6c0-.5 0-.9-.1-1.3 24.8-10.9 42.2-35.6 42.2-64.4zM91.7 55.15h305.9l56.9 100.1H34.9l56.8-100.1zm256.6 124c-3.8 21.6-22.7 38-45.4 38s-41.6-16.4-45.4-38h90.8zm-116.3 0c-3.8 21.6-22.7 38-45.4 38s-41.6-16.4-45.5-38H232zm-207.2 0h90.9c-3.8 21.6-22.8 38-45.5 38-22.7.1-41.6-16.4-45.4-38zm176.8 255.2h-69v-129.5c0-9.4 7.6-17.1 17.1-17.1h34.9c9.4 0 17.1 7.6 17.1 17.1v129.5h-.1zm221.7 0H225.6v-129.5c0-22.6-18.4-41.1-41.1-41.1h-34.9c-22.6 0-41.1 18.4-41.1 41.1v129.6H66v-193.3c1.4.1 2.8.1 4.2.1 24.2 0 45.6-12.3 58.2-31 12.6 18.7 34 31 58.2 31s45.5-12.3 58.2-31c12.6 18.7 34 31 58.1 31 24.2 0 45.5-12.3 58.1-31 12.6 18.7 34 31 58.2 31 1.4 0 2.7-.1 4.1-.1v193.2zm-4.1-217.1c-22.7 0-41.6-16.4-45.4-38h90.9c-3.9 21.5-22.8 38-45.5 38z"/>
|
||||
</svg>
|
||||
<div>Usługodawca</div>
|
||||
<div class="tip">Usługodawca posiadający stałe usługi lub produkty w ofercie</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<form-navigation style="display: none;"></form-navigation>
|
||||
`;
|
||||
|
||||
const user = this.shadowRoot.querySelector('#user');
|
||||
user.addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('account:type', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { account_type: 'User' }
|
||||
}));
|
||||
this.shadowRoot.querySelector('form-navigation').next();
|
||||
});
|
||||
const service = this.shadowRoot.querySelector('#local-service');
|
||||
service.addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('account:type', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { account_type: 'Business' }
|
||||
}));
|
||||
this.shadowRoot.querySelector('form-navigation').next();
|
||||
});
|
||||
}
|
||||
});
|
@ -1,57 +0,0 @@
|
||||
import { FORM_STYLE } from "../shared";
|
||||
import { RegisterFormComponent } from "./model";
|
||||
|
||||
customElements.define('register-business-account-form', class extends RegisterFormComponent {
|
||||
static get observedAttributes() {
|
||||
return ['login', 'password', 'email']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<form id="step-1">
|
||||
<div>
|
||||
<label>Login</label>
|
||||
<input id="login" name="login" placeholder="Login" type="text" required autofocus />
|
||||
</div>
|
||||
<div>
|
||||
<label>E-Mail</label>
|
||||
<input id="email" name="email" placeholder="Email" type="email" required />
|
||||
</div>
|
||||
<div>
|
||||
<label>Hasło</label>
|
||||
<input id="password" name="pass" placeholder="Hasło" type="password" required />
|
||||
</div>
|
||||
<input type="submit" style="display: none">
|
||||
<form-navigation prev="hidden" next="right"></form-navigation>
|
||||
</form>
|
||||
`);
|
||||
|
||||
this.mountFormHandler();
|
||||
}
|
||||
|
||||
get submitEventName() {
|
||||
return 'account:basic';
|
||||
}
|
||||
|
||||
set email(v) {
|
||||
this.#input('email').value = v;
|
||||
}
|
||||
|
||||
set login(v) {
|
||||
this.#input('login').value = v;
|
||||
}
|
||||
|
||||
set password(v) {
|
||||
this.#input('password').value = v;
|
||||
}
|
||||
|
||||
#input(id) {
|
||||
return this.shadowRoot.querySelector(`#${ id }`);
|
||||
}
|
||||
});
|
@ -1,90 +0,0 @@
|
||||
import { BUTTON_STYLE } from "../shared.js";
|
||||
import { RegisterFormComponent } from "./model.js";
|
||||
|
||||
customElements.define('register-business-contacts-form', class extends RegisterFormComponent {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
contact-info-editor > input[type='button'] {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
::slotted(edit-contact-info) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
#form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<h2>Edycja listy danych kontaktowych</h2>
|
||||
<p>
|
||||
Adres e-mail podany w formularzu będzie domyślnym adresem kontaktowym.
|
||||
Tutaj możesz dodać dodatkowe sposoby kontaktu takie jak numer telefonu lub link do facebook'a.
|
||||
</p>
|
||||
<form>
|
||||
<section id="form">
|
||||
<contact-info-editor
|
||||
save="false"
|
||||
>
|
||||
<input type="button" id="addButton" value="Dodaj" />
|
||||
</contact-info-editor>
|
||||
</section>
|
||||
<slot></slot>
|
||||
<form-navigation></form-navigation>
|
||||
</form>
|
||||
</article>
|
||||
`);
|
||||
|
||||
const editor = this.shadowRoot.querySelector('contact-info-editor');
|
||||
editor.addEventListener('submit', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.#addContact(editor);
|
||||
});
|
||||
editor.querySelector('#addButton').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.#addContact(editor);
|
||||
});
|
||||
|
||||
this.mountFormHandler(() => this.#emitChange());
|
||||
}
|
||||
|
||||
get submitEventName() {
|
||||
return 'account:contacts';
|
||||
}
|
||||
|
||||
get #rows() {
|
||||
return Array.from(this.querySelectorAll('contact-info'));
|
||||
}
|
||||
|
||||
#addContact(editor) {
|
||||
const { type, content } = editor;
|
||||
this.innerHTML += `
|
||||
<edit-contact-info mode="view" delete="false">
|
||||
<contact-info
|
||||
type="${ type }"
|
||||
content="${ content }"
|
||||
></contact-info>
|
||||
</edit-contact-info>
|
||||
`;
|
||||
}
|
||||
|
||||
#emitChange() {
|
||||
const rows = this.#rows;
|
||||
const contacts = rows.map(({ type, content }) => ({
|
||||
type,
|
||||
content
|
||||
}));
|
||||
this.dispatchEvent(new CustomEvent(this.submitEventName, {
|
||||
bubbles: true, composed: true, detail: { contacts: contacts.length ? contacts : null },
|
||||
}));
|
||||
}
|
||||
});
|
@ -1,53 +0,0 @@
|
||||
import { FORM_STYLE, TIP_STYLE } from "../shared";
|
||||
import { RegisterFormComponent } from "./model";
|
||||
|
||||
customElements.define('register-business-details-form', class extends RegisterFormComponent {
|
||||
static get observedAttributes() {
|
||||
return ["name", "description"];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
textarea { min-height: 200px; }
|
||||
form > div > input,
|
||||
form > div > textarea {
|
||||
width: 100%;
|
||||
}
|
||||
${ FORM_STYLE }${ TIP_STYLE }
|
||||
</style>
|
||||
<form id="step-2">
|
||||
<div>
|
||||
<label>Nazwa usługodawcy</label>
|
||||
<input id="name" name="name" placeholder="Nazwa usługi" type="text" required autofocus />
|
||||
</div>
|
||||
<div>
|
||||
<label>Opis usługodawcy</label>
|
||||
<textarea id="description" name="description" required></textarea>
|
||||
<div class="tip">Produkty dodawane są w nastepnym kroku</div>
|
||||
</div>
|
||||
<form-navigation></form-navigation>
|
||||
</form>
|
||||
`);
|
||||
|
||||
this.mountFormHandler();
|
||||
}
|
||||
|
||||
get submitEventName() {
|
||||
return 'account:business';
|
||||
}
|
||||
|
||||
set name(v) {
|
||||
this.#input('name').value = v;
|
||||
}
|
||||
|
||||
set description(v) {
|
||||
this.#input('description').value = (v || '').trim();
|
||||
}
|
||||
|
||||
#input(id) {
|
||||
return this.shadowRoot.querySelector(`#${ id }`);
|
||||
}
|
||||
})
|
@ -1,204 +0,0 @@
|
||||
import { FORM_STYLE } from "../shared";
|
||||
import { RegisterFormComponent } from "./model";
|
||||
|
||||
customElements.define('register-business-item-form', class extends RegisterFormComponent {
|
||||
static get observedAttributes() {
|
||||
return ['idx', 'name', 'price', 'picture-url', 'action', 'remove', 'save']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
section > form input[type=button], section > form input[type=submit] {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
form > #fields > div, form > #fields > div > label, form > #fields > div > .input {
|
||||
width: 90% !important;
|
||||
}
|
||||
@media only screen and (min-device-width: 1200px) {
|
||||
section > form {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
section > form input[type=button], section > form input[type=submit] {
|
||||
width: 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
form > #fields {
|
||||
width: 50%;
|
||||
}
|
||||
form > #fields > div, form > #fields > div > label, form > #fields > div > .input {
|
||||
width: 90% !important;
|
||||
}
|
||||
}
|
||||
img[src=""] { display: none; }
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<section>
|
||||
<form method="post">
|
||||
<slot name="head"></slot>
|
||||
<image-input send-original="true"></image-input>
|
||||
<div id="fields">
|
||||
<div id="name">
|
||||
<label>Nazwa</label>
|
||||
<input id="name" class="item-name" name="name" type="text" required />
|
||||
</div>
|
||||
<div id="priceWrapper">
|
||||
<label>Cena</label>
|
||||
<price-input id="price" class="item-price" name="price" required >
|
||||
</price-input>
|
||||
</div>
|
||||
</div>
|
||||
<input id="submit-button" type="submit" value="Zapisz" />
|
||||
<input id="remove-button" type="submit" value="Usuń" />
|
||||
|
||||
<input type="hidden" name="picture_url" id="picture_url" />
|
||||
<slot name="tail"></slot>
|
||||
</form>
|
||||
</section>
|
||||
`);
|
||||
|
||||
const imageInput = this.shadowRoot.querySelector('image-input');
|
||||
this.addEventListener('item:removed', () => {
|
||||
this.setAttribute('removed', 'removed');
|
||||
const parent = this.parentElement;
|
||||
this.remove();
|
||||
parent.dispatchEvent(new CustomEvent('item:removed', { bubbles: true, composed: true }));
|
||||
});
|
||||
this.addEventListener('image-input:uploaded', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.picture_url = imageInput.url;
|
||||
});
|
||||
|
||||
this.shadowRoot.querySelector('form').addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
const input = ev.target;
|
||||
const { name, value } = input;
|
||||
|
||||
switch (name) {
|
||||
case 'price': {
|
||||
this.price = parseInt(value);
|
||||
break;
|
||||
}
|
||||
case 'name': {
|
||||
this.name = value;
|
||||
break;
|
||||
}
|
||||
case 'picture_url': {
|
||||
this.picture_url = value;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.shadowRoot.querySelector('form').addEventListener('submit', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
if (this.reportValidity()) {
|
||||
const detail = {
|
||||
name: this.name,
|
||||
price: this.price,
|
||||
picture_url: this.picture_url,
|
||||
item_order: this.idx,
|
||||
};
|
||||
this.dispatchEvent(new CustomEvent('item:submit', { bubbles: true, composed: true, detail }));
|
||||
}
|
||||
});
|
||||
this.shadowRoot.querySelector('#remove-button').addEventListener('click', ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.dispatchEvent(new CustomEvent('item:removed', { bubbles: true, composed: false }));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get submitEventName() {
|
||||
return 'account:items';
|
||||
}
|
||||
|
||||
static attr2Field(name) {
|
||||
const field = super.attr2Field(name);
|
||||
if (field === 'remove') return 'showRemove';
|
||||
if (field === 'save') return 'showSave';
|
||||
return field;
|
||||
}
|
||||
|
||||
get idx() {
|
||||
const idx = parseInt(this.getAttribute('idx'));
|
||||
return isNaN(idx) ? null : idx;
|
||||
}
|
||||
|
||||
set idx(idx) {
|
||||
this.setAttribute('idx', idx);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.getAttribute('name');
|
||||
}
|
||||
|
||||
set name(v) {
|
||||
this.setAttribute('name', v);
|
||||
this.shadowRoot.querySelector('.item-name').value = v;
|
||||
}
|
||||
|
||||
get price() {
|
||||
return this.getAttribute('price');
|
||||
}
|
||||
|
||||
set price(v) {
|
||||
v = parseInt(v);
|
||||
this.setAttribute('price', v);
|
||||
this.shadowRoot.querySelector('#price').value = v / 100.0;
|
||||
}
|
||||
|
||||
get picture_url() {
|
||||
return this.getAttribute('picture-url');
|
||||
}
|
||||
|
||||
set picture_url(v) {
|
||||
if (!(v || '').startsWith("/")) v = '';
|
||||
this.setAttribute('picture-url', v);
|
||||
this.shadowRoot.querySelector('image-input').url = v;
|
||||
this.shadowRoot.querySelector('#picture_url').value = v;
|
||||
}
|
||||
|
||||
get action() {
|
||||
return this.shadowRoot.querySelector('form').action;
|
||||
}
|
||||
|
||||
set action(v) {
|
||||
this.shadowRoot.querySelector('form').action = v;
|
||||
}
|
||||
|
||||
get showRemove() {
|
||||
return this.getAttribute('remove') !== 'hidden';
|
||||
}
|
||||
|
||||
set showRemove(v) {
|
||||
this.setAttribute('remove', v === 'show' || v === true ? 'show' : 'hidden');
|
||||
|
||||
this.shadowRoot.querySelector('#remove-button').setAttribute('type', this.showRemove ? 'submit' : 'hidden');
|
||||
}
|
||||
|
||||
get showSave() {
|
||||
return this.getAttribute('save') === 'show';
|
||||
}
|
||||
|
||||
set showSave(v) {
|
||||
this.setAttribute('save', v === 'show' || v === true ? 'show' : 'hidden');
|
||||
|
||||
this.shadowRoot.querySelector('#submit-button').setAttribute('type', this.showSave ? 'submit' : 'hidden');
|
||||
}
|
||||
|
||||
reportValidity() {
|
||||
return super.reportValidity() && this.shadowRoot.querySelector('price-input').reportValidity();
|
||||
}
|
||||
});
|
||||
|
||||
const extract = ({ name, value }) => ({ name, value })
|
@ -1,78 +0,0 @@
|
||||
import { FORM_STYLE } from "../shared";
|
||||
import { RegisterFormComponent } from "./model";
|
||||
|
||||
const updateItems = (rows) => {
|
||||
let idx = 0;
|
||||
for (const el of rows) {
|
||||
el.idx = idx++;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
customElements.define('register-business-items-form', class extends RegisterFormComponent {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
|
||||
::slotted(section) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
::slotted(register-business-item-form) {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
form input[type=button], form input[type=submit] {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<form>
|
||||
<article id="items">
|
||||
<slot></slot>
|
||||
</article>
|
||||
<div>
|
||||
<input type="button" id="add-item" value="Dodaj usługę/produkt" />
|
||||
</div>
|
||||
<input type="submit" style="display: none" />
|
||||
<form-navigation></form-navigation>
|
||||
</form>
|
||||
`);
|
||||
this.addEventListener('item:removed', ev => {
|
||||
ev.stopPropagation();
|
||||
updateItems(this.#rows)
|
||||
});
|
||||
this.addEventListener('form:next', ev => {
|
||||
updateItems(this.#rows);
|
||||
for (const el of this.#rows) {
|
||||
if (!el.reportValidity()) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.shadowRoot.querySelector('#add-item').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.appendChild(document.createElement('register-business-item-form'));
|
||||
updateItems(this.#rows);
|
||||
});
|
||||
this.mountFormHandler(() => this.#emitChange());
|
||||
}
|
||||
|
||||
get submitEventName() {
|
||||
return 'account:items';
|
||||
}
|
||||
|
||||
get #rows() {
|
||||
return Array.from(this.querySelectorAll("register-business-item-form"));
|
||||
}
|
||||
|
||||
#emitChange() {
|
||||
const items = this.#rows.map(({ price, picture_url, name }) => ({ price, picture_url, name }));
|
||||
this.dispatchEvent(new CustomEvent(this.submitEventName, { bubbles: true, composed: true, detail: { items } }));
|
||||
}
|
||||
});
|
@ -1,173 +0,0 @@
|
||||
import { FORM_STYLE, PseudoForm } from "../shared.js";
|
||||
import { ErrorMessage } from "../shared/error-message.js";
|
||||
import * as api from "../api.js";
|
||||
|
||||
customElements.define('register-business-submit-form', class extends PseudoForm {
|
||||
static get observedAttributes() {
|
||||
return ['name', 'description', 'login', 'email', 'password', 'account-type']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
${ FORM_STYLE }
|
||||
img[src=''] { display: none; }
|
||||
input[type=submit] {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
form > .field, form > .field > label, form > .field > input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
form > .field {
|
||||
display: flex;
|
||||
}
|
||||
form > .field > label {
|
||||
min-width: 200px;
|
||||
}
|
||||
form > .field > input {
|
||||
align-self: stretch;
|
||||
width: calc(100% - 220px);
|
||||
}
|
||||
.item-view {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.item-view > * {
|
||||
min-width: 100px;
|
||||
max-width: 48%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<form method="post" action="/register">
|
||||
<div class="field">
|
||||
<label>Login</label>
|
||||
<input readonly id="login">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Email</label>
|
||||
<input readonly id="email">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Password</label>
|
||||
<input readonly id="password" type="password">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input readonly id="name">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Description</label>
|
||||
<input readonly id="description">
|
||||
</div>
|
||||
<input type="hidden" name="account_type" id="account_type" value="Business" />
|
||||
|
||||
<div id="contacts">
|
||||
<h3>Dane kontaktowe</h3>
|
||||
<slot name="contacts"></slot>
|
||||
</div>
|
||||
|
||||
<div id="items">
|
||||
<h3>Produkty/usługi</h3>
|
||||
<slot name="items"></slot>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<form-navigation next="hidden"></form-navigation>
|
||||
<input type="submit" value="Utwórz konto" />
|
||||
</div>
|
||||
</form>
|
||||
`);
|
||||
this.shadowRoot.querySelector('form').addEventListener('submit', async (ev) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
const { error } = await api.register(this.#form);
|
||||
console.info(error);
|
||||
|
||||
if (!error) {
|
||||
// Router.goTo("/account?success");
|
||||
location.href = '/account?success';
|
||||
} else {
|
||||
ErrorMessage.errorMessage = error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
set account_type(v) {
|
||||
this.shadowRoot.querySelector('#account_type').value = v;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.#hidden('name').value
|
||||
}
|
||||
|
||||
set name(v) {
|
||||
this.#hidden('name').value = v;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.#hidden('description').value
|
||||
}
|
||||
|
||||
set description(v) {
|
||||
this.#hidden('description').value = v;
|
||||
}
|
||||
|
||||
get login() {
|
||||
return this.#hidden('login').value
|
||||
}
|
||||
|
||||
set login(v) {
|
||||
this.#hidden('login').value = v;
|
||||
}
|
||||
|
||||
get email() {
|
||||
return this.#hidden('email').value
|
||||
}
|
||||
|
||||
set email(v) {
|
||||
this.#hidden('email').value = v;
|
||||
}
|
||||
|
||||
get password() {
|
||||
return this.#hidden('password').value
|
||||
}
|
||||
|
||||
set password(v) {
|
||||
this.#hidden('password').value = v;
|
||||
}
|
||||
|
||||
#hidden(selector) {
|
||||
return this.shadowRoot.querySelector(`#${ selector }`)
|
||||
}
|
||||
|
||||
get #form() {
|
||||
return [
|
||||
'login',
|
||||
'email',
|
||||
'password',
|
||||
'name',
|
||||
'description',
|
||||
'account_type',
|
||||
].reduce((memo, name) => ({
|
||||
...memo,
|
||||
[name]: this.#hidden(name).value,
|
||||
}), {
|
||||
items: Array.from(this.querySelectorAll('local-business-item')).map(({ name, price, picture_url }) => ({
|
||||
name, price, picture_url
|
||||
})),
|
||||
contacts: Array.from(this.querySelectorAll('contact-info')).map(({ content, type }) => ({
|
||||
content,
|
||||
type
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
get payload() {
|
||||
return this.#form
|
||||
}
|
||||
});
|
@ -1,159 +0,0 @@
|
||||
import { FORM_STYLE, Router } from "../shared.js";
|
||||
import { RegisterFormComponent } from "./model.js";
|
||||
import { ErrorMessage } from "../shared/error-message";
|
||||
|
||||
customElements.define('register-user-account-form', class extends RegisterFormComponent {
|
||||
static get observedAttributes() {
|
||||
return ['mode', 'login', 'password', 'email']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
svg > path {
|
||||
fill: var(--border-slim-color);
|
||||
}
|
||||
.option {
|
||||
display: block;
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: var(--border-slim-color);
|
||||
margin: 8px;
|
||||
}
|
||||
#or {
|
||||
text-align: center;
|
||||
}
|
||||
@media only screen and (min-device-width: 1200px) {
|
||||
#formSection svg {
|
||||
width: 200px;
|
||||
display: inline-block;
|
||||
}
|
||||
#social {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
#social > div {
|
||||
margin-right: 16px;
|
||||
}
|
||||
facebook-button {
|
||||
width: 64px;
|
||||
}
|
||||
facebook-button[fb-sdk="false"] {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<h2>Utwórz konto</h2>
|
||||
<section id="formSection">
|
||||
<form id="form" method="post" action="/register">
|
||||
<input type="hidden" id="account_type" name="account_type" value="User">
|
||||
<input type="hidden" id="facebook_id" name="facebook_id">
|
||||
<div>
|
||||
<label>E-Mail</label>
|
||||
<input id="email" type="email" name="email">
|
||||
</div>
|
||||
<div>
|
||||
<label>Login</label>
|
||||
<input id="login" type="text" name="login">
|
||||
</div>
|
||||
<div>
|
||||
<label>Hasło</label>
|
||||
<input id="password" type="password" name="password">
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Zarejestruj" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="social">
|
||||
<div id="or">lub zaloguj się za pomocą:</div>
|
||||
<facebook-button></facebook-button>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
|
||||
const form = this.shadowRoot.querySelector('form');
|
||||
|
||||
this.addEventListener('facebook:account', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.mode = 'facebook';
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
form.addEventListener('submit', async ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
const json = Array.from(form.elements).filter(el => el.name && el.name.trim().length).reduce((memo, {
|
||||
name,
|
||||
value
|
||||
}) => ({
|
||||
...memo,
|
||||
[name]: value.trim(),
|
||||
}), {});
|
||||
|
||||
const res = await fetch('/register', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(json),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
// Router.goTo("/account?success");
|
||||
location.href ='/account?success';
|
||||
} else {
|
||||
const { error } = await res.json();
|
||||
ErrorMessage.errorMessage = error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get submitEventName() {
|
||||
return 'account:user:details';
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.mode = 'email';
|
||||
}
|
||||
|
||||
get mode() {
|
||||
return this.getAttribute('mode') || ''
|
||||
}
|
||||
|
||||
set mode(v) {
|
||||
this.setAttribute('mode', v);
|
||||
}
|
||||
|
||||
set email(v) {
|
||||
this.#input('email').value = v;
|
||||
}
|
||||
|
||||
set login(v) {
|
||||
this.#input('login').value = v;
|
||||
}
|
||||
|
||||
set password(v) {
|
||||
this.#input('password').value = v;
|
||||
}
|
||||
|
||||
#input(id) {
|
||||
return this.shadowRoot.querySelector(`#${ id }`);
|
||||
}
|
||||
});
|
@ -1,328 +0,0 @@
|
||||
export const BUTTON_STYLE = `
|
||||
input[type="button"], input[type="submit"], button {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
width: auto;
|
||||
height: calc(1.5em + 0.75rem + 2px);
|
||||
border: 1px solid black;
|
||||
color: white;
|
||||
background: black;
|
||||
padding: 10px 20px;
|
||||
font-family: "Cardo", Sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 1em;
|
||||
letter-spacing: 0;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
width: auto;
|
||||
height: calc(1.5em + 0.75rem + 2px);
|
||||
border: 1px solid black;
|
||||
color: white;
|
||||
background: black;
|
||||
padding: 10px 20px;
|
||||
font-family: "Cardo", Sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 1em;
|
||||
letter-spacing: 0;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
a.btn {
|
||||
text-decoration: none;
|
||||
line-height: 2;
|
||||
height: auto;
|
||||
}
|
||||
input.link {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
input.link:hover {
|
||||
color: var(--hover-color);
|
||||
}
|
||||
`;
|
||||
|
||||
export const INPUT_STYLE = `
|
||||
input, textarea, select, option {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--ast-border-color);
|
||||
padding: 6px 6px 5px;
|
||||
margin: 0 4px 0 0;
|
||||
outline: 0;
|
||||
line-height: 1;
|
||||
|
||||
color: #666;
|
||||
padding: .75em;
|
||||
height: auto;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(--ast-border-color);
|
||||
border-radius: 2px;
|
||||
background: #fafafa;
|
||||
background-color: rgb(250, 250, 250);
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
transition: all .2s linear;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
textarea {
|
||||
width: calc(100% - 1.5rem - 2px);
|
||||
}
|
||||
`;
|
||||
|
||||
export const BLOCK_QUOTE_STYLE = `
|
||||
blockquote {
|
||||
font: 14px/22px normal helvetica, sans-serif;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-left: 50px;
|
||||
padding-left: 15px;
|
||||
border-left: 3px solid var(--border-slim-color);
|
||||
}
|
||||
`;
|
||||
|
||||
export const FORM_STYLE = `
|
||||
form {
|
||||
display: block;
|
||||
}
|
||||
form legend {
|
||||
margin: 16px 0;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
form.inline div {
|
||||
display: flex;
|
||||
}
|
||||
form > div {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
label {
|
||||
color: #000;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
|
||||
display: inline-block;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
${ INPUT_STYLE }
|
||||
${ BUTTON_STYLE }
|
||||
`;
|
||||
export const TIP_STYLE = `.tip { text-align: center; font-style: italic; font-size: 10px; color: var(--border-slim-color); }`;
|
||||
|
||||
export class Component extends HTMLElement {
|
||||
#shadow;
|
||||
|
||||
static get observedAttributes() {
|
||||
return []
|
||||
}
|
||||
|
||||
constructor(html) {
|
||||
super();
|
||||
|
||||
this.#shadow = this.attachShadow({ mode: 'open' });
|
||||
this.#shadow.innerHTML = html;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const observed = this.constructor.observedAttributes;
|
||||
if (!Array.isArray(observed))
|
||||
return;
|
||||
for (const name of observed) {
|
||||
const field = this.constructor.attr2Field(name);
|
||||
if (!field) continue;
|
||||
this.#setFieldValue(field, this[field]);
|
||||
}
|
||||
{
|
||||
const listener = this.listenHistory;
|
||||
listener && listener(Router.historyDetails());
|
||||
this.#dropHistory = Router.subscribeHistory(listener);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this.#dropHistory) this.#dropHistory();
|
||||
}
|
||||
|
||||
#dropHistory;
|
||||
|
||||
listenHistory() {
|
||||
}
|
||||
|
||||
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.#setFieldValue(field, newV);
|
||||
}
|
||||
|
||||
static attr2Field(name) {
|
||||
if ((this.constructor['attr2FieldBlacklist'] || []).includes(name))
|
||||
return;
|
||||
return name.replace(/-/g, '_');
|
||||
}
|
||||
|
||||
static get attr2FieldBlacklist() {
|
||||
return []
|
||||
}
|
||||
|
||||
#setFieldValue(name, value) {
|
||||
if (value === undefined || value === null) return;
|
||||
this[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
export class PseudoForm extends Component {
|
||||
reportValidity() {
|
||||
return this.shadowRoot.querySelector('form').reportValidity();
|
||||
}
|
||||
|
||||
checkValidity() {
|
||||
return this.shadowRoot.querySelector('form').checkValidity();
|
||||
}
|
||||
|
||||
get elements() {
|
||||
return this.shadowRoot.querySelector('form').elements;
|
||||
}
|
||||
}
|
||||
|
||||
export class PriceRange {
|
||||
#min;
|
||||
#max;
|
||||
|
||||
constructor(min, max) {
|
||||
this.min = min || 0;
|
||||
this.max = max || 0;
|
||||
}
|
||||
|
||||
get isFree() {
|
||||
return this.#min === 'free' || (this.#min === 0 && this.#max === 0)
|
||||
}
|
||||
|
||||
get isRange() {
|
||||
return this.#max > 0;
|
||||
}
|
||||
|
||||
get isFixed() {
|
||||
return !this.isFree && !this.isRange
|
||||
}
|
||||
|
||||
get min() {
|
||||
return this.#min
|
||||
}
|
||||
|
||||
set min(v) {
|
||||
v = parseInt(v);
|
||||
if (isNaN(v)) v = 0;
|
||||
this.#min = v;
|
||||
}
|
||||
|
||||
get max() {
|
||||
return this.#max
|
||||
}
|
||||
|
||||
set max(v) {
|
||||
v = parseInt(v);
|
||||
if (isNaN(v)) v = 0;
|
||||
this.#max = v;
|
||||
}
|
||||
|
||||
[Symbol.toStringTag]() {
|
||||
return this.toString()
|
||||
}
|
||||
|
||||
toString() {
|
||||
if (this.isFixed) return this.min.toString()
|
||||
if (this.isFree) return 'free';
|
||||
return `${ this.min }|${ this.max }`;
|
||||
}
|
||||
}
|
||||
|
||||
export const fireFbReady = () => {
|
||||
fbReady = true;
|
||||
for (const fn of fbQueue) fn();
|
||||
};
|
||||
export const runFbReady = (fn) => {
|
||||
if (!fbReady) fbQueue.push(fn);
|
||||
else fn();
|
||||
};
|
||||
const fbQueue = [];
|
||||
let fbReady = false;
|
||||
|
||||
export class Router {
|
||||
static historyQueue = new Set;
|
||||
|
||||
static goTo(url) {
|
||||
history.pushState({}, document.title, url);
|
||||
Router.onChange();
|
||||
}
|
||||
|
||||
static historyDetails() {
|
||||
return {
|
||||
parts: location.pathname.split('/').filter(s => s && s.length),
|
||||
}
|
||||
}
|
||||
|
||||
static subscribeHistory(cb) {
|
||||
if (cb) {
|
||||
const call = () => {
|
||||
cb(Router.historyDetails());
|
||||
}
|
||||
Router.historyQueue.add(call);
|
||||
return () => Router.historyQueue.delete(call);
|
||||
}
|
||||
}
|
||||
|
||||
static onChange() {
|
||||
for (const call of Router.historyQueue) call();
|
||||
document.dispatchEvent(new CustomEvent('history:push', {
|
||||
composed: true,
|
||||
bubbles: true,
|
||||
details: Router.historyDetails()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', () => Router.onChange());
|
||||
|
||||
export const onKeyDown = (input, callback) => {
|
||||
let timeout;
|
||||
input.addEventListener('change', ev => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = null;
|
||||
|
||||
ev.stopPropagation();
|
||||
callback(ev, input)
|
||||
});
|
||||
input.addEventListener('keyup', ev => {
|
||||
ev.stopPropagation();
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
callback(ev, input);
|
||||
}, 1000 / 3)
|
||||
});
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('date-time', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['datetime', "hide-date", "hide-time"]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
section { display: flex; justify-content: flex-start; }
|
||||
:host([hide-time = "true"]) #time { display: none; }
|
||||
:host([hide-time = "false"]) #time { margin-left: 8px; }
|
||||
:host([hide-date = "true"]) #date { display: none; }
|
||||
</style>
|
||||
<section>
|
||||
<div id="date"></div>
|
||||
<div id="time"></div>
|
||||
</section>
|
||||
`);
|
||||
}
|
||||
|
||||
get datetime() {
|
||||
return Date.parse(this.getAttribute('datetime'));
|
||||
}
|
||||
|
||||
set datetime(v) {
|
||||
this.setAttribute('datetime', v);
|
||||
this.#format();
|
||||
}
|
||||
|
||||
get hide_date() {
|
||||
return this.getAttribute('hide-date') === 'true';
|
||||
}
|
||||
|
||||
set hide_date(v) {
|
||||
this.setAttribute('hide-date', v === true || v === 'true' ? 'true' : 'false');
|
||||
this.#format();
|
||||
}
|
||||
|
||||
get hide_time() {
|
||||
return this.getAttribute('hide-time') === 'true';
|
||||
}
|
||||
|
||||
set hide_time(v) {
|
||||
this.setAttribute('hide-time', v === true || v === 'true' ? 'true' : 'false');
|
||||
this.#format();
|
||||
}
|
||||
|
||||
#format() {
|
||||
{
|
||||
const el = this.shadowRoot.querySelector('#date');
|
||||
if (!this.hide_date)
|
||||
el.textContent = new Date().toLocaleDateString(navigator.language || navigator.userLanguage, {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric"
|
||||
});
|
||||
else
|
||||
el.textContent = '';
|
||||
}
|
||||
{
|
||||
const el = this.shadowRoot.querySelector('#time');
|
||||
if (!this.hide_date)
|
||||
el.textContent = new Date().toLocaleDateString(navigator.language || navigator.userLanguage, {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
date: "short"
|
||||
}).split(' ')[1];
|
||||
else
|
||||
el.textContent = '';
|
||||
}
|
||||
}
|
||||
});
|
@ -1,42 +0,0 @@
|
||||
import { Component } from "../shared.js";
|
||||
|
||||
export class ErrorMessage extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['message'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: none; }
|
||||
:host([message]) { display: block; }
|
||||
div {
|
||||
width: 1280px;
|
||||
background: #ffe0e0;
|
||||
border: 1px solid var(--red-color);
|
||||
margin: 8px auto auto;
|
||||
padding: 8px;
|
||||
color: var(--red-color);
|
||||
}
|
||||
</style>
|
||||
<div></div>
|
||||
`);
|
||||
}
|
||||
|
||||
get message() {
|
||||
return this.getAttribute('message');
|
||||
}
|
||||
|
||||
set message(m) {
|
||||
this.setAttribute('message', m);
|
||||
this.shadowRoot.querySelector('div').textContent = m;
|
||||
}
|
||||
|
||||
static set errorMessage(v) {
|
||||
const el = document.querySelector('error-message');
|
||||
if (!el) return;
|
||||
el.message = v;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('error-message', ErrorMessage)
|
@ -1,97 +0,0 @@
|
||||
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;
|
||||
cursor: pointer;
|
||||
}
|
||||
#not-available {
|
||||
display: block;
|
||||
}
|
||||
#svg {
|
||||
display: none;
|
||||
}
|
||||
svg path {
|
||||
fill: var(--border-slim-color);
|
||||
}
|
||||
:host([fb-sdk="available"]) #not-available {
|
||||
display: none;
|
||||
}
|
||||
:host([fb-sdk="available"]) #svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<p id="not-available">Skrypty Facebook są 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" 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.warn("Facebook SDK is not available");
|
||||
FB.login((res) => {
|
||||
if (res.status === 'connected') {
|
||||
FB.api("/me?fields=id,name,email", ({ 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';
|
||||
}
|
||||
});
|
@ -1,85 +0,0 @@
|
||||
import { Component, BUTTON_STYLE } from "../shared";
|
||||
|
||||
customElements.define('form-navigation', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['next', 'prev']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 16px 0;
|
||||
}
|
||||
form > .actions > input.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
form > .actions > input {
|
||||
width: calc(50% - 16px);
|
||||
max-width: 200px;
|
||||
}
|
||||
:host([next=right]) .actions {
|
||||
justify-content: end;
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
form > .actions > input {
|
||||
width: auto;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<form>
|
||||
<div class="actions">
|
||||
<input id="prev" type="button" value="Wróć" />
|
||||
<input id="next" type="submit" value="Następny" />
|
||||
</div>
|
||||
</form>
|
||||
`);
|
||||
|
||||
this.shadowRoot.querySelector('#prev').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.prev();
|
||||
});
|
||||
this.shadowRoot.querySelector('#next').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.next();
|
||||
});
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
if (oldV === newV) return;
|
||||
switch (name) {
|
||||
case 'next': {
|
||||
this.shadowRoot.querySelector('#next').className = newV;
|
||||
break;
|
||||
}
|
||||
case 'prev': {
|
||||
this.shadowRoot.querySelector('#prev').className = newV;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next() {
|
||||
this.dispatchEvent(new CustomEvent('form:next', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: this.parentElement
|
||||
}));
|
||||
}
|
||||
|
||||
prev() {
|
||||
this.dispatchEvent(new CustomEvent('form:prev', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: this.parentElement
|
||||
}));
|
||||
}
|
||||
});
|
@ -1,210 +0,0 @@
|
||||
import { BUTTON_STYLE, Component } from "../shared.js";
|
||||
|
||||
customElements.define('image-input', class extends Component {
|
||||
#file;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['width', 'height', "account-id", "url", "send-original"];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
border: 1px solid var(--border-light-gray-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
#hidden { overflow: hidden; width: 1px; height: 1px; position: relative; }
|
||||
input[type=file] { position: absolute; top: -10px; left: -10px; display: none; }
|
||||
#view { width: 200px; height: 200px; cursor: pointer; }
|
||||
#view > span {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
color: var(--border-slim-color)
|
||||
}
|
||||
div > input[type=button] {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
border-color: var(--border-light-gray-color);
|
||||
}
|
||||
img[src=""] { display: none; }
|
||||
img {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section id="hidden">
|
||||
<input id="file" type="file" accept="image/*" />
|
||||
</section>
|
||||
<div id="view">
|
||||
Kliknij żeby przesłać zdjęcie (opcjonalne)
|
||||
</div>
|
||||
<div style="display: none;">
|
||||
<input id="save" type="button" value="Wyślij" />
|
||||
</div>
|
||||
</article>
|
||||
`);
|
||||
|
||||
this.shadowRoot.querySelector('#save').addEventListener('click', async ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
await this.#sendFile(this.#file);
|
||||
});
|
||||
|
||||
const input = this.shadowRoot.querySelector('#file');
|
||||
const view = this.shadowRoot.querySelector('#view');
|
||||
|
||||
const toFile = (canvas) => new Promise((resolve) => {
|
||||
canvas.toBlob(async (blob) => {
|
||||
this.#file = new File([blob], `${ crypto.randomUUID() }.webp`, { type: blob.type });
|
||||
resolve();
|
||||
await this.#sendFile(this.#file); // TODO: Send on form submit
|
||||
}, 'image/webp');
|
||||
})
|
||||
|
||||
input.addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
|
||||
const image = new Image();
|
||||
const canvas = document.createElement('canvas');
|
||||
let maxWidth = this.width;
|
||||
let maxHeight = this.height;
|
||||
|
||||
image.onload = async () => {
|
||||
if (this.send_original) {
|
||||
maxWidth = maxWidth < image.naturalWidth ? maxWidth : image.naturalWidth;
|
||||
maxHeight = maxHeight < image.naturalHeight ? maxHeight : image.naturalHeight;
|
||||
}
|
||||
console.warn(this.send_original, maxWidth, maxHeight)
|
||||
|
||||
const width = image.width > image.height
|
||||
? maxWidth
|
||||
: (image.width * maxHeight) / image.height;
|
||||
const height = image.width > image.height
|
||||
? (image.height * maxWidth) / image.width
|
||||
: maxHeight;
|
||||
|
||||
canvas.width = image.width = width;
|
||||
canvas.height = image.height = height;
|
||||
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
|
||||
|
||||
await toFile(canvas);
|
||||
|
||||
image.width = width > image ? 200 : (width * 200) / height;
|
||||
image.height = width > image ? (width * 200) / height : 200;
|
||||
view.appendChild(image);
|
||||
};
|
||||
|
||||
image.src = URL.createObjectURL(input.files[0]);
|
||||
view.innerHTML = '';
|
||||
});
|
||||
|
||||
view.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
input.click();
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.account_id = this.account_id;
|
||||
this.url = this.url;
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
super.attributeChangedCallback(name, oldV, newV);
|
||||
}
|
||||
|
||||
get account_id() {
|
||||
return this.getAttribute('account-id');
|
||||
}
|
||||
|
||||
set account_id(v) {
|
||||
this.setAttribute('account-id', v);
|
||||
}
|
||||
|
||||
get width() {
|
||||
const v = parseInt(this.getAttribute('width'));
|
||||
return isNaN(v) ? 1000 : v;
|
||||
}
|
||||
|
||||
set width(v) {
|
||||
this.setAttribute('width', v);
|
||||
}
|
||||
|
||||
get height() {
|
||||
const v = parseInt(this.getAttribute('height'));
|
||||
return isNaN(v) ? 1000 : v;
|
||||
}
|
||||
|
||||
set height(v) {
|
||||
this.setAttribute('height', v);
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this.getAttribute('url');
|
||||
}
|
||||
|
||||
set url(v) {
|
||||
if (!(v || '').startsWith("/")) v = '';
|
||||
this.setAttribute('url', v);
|
||||
const view = this.shadowRoot.querySelector('#view');
|
||||
if (v === '') {
|
||||
view.innerHTML = '<span>Kliknij żeby przesłać zdjęcie (opcjonalne)</span>';
|
||||
} else {
|
||||
view.innerHTML = `<img src="${ v }" alt=""/>`;
|
||||
}
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
set value(v) {
|
||||
this.url = v;
|
||||
}
|
||||
|
||||
async #sendFile(file) {
|
||||
if (!file) return;
|
||||
const form = new FormData;
|
||||
form.append(`${ crypto.randomUUID() }.webp`, file);
|
||||
await fetch("/upload", {
|
||||
method: "POST",
|
||||
body: form,
|
||||
headers: { 'Accept': 'application/json' },
|
||||
}).then(res => res.json()).then(({ path }) => {
|
||||
this.url = path;
|
||||
this.dispatchEvent(new CustomEvent('image-input:uploaded', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: path
|
||||
}));
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: path
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
get send_original() {
|
||||
return this.hasAttribute('send-original');
|
||||
}
|
||||
|
||||
set send_original(v) {
|
||||
v = v === true || v === 'true';
|
||||
if (v)
|
||||
this.setAttribute('send-original', 'true')
|
||||
else
|
||||
this.removeAttribute('send-original');
|
||||
}
|
||||
});
|
@ -1,120 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('image-popup', class extends Component {
|
||||
#listener;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['src', 'popup'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
#small {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#large {
|
||||
display: none;
|
||||
}
|
||||
#small > img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
img[src=''] {
|
||||
display: none;
|
||||
}
|
||||
:host([popup="true"]) #large {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
#background {
|
||||
background: rgba(120, 120, 120, .6);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
#large > #image {
|
||||
margin: 16px;
|
||||
height: calc(100% - 32px);
|
||||
width: calc(100% - 32px);
|
||||
text-align: center;
|
||||
}
|
||||
#large > #image > img {
|
||||
margin: auto;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<div id="small">
|
||||
<img alt="" src="" />
|
||||
</div>
|
||||
<div id="large">
|
||||
<div id="background"></div>
|
||||
<div id="image">
|
||||
<img src="" alt="">
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
this.shadowRoot.querySelector('#small').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.popup = true;
|
||||
});
|
||||
|
||||
this.shadowRoot.querySelector('#large').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.popup = false;
|
||||
});
|
||||
this.#listener = ({ key }) => {
|
||||
if (key !== 'Escape') return;
|
||||
if (!this.popup) return;
|
||||
this.popup = false;
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener('keydown', this.#listener, true);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener('keydown', this.#listener, true);
|
||||
}
|
||||
|
||||
get popup() {
|
||||
return this.getAttribute('popup') === 'true';
|
||||
}
|
||||
|
||||
set popup(v) {
|
||||
if (v === true || v === 'true')
|
||||
this.setAttribute('popup', 'true');
|
||||
else
|
||||
this.removeAttribute('popup');
|
||||
}
|
||||
|
||||
get src() {
|
||||
return this.getAttribute('src');
|
||||
}
|
||||
|
||||
set src(v) {
|
||||
this.setAttribute('src', v);
|
||||
this.shadowRoot.querySelector('#small img').src = v;
|
||||
this.shadowRoot.querySelector('#large img').src = v;
|
||||
}
|
||||
});
|
@ -1,109 +0,0 @@
|
||||
import { Component, Router } from "../../shared.js";
|
||||
|
||||
customElements.define('ow-nav', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
article {
|
||||
border-style: solid;
|
||||
border-width: 0 0 1px 0;
|
||||
border-color: #F2F2F2;
|
||||
transition: background 0.3s, border 0.3s, border-radius 0.3s, box-shadow 0.3s;
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
padding: 13px 10px 13px 10px;
|
||||
}
|
||||
section, section > div {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
svg {
|
||||
height: 48px;
|
||||
}
|
||||
#logo {
|
||||
display: none;
|
||||
}
|
||||
@media print {
|
||||
:host { display: none; }
|
||||
}
|
||||
@media only screen and (min-device-width: 1000px) {
|
||||
section > div {
|
||||
width: 33%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#logo {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
#right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<article>
|
||||
<section>
|
||||
<div class="left">
|
||||
<slot name="left"></slot>
|
||||
</div>
|
||||
<div id="logo">
|
||||
<a href="/">
|
||||
<svg viewBox="0 0 911 550" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="293" cy="335.2" rx="121" ry="170.61" fill="none" stroke="#555" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="653.783" cy="113.849" rx="121.002" ry="170.613" fill="none" stroke="#555" transform="rotate(11.25)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="428.125" cy="166.014" rx="125.003" ry="176.254" fill="none" stroke="#555" transform="rotate(22.5)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="402.034" cy="23.889" rx="123.005" ry="173.437" fill="none" stroke="#555" transform="rotate(33.75)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="464.706" cy="-31.682" rx="124.999" ry="176.248" fill="none" stroke="#555" transform="rotate(45)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="540.521" cy="-306.337" rx="129.005" ry="181.898" fill="none" stroke="#555" transform="rotate(56.25)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="494.207" cy="-442.566" rx="124.003" ry="174.844" fill="none" stroke="#555" transform="rotate(67.5)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="239.113" cy="-150.236" rx="126.002" ry="177.663" fill="none" stroke="#555" transform="rotate(78.75)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="304.2" cy="-330" rx="128" ry="180.48" fill="none" stroke="#555" transform="rotate(90)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="143.181" cy="-572.932" rx="120.002" ry="169.203" fill="none" stroke="#555" transform="rotate(101.25)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="142" cy="-386.769" rx="123.003" ry="173.434" fill="none" stroke="#555" transform="rotate(112.5)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-91.151" cy="-617.377" rx="123.005" ry="173.437" fill="none" stroke="#555" transform="rotate(123.75)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-17.541" cy="-483.099" rx="124.999" ry="176.248" fill="none" stroke="#555" transform="rotate(135)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-89.814" cy="-445.118" rx="128.005" ry="180.488" fill="none" stroke="#555" transform="rotate(146.25)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-309.998" cy="-338.613" rx="125.003" ry="176.254" fill="none" stroke="#555" transform="rotate(157.5)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-514.833" cy="-456.414" rx="120.002" ry="169.203" fill="none" stroke="#555" transform="rotate(168.75)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-534" cy="-351.2" rx="126" ry="177.66" fill="none" stroke="#555" transform="scale(-1)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-434.371" cy="-246.176" rx="128.002" ry="180.483" fill="none" stroke="#555" transform="rotate(-168.75)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-710.531" cy="4.041" rx="126.003" ry="177.664" fill="none" stroke="#555" transform="rotate(-157.5)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-382.027" cy="-19.162" rx="130.005" ry="183.308" fill="none" stroke="#555" transform="rotate(-146.25)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-721.398" cy="240.271" rx="129.999" ry="183.298" fill="none" stroke="#555" transform="rotate(-135)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-304.791" cy="106.633" rx="123.005" ry="173.437" fill="none" stroke="#555" transform="rotate(-123.75)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-495.302" cy="413.961" rx="128.003" ry="180.485" fill="none" stroke="#555" transform="rotate(-112.5)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-265.515" cy="206.161" rx="125.002" ry="176.253" fill="none" stroke="#555" transform="rotate(-101.25)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-180.2" cy="605" rx="122" ry="172.02" fill="none" stroke="#555" transform="rotate(-90)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-137.223" cy="326.045" rx="121.002" ry="170.613" fill="none" stroke="#555" transform="rotate(-78.75)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-80.373" cy="352.613" rx="125.003" ry="176.254" fill="none" stroke="#555" transform="rotate(-67.5)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-50.619" cy="275.587" rx="126.005" ry="177.667" fill="none" stroke="#555" transform="rotate(-56.25)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="-22.773" cy="406.017" rx="126.999" ry="179.068" fill="none" stroke="#555" transform="rotate(-45)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="97.486" cy="309.523" rx="121.005" ry="170.617" fill="none" stroke="#555" transform="rotate(-33.75)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="507.007" cy="435.361" rx="129.003" ry="181.895" fill="none" stroke="#555" transform="rotate(-22.5)" style="opacity:.5" opacity=".5"/>
|
||||
<ellipse cx="261.461" cy="254.093" rx="130.002" ry="183.303" fill="none" stroke="#555" transform="rotate(-11.25)" style="opacity:.5" opacity=".5"/>
|
||||
<text xml:space="preserve" style="font-style:normal;font-weight:400;font-size:200px;line-height:1.25;font-family:sans-serif;fill:#000;fill-opacity:1;stroke:none" x="36.404" y="311.43">
|
||||
<tspan x="36.404" y="311.43" style="font-style:normal;font-variant:normal;font-weight:400;font-stretch:normal;font-family:'Cardo';">OS Wilno</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div id="right">
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
|
||||
#mount(selector, path) {
|
||||
const el = this.querySelector(selector);
|
||||
if (!el) return;
|
||||
el.addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
Router.goTo(path);
|
||||
});
|
||||
}
|
||||
});
|
@ -1,65 +0,0 @@
|
||||
import { Component } from "../../shared";
|
||||
|
||||
customElements.define('ow-path', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['selected', 'path'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
a {
|
||||
padding: 8px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
color: var(--hover-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
|
||||
}
|
||||
a:hover {
|
||||
color: var(--hover-color);
|
||||
}
|
||||
:host(:not([selected])) a {
|
||||
color: #495057;
|
||||
}
|
||||
@media print {
|
||||
:host { display: none; }
|
||||
}
|
||||
</style>
|
||||
<a><slot></slot></a>
|
||||
`);
|
||||
this.shadowRoot.querySelector('a').addEventListener('click', ev => {
|
||||
document.location = this.path;
|
||||
});
|
||||
}
|
||||
|
||||
get selected() {
|
||||
return this.getAttribute('selected') === 'selected';
|
||||
}
|
||||
|
||||
set selected(value) {
|
||||
if (value === true || value === 'selected')
|
||||
this.setAttribute('selected', 'selected');
|
||||
else
|
||||
this.removeAttribute('selected');
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this.getAttribute('path') || ''
|
||||
}
|
||||
|
||||
set path(value) {
|
||||
if (!value || value === '') {
|
||||
this.removeAttribute('path');
|
||||
return;
|
||||
}
|
||||
this.setAttribute('path', value);
|
||||
this.shadowRoot.querySelector('a').setAttribute('href', value);
|
||||
}
|
||||
});
|
@ -1,101 +0,0 @@
|
||||
import { Component, BUTTON_STYLE } from "../shared.js";
|
||||
|
||||
customElements.define('popup-window', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['index', 'required', 'open'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
article {
|
||||
position: relative;
|
||||
}
|
||||
#bg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background: rgba(100,100,100, .6);
|
||||
}
|
||||
#content {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
${ BUTTON_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section id="bg"> </section>
|
||||
<section id="content">
|
||||
<div><slot></slot></div>
|
||||
<div id="required"><input id="submit" value="Zakończ" /></div>
|
||||
<div id="optional">
|
||||
<input id="yes" value="Tak" />
|
||||
<input id="no" value="Nie" />
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
`);
|
||||
}
|
||||
|
||||
static attr2Field(name) {
|
||||
if (name === 'open')
|
||||
return 'is_open';
|
||||
super.attr2Field(name);
|
||||
}
|
||||
|
||||
get required() {
|
||||
return this.getAttribute('required') === 'yes';
|
||||
}
|
||||
|
||||
set required(v) {
|
||||
if (v === true || v === 'yes')
|
||||
this.setAttribute('required', 'yes');
|
||||
else
|
||||
this.removeAttribute('required');
|
||||
}
|
||||
|
||||
get index() {
|
||||
const v = parseInt(this.getAttribute('index'));
|
||||
return isNaN(v) ? null : v;
|
||||
}
|
||||
|
||||
set index(v) {
|
||||
v = parseInt(v);
|
||||
if (isNaN(v)) return;
|
||||
this.setAttribute('index', v.toString());
|
||||
this.style.zIndex = v;
|
||||
}
|
||||
|
||||
get is_open() {
|
||||
return this.getAttribute('open') === 'true';
|
||||
}
|
||||
|
||||
set is_open(v) {
|
||||
if (v === 'true' || v === true)
|
||||
this.setAttribute('open', 'true');
|
||||
else
|
||||
this.removeAttribute('open');
|
||||
}
|
||||
|
||||
close() {
|
||||
this.is_open = false;
|
||||
}
|
||||
|
||||
open() {
|
||||
this.is_open = true;
|
||||
}
|
||||
});
|
@ -1,129 +0,0 @@
|
||||
import { Component, FORM_STYLE } from "../../shared";
|
||||
|
||||
customElements.define('price-input', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['value', 'currency', 'required', 'name']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
#price {
|
||||
font-weight: bold;
|
||||
}
|
||||
#view {
|
||||
display: flex;
|
||||
}
|
||||
#value {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
}
|
||||
${ FORM_STYLE }
|
||||
</style>
|
||||
<div id="view">
|
||||
<input
|
||||
id="price"
|
||||
type="number"
|
||||
min="0.00"
|
||||
max="10000.00"
|
||||
step="0.01"
|
||||
placeholder="Cena, np: 12.23"
|
||||
/>
|
||||
<span id="currency"></span>
|
||||
</div>
|
||||
`);
|
||||
const price = this.shadowRoot.querySelector('#price');
|
||||
price.addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
this.value = price.value;
|
||||
this.dispatchEvent(new CustomEvent('change', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { price: this.price }
|
||||
}));
|
||||
this.dispatchEvent(new CustomEvent('price:changed', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { price: this.price }
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.shadowRoot.querySelector('#currency').textContent = this.currency;
|
||||
this.shadowRoot.querySelector('#price').value = this.value;
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
super.attributeChangedCallback(name, oldV, newV);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return Math.floor(parseFloat(this.shadowRoot.querySelector('#price').value) * 100);
|
||||
}
|
||||
|
||||
set value(v) {
|
||||
this.setAttribute('value', v);
|
||||
this.shadowRoot.querySelector('#price').value = v;
|
||||
}
|
||||
|
||||
get price() {
|
||||
return Math.floor(parseFloat(this.shadowRoot.querySelector('#price').value) * 100);
|
||||
}
|
||||
|
||||
set price(v) {
|
||||
this.setAttribute('price', v);
|
||||
this.shadowRoot.querySelector('#price').value = v;
|
||||
}
|
||||
|
||||
get currency() {
|
||||
return this.getAttribute('currency') || 'PLN';
|
||||
}
|
||||
|
||||
set currency(value) {
|
||||
this.setAttribute('currency', value);
|
||||
this.shadowRoot.querySelector('#currency').textContent = this.currency;
|
||||
}
|
||||
|
||||
reportValidity() {
|
||||
return this.shadowRoot.querySelector('input').reportValidity();
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.getAttribute('name');
|
||||
}
|
||||
|
||||
set name(value) {
|
||||
this.setAttribute('name', value);
|
||||
}
|
||||
|
||||
get readonly() {
|
||||
return this.hasAttribute('readonly');
|
||||
}
|
||||
|
||||
set readolny(v) {
|
||||
v = v === true || v === 'readonly';
|
||||
const price = this.shadowRoot.querySelector('#price');
|
||||
if (v) {
|
||||
price.setAttribute('readonly', 'readonly')
|
||||
} else {
|
||||
price.removeAttribute('readonly');
|
||||
}
|
||||
}
|
||||
|
||||
get required() {
|
||||
return this.hasAttribute('required');
|
||||
}
|
||||
|
||||
set required(v) {
|
||||
v = v === true || v === 'required';
|
||||
const price = this.shadowRoot.querySelector('#price');
|
||||
if (v) {
|
||||
price.setAttribute('required', 'required')
|
||||
} else {
|
||||
price.removeAttribute('required');
|
||||
}
|
||||
}
|
||||
});
|
@ -1,58 +0,0 @@
|
||||
customElements.define('price-view', class extends HTMLElement {
|
||||
#form;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['value', 'currency']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const shadow = this.#form = this.attachShadow({ mode: 'closed' });
|
||||
shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; }
|
||||
* { font-family: 'Cardo', sans-serif; }
|
||||
#price {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
<span id="price"></span>
|
||||
`;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.#form.querySelector('#price').textContent = this.formatted;
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
if (oldV === newV) return;
|
||||
switch (name) {
|
||||
case 'price': {
|
||||
this.value = newV;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get formatted() {
|
||||
let v = this.value;
|
||||
let major = Math.ceil(v / 100);
|
||||
let minor = v % 100;
|
||||
let formatted = `${ major },${ minor < 10 ? `0${ minor }` : minor }`;
|
||||
return `${ formatted }${ this.currency }`
|
||||
}
|
||||
|
||||
get value() {
|
||||
const n = parseInt(this.getAttribute('value'));
|
||||
return isNaN(n) ? 0 : n;
|
||||
}
|
||||
|
||||
set value(v) {
|
||||
this.setAttribute('value', v);
|
||||
this.#form.querySelector('#price').textContent = this.formatted;
|
||||
}
|
||||
|
||||
get currency() {
|
||||
return this.getAttribute('currency') || 'PLN';
|
||||
}
|
||||
});
|
@ -1,513 +0,0 @@
|
||||
import { Component, BLOCK_QUOTE_STYLE } from "../shared";
|
||||
|
||||
customElements.define('rich-text-editor', class extends Component {
|
||||
#selection;
|
||||
#range;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['upload-url'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
#tools > #first-row {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
#edit {
|
||||
border: 1px solid var(--border-slim-color);
|
||||
min-height: 90px;
|
||||
padding: 8px;
|
||||
}
|
||||
button, select {
|
||||
background: none;
|
||||
border: 1px solid var(--border-slim-color);
|
||||
margin-right: 8px;
|
||||
}
|
||||
button {
|
||||
padding: 4px;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
}
|
||||
select {
|
||||
padding: 4px;
|
||||
height: 46px;
|
||||
}
|
||||
svg {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
section > div {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
section ul {
|
||||
list-style: circle;
|
||||
}
|
||||
section ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
#image input {
|
||||
display: none;
|
||||
}
|
||||
#align, #colors, #lists, #insert {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#text-color input, #background-color input {
|
||||
display: none;
|
||||
}
|
||||
${ BLOCK_QUOTE_STYLE }
|
||||
</style>
|
||||
<article>
|
||||
<section id="tools">
|
||||
<div id="first-row">
|
||||
<div>
|
||||
<select id="header">
|
||||
<option value="normal">Normal</option>
|
||||
<option value="h1">H1</option>
|
||||
<option value="h2">H2</option>
|
||||
<option value="h3">H3</option>
|
||||
<option value="h4">H4</option>
|
||||
<option value="h5">H5</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="colors">
|
||||
<button id="text-color" title="Kolor czcionki">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 278 278" xml:space="preserve">
|
||||
<path d="M254.833 0h-231C15.549 0 8.5 6.716 8.5 15v33c0 8.284 6.716 15 15 15s15-6.716 15-15V30h69v218H89.833c-8.284 0-15 6.716-15 15s6.716 15 15 15h99c8.284 0 15-6.716 15-15s-6.716-15-15-15H170.5V30h69v18c0 8.284 6.716 15 15 15s15-6.716 15-15V15c0-8.284-6.383-15-14.667-15z"/>
|
||||
</svg>
|
||||
<input type="color" value="#000" />
|
||||
</button>
|
||||
<button id="background-color" title="Kolor tła">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" xml:space="preserve">
|
||||
<path d="m25.9 16 4.3 10h-9l3.9-10h.8zM48 8v36c0 2.2-1.8 4-4 4H8c-2.2 0-4-1.8-4-4V8c0-2.2 1.8-4 4-4h36c2.2 0 4 1.8 4 4zm-5.5 32.7L30.5 11c-.3-.6-.8-1-1.5-1h-7.1c-.6 0-1.2.4-1.4 1l-11 29.7c-.2.6.2 1.3.9 1.3h4.1c.6 0 1.2-.5 1.4-1.1l3.2-8.9h13.4l3.5 8.9c.2.6.8 1.1 1.4 1.1h4.1c.7 0 1.2-.7 1-1.3z"/>
|
||||
</svg>
|
||||
<input type="color" value="#000" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="decorations">
|
||||
<button id="underscore" title="Podkreślenie">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52" xml:space="preserve">
|
||||
<path d="M10.4 36h4.1c.6 0 1.2-.5 1.4-1.1l3.2-8.9h13.4l3.5 8.9c.2.6.8 1.1 1.4 1.1h4.1c.7 0 1.2-.7.9-1.3L30.4 5c-.2-.6-.7-1-1.3-1H22c-.6 0-1.2.4-1.4 1l-11 29.7c-.3.6.2 1.3.8 1.3zm14.7-26h.9l4.3 10h-9l3.8-10zM48.5 42h-45c-.8 0-1.5.7-1.5 1.5v3c0 .8.7 1.5 1.5 1.5h45c.8 0 1.5-.7 1.5-1.5v-3c0-.8-.7-1.5-1.5-1.5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="superscript" title="Indeks górny">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M11 7v13H9V7H3V5h12v2h-4zm8.55-.42a.8.8 0 1 0-1.32-.36l-1.154.33A2.001 2.001 0 0 1 19 4a2 2 0 0 1 1.373 3.454L18.744 9H21v1h-4V9l2.55-2.42z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="subscription" title="Indeks dolny">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.488 4.43a.75.75 0 0 1 .082 1.058L10.988 12l5.032 5.871c-.302.41-.555.906-.682 1.51L10 13.151l-5.43 6.336a.75.75 0 0 1-1.14-.976L9.013 12 3.431 5.488a.75.75 0 0 1 1.139-.976L10 10.848l5.43-6.336a.75.75 0 0 1 1.058-.081ZM17.75 15.523c0-.528.444-1.023.986-1.023.407 0 .735.19.893.434.136.21.218.566-.093 1.095-.15.255-.376.482-.682.724-.152.12-.316.237-.493.363l-.074.052c-.152.107-.315.222-.472.34-.744.56-1.565 1.346-1.565 2.742 0 .414.336.75.75.75h3.451a.75.75 0 0 0 0-1.5h-2.513c.16-.282.423-.525.779-.793.137-.103.279-.203.432-.312l.078-.054c.178-.127.37-.264.557-.41.372-.295.76-.658 1.045-1.142.557-.948.546-1.921.058-2.672C20.42 13.4 19.59 13 18.736 13c-1.478 0-2.486 1.278-2.486 2.523a.75.75 0 0 0 1.5 0Z" fill="#212121"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="lists">
|
||||
<button id="ordered-list" title="Lista uporządkowana">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 317.109 317.109" xml:space="preserve">
|
||||
<path d="M102.109 53.555h200c8.284 0 15-6.716 15-15s-6.716-15-15-15h-200c-8.284 0-15 6.716-15 15s6.716 15 15 15zM302.109 83.555h-200c-8.284 0-15 6.716-15 15s6.716 15 15 15h200c8.284 0 15-6.716 15-15s-6.715-15-15-15zM302.109 143.555h-200c-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15h200c8.284 0 15-6.716 15-15 0-8.285-6.715-15-15-15zM302.109 263.555h-200c-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15h200c8.284 0 15-6.716 15-15 0-8.284-6.715-15-15-15zM302.109 203.555h-200c-8.284 0-15 6.716-15 15 0 8.284 6.716 15 15 15h200c8.284 0 15-6.716 15-15 0-8.285-6.715-15-15-15zM17.826 49.036V86.6c0 4.074 3.32 7.146 7.724 7.146 4.33 0 7.721-3.139 7.721-7.146V30.425c0-3.96-3.247-7.062-7.392-7.062-3.647 0-5.471 2.447-6.07 3.251a6.54 6.54 0 0 0-.074.102l-6.526 9.233c-1.267 1.378-2.394 3.582-2.394 5.696-.001 4.03 3.133 7.317 7.011 7.391zM7.63 193.746h29.406c3.849 0 6.981-3.391 6.981-7.559 0-4.124-3.131-7.479-6.981-7.479H15.684v-.122c0-2.246 5.148-5.878 9.285-8.797 8.229-5.807 18.47-13.033 18.47-25.565 0-11.893-9.216-20.86-21.438-20.86-11.703 0-20.527 8.044-20.527 18.711 0 6.19 4.029 8.387 7.479 8.387 4.938 0 7.889-3.676 7.889-7.23 0-2.21.568-4.746 4.994-4.746 5.979 0 6.151 5.298 6.151 5.902 0 4.762-6.18 9.213-12.157 13.519C8.442 163.228.068 169.26.068 178.587v8.011c-.001 4.276 3.91 7.148 7.562 7.148zM42.446 242.783c0-12.342-7.288-19.42-19.994-19.42-16.66 0-21.062 11.898-21.062 18.189 0 7.325 5.445 8.115 7.786 8.115 4.559 0 7.621-3.063 7.621-7.622 0-1.753.624-3.766 5.487-3.766 3.495 0 4.918.503 4.918 5.568 0 4.948-1.062 5.487-5.245 5.487-4.018 0-7.047 3.17-7.047 7.375 0 4.159 3.066 7.295 7.131 7.295 5.525 0 6.635 2.256 6.635 5.897v1.558c0 6.126-2.389 7.288-6.798 7.288-6.083 0-6.556-3.133-6.556-4.093 0-3.631-2.407-7.294-7.785-7.294-4.72 0-7.538 2.942-7.538 7.869 0 8.976 7.696 18.516 21.958 18.516 13.854 0 22.126-8.331 22.126-22.286v-1.558c0-5.722-1.83-10.465-5.264-13.876 2.352-3.403 3.627-7.944 3.627-13.242z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="unordered-list" title="Lista nieuporządkowana">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 487.3 487.3" xml:space="preserve">
|
||||
<path d="M487.2 69.7c0 12.9-10.5 23.4-23.4 23.4h-322c-12.9 0-23.4-10.5-23.4-23.4s10.5-23.4 23.4-23.4h322.1c12.9.1 23.3 10.5 23.3 23.4zm-23.3 92.6H141.8c-12.9 0-23.4 10.5-23.4 23.4s10.5 23.4 23.4 23.4h322.1c12.9 0 23.4-10.5 23.4-23.4-.1-12.9-10.5-23.4-23.4-23.4zm0 116H141.8c-12.9 0-23.4 10.5-23.4 23.4s10.5 23.4 23.4 23.4h322.1c12.9 0 23.4-10.5 23.4-23.4-.1-12.9-10.5-23.4-23.4-23.4zm0 116H141.8c-12.9 0-23.4 10.5-23.4 23.4s10.5 23.4 23.4 23.4h322.1c12.9 0 23.4-10.5 23.4-23.4-.1-12.9-10.5-23.4-23.4-23.4zM38.9 30.8C17.4 30.8 0 48.2 0 69.7s17.4 39 38.9 39 38.9-17.5 38.9-39-17.4-38.9-38.9-38.9zm0 116C17.4 146.8 0 164.2 0 185.7s17.4 38.9 38.9 38.9 38.9-17.4 38.9-38.9-17.4-38.9-38.9-38.9zm0 116C17.4 262.8 0 280.2 0 301.7s17.4 38.9 38.9 38.9 38.9-17.4 38.9-38.9-17.4-38.9-38.9-38.9zm0 115.9C17.4 378.7 0 396.1 0 417.6s17.4 38.9 38.9 38.9 38.9-17.4 38.9-38.9c0-21.4-17.4-38.9-38.9-38.9z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="short-quote" title="Cytat">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M11.192 15.757c0-.88-.23-1.618-.69-2.217-.326-.412-.768-.683-1.327-.812-.55-.128-1.07-.137-1.54-.028-.16-.95.1-1.956.76-3.022.66-1.065 1.515-1.867 2.558-2.403L9.373 5c-.8.396-1.56.898-2.26 1.505-.71.607-1.34 1.305-1.9 2.094s-.98 1.68-1.25 2.69-.346 2.04-.217 3.1c.168 1.4.62 2.52 1.356 3.35.735.84 1.652 1.26 2.748 1.26.965 0 1.766-.29 2.4-.878.628-.576.94-1.365.94-2.368l.002.003zm9.124 0c0-.88-.23-1.618-.69-2.217-.326-.42-.77-.692-1.327-.817-.56-.124-1.074-.13-1.54-.022-.16-.94.09-1.95.75-3.02.66-1.06 1.514-1.86 2.557-2.4L18.49 5c-.8.396-1.555.898-2.26 1.505a11.29 11.29 0 0 0-1.894 2.094c-.556.79-.97 1.68-1.24 2.69a8.04 8.04 0 0 0-.217 3.1c.165 1.4.615 2.52 1.35 3.35.732.833 1.646 1.25 2.742 1.25.967 0 1.768-.29 2.402-.876.627-.576.942-1.365.942-2.368v.01z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="insert">
|
||||
<button id="image" title="Wstaw obrazek">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" xml:space="preserve">
|
||||
<path d="M31 56h24V32H31v24zm2-22h20v20h-9V41.414l4.293 4.293 1.414-1.414L43 37.586l-6.707 6.707 1.414 1.414L42 41.414V54h-9V34zM21.569 13.569C21.569 10.498 19.071 8 16 8s-5.569 2.498-5.569 5.569c0 3.07 2.498 5.568 5.569 5.568s5.569-2.497 5.569-5.568zm-9.138 0C12.431 11.602 14.032 10 16 10s3.569 1.602 3.569 3.569-1.601 3.569-3.569 3.569-3.569-1.601-3.569-3.569zM6.25 36.661a.997.997 0 0 0 1.41.09l16.313-14.362 7.319 7.318a.999.999 0 1 0 1.414-1.414l-1.825-1.824 9.181-10.054 11.261 10.323a1 1 0 0 0 1.351-1.475l-12-11a1.002 1.002 0 0 0-1.414.063l-9.794 10.727-4.743-4.743a1.003 1.003 0 0 0-1.368-.044L6.339 35.249a1 1 0 0 0-.089 1.412z"/><path d="M57 2H1a1 1 0 0 0-1 1v44a1 1 0 0 0 1 1h24a1 1 0 1 0 0-2H2V4h54v23a1 1 0 1 0 2 0V3a1 1 0 0 0-1-1z"/>
|
||||
</svg>
|
||||
<input type="file" accept="image/*" />
|
||||
</button>
|
||||
</div>
|
||||
<div id="align">
|
||||
<button id="align-left" title="Wyrównaj do lewej">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200" xml:space="preserve">
|
||||
<path d="M618.75 99.202v178.006H0V99.202h618.75zm328.125 274.53v178.006H0V373.732h946.875zM731.25 648.262v178.006H0V648.262h731.25zM1200 922.792v178.006H0V922.792h1200z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button id="align-center" title="Wyrównaj do środka">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 31.668 31.668" xml:space="preserve">
|
||||
<path d="M25.501 5H6.167V0h19.334v5zM.168 8.889v5H31.5v-5H.168zm5.999 8.888v5h19.334v-5H6.167zM.168 31.668H31.5v-5H.168v5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button id="align-right" title="Wyrównaj do prawej">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200" xml:space="preserve">
|
||||
<path d="M581.25 99.202v178.006H1200V99.202H581.25zm-328.125 274.53v178.006H1200V373.732H253.125zm215.625 274.53v178.006H1200V648.262H468.75zM0 922.792v178.006h1200V922.792H0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button id="align-justify" title="Wyrównaj do lewej i prawej">
|
||||
<svg viewBox="-32 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M432 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-128H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="second-row">
|
||||
</div>
|
||||
</section>
|
||||
<section id="edit" contenteditable="true"></section>
|
||||
</article>
|
||||
`);
|
||||
|
||||
const header = this.shadowRoot.querySelector('#tools #header');
|
||||
header.addEventListener('click', () => {
|
||||
this.#saveSelection();
|
||||
});
|
||||
header.addEventListener('change', ev => {
|
||||
switch (ev.target.value) {
|
||||
case 'normal':
|
||||
return this.#removeWrapper();
|
||||
case 'h1':
|
||||
return this.#setWrap(['H1'], { repeat: 'ignore' });
|
||||
case 'h2':
|
||||
return this.#setWrap(['H2'], { repeat: 'ignore' });
|
||||
case 'h3':
|
||||
return this.#setWrap(['H3'], { repeat: 'ignore' });
|
||||
case 'h4':
|
||||
return this.#setWrap(['H4'], { repeat: 'ignore' });
|
||||
case 'h5':
|
||||
return this.#setWrap(['H5'], { repeat: 'ignore' });
|
||||
}
|
||||
});
|
||||
{
|
||||
this.shadowRoot.querySelector('#ordered-list').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#saveSelection();
|
||||
this.#setWrap(['ol', 'li'], { repeat: 'perform' });
|
||||
});
|
||||
this.shadowRoot.querySelector('#unordered-list').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#saveSelection();
|
||||
this.#setWrap(['ul', 'li'], { repeat: 'perform' });
|
||||
});
|
||||
this.shadowRoot.querySelector('#short-quote').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#saveSelection();
|
||||
this.#setWrap(['blockquote'], { repeat: 'drop' });
|
||||
});
|
||||
}
|
||||
{
|
||||
let timeout = null;
|
||||
this.shadowRoot.querySelector('#edit').addEventListener('keyup', (ev) => {
|
||||
ev.stopPropagation();
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
this.#emitChange();
|
||||
}, 1000 / 3);
|
||||
});
|
||||
}
|
||||
{
|
||||
const imgBtn = this.shadowRoot.querySelector('#image');
|
||||
const imgInput = imgBtn.querySelector('input');
|
||||
imgInput.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
});
|
||||
imgInput.addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
const file = imgInput.files[0];
|
||||
const reader = new FileReader();
|
||||
let uploaded = false;
|
||||
const uuid = crypto.randomUUID();
|
||||
reader.onloadend = () => {
|
||||
let el = this.#targetElement;
|
||||
if (!el) return;
|
||||
const img = new Image();
|
||||
img.id = uuid;
|
||||
img.src = reader.result || '';
|
||||
if (uploaded) return;
|
||||
el.appendChild(img);
|
||||
this.#emitChange();
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener('loadend', (ev) => {
|
||||
uploaded = true;
|
||||
reader.abort();
|
||||
const response = JSON.parse(ev.target.response);
|
||||
{
|
||||
const img = this.shadowRoot.querySelector(`img[id="${ uuid }"]`);
|
||||
img && img.remove();
|
||||
}
|
||||
let el = this.#targetElement;
|
||||
if (!el) return;
|
||||
const img = new Image();
|
||||
img.id = uuid;
|
||||
img.src = response.path;
|
||||
el.appendChild(img);
|
||||
this.#emitChange();
|
||||
});
|
||||
xhr.open("POST", this.getAttribute('upload-url'));
|
||||
const f = new FormData;
|
||||
let name;
|
||||
if (file.name.includes('.')) {
|
||||
name = `${ uuid }.${ file.name.split('.').pop() }`;
|
||||
} else {
|
||||
name = uuid;
|
||||
}
|
||||
|
||||
f.append(name, file);
|
||||
xhr.send(f);
|
||||
});
|
||||
imgBtn.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.#saveSelection();
|
||||
imgInput.click();
|
||||
});
|
||||
}
|
||||
{
|
||||
const el = this.shadowRoot.querySelector("#align-justify");
|
||||
el.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.#saveSelection();
|
||||
this.#setStyle('textAlign', 'justify');
|
||||
});
|
||||
}
|
||||
{
|
||||
const el = this.shadowRoot.querySelector("#align-left");
|
||||
el.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.#saveSelection();
|
||||
this.#setStyle('textAlign', 'left');
|
||||
});
|
||||
}
|
||||
{
|
||||
const el = this.shadowRoot.querySelector("#align-center");
|
||||
el.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.#saveSelection();
|
||||
this.#setStyle('textAlign', 'center');
|
||||
});
|
||||
}
|
||||
{
|
||||
const el = this.shadowRoot.querySelector("#align-right");
|
||||
el.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.#saveSelection();
|
||||
this.#setStyle('textAlign', 'right');
|
||||
});
|
||||
}
|
||||
{
|
||||
const button = this.shadowRoot.querySelector("#text-color");
|
||||
const input = button.querySelector('input');
|
||||
input.addEventListener('click', ev => ev.stopPropagation());
|
||||
input.addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#setStyle('color', input.value);
|
||||
});
|
||||
button.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
input.click();
|
||||
this.#saveSelection();
|
||||
});
|
||||
}
|
||||
{
|
||||
const button = this.shadowRoot.querySelector("#background-color");
|
||||
const input = button.querySelector('input');
|
||||
input.addEventListener('click', ev => ev.stopPropagation());
|
||||
input.addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#setStyle('backgroundColor', input.value);
|
||||
});
|
||||
button.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
input.click();
|
||||
this.#saveSelection();
|
||||
});
|
||||
}
|
||||
{
|
||||
const el = this.shadowRoot.querySelector('#underscore');
|
||||
el.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#saveSelection();
|
||||
this.#setStyle("textDecoration", 'underline');
|
||||
});
|
||||
}
|
||||
{
|
||||
const el = this.shadowRoot.querySelector('#superscript');
|
||||
el.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#saveSelection();
|
||||
this.#setWrap(['sup'], { repeat: 'drop' });
|
||||
});
|
||||
}
|
||||
{
|
||||
const el = this.shadowRoot.querySelector('#subscription');
|
||||
el.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
this.#saveSelection();
|
||||
this.#setWrap(['sub'], { repeat: 'drop' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static #isEditNode(el) {
|
||||
while (true) {
|
||||
if (!el) return false;
|
||||
if (el.nodeType === Node.TEXT_NODE) {
|
||||
el = el.parentElement;
|
||||
continue;
|
||||
}
|
||||
if (!el.tagName) return false;
|
||||
if (el.tagName.toLocaleLowerCase() === 'body') return false;
|
||||
if (el.id === 'edit') return true;
|
||||
el = el.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
#setWrap(tags, { repeat }) {
|
||||
tags = tags.map(s => s.toLowerCase());
|
||||
this.#isWrapped(tags);
|
||||
if (this.#isWrapped(tags)) {
|
||||
switch (repeat) {
|
||||
case 'drop':
|
||||
return this.#removeWrap(tags);
|
||||
case 'perform':
|
||||
return this.#createWrap(tags);
|
||||
case 'ignore':
|
||||
return this.#targetElement;
|
||||
}
|
||||
} else {
|
||||
return this.#createWrap(tags);
|
||||
}
|
||||
}
|
||||
|
||||
#isWrapped(tags) {
|
||||
let el = this.#targetElement;
|
||||
if (!el) return false;
|
||||
for (let i = tags.length - 1; i >= 0; i--) {
|
||||
if (!el || !el.tagName || el.tagName.toLowerCase() !== tags[i]) {
|
||||
return false;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#createWrap(tags) {
|
||||
if (!this.#range) return;
|
||||
|
||||
let el;
|
||||
let root = el = document.createElement(tags.shift());
|
||||
const content = this.#range.extractContents();
|
||||
this.#range.insertNode(root);
|
||||
|
||||
for (const tag of tags) {
|
||||
const current = document.createElement(tag);
|
||||
if (el) el.appendChild(current);
|
||||
el = current;
|
||||
}
|
||||
|
||||
for (const node of content.childNodes) {
|
||||
el.appendChild(node);
|
||||
}
|
||||
this.#restoreSelection(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
#removeWrap(tags) {
|
||||
let el = this.#targetElement;
|
||||
if (!el) return false;
|
||||
for (let i = tags.length - 1; i > 0; i--) {
|
||||
if (!el || !el.tagName || el.tagName.toLowerCase() !== tags[i]) {
|
||||
return false;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
const parent = el.parentElement;
|
||||
parent.replaceChild(this.#selected, el);
|
||||
}
|
||||
|
||||
#removeWrapper() {
|
||||
const el = this.#selected;
|
||||
if (!this.constructor.#isEditNode(el)) return;
|
||||
const headerNode = el.parentElement;
|
||||
if (!headerNode) return;
|
||||
if (!headerNode.tagName.toLocaleLowerCase().startsWith('h')) {
|
||||
return;
|
||||
}
|
||||
const parent = headerNode.parentElement;
|
||||
parent.replaceChild(el, headerNode);
|
||||
this.#restoreSelection();
|
||||
}
|
||||
|
||||
#setStyle(setter, value) {
|
||||
let el = this.#targetElement;
|
||||
if (!el) return;
|
||||
if (el.id === 'edit') {
|
||||
const div = el.appendChild(document.createElement('div'));
|
||||
div.appendChild(el);
|
||||
el = div;
|
||||
}
|
||||
if (el.style[setter] === value) {
|
||||
el.style[setter] = null;
|
||||
} else {
|
||||
el.style[setter] = value;
|
||||
}
|
||||
this.#restoreSelection();
|
||||
}
|
||||
|
||||
#saveSelection() {
|
||||
this.#selection = window.getSelection();
|
||||
this.#range = this.#selection.getRangeAt(0);
|
||||
this.#selection.removeAllRanges();
|
||||
this.shadowRoot.querySelector('#edit').blur();
|
||||
}
|
||||
|
||||
#restoreSelection(node) {
|
||||
if (node) this.#range.selectNodeContents(node);
|
||||
this.#emitChange();
|
||||
}
|
||||
|
||||
get #targetElement() {
|
||||
const selected = this.#selected;
|
||||
if (!this.constructor.#isEditNode(selected)) return;
|
||||
let el = selected;
|
||||
if (el.nodeType === Node.TEXT_NODE)
|
||||
el = el.parentElement;
|
||||
if (!el) return;
|
||||
return el;
|
||||
}
|
||||
|
||||
get #selected() {
|
||||
return this.#range.startContainer
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.shadowRoot.querySelector('#edit').innerHTML;
|
||||
}
|
||||
|
||||
set value(html) {
|
||||
this.shadowRoot.querySelector('#edit').innerHTML = html;
|
||||
}
|
||||
|
||||
#emitChange() {
|
||||
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
|
||||
}
|
||||
});
|
@ -1,116 +0,0 @@
|
||||
import { Component } from "../shared.js";
|
||||
|
||||
customElements.define('search-input', class extends Component {
|
||||
#host;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['filter', 'target']
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host { display: block; }
|
||||
input {
|
||||
font-size: 1rem;
|
||||
line-height: 2.6em;
|
||||
height: 2.6em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
border:none;
|
||||
outline:none;
|
||||
display: block;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid #ccc;
|
||||
text-indent: 20px;
|
||||
}
|
||||
svg { height: 24px; }
|
||||
section {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
<section>
|
||||
<svg
|
||||
viewBox="0 0 310.42 310.42"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M273.587 214.965c49.11-49.111 49.11-129.021 0-178.132s-129.021-49.111-178.131 0C53.792 78.497 47.482 140.462 76.509 188.85c0 0 2.085 3.496-.731 6.312l-64.263 64.263c-12.791 12.79-15.837 30.675-4.493 42.02l1.953 1.951c11.343 11.345 29.229 8.301 42.019-4.49l64.128-64.128c2.951-2.951 6.448-.866 6.448-.866 48.387 29.026 110.353 22.717 152.017-18.947zM118.71 191.71c-36.288-36.288-36.287-95.332 0-131.62 36.288-36.287 95.333-36.288 131.62 0 36.288 36.287 36.288 95.332 0 131.62-36.287 36.287-95.331 36.287-131.62 0z"
|
||||
/>
|
||||
</svg>
|
||||
<input type="text" id="filter" placeholder="Znajdź (wyrażenia regularne są wspierane)" />
|
||||
</section>
|
||||
`);
|
||||
const filter = this.shadowRoot.querySelector('#filter');
|
||||
let t = null;
|
||||
filter.addEventListener('change', ev => {
|
||||
ev.stopPropagation();
|
||||
this.filter = ev.target.value;
|
||||
});
|
||||
filter.addEventListener('keyup', ev => {
|
||||
ev.stopPropagation();
|
||||
const value = ev.target.value;
|
||||
|
||||
if (t) clearTimeout(t);
|
||||
t = setTimeout(() => {
|
||||
this.filter = value;
|
||||
t = null;
|
||||
}, 1000 / 3);
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
let node = this;
|
||||
while (node) {
|
||||
if (node == null) return console.warn('no parent node', node);
|
||||
if (node instanceof ShadowRoot) {
|
||||
this.#host = node.host;
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
get target() {
|
||||
return this.getAttribute('target');
|
||||
}
|
||||
|
||||
set target(tagName) {
|
||||
this.setAttribute('target', tagName);
|
||||
}
|
||||
|
||||
get filter() {
|
||||
return this.getAttribute('filter');
|
||||
}
|
||||
|
||||
set filter(value) {
|
||||
console.warn(this.#host, value);
|
||||
if (!this.#host) return;
|
||||
const v = this.#host.querySelectorAll(this.target);
|
||||
|
||||
if (!value || value === '') {
|
||||
this.removeAttribute('filter');
|
||||
for (const el of v) {
|
||||
el.removeAttribute('search-visible');
|
||||
}
|
||||
} else {
|
||||
this.setAttribute('filter', value);
|
||||
value = value.split(' ').filter(s => s && s.length).map(s => `(${ s })`).join('|');
|
||||
for (const el of v) {
|
||||
if (!el.matches) continue;
|
||||
if (el.matches(new RegExp(value, 'ig'))) {
|
||||
el.setAttribute('search-visible', 'visible');
|
||||
} else {
|
||||
el.setAttribute('search-visible', 'invisible');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,73 +0,0 @@
|
||||
import { Component, BUTTON_STYLE } from "../shared";
|
||||
|
||||
customElements.define('privacy-policy-bar', class extends Component {
|
||||
static get observedAttributes() {
|
||||
return ['accepted'];
|
||||
}
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
article {
|
||||
background: white;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--border-slim-color);
|
||||
margin: 4px;
|
||||
}
|
||||
article > input#accept {
|
||||
margin-left: 16px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
:host([accepted]) {
|
||||
display: none;
|
||||
}
|
||||
${BUTTON_STYLE}
|
||||
</style>
|
||||
<article>
|
||||
Strona korzysta z plików cookies w celu realizacji usług i zgodnie z
|
||||
<a href="/privacy-policy" target="_blank">Polityką Prywatności</a>.
|
||||
Możesz określić warunki przechowywania lub dostępu do plików cookies w Twojej przeglądarce
|
||||
<input id="accept" value="Akceptuję" type="button" />
|
||||
</article>
|
||||
`);
|
||||
this.shadowRoot.querySelector('#accept').addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.accept();
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.accepted = localStorage.getItem('cookies-accepted') === 'true';
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldV, newV) {
|
||||
super.attributeChangedCallback(name, oldV, newV);
|
||||
}
|
||||
|
||||
accept() {
|
||||
this.accepted = true;
|
||||
}
|
||||
|
||||
get accepted() {
|
||||
return this.getAttribute('accepted') === 'true';
|
||||
}
|
||||
|
||||
set accepted(v) {
|
||||
v = v || localStorage.getItem('cookies-accepted') === 'true';
|
||||
if (v === true || v === 'true') {
|
||||
this.setAttribute('accepted', 'true');
|
||||
localStorage.setItem('cookies-accepted', 'true');
|
||||
} else {
|
||||
this.removeAttribute('accepted');
|
||||
localStorage.removeItem('cookies-accepted');
|
||||
}
|
||||
}
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('privacy-policy', class extends Component {
|
||||
constructor() {
|
||||
super(`<style>:host{display:block;}</style> <slot></slot>`);
|
||||
}
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('terms-and-condition-rule', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<slot></slot>
|
||||
`);
|
||||
}
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import { Component } from "../shared";
|
||||
|
||||
customElements.define('terms-and-conditions', class extends Component {
|
||||
constructor() {
|
||||
super(`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<slot></slot>
|
||||
`);
|
||||
}
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "contract"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
db = ['sqlx', 'byteorder']
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "*", features = ['derive'] }
|
||||
chrono = { version = "*", features = ['serde'] }
|
||||
sqlx = { version = "*", optional = true }
|
||||
byteorder = { version = "1.4.3", optional = true }
|
||||
base64 = { version = "*" }
|
@ -1,78 +0,0 @@
|
||||
use byteorder::ByteOrder;
|
||||
use sqlx::database::{HasArguments, HasValueRef};
|
||||
use sqlx::encode::IsNull;
|
||||
use sqlx::error::BoxDynError;
|
||||
use sqlx::postgres::PgValueFormat;
|
||||
use sqlx::Postgres;
|
||||
|
||||
use crate::PriceRange;
|
||||
|
||||
fn take_i32(bytes: &mut &[u8]) -> i32 {
|
||||
let value = byteorder::BigEndian::read_i32(&bytes[0..4]);
|
||||
*bytes = &bytes[4..];
|
||||
value
|
||||
}
|
||||
|
||||
impl<'l> sqlx::Decode<'l, Postgres> for PriceRange {
|
||||
fn decode(value: <Postgres as HasValueRef<'l>>::ValueRef) -> Result<Self, BoxDynError> {
|
||||
match value.format() {
|
||||
PgValueFormat::Text => {
|
||||
let s = value.as_str()?;
|
||||
eprintln!("{s:?}");
|
||||
Ok(Self::Free)
|
||||
}
|
||||
PgValueFormat::Binary => {
|
||||
let mut bytes = value.as_bytes()?;
|
||||
// println!("{bytes:?}");
|
||||
|
||||
let _len = take_i32(&mut bytes);
|
||||
|
||||
let _ty = take_i32(&mut bytes);
|
||||
let _min_len = take_i32(&mut bytes);
|
||||
let min = take_i32(&mut bytes);
|
||||
|
||||
let _ty = take_i32(&mut bytes);
|
||||
let _max_len = take_i32(&mut bytes);
|
||||
let max = take_i32(&mut bytes);
|
||||
|
||||
Ok((min, max).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(n: i32, buf: &mut <Postgres as HasArguments<'_>>::ArgumentBuffer) {
|
||||
let _ = <i32 as sqlx::Encode<'_, Postgres>>::encode(n, buf);
|
||||
}
|
||||
|
||||
impl<'l> sqlx::Encode<'l, Postgres> for PriceRange {
|
||||
fn encode_by_ref(&self, buf: &mut <Postgres as HasArguments<'l>>::ArgumentBuffer) -> IsNull {
|
||||
encode(2i32, buf);
|
||||
fn write_value(n: &i32, buf: &mut <Postgres as HasArguments<'_>>::ArgumentBuffer) {
|
||||
encode(23i32, buf);
|
||||
encode(4i32, buf);
|
||||
encode(*n, buf);
|
||||
}
|
||||
match self {
|
||||
PriceRange::Free => {
|
||||
write_value(&0, buf);
|
||||
write_value(&0, buf);
|
||||
}
|
||||
PriceRange::Fixed { value } => {
|
||||
write_value(value, buf);
|
||||
write_value(&0, buf);
|
||||
}
|
||||
PriceRange::Range { min, max } => {
|
||||
write_value(min, buf);
|
||||
write_value(max, buf);
|
||||
}
|
||||
}
|
||||
IsNull::No
|
||||
}
|
||||
}
|
||||
|
||||
impl sqlx::Type<Postgres> for PriceRange {
|
||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
||||
sqlx::postgres::PgTypeInfo::with_name("PriceRange")
|
||||
}
|
||||
}
|
@ -1,454 +0,0 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
#[cfg(feature = "db")]
|
||||
pub use db::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "db")]
|
||||
mod db;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Account {
|
||||
pub id: i32,
|
||||
pub login: String,
|
||||
pub email: String,
|
||||
pub pass: String,
|
||||
pub facebook_id: Option<String>,
|
||||
pub account_type: AccountType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NewsArticle {
|
||||
pub id: i32,
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
pub status: NewsStatus,
|
||||
pub published_at: Option<NaiveDateTime>,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
impl Default for NewsArticle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
title: "".to_string(),
|
||||
body: "".to_string(),
|
||||
status: NewsStatus::Pending,
|
||||
published_at: None,
|
||||
created_at: Utc::now().naive_utc(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NewsStatus {
|
||||
Pending,
|
||||
Published,
|
||||
Hidden,
|
||||
}
|
||||
|
||||
impl NewsStatus {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Pending => "Pending",
|
||||
Self::Published => "Published",
|
||||
Self::Hidden => "Hidden",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum PriceRange {
|
||||
#[default]
|
||||
Free,
|
||||
Fixed {
|
||||
value: i32,
|
||||
},
|
||||
Range {
|
||||
min: i32,
|
||||
max: i32,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<(i32, i32)> for PriceRange {
|
||||
fn from((min, max): (i32, i32)) -> Self {
|
||||
match (min, max) {
|
||||
(0, 0) => Self::Free,
|
||||
(_, 0) => Self::Fixed { value: min },
|
||||
_ => Self::Range { min, max },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[derive(Debug, Default, PartialOrd, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
pub enum AccountType {
|
||||
#[default]
|
||||
User = 1,
|
||||
Business = 10,
|
||||
Admin = 100,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum LocalBusinessState {
|
||||
#[default]
|
||||
Pending = 1,
|
||||
Approved = 2,
|
||||
Banned = 3,
|
||||
Pinned = 4,
|
||||
Internal = 5,
|
||||
}
|
||||
|
||||
impl Display for LocalBusinessState {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalBusinessState {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Pending => "Pending",
|
||||
Self::Approved => "Approved",
|
||||
Self::Banned => "Banned",
|
||||
Self::Pinned => "Pinned",
|
||||
Self::Internal => "Internal",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
pub enum OfferState {
|
||||
#[default]
|
||||
Pending = 0,
|
||||
Approved = 1,
|
||||
Banned = 2,
|
||||
Finished = 3,
|
||||
}
|
||||
|
||||
impl OfferState {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
OfferState::Pending => "Pending",
|
||||
OfferState::Approved => "Approved",
|
||||
OfferState::Banned => "Banned",
|
||||
OfferState::Finished => "Finished",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub enum Page {
|
||||
#[default]
|
||||
LocalBusinesses,
|
||||
News,
|
||||
Account,
|
||||
Register,
|
||||
Login,
|
||||
AccountBusinessItems,
|
||||
Marketplace,
|
||||
AccountOffers,
|
||||
AdminNews,
|
||||
AdminCreateNews,
|
||||
AdminBusinesses,
|
||||
AdminOffers,
|
||||
Terms,
|
||||
Privacy,
|
||||
Business,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn is_public(&self) -> bool {
|
||||
!self.is_admin()
|
||||
}
|
||||
|
||||
pub fn is_admin(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Page::AdminNews | Page::AdminCreateNews | Page::AdminBusinesses | Page::AdminOffers
|
||||
)
|
||||
}
|
||||
|
||||
pub fn select_index(&self) -> &str {
|
||||
if matches!(self, Page::LocalBusinesses) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_news(&self) -> &str {
|
||||
if matches!(self, Page::News) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_account(&self) -> &str {
|
||||
if matches!(self, Page::Account) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_marketplace(&self) -> &str {
|
||||
if matches!(self, Page::Marketplace | Page::AccountOffers) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_admin_news(&self) -> &str {
|
||||
if matches!(self, Page::AdminNews) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_admin_businesses(&self) -> &str {
|
||||
if matches!(self, Page::AdminBusinesses) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_admin_offers(&self) -> &str {
|
||||
if matches!(self, Page::AdminOffers) {
|
||||
"selected"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BusinessItemInput {
|
||||
pub name: String,
|
||||
pub price: u32,
|
||||
pub picture_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SetStateBusinessInput {
|
||||
pub id: i32,
|
||||
pub state: LocalBusinessState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LocalBusiness {
|
||||
pub id: i32,
|
||||
pub owner_id: i32,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub state: LocalBusinessState,
|
||||
pub items: Vec<LocalBusinessItem>,
|
||||
pub contacts: Vec<ContactInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ContactInfo {
|
||||
pub id: i32,
|
||||
pub owner_id: i32,
|
||||
pub contact_type: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LocalBusinessItem {
|
||||
pub id: i32,
|
||||
pub local_business_id: i32,
|
||||
pub name: String,
|
||||
pub price: i64,
|
||||
pub item_order: i32,
|
||||
pub picture_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateBusinessItemInput {
|
||||
pub name: String,
|
||||
pub price: i64,
|
||||
pub picture_url: String,
|
||||
pub item_order: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AtomicUpdateBusinessItemInput {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateBusinessItemInput {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub price: i64,
|
||||
pub picture_url: String,
|
||||
pub item_order: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ModifyBusinessItemInput {
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct MoveBusinessItemInput {
|
||||
pub id: i32,
|
||||
pub item_order: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateNewsArticleInput {
|
||||
pub id: i32,
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
pub status: NewsStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateNewsArticleInput {
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
pub status: NewsStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DeleteNewsArticleInput {
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateContactInfoInput {
|
||||
#[serde(rename = "type")]
|
||||
pub contact_type: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateContactInfoInput {
|
||||
pub id: i32,
|
||||
#[serde(rename = "type")]
|
||||
pub contact_type: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DeleteContactInfoInput {
|
||||
pub id: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateOfferInput {
|
||||
pub description: String,
|
||||
pub picture_url: String,
|
||||
pub price_min: i32,
|
||||
pub price_max: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateOfferInput {
|
||||
pub id: i32,
|
||||
pub description: String,
|
||||
pub picture_url: String,
|
||||
pub price_min: i32,
|
||||
pub price_max: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Offer {
|
||||
pub id: i32,
|
||||
pub owner_id: i32,
|
||||
pub price_range: PriceRange,
|
||||
pub description: String,
|
||||
pub picture_url: String,
|
||||
pub state: OfferState,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub contacts: Vec<ContactInfo>,
|
||||
}
|
||||
|
||||
impl Default for Offer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: 0,
|
||||
owner_id: 0,
|
||||
price_range: Default::default(),
|
||||
description: "".to_string(),
|
||||
picture_url: "".to_string(),
|
||||
state: Default::default(),
|
||||
created_at: chrono::Utc::now().naive_utc(),
|
||||
contacts: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod businesses {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct BusinessList {
|
||||
pub businesses: Vec<LocalBusiness>,
|
||||
pub account: Option<Account>,
|
||||
pub error: Option<String>,
|
||||
pub page: Page,
|
||||
#[serde(skip)]
|
||||
pub h: Helper,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct Helper;
|
||||
|
||||
impl Helper {
|
||||
pub fn is_admin(&self, account: &Option<Account>) -> bool {
|
||||
account
|
||||
.as_ref()
|
||||
.map(|a| a.account_type == AccountType::Admin)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_above_user(&self, account: &Option<Account>) -> bool {
|
||||
account
|
||||
.as_ref()
|
||||
.map(|a| a.account_type > AccountType::User)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn account_id_tag(&self, account: &Option<Account>) -> String {
|
||||
account
|
||||
.as_ref()
|
||||
.map(|a| format!("account-id={}", a.id))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn email<'a>(&self, account: &'a Option<Account>) -> &'a str {
|
||||
account
|
||||
.as_ref()
|
||||
.map(|a| a.email.as_str())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn render_contact(&self, ty: &str, val: &str) -> String {
|
||||
if ty == "mobile" {
|
||||
self.mobile(val)
|
||||
} else {
|
||||
val.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn mobile(&self, phone: &str) -> String {
|
||||
base64::encode(phone)
|
||||
}
|
||||
}
|
19
crates/migration/Cargo.toml
Normal file
19
crates/migration/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "migration"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "migration"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "1", features = ["attributes", "tokio1"] }
|
||||
oswilno-contract = { path = "../oswilno-contract" }
|
||||
sea-orm = { version = "0.11.3", features = ["runtime-actix-rustls", "sqlx-postgres", "postgres-array", "sqlx"] }
|
||||
tokio = { version = "1.29.1", features = ["full"] }
|
||||
|
||||
[dependencies.sea-orm-migration]
|
||||
version = "0.11.0"
|
||||
features = ["sqlx-postgres", "runtime-actix-rustls", "sea-orm-cli"]
|
41
crates/migration/README.md
Normal file
41
crates/migration/README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# Running Migrator CLI
|
||||
|
||||
- Generate a new migration file
|
||||
```sh
|
||||
cargo run -- migrate generate MIGRATION_NAME
|
||||
```
|
||||
- Apply all pending migrations
|
||||
```sh
|
||||
cargo run
|
||||
```
|
||||
```sh
|
||||
cargo run -- up
|
||||
```
|
||||
- Apply first 10 pending migrations
|
||||
```sh
|
||||
cargo run -- up -n 10
|
||||
```
|
||||
- Rollback last applied migrations
|
||||
```sh
|
||||
cargo run -- down
|
||||
```
|
||||
- Rollback last 10 applied migrations
|
||||
```sh
|
||||
cargo run -- down -n 10
|
||||
```
|
||||
- Drop all tables from the database, then reapply all migrations
|
||||
```sh
|
||||
cargo run -- fresh
|
||||
```
|
||||
- Rollback all applied migrations, then reapply all migrations
|
||||
```sh
|
||||
cargo run -- refresh
|
||||
```
|
||||
- Rollback all applied migrations
|
||||
```sh
|
||||
cargo run -- reset
|
||||
```
|
||||
- Check the status of all migrations
|
||||
```sh
|
||||
cargo run -- status
|
||||
```
|
65
crates/migration/src/lib.rs
Normal file
65
crates/migration/src/lib.rs
Normal file
@ -0,0 +1,65 @@
|
||||
pub use sea_orm::Iterable;
|
||||
pub use sea_orm_migration::prelude::*;
|
||||
use sea_orm_migration::sea_orm::{DbBackend, Statement};
|
||||
|
||||
mod m20220101_000001_create_table;
|
||||
mod m20230726_124452_images;
|
||||
mod m20230726_135630_parking_spaces;
|
||||
mod m20230805_000001_add_email;
|
||||
mod m20230809_135630_add_spot;
|
||||
mod m20230810_105100_create_parking_space_locations;
|
||||
mod m20230919_162830_create_rent_requests;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![
|
||||
Box::new(m20220101_000001_create_table::Migration),
|
||||
Box::new(m20230726_124452_images::Migration),
|
||||
Box::new(m20230726_135630_parking_spaces::Migration),
|
||||
Box::new(m20230805_000001_add_email::Migration),
|
||||
Box::new(m20230809_135630_add_spot::Migration),
|
||||
Box::new(m20230810_105100_create_parking_space_locations::Migration),
|
||||
Box::new(m20230919_162830_create_rent_requests::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn drop_enum<'m, Enum>(m: &SchemaManager<'m>) -> Result<(), DbErr>
|
||||
where
|
||||
Enum: Iterable + Iden + IntoIden,
|
||||
{
|
||||
let db_postgres = DbBackend::Postgres;
|
||||
let name = Enum::iter().next().unwrap().to_string();
|
||||
let s = format!("DROP TYPE {name};");
|
||||
let stmt = Statement::from_string(db_postgres, s);
|
||||
m.get_connection().execute(stmt).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_enum<'m, Enum>(m: &SchemaManager<'m>) -> Result<(), DbErr>
|
||||
where
|
||||
Enum: Iterable + Iden + IntoIden,
|
||||
{
|
||||
let db_postgres = DbBackend::Postgres;
|
||||
let s = {
|
||||
let name = Enum::iter().next().unwrap().to_string();
|
||||
let mut s = Enum::iter().skip(1).enumerate().fold(
|
||||
format!("CREATE TYPE {name} AS ENUM ("),
|
||||
|mut s, (idx, variant)| {
|
||||
if idx != 0 {
|
||||
s.push(',');
|
||||
}
|
||||
format!("{s} '{}'", variant.into_iden().quoted('\''))
|
||||
},
|
||||
);
|
||||
s.push_str(" );");
|
||||
eprintln!("{s:?}");
|
||||
s
|
||||
};
|
||||
let stmt = Statement::from_string(db_postgres, s);
|
||||
m.get_connection().execute(stmt).await?;
|
||||
Ok(())
|
||||
}
|
106
crates/migration/src/m20220101_000001_create_table.rs
Normal file
106
crates/migration/src/m20220101_000001_create_table.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use sea_orm::{EnumIter, Iterable};
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::{create_enum, drop_enum};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
create_enum::<Role>(m).await?;
|
||||
|
||||
let table = Table::create()
|
||||
.table(Account::Accounts)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Account::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Account::Login).string().not_null())
|
||||
.col(ColumnDef::new(Account::PassHash).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Account::Role)
|
||||
.enumeration(
|
||||
Role::Role, //
|
||||
Role::iter().skip(1),
|
||||
)
|
||||
.default(Role::User.to_string())
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Account::Banned)
|
||||
.boolean()
|
||||
.default(false)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Account::Confirmed)
|
||||
.boolean()
|
||||
.default(false)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Account::Verified)
|
||||
.boolean()
|
||||
.default(false)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Account::CreatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Account::UpdatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.if_not_exists()
|
||||
.to_owned();
|
||||
|
||||
m.create_table(table).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.drop_table(Table::drop().table(Account::Accounts).to_owned())
|
||||
.await?;
|
||||
|
||||
drop_enum::<Role>(m).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden, EnumIter)]
|
||||
#[iden(rename = "UserRole")]
|
||||
pub enum Role {
|
||||
#[iden(rename = "UserRole")]
|
||||
Role,
|
||||
#[iden(rename = "User")]
|
||||
User,
|
||||
#[iden(rename = "Admin")]
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum Account {
|
||||
Accounts,
|
||||
Id,
|
||||
Login,
|
||||
Email,
|
||||
PassHash,
|
||||
Role,
|
||||
Banned,
|
||||
Confirmed,
|
||||
Verified,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
93
crates/migration/src/m20230726_124452_images.rs
Normal file
93
crates/migration/src/m20230726_124452_images.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use sea_orm::{EnumIter, Iterable};
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::m20220101_000001_create_table::Account;
|
||||
use crate::{create_enum, drop_enum};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
create_enum::<ImageState>(m).await?;
|
||||
|
||||
m.create_table(
|
||||
Table::create()
|
||||
.table(Image::Images)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Image::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Image::LocalPath).string().not_null())
|
||||
.col(ColumnDef::new(Image::PublicPath).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Image::ImageState)
|
||||
.enumeration(ImageState::ImageState, ImageState::iter().skip(1))
|
||||
.default(ImageState::Pending.to_string())
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Image::AccountId).integer().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Image::CreatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Image::UpdatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.if_not_exists()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.create_foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
.from_tbl(Image::Images)
|
||||
.from_col(Image::AccountId)
|
||||
.to_tbl(Account::Accounts)
|
||||
.to_col(Account::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.drop_table(Table::drop().table(Image::Images).to_owned())
|
||||
.await?;
|
||||
|
||||
drop_enum::<ImageState>(m).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum Image {
|
||||
Images,
|
||||
Id,
|
||||
LocalPath,
|
||||
PublicPath,
|
||||
ImageState,
|
||||
AccountId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[derive(Iden, EnumIter)]
|
||||
pub enum ImageState {
|
||||
ImageState,
|
||||
Pending,
|
||||
Approved,
|
||||
Banned,
|
||||
}
|
178
crates/migration/src/m20230726_135630_parking_spaces.rs
Normal file
178
crates/migration/src/m20230726_135630_parking_spaces.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use sea_orm::{EnumIter, Iterable};
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::m20220101_000001_create_table::Account;
|
||||
use crate::{create_enum, drop_enum};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
create_enum::<ParkingSpaceState>(m).await?;
|
||||
create_enum::<Side>(m).await?;
|
||||
|
||||
m.create_table(
|
||||
Table::create()
|
||||
.table(ParkingSpace::ParkingSpaces)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpace::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpace::State)
|
||||
.enumeration(
|
||||
ParkingSpaceState::ParkingSpaceState,
|
||||
ParkingSpaceState::iter().skip(1),
|
||||
)
|
||||
.default(ParkingSpaceState::Pending.to_string())
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(ParkingSpace::Location).string().not_null())
|
||||
.col(ColumnDef::new(ParkingSpace::AccountId).integer().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpace::CreatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpace::UpdatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.if_not_exists()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.create_foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
.from_tbl(ParkingSpace::ParkingSpaces)
|
||||
.from_col(ParkingSpace::AccountId)
|
||||
.to_tbl(Account::Accounts)
|
||||
.to_col(Account::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.create_table(
|
||||
Table::create()
|
||||
.table(ParkingSpaceRent::ParkingSpaceRents)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceRent::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ParkingSpaceRent::Price).integer().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceRent::ParkingSpaceId)
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceRent::Available)
|
||||
.boolean()
|
||||
.default(true)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceRent::CreatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceRent::UpdatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.if_not_exists()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
m.create_foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
.from_tbl(ParkingSpaceRent::ParkingSpaceRents)
|
||||
.from_col(ParkingSpaceRent::ParkingSpaceId)
|
||||
.to_tbl(ParkingSpace::ParkingSpaces)
|
||||
.to_col(ParkingSpace::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.drop_table(
|
||||
Table::drop()
|
||||
.table(ParkingSpaceRent::ParkingSpaceRents)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.drop_table(Table::drop().table(ParkingSpace::ParkingSpaces).to_owned())
|
||||
.await?;
|
||||
|
||||
drop_enum::<Side>(m).await?;
|
||||
drop_enum::<ParkingSpaceState>(m).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum ParkingSpace {
|
||||
ParkingSpaces,
|
||||
Id,
|
||||
State,
|
||||
Location,
|
||||
LocationId,
|
||||
Spot,
|
||||
AccountId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum ParkingSpaceRent {
|
||||
ParkingSpaceRents,
|
||||
Id,
|
||||
Price,
|
||||
Available,
|
||||
ParkingSpaceId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[derive(Iden, EnumIter)]
|
||||
pub enum ParkingSpaceState {
|
||||
ParkingSpaceState,
|
||||
#[iden(rename = "Pending")]
|
||||
Pending,
|
||||
#[iden(rename = "Verified")]
|
||||
Verified,
|
||||
#[iden(rename = "Banned")]
|
||||
Banned,
|
||||
}
|
||||
|
||||
#[derive(Iden, EnumIter)]
|
||||
pub enum Side {
|
||||
Side,
|
||||
#[iden(rename = "Left")]
|
||||
Left,
|
||||
#[iden(rename = "Right")]
|
||||
Right,
|
||||
#[iden(rename = "Front")]
|
||||
Front,
|
||||
}
|
37
crates/migration/src/m20230805_000001_add_email.rs
Normal file
37
crates/migration/src/m20230805_000001_add_email.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::m20220101_000001_create_table::Account;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
let table = Table::alter()
|
||||
.table(Account::Accounts)
|
||||
.add_column(
|
||||
ColumnDef::new(Account::Email)
|
||||
.string()
|
||||
.unique_key()
|
||||
.default("filler@example.com")
|
||||
.not_null(),
|
||||
)
|
||||
.to_owned();
|
||||
|
||||
m.alter_table(table).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.alter_table(
|
||||
Table::alter()
|
||||
.table(Account::Accounts)
|
||||
.drop_column(Account::Email)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
33
crates/migration/src/m20230809_135630_add_spot.rs
Normal file
33
crates/migration/src/m20230809_135630_add_spot.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::m20230726_135630_parking_spaces::ParkingSpace;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.alter_table(
|
||||
Table::alter()
|
||||
.table(ParkingSpace::ParkingSpaces)
|
||||
.add_column(ColumnDef::new(ParkingSpace::Spot).integer())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.alter_table(
|
||||
Table::alter()
|
||||
.table(ParkingSpace::ParkingSpaces)
|
||||
.drop_column(ParkingSpace::Spot)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::m20230726_135630_parking_spaces::ParkingSpace;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.create_table(
|
||||
Table::create()
|
||||
.table(ParkingSpaceLocation::ParkingSpaceLocations)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceLocation::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceLocation::Name)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceLocation::Number)
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceLocation::Stage)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceLocation::CreatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ParkingSpaceLocation::UpdatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.if_not_exists()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.alter_table(
|
||||
Table::alter()
|
||||
.table(ParkingSpace::ParkingSpaces)
|
||||
.drop_column(ParkingSpace::Location)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
m.alter_table(
|
||||
Table::alter()
|
||||
.table(ParkingSpace::ParkingSpaces)
|
||||
.add_column(ColumnDef::new(ParkingSpace::LocationId).integer())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.create_foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
.from_tbl(ParkingSpace::ParkingSpaces)
|
||||
.from_col(ParkingSpace::LocationId)
|
||||
.to_tbl(ParkingSpaceLocation::ParkingSpaceLocations)
|
||||
.to_col(ParkingSpaceLocation::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.create_index(
|
||||
IndexCreateStatement::default()
|
||||
.unique()
|
||||
.name("uniq-location")
|
||||
.table(ParkingSpaceLocation::ParkingSpaceLocations)
|
||||
.col(ParkingSpaceLocation::Name)
|
||||
.col(ParkingSpaceLocation::Number)
|
||||
.col(ParkingSpaceLocation::Stage)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.drop_table(
|
||||
Table::drop()
|
||||
.table(ParkingSpaceLocation::ParkingSpaceLocations)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum ParkingSpaceLocation {
|
||||
ParkingSpaceLocations,
|
||||
Id,
|
||||
Name,
|
||||
Number,
|
||||
Stage,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::m20220101_000001_create_table::Account;
|
||||
use crate::m20230726_135630_parking_spaces::{ParkingSpace, ParkingSpaceRent};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.create_table(
|
||||
Table::create()
|
||||
.table(RentRequest::RentRequests)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(RentRequest::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(RentRequest::ParkingSpaceId).integer())
|
||||
.col(ColumnDef::new(RentRequest::ParkingSpaceRentId).integer())
|
||||
.col(ColumnDef::new(RentRequest::AccountId).integer())
|
||||
.col(
|
||||
ColumnDef::new(RentRequest::CreatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(RentRequest::UpdatedAt)
|
||||
.timestamp()
|
||||
.default(SimpleExpr::Custom("NOW()".to_owned()))
|
||||
.not_null(),
|
||||
)
|
||||
.if_not_exists()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
m.create_foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
.from_tbl(RentRequest::RentRequests)
|
||||
.from_col(RentRequest::ParkingSpaceId)
|
||||
.to_tbl(ParkingSpace::ParkingSpaces)
|
||||
.to_col(ParkingSpace::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
m.create_foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
.from_tbl(RentRequest::RentRequests)
|
||||
.from_col(RentRequest::ParkingSpaceId)
|
||||
.to_tbl(ParkingSpaceRent::ParkingSpaceRents)
|
||||
.to_col(ParkingSpaceRent::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
m.create_foreign_key(
|
||||
ForeignKeyCreateStatement::new()
|
||||
.from_tbl(RentRequest::RentRequests)
|
||||
.from_col(RentRequest::AccountId)
|
||||
.to_tbl(Account::Accounts)
|
||||
.to_col(Account::Id)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
m.drop_table(Table::drop().table(RentRequest::RentRequests).to_owned())
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum RentRequest {
|
||||
RentRequests,
|
||||
Id,
|
||||
ParkingSpaceId,
|
||||
ParkingSpaceRentId,
|
||||
AccountId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
6
crates/migration/src/main.rs
Normal file
6
crates/migration/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
cli::run_cli(migration::Migrator).await;
|
||||
}
|
10
crates/oswilno-actix-admin/Cargo.toml
Normal file
10
crates/oswilno-actix-admin/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "oswilno-actix-admin"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
|
162
crates/oswilno-actix-admin/src/lib.rs
Normal file
162
crates/oswilno-actix-admin/src/lib.rs
Normal file
@ -0,0 +1,162 @@
|
||||
extern crate proc_macro;
|
||||
use std::iter::Peekable;
|
||||
|
||||
use proc_macro::token_stream::IntoIter;
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
|
||||
fn expect_iden<F: FnMut(String) -> bool>(s: &mut Peekable<IntoIter>, mut name: F) {
|
||||
let token = s.next().unwrap();
|
||||
match token {
|
||||
TokenTree::Ident(iden) => {
|
||||
if !name(iden.to_string()) {
|
||||
panic!("expect identifier");
|
||||
}
|
||||
}
|
||||
_ => panic!("expect identifier got {token:?}"),
|
||||
};
|
||||
}
|
||||
|
||||
fn skip_proc_macro(s: &mut Peekable<IntoIter>) {
|
||||
while let Some(token) = s.peek() {
|
||||
match token {
|
||||
TokenTree::Punct(p) if p.to_string().as_str() == "#" => {
|
||||
s.next();
|
||||
s.next();
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct VariantInfo {
|
||||
rs_name: String,
|
||||
db_name: String,
|
||||
}
|
||||
#[derive(Default, Debug)]
|
||||
struct EnumDef {
|
||||
name: String,
|
||||
variants: Vec<VariantInfo>,
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ActixAdminEnum)]
|
||||
pub fn derive_actix_admin_enum(item: TokenStream) -> TokenStream {
|
||||
let clone = item.clone();
|
||||
let mut it = clone.into_iter().peekable();
|
||||
skip_proc_macro(&mut it);
|
||||
expect_iden(&mut it, |iden| &iden == "pub");
|
||||
expect_iden(&mut it, |iden| &iden == "enum");
|
||||
|
||||
let mut def = EnumDef::default();
|
||||
expect_iden(&mut it, |iden| {
|
||||
def.name = iden;
|
||||
true
|
||||
});
|
||||
|
||||
let mut it = if let TokenTree::Group(g) = it.next().unwrap() {
|
||||
g.stream().into_iter().peekable()
|
||||
} else {
|
||||
panic!("expect enum body");
|
||||
};
|
||||
|
||||
// Parse macros
|
||||
parse_enum_variants(&mut it, &mut def);
|
||||
let mut buffer = String::new();
|
||||
buffer.push_str(&format!("impl std::str::FromStr for {} {{\n type Err = ();\n fn from_str(s: &str) -> Result<Self, ()> {{\n match s {{\n", def.name));
|
||||
for v in &def.variants {
|
||||
buffer.push_str(&format!(
|
||||
" {:?} => Ok({}::{}),\n",
|
||||
v.db_name, def.name, v.rs_name
|
||||
));
|
||||
}
|
||||
buffer.push_str(" _ => Err(()),\n");
|
||||
buffer.push_str(" }\n }\n}");
|
||||
buffer.push_str(&format!(
|
||||
"impl ToString for {} {{\n fn to_string(&self) -> String {{\n match self {{\n",
|
||||
def.name
|
||||
));
|
||||
for v in def.variants {
|
||||
buffer.push_str(&format!(
|
||||
" {}::{} => {:?},\n",
|
||||
def.name, v.rs_name, v.db_name
|
||||
));
|
||||
}
|
||||
buffer.push_str(" }.to_string()\n }\n}");
|
||||
// eprintln!("{buffer}");
|
||||
buffer.as_str().parse().unwrap()
|
||||
}
|
||||
|
||||
fn parse_enum_variants(body_it: &mut Peekable<IntoIter>, def: &mut EnumDef) {
|
||||
while body_it.peek().is_some() {
|
||||
if let Some(variant) = parse_enum_variant(body_it) {
|
||||
def.variants.push(variant);
|
||||
}
|
||||
if let Some(TokenTree::Punct(p)) = body_it.peek() {
|
||||
if p.to_string().as_str() == "," {
|
||||
body_it.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_enum_variant(body_it: &mut Peekable<IntoIter>) -> Option<VariantInfo> {
|
||||
let mut variant_info = VariantInfo::default();
|
||||
|
||||
while body_it.peek().is_some() {
|
||||
if let Some(db_name) = parse_enum_variant_macro(body_it) {
|
||||
variant_info.db_name = db_name;
|
||||
break;
|
||||
}
|
||||
if let Some(TokenTree::Punct(p)) = body_it.peek() {
|
||||
if p.to_string().as_str() == "," {
|
||||
body_it.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let iden = body_it.next().expect("No variant name");
|
||||
variant_info.rs_name = iden.to_string();
|
||||
|
||||
Some(variant_info)
|
||||
}
|
||||
|
||||
fn parse_enum_variant_macro(body_it: &mut Peekable<IntoIter>) -> Option<String> {
|
||||
let (_punc, group) = (
|
||||
body_it.next().expect("No # for macro"),
|
||||
body_it.next().expect("No macro body"),
|
||||
);
|
||||
// eprintln!("punc {_punc:#?} group {group:#?}");
|
||||
let mut it = if let TokenTree::Group(g) = group {
|
||||
// parse #[sea_orm]
|
||||
g.stream().into_iter().peekable()
|
||||
} else {
|
||||
panic!("Unexpected token after #: {group:?}");
|
||||
};
|
||||
let token = it.next();
|
||||
if let Some(TokenTree::Ident(iden)) = token {
|
||||
if iden.to_string().as_str() != "sea_orm" {
|
||||
eprintln!("iden is not sea_orm");
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
eprintln!("token should be iden but is {token:#?}");
|
||||
return None;
|
||||
}
|
||||
|
||||
let it = if let Some(TokenTree::Group(g)) = it.next() {
|
||||
g.stream().into_iter().peekable()
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut it = it.skip_while(|t| match t {
|
||||
TokenTree::Ident(id) => id.to_string().as_str() != "string_value",
|
||||
_ => true,
|
||||
});
|
||||
let (_id, _punct, literal) = (it.next(), it.next(), it.next());
|
||||
|
||||
match literal {
|
||||
Some(TokenTree::Literal(l)) => Some(l.to_string().replace('\"', "")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
17
crates/oswilno-admin/Cargo.toml
Normal file
17
crates/oswilno-admin/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "oswilno-admin"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
# actix-admin = "0.5.0"
|
||||
actix-admin = { git = "https://github.com/mgugger/actix-admin.git" }
|
||||
# actix-admin = { git = "https://code.ita-prog.pl/Tsumanu/actix-admin.git", features = ['enable-tracing'] }
|
||||
actix-web = "4.3.1"
|
||||
actix-web-grants = "3.0.2"
|
||||
askama = "0.12.0"
|
||||
chrono = "0.4.26"
|
||||
oswilno-contract = { path = "../oswilno-contract" }
|
||||
tera = "1.17.1"
|
||||
uuid = { version = "1.4.1", features = ["v4"] }
|
||||
tracing = "0.1.37"
|
2
crates/oswilno-admin/askama.toml
Normal file
2
crates/oswilno-admin/askama.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[general]
|
||||
dirs = ["templates", "../oswilno-view/templates"]
|
44
crates/oswilno-admin/src/lib.rs
Normal file
44
crates/oswilno-admin/src/lib.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use actix_admin::prelude::*;
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
|
||||
pub fn mount(config: &mut ServiceConfig) {
|
||||
let actix_admin_builder = create_actix_admin_builder();
|
||||
|
||||
config
|
||||
.app_data(Data::new(actix_admin_builder.get_actix_admin()))
|
||||
.service(actix_admin_builder.get_scope());
|
||||
}
|
||||
|
||||
fn create_actix_admin_builder() -> ActixAdminBuilder {
|
||||
let configuration = ActixAdminConfiguration {
|
||||
enable_auth: false,
|
||||
user_is_logged_in: None,
|
||||
login_link: None,
|
||||
logout_link: None,
|
||||
file_upload_directory: "./file_uploads",
|
||||
navbar_title: "oswilno - admin",
|
||||
user_tenant_ref: None,
|
||||
};
|
||||
|
||||
let mut admin_builder = ActixAdminBuilder::new(configuration);
|
||||
{
|
||||
use oswilno_contract::prelude::Accounts;
|
||||
let view = ActixAdminViewModel::from(Accounts);
|
||||
admin_builder.add_entity::<Accounts>(&view);
|
||||
}
|
||||
{
|
||||
use oswilno_contract::prelude::ParkingSpaces;
|
||||
let view = ActixAdminViewModel::from(ParkingSpaces);
|
||||
admin_builder.add_entity_to_category::<ParkingSpaces>(&view, "Parking spaces");
|
||||
}
|
||||
{
|
||||
use oswilno_contract::prelude::ParkingSpaceRents;
|
||||
let view = ActixAdminViewModel::from(ParkingSpaceRents);
|
||||
admin_builder.add_entity_to_category::<ParkingSpaceRents>(&view, "Parking spaces");
|
||||
}
|
||||
for scope in admin_builder.scopes.keys() {
|
||||
tracing::trace!("Scope {scope:?}");
|
||||
}
|
||||
|
||||
admin_builder
|
||||
}
|
10
crates/oswilno-config/Cargo.toml
Normal file
10
crates/oswilno-config/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "oswilno-config"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.175", features = ["derive"] }
|
||||
serde_json = "1.0.103"
|
||||
toml = "0.7.6"
|
||||
|
7
crates/oswilno-config/src/lib.rs
Normal file
7
crates/oswilno-config/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
bind: Option<String>,
|
||||
port: Option<u16>,
|
||||
}
|
18
crates/oswilno-contract/Cargo.toml
Normal file
18
crates/oswilno-contract/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "oswilno-contract"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.13.0"
|
||||
# actix-admin = "0.5.0"
|
||||
# actix-admin = { git = "https://github.com/Eraden/actix-admin.git", features = ['enable-tracing'] }
|
||||
actix-admin = { git = "https://github.com/mgugger/actix-admin.git" }
|
||||
# actix-admin = { git = "https://code.ita-prog.pl/Tsumanu/actix-admin.git", features = ['enable-tracing'] }
|
||||
actix-rt = { version = "2.8.0", features = [] }
|
||||
chrono = { version = "0.4.26", features = ["serde"] }
|
||||
oswilno-actix-admin = { path = "../oswilno-actix-admin" }
|
||||
regex = "1.9.1"
|
||||
sea-orm = { version = "0.12", features = ["postgres-array", "runtime-actix-rustls", "sqlx-postgres", "macros", "sqlx"] }
|
||||
serde = { version = "1.0.175", features = ["derive"] }
|
||||
uuid = { version = "1.4.1", features = ["v4"] }
|
129
crates/oswilno-contract/src/accounts.rs
Normal file
129
crates/oswilno-contract/src/accounts.rs
Normal file
@ -0,0 +1,129 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
|
||||
|
||||
use actix_admin::prelude::*;
|
||||
use sea_orm::entity::prelude::*;
|
||||
#[allow(unused_imports)]
|
||||
use sea_orm::Iterable;
|
||||
|
||||
use super::sea_orm_active_enums::Userrole;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl ActixAdminModelValidationTrait<ActiveModel> for Entity {}
|
||||
impl ActixAdminModelFilterTrait<Entity> for Entity {}
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"accounts"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
DeriveModel,
|
||||
DeriveActiveModel,
|
||||
Eq,
|
||||
DeriveActixAdmin,
|
||||
DeriveActixAdminModel,
|
||||
DeriveActixAdminViewModel,
|
||||
)]
|
||||
pub struct Model {
|
||||
#[actix_admin(primary_key)]
|
||||
pub id: i32,
|
||||
pub login: String,
|
||||
#[actix_admin(list_hide_column)]
|
||||
pub pass_hash: String,
|
||||
pub role: Userrole,
|
||||
pub banned: bool,
|
||||
pub confirmed: bool,
|
||||
pub verified: bool,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub created_at: DateTime,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub updated_at: DateTime,
|
||||
#[actix_admin(html_input_type = "email")]
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
Login,
|
||||
PassHash,
|
||||
Role,
|
||||
Banned,
|
||||
Confirmed,
|
||||
Verified,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
Email,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
type ValueType = i32;
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
Images,
|
||||
ParkingSpaces,
|
||||
RentRequests,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnDef {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer.def(),
|
||||
Self::Login => ColumnType::String(None).def(),
|
||||
Self::PassHash => ColumnType::String(None).def(),
|
||||
Self::Role => Userrole::db_type(),
|
||||
Self::Banned => ColumnType::Boolean.def(),
|
||||
Self::Confirmed => ColumnType::Boolean.def(),
|
||||
Self::Verified => ColumnType::Boolean.def(),
|
||||
Self::CreatedAt => ColumnType::DateTime.def(),
|
||||
Self::UpdatedAt => ColumnType::DateTime.def(),
|
||||
Self::Email => ColumnType::String(None).def().unique(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::Images => Entity::has_many(super::images::Entity).into(),
|
||||
Self::ParkingSpaces => Entity::has_many(super::parking_spaces::Entity).into(),
|
||||
Self::RentRequests => Entity::has_many(super::rent_requests::Entity).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::images::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Images.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::parking_spaces::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ParkingSpaces.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::rent_requests::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::RentRequests.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
105
crates/oswilno-contract/src/images.rs
Normal file
105
crates/oswilno-contract/src/images.rs
Normal file
@ -0,0 +1,105 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
|
||||
|
||||
use actix_admin::prelude::*;
|
||||
use sea_orm::entity::prelude::*;
|
||||
#[allow(unused_imports)]
|
||||
use sea_orm::Iterable;
|
||||
|
||||
use super::sea_orm_active_enums::ImageState;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl ActixAdminModelValidationTrait<ActiveModel> for Entity {}
|
||||
impl ActixAdminModelFilterTrait<Entity> for Entity {}
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"images"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
DeriveModel,
|
||||
DeriveActiveModel,
|
||||
Eq,
|
||||
DeriveActixAdmin,
|
||||
DeriveActixAdminModel,
|
||||
DeriveActixAdminViewModel,
|
||||
)]
|
||||
pub struct Model {
|
||||
#[actix_admin(primary_key)]
|
||||
pub id: i32,
|
||||
pub local_path: String,
|
||||
pub public_path: String,
|
||||
pub image_state: ImageState,
|
||||
pub account_id: i32,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub created_at: DateTime,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
LocalPath,
|
||||
PublicPath,
|
||||
ImageState,
|
||||
AccountId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
type ValueType = i32;
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
Accounts,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnDef {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer.def(),
|
||||
Self::LocalPath => ColumnType::String(None).def(),
|
||||
Self::PublicPath => ColumnType::String(None).def(),
|
||||
Self::ImageState => ImageState::db_type(),
|
||||
Self::AccountId => ColumnType::Integer.def(),
|
||||
Self::CreatedAt => ColumnType::DateTime.def(),
|
||||
Self::UpdatedAt => ColumnType::DateTime.def(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::Accounts => Entity::belongs_to(super::accounts::Entity)
|
||||
.from(Column::AccountId)
|
||||
.to(super::accounts::Column::Id)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::accounts::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Accounts.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
12
crates/oswilno-contract/src/lib.rs
Normal file
12
crates/oswilno-contract/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod accounts;
|
||||
pub mod images;
|
||||
pub mod parking_space_locations;
|
||||
pub mod parking_space_rents;
|
||||
pub mod parking_spaces;
|
||||
pub mod rent_requests;
|
||||
pub mod sea_orm_active_enums;
|
||||
pub use {::chrono, actix_admin, sea_orm};
|
97
crates/oswilno-contract/src/parking_space_locations.rs
Normal file
97
crates/oswilno-contract/src/parking_space_locations.rs
Normal file
@ -0,0 +1,97 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
|
||||
|
||||
use actix_admin::prelude::*;
|
||||
use sea_orm::entity::prelude::*;
|
||||
#[allow(unused_imports)]
|
||||
use sea_orm::Iterable;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl ActixAdminModelValidationTrait<ActiveModel> for Entity {}
|
||||
impl ActixAdminModelFilterTrait<Entity> for Entity {}
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"parking_space_locations"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
DeriveModel,
|
||||
DeriveActiveModel,
|
||||
Eq,
|
||||
DeriveActixAdmin,
|
||||
DeriveActixAdminModel,
|
||||
DeriveActixAdminViewModel,
|
||||
)]
|
||||
pub struct Model {
|
||||
#[actix_admin(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub number: i32,
|
||||
pub stage: String,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub created_at: DateTime,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
Name,
|
||||
Number,
|
||||
Stage,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
type ValueType = i32;
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
ParkingSpaces,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnDef {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer.def(),
|
||||
Self::Name => ColumnType::String(None).def(),
|
||||
Self::Number => ColumnType::Integer.def(),
|
||||
Self::Stage => ColumnType::String(None).def(),
|
||||
Self::CreatedAt => ColumnType::DateTime.def(),
|
||||
Self::UpdatedAt => ColumnType::DateTime.def(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::ParkingSpaces => Entity::has_many(super::parking_spaces::Entity).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::parking_spaces::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ParkingSpaces.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
109
crates/oswilno-contract/src/parking_space_rents.rs
Normal file
109
crates/oswilno-contract/src/parking_space_rents.rs
Normal file
@ -0,0 +1,109 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
|
||||
|
||||
use actix_admin::prelude::*;
|
||||
use sea_orm::entity::prelude::*;
|
||||
#[allow(unused_imports)]
|
||||
use sea_orm::Iterable;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl ActixAdminModelValidationTrait<ActiveModel> for Entity {}
|
||||
impl ActixAdminModelFilterTrait<Entity> for Entity {}
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"parking_space_rents"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
DeriveModel,
|
||||
DeriveActiveModel,
|
||||
Eq,
|
||||
DeriveActixAdmin,
|
||||
DeriveActixAdminModel,
|
||||
DeriveActixAdminViewModel,
|
||||
)]
|
||||
pub struct Model {
|
||||
#[actix_admin(primary_key)]
|
||||
pub id: i32,
|
||||
pub price: i32,
|
||||
#[actix_admin(select_list=crate::parking_spaces::Entity)]
|
||||
pub parking_space_id: i32,
|
||||
pub available: bool,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub created_at: DateTime,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub updated_at: DateTime,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
Price,
|
||||
ParkingSpaceId,
|
||||
Available,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
type ValueType = i32;
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
ParkingSpaces,
|
||||
RentRequests,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnDef {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer.def(),
|
||||
Self::Price => ColumnType::Integer.def(),
|
||||
Self::ParkingSpaceId => ColumnType::Integer.def(),
|
||||
Self::Available => ColumnType::Boolean.def(),
|
||||
Self::CreatedAt => ColumnType::DateTime.def(),
|
||||
Self::UpdatedAt => ColumnType::DateTime.def(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::ParkingSpaces => Entity::belongs_to(super::parking_spaces::Entity)
|
||||
.from(Column::ParkingSpaceId)
|
||||
.to(super::parking_spaces::Column::Id)
|
||||
.into(),
|
||||
Self::RentRequests => Entity::has_many(super::rent_requests::Entity).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::parking_spaces::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ParkingSpaces.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::rent_requests::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::RentRequests.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
136
crates/oswilno-contract/src/parking_spaces.rs
Normal file
136
crates/oswilno-contract/src/parking_spaces.rs
Normal file
@ -0,0 +1,136 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
|
||||
|
||||
use actix_admin::prelude::*;
|
||||
use sea_orm::entity::prelude::*;
|
||||
#[allow(unused_imports)]
|
||||
use sea_orm::Iterable;
|
||||
|
||||
use super::sea_orm_active_enums::ParkingSpaceState;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
|
||||
pub struct Entity;
|
||||
|
||||
impl ActixAdminModelValidationTrait<ActiveModel> for Entity {}
|
||||
impl ActixAdminModelFilterTrait<Entity> for Entity {}
|
||||
impl EntityName for Entity {
|
||||
fn table_name(&self) -> &str {
|
||||
"parking_spaces"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
DeriveModel,
|
||||
DeriveActiveModel,
|
||||
Eq,
|
||||
DeriveActixAdmin,
|
||||
DeriveActixAdminModel,
|
||||
DeriveActixAdminViewModel,
|
||||
)]
|
||||
pub struct Model {
|
||||
#[actix_admin(primary_key)]
|
||||
pub id: i32,
|
||||
#[actix_admin(select_list=crate::parking_spaces::ParkingSpaceState)]
|
||||
pub state: ParkingSpaceState,
|
||||
pub account_id: i32,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub created_at: DateTime,
|
||||
#[actix_admin(list_hide_column, column_type = "NaiveDateTime")]
|
||||
pub updated_at: DateTime,
|
||||
pub spot: Option<i32>,
|
||||
#[actix_admin(select_list=crate::parking_space_locations::Entity)]
|
||||
pub location_id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
pub enum Column {
|
||||
Id,
|
||||
State,
|
||||
AccountId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
Spot,
|
||||
LocationId,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
|
||||
pub enum PrimaryKey {
|
||||
Id,
|
||||
}
|
||||
|
||||
impl PrimaryKeyTrait for PrimaryKey {
|
||||
type ValueType = i32;
|
||||
fn auto_increment() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
Accounts,
|
||||
ParkingSpaceLocations,
|
||||
ParkingSpaceRents,
|
||||
RentRequests,
|
||||
}
|
||||
|
||||
impl ColumnTrait for Column {
|
||||
type EntityName = Entity;
|
||||
fn def(&self) -> ColumnDef {
|
||||
match self {
|
||||
Self::Id => ColumnType::Integer.def(),
|
||||
Self::State => ParkingSpaceState::db_type(),
|
||||
Self::AccountId => ColumnType::Integer.def(),
|
||||
Self::CreatedAt => ColumnType::DateTime.def(),
|
||||
Self::UpdatedAt => ColumnType::DateTime.def(),
|
||||
Self::Spot => ColumnType::Integer.def().null(),
|
||||
Self::LocationId => ColumnType::Integer.def().null(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::Accounts => Entity::belongs_to(super::accounts::Entity)
|
||||
.from(Column::AccountId)
|
||||
.to(super::accounts::Column::Id)
|
||||
.into(),
|
||||
Self::ParkingSpaceLocations => {
|
||||
Entity::belongs_to(super::parking_space_locations::Entity)
|
||||
.from(Column::LocationId)
|
||||
.to(super::parking_space_locations::Column::Id)
|
||||
.into()
|
||||
}
|
||||
Self::ParkingSpaceRents => Entity::has_many(super::parking_space_rents::Entity).into(),
|
||||
Self::RentRequests => Entity::has_many(super::rent_requests::Entity).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::accounts::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Accounts.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::parking_space_locations::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ParkingSpaceLocations.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::parking_space_rents::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ParkingSpaceRents.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::rent_requests::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::RentRequests.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user