Compare commits
3 Commits
0b6b5764ae
...
9089589d36
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9089589d36 | ||
![]() |
fd07001983 | ||
![]() |
0266b2a559 |
@ -1,3 +1,5 @@
|
|||||||
|
@import "shared.css";
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -342,4 +344,3 @@ a {
|
|||||||
.sidebar ul li a:hover {
|
.sidebar ul li a:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,24 @@
|
|||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
// THE SOFTWARE.
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
const getAdoptionStyles = () => {
|
||||||
|
return Array.from(document.styleSheets)
|
||||||
|
.filter((styleSheet) => styleSheet.href?.endsWith("shared.css"))
|
||||||
|
.map(x => {
|
||||||
|
const sheet = new CSSStyleSheet();
|
||||||
|
|
||||||
|
sheet.replaceSync(Array.from(x.cssRules).flatMap(rule => {
|
||||||
|
if (rule instanceof CSSImportRule) {
|
||||||
|
return Array.from(rule.styleSheet.cssRules).map(rule => rule.cssText);
|
||||||
|
} else {
|
||||||
|
return rule.cssText;
|
||||||
|
}
|
||||||
|
}).join(' '));
|
||||||
|
|
||||||
|
return sheet;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class GenericField extends HTMLElement {
|
class GenericField extends HTMLElement {
|
||||||
form = null;
|
form = null;
|
||||||
field = null;
|
field = null;
|
||||||
@ -83,18 +101,6 @@ class GenericField extends HTMLElement {
|
|||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
color: #e6e6e6;
|
color: #e6e6e6;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: 2px solid #f05a28 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
transition: opacity 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus::placeholder {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@ -155,6 +161,9 @@ class GenericField extends HTMLElement {
|
|||||||
this.container.appendChild(this.styleElement);
|
this.container.appendChild(this.styleElement);
|
||||||
|
|
||||||
this.shadowRoot.appendChild(this.container);
|
this.shadowRoot.appendChild(this.container);
|
||||||
|
|
||||||
|
|
||||||
|
this.shadowRoot.adoptedStyleSheets = getAdoptionStyles();
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@ -165,7 +174,13 @@ class GenericField extends HTMLElement {
|
|||||||
this[name] = value;
|
this[name] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focus(options) {
|
||||||
|
this.inputElement?.focus(options);
|
||||||
|
}
|
||||||
|
|
||||||
updateAttributes() {
|
updateAttributes() {
|
||||||
|
const inputUpdate = this.inputElement != null;
|
||||||
|
|
||||||
if (this.inputElement == null && this.field) {
|
if (this.inputElement == null && this.field) {
|
||||||
this.inputElement = document.createElement(this.field.tag);
|
this.inputElement = document.createElement(this.field.tag);
|
||||||
if (this.field.tag === 'button' && this.field.value === "submit") {
|
if (this.field.tag === 'button' && this.field.value === "submit") {
|
||||||
@ -218,7 +233,9 @@ class GenericField extends HTMLElement {
|
|||||||
}
|
}
|
||||||
this.inputElement.setAttribute("tabindex", this.field.index);
|
this.inputElement.setAttribute("tabindex", this.field.index);
|
||||||
this.inputElement.classList.add(this.field.name);
|
this.inputElement.classList.add(this.field.name);
|
||||||
|
if (this.field.value != null || !inputUpdate) {
|
||||||
this.value = this.field.value;
|
this.value = this.field.value;
|
||||||
|
}
|
||||||
|
|
||||||
let place_holder = this.field.place_holder ?? null;
|
let place_holder = this.field.place_holder ?? null;
|
||||||
if (this.field.required && place_holder) {
|
if (this.field.required && place_holder) {
|
||||||
@ -281,6 +298,15 @@ class GenericForm extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
const preloadedForm = this.getAttribute('preloaded-structure');
|
||||||
|
if (preloadedForm) {
|
||||||
|
try {
|
||||||
|
const form = JSON.parse(preloadedForm);
|
||||||
|
this.constructForm(form)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error, preloadedForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
const url = this.getAttribute('url');
|
const url = this.getAttribute('url');
|
||||||
if (url) {
|
if (url) {
|
||||||
const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get");
|
const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get");
|
||||||
@ -293,25 +319,35 @@ class GenericForm extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadForm(url) {
|
async constructForm(formPayload) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
this.form = formPayload;
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
this.form = await response.json();
|
|
||||||
|
|
||||||
let fields = Object.values(this.form.fields);
|
let fields = Object.values(this.form.fields);
|
||||||
|
|
||||||
|
let hasAutoFocus = Object.keys(this.fields).length !== 0;
|
||||||
|
|
||||||
fields.sort((a, b) => a.index - b.index);
|
fields.sort((a, b) => a.index - b.index);
|
||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
const fieldElement = document.createElement('generic-field');
|
const updatingField = field.name in this.fields
|
||||||
this.fields[field.name] = fieldElement;
|
|
||||||
|
this.fields[field.name] ??= document.createElement('generic-field');
|
||||||
|
|
||||||
|
const fieldElement = this.fields[field.name];
|
||||||
|
|
||||||
fieldElement.setAttribute("form", this);
|
fieldElement.setAttribute("form", this);
|
||||||
fieldElement.setAttribute("field", field);
|
fieldElement.setAttribute("field", field);
|
||||||
this.container.appendChild(fieldElement);
|
|
||||||
fieldElement.updateAttributes();
|
fieldElement.updateAttributes();
|
||||||
|
|
||||||
|
if (!updatingField) {
|
||||||
|
this.container.appendChild(fieldElement);
|
||||||
|
|
||||||
|
if (!hasAutoFocus && field.tag === "input") {
|
||||||
|
fieldElement.focus();
|
||||||
|
hasAutoFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
fieldElement.addEventListener("change", (e) => {
|
fieldElement.addEventListener("change", (e) => {
|
||||||
this.form.fields[e.detail.name].value = e.detail.value;
|
this.form.fields[e.detail.name].value = e.detail.value;
|
||||||
});
|
});
|
||||||
@ -337,8 +373,21 @@ class GenericForm extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.container.textContent = `Error: ${error.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadForm(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.constructForm(await response.json());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.container.textContent = `Error: ${error.message}`;
|
this.container.textContent = `Error: ${error.message}`;
|
||||||
}
|
}
|
||||||
|
15
src/snek/static/shared.css
Normal file
15
src/snek/static/shared.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
|
||||||
|
input, textarea {
|
||||||
|
&:focus {
|
||||||
|
outline: 2px solid #f05a28 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus::placeholder {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,10 @@
|
|||||||
|
@import "shared.css";
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog {
|
|
||||||
|
|
||||||
|
.dialog {
|
||||||
background-color: #0f0f0f;
|
background-color: #0f0f0f;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
@ -14,17 +13,18 @@
|
|||||||
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
@media screen and (max-width: 500px) {
|
||||||
.center {
|
.center {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
@ -37,6 +37,7 @@ h2 {
|
|||||||
color: #f05a28;
|
color: #f05a28;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
@ -49,6 +50,7 @@ body {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
<script src="/html-frame.js"></script>
|
<script src="/html-frame.js"></script>
|
||||||
<script src="/generic-form.js"></script>
|
<script src="/generic-form.js"></script>
|
||||||
<link rel="stylesheet" href="/html-frame.css">
|
<link rel="stylesheet" href="/html-frame.css">
|
||||||
|
<link rel="stylesheet" href="/shared.css">
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -11,6 +11,6 @@
|
|||||||
{% block main %}
|
{% block main %}
|
||||||
<div class="back-form">
|
<div class="back-form">
|
||||||
<fancy-button url="/back" text="Back" size="auto"></fancy-button>
|
<fancy-button url="/back" text="Back" size="auto"></fancy-button>
|
||||||
<generic-form class="center" url="/login.json"></generic-form>
|
<generic-form class="center" url="/login.json" preloaded-structure='{{ form|tojson|safe }}'></generic-form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -12,6 +12,6 @@
|
|||||||
<div class="back-form">
|
<div class="back-form">
|
||||||
<fancy-button url="/back" text="Back" size="auto"></fancy-button>
|
<fancy-button url="/back" text="Back" size="auto"></fancy-button>
|
||||||
|
|
||||||
<generic-form class="center" url="/register.json"></generic-form>
|
<generic-form class="center" url="/register.json" preloaded-structure='{{ form|tojson|safe }}'></generic-form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -18,7 +18,7 @@ class LoginView(BaseFormView):
|
|||||||
return web.HTTPFound("/web.html")
|
return web.HTTPFound("/web.html")
|
||||||
if self.request.path.endswith(".json"):
|
if self.request.path.endswith(".json"):
|
||||||
return await super().get()
|
return await super().get()
|
||||||
return await self.render_template("login.html")
|
return await self.render_template("login.html", {"form": await self.form(app=self.app).to_json()})
|
||||||
|
|
||||||
async def submit(self, form):
|
async def submit(self, form):
|
||||||
if await form.is_valid:
|
if await form.is_valid:
|
||||||
|
@ -18,7 +18,7 @@ class RegisterView(BaseFormView):
|
|||||||
return web.HTTPFound("/web.html")
|
return web.HTTPFound("/web.html")
|
||||||
if self.request.path.endswith(".json"):
|
if self.request.path.endswith(".json"):
|
||||||
return await super().get()
|
return await super().get()
|
||||||
return await self.render_template("register.html")
|
return await self.render_template("register.html", {"form": await self.form(app=self.app).to_json()})
|
||||||
|
|
||||||
async def submit(self, form):
|
async def submit(self, form):
|
||||||
result = await self.app.services.user.register(
|
result = await self.app.services.user.register(
|
||||||
|
Loading…
Reference in New Issue
Block a user