mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 17:02:32 +00:00
frontend: Add dropdown for timezone selection
This commit is contained in:
@@ -39,3 +39,6 @@ headers.workspace = true
|
||||
tower-sessions.workspace = true
|
||||
percent-encoding.workspace = true
|
||||
tower-http = { workspace = true, optional = true }
|
||||
vtimezones-rs.workspace = true
|
||||
serde_json.workspace = true
|
||||
itertools.workspace = true
|
||||
|
||||
@@ -7,6 +7,11 @@ import { escapeXml } from ".";
|
||||
export class CreateCalendarForm extends LitElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.fetchTimezones()
|
||||
}
|
||||
|
||||
async fetchTimezones() {
|
||||
this.timezones = await getTimezones()
|
||||
}
|
||||
|
||||
protected override createRenderRoot() {
|
||||
@@ -36,6 +41,8 @@ export class CreateCalendarForm extends LitElement {
|
||||
|
||||
dialog: Ref<HTMLDialogElement> = createRef()
|
||||
form: Ref<HTMLFormElement> = createRef()
|
||||
@property()
|
||||
timezones: Array<String> = []
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
@@ -65,7 +72,12 @@ export class CreateCalendarForm extends LitElement {
|
||||
<br>
|
||||
<label>
|
||||
Timezone (optional)
|
||||
<input type="text" name="timezone" .value=${this.timezone_id} @change=${e => this.timezone_id = e.target.value} />
|
||||
<select name="timezone" .value=${this.timezone_id} @change=${e => this.timezone_id = e.target.value}>
|
||||
<option value="">No timezone</option>
|
||||
${this.timezones.map(timezone => html`
|
||||
<option value=${timezone} ?selected=${timezone === this.timezone_id}>${timezone}</option>
|
||||
`)}
|
||||
</select>
|
||||
</label>
|
||||
<br>
|
||||
<label>
|
||||
|
||||
@@ -2,11 +2,17 @@ import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { Ref, createRef, ref } from 'lit/directives/ref.js';
|
||||
import { escapeXml } from ".";
|
||||
import { getTimezones } from "./timezones.ts";
|
||||
|
||||
@customElement("edit-calendar-form")
|
||||
export class EditCalendarForm extends LitElement {
|
||||
constructor() {
|
||||
super()
|
||||
this.fetchTimezones()
|
||||
}
|
||||
|
||||
async fetchTimezones() {
|
||||
this.timezones = await getTimezones()
|
||||
}
|
||||
|
||||
protected override createRenderRoot() {
|
||||
@@ -36,7 +42,8 @@ export class EditCalendarForm extends LitElement {
|
||||
|
||||
dialog: Ref<HTMLDialogElement> = createRef()
|
||||
form: Ref<HTMLFormElement> = createRef()
|
||||
|
||||
@property()
|
||||
timezones: Array<String> = []
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
@@ -51,7 +58,12 @@ export class EditCalendarForm extends LitElement {
|
||||
<br>
|
||||
<label>
|
||||
Timezone (optional)
|
||||
<input type="text" name="timezone" .value=${this.timezone_id} @change=${e => this.timezone_id = e.target.value} />
|
||||
<select name="timezone" .value=${this.timezone_id} @change=${e => this.timezone_id = e.target.value}>
|
||||
<option value="">No timezone</option>
|
||||
${this.timezones.map(timezone => html`
|
||||
<option value=${timezone} ?selected=${timezone === this.timezone_id}>${timezone}</option>
|
||||
`)}
|
||||
</select>
|
||||
</label>
|
||||
<br>
|
||||
<label>
|
||||
|
||||
12
crates/frontend/js-components/lib/timezones.ts
Normal file
12
crates/frontend/js-components/lib/timezones.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
let timezonesPromise = null
|
||||
export async function getTimezones() {
|
||||
timezonesPromise ||= new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let response = await fetch('/frontend/_timezones.json')
|
||||
resolve(await response.json())
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
return await timezonesPromise
|
||||
}
|
||||
@@ -27,6 +27,11 @@ let CreateCalendarForm = class extends i {
|
||||
this.components = /* @__PURE__ */ new Set();
|
||||
this.dialog = e();
|
||||
this.form = e();
|
||||
this.timezones = [];
|
||||
this.fetchTimezones();
|
||||
}
|
||||
async fetchTimezones() {
|
||||
this.timezones = await getTimezones();
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
@@ -59,7 +64,12 @@ let CreateCalendarForm = class extends i {
|
||||
<br>
|
||||
<label>
|
||||
Timezone (optional)
|
||||
<input type="text" name="timezone" .value=${this.timezone_id} @change=${(e2) => this.timezone_id = e2.target.value} />
|
||||
<select name="timezone" .value=${this.timezone_id} @change=${(e2) => this.timezone_id = e2.target.value}>
|
||||
<option value="">No timezone</option>
|
||||
${this.timezones.map((timezone) => x`
|
||||
<option value=${timezone} ?selected=${timezone === this.timezone_id}>${timezone}</option>
|
||||
`)}
|
||||
</select>
|
||||
</label>
|
||||
<br>
|
||||
<label>
|
||||
@@ -179,6 +189,9 @@ __decorateClass([
|
||||
__decorateClass([
|
||||
n$1()
|
||||
], CreateCalendarForm.prototype, "components", 2);
|
||||
__decorateClass([
|
||||
n$1()
|
||||
], CreateCalendarForm.prototype, "timezones", 2);
|
||||
CreateCalendarForm = __decorateClass([
|
||||
t("create-calendar-form")
|
||||
], CreateCalendarForm);
|
||||
|
||||
@@ -2,6 +2,18 @@ import { i, x } from "./lit-DkXrt_Iv.mjs";
|
||||
import { n as n$1, t } from "./property-B8WoKf1Y.mjs";
|
||||
import { e, n } from "./ref-BwbQvJBB.mjs";
|
||||
import { e as escapeXml } from "./index-_IB1wMbZ.mjs";
|
||||
let timezonesPromise = null;
|
||||
async function getTimezones() {
|
||||
timezonesPromise ||= new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let response = await fetch("/frontend/_timezones.json");
|
||||
resolve(await response.json());
|
||||
} catch (e2) {
|
||||
reject(e2);
|
||||
}
|
||||
});
|
||||
return await timezonesPromise;
|
||||
}
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __decorateClass = (decorators, target, key, kind) => {
|
||||
@@ -22,6 +34,11 @@ let EditCalendarForm = class extends i {
|
||||
this.components = /* @__PURE__ */ new Set();
|
||||
this.dialog = e();
|
||||
this.form = e();
|
||||
this.timezones = [];
|
||||
this.fetchTimezones();
|
||||
}
|
||||
async fetchTimezones() {
|
||||
this.timezones = await getTimezones();
|
||||
}
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
@@ -39,7 +56,12 @@ let EditCalendarForm = class extends i {
|
||||
<br>
|
||||
<label>
|
||||
Timezone (optional)
|
||||
<input type="text" name="timezone" .value=${this.timezone_id} @change=${(e2) => this.timezone_id = e2.target.value} />
|
||||
<select name="timezone" .value=${this.timezone_id} @change=${(e2) => this.timezone_id = e2.target.value}>
|
||||
<option value="">No timezone</option>
|
||||
${this.timezones.map((timezone) => x`
|
||||
<option value=${timezone} ?selected=${timezone === this.timezone_id}>${timezone}</option>
|
||||
`)}
|
||||
</select>
|
||||
</label>
|
||||
<br>
|
||||
<label>
|
||||
@@ -150,6 +172,9 @@ __decorateClass([
|
||||
}
|
||||
})
|
||||
], EditCalendarForm.prototype, "components", 2);
|
||||
__decorateClass([
|
||||
n$1()
|
||||
], EditCalendarForm.prototype, "timezones", 2);
|
||||
EditCalendarForm = __decorateClass([
|
||||
t("edit-calendar-form")
|
||||
], EditCalendarForm);
|
||||
|
||||
@@ -85,4 +85,3 @@
|
||||
{% endif %}
|
||||
<create-calendar-form user="{{ user.id }}"></create-calendar-form>
|
||||
<import-calendar-form user="{{ user.id }}"></import-calendar-form>
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ use crate::routes::{
|
||||
app_token::{route_delete_app_token, route_post_app_token},
|
||||
calendar::{route_calendar, route_calendar_restore},
|
||||
login::{route_get_login, route_post_login, route_post_logout},
|
||||
timezones::route_timezones,
|
||||
user::{route_get_home, route_root, route_user_named},
|
||||
};
|
||||
#[cfg(not(feature = "dev"))]
|
||||
@@ -79,7 +80,11 @@ pub fn frontend_router<AP: AuthenticationProvider, CS: CalendarStore, AS: Addres
|
||||
.route("/", get(route_root))
|
||||
.nest("/user", user_router)
|
||||
.route("/login", get(route_get_login).post(route_post_login::<AP>))
|
||||
.route("/logout", post(route_post_logout));
|
||||
.route("/logout", post(route_post_logout))
|
||||
.route(
|
||||
"/_timezones.json",
|
||||
get(route_timezones).head(route_timezones),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "dev"))]
|
||||
let mut router = router.route_service("/assets/{*file}", EmbedService::<Assets>::default());
|
||||
|
||||
@@ -4,4 +4,5 @@ pub mod app_token;
|
||||
pub mod calendar;
|
||||
pub mod calendars;
|
||||
pub mod login;
|
||||
pub mod timezones;
|
||||
pub mod user;
|
||||
|
||||
30
crates/frontend/src/routes/timezones.rs
Normal file
30
crates/frontend/src/routes/timezones.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use headers::{CacheControl, ContentType, HeaderMapExt};
|
||||
use http::{HeaderMap, HeaderValue, Method};
|
||||
use itertools::Itertools;
|
||||
use std::{sync::LazyLock, time::Duration};
|
||||
|
||||
static VTIMEZONES_JSON: LazyLock<String> = LazyLock::new(|| {
|
||||
serde_json::to_string(
|
||||
&vtimezones_rs::VTIMEZONES
|
||||
.keys()
|
||||
.sorted()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
pub async fn route_timezones(method: Method) -> Response {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.typed_insert(ContentType::json());
|
||||
headers.insert(
|
||||
"ETag",
|
||||
HeaderValue::from_static(vtimezones_rs::IANA_TZDB_VERSION),
|
||||
);
|
||||
headers.typed_insert(CacheControl::new().with_max_age(Duration::from_hours(2)));
|
||||
|
||||
if method == Method::HEAD {
|
||||
return headers.into_response();
|
||||
}
|
||||
(headers, VTIMEZONES_JSON.as_str()).into_response()
|
||||
}
|
||||
Reference in New Issue
Block a user