Compare commits
7 commits
7bc0ad21ba
...
e2a14821ba
Author | SHA1 | Date | |
---|---|---|---|
e2a14821ba | |||
41889d9160 | |||
d396c2a8c3 | |||
f9f1de8987 | |||
090da8c679 | |||
082f50c66b | |||
d66019f53c |
11 changed files with 134 additions and 70 deletions
4
Ideen.md
4
Ideen.md
|
@ -1,7 +1,5 @@
|
||||||
# MUSS
|
# MUSS
|
||||||
|
|
||||||
- api: Config-Liste von Extra-Türchen (kein Buchstabe, nur manuelles Bild)
|
|
||||||
- api: Config-Option "Überspringe leere Türchen" (standard ja)
|
|
||||||
|
|
||||||
# KANN
|
# KANN
|
||||||
|
|
||||||
|
@ -21,3 +19,5 @@
|
||||||
- `alert` durch bulma Komponente(n) ersetzen
|
- `alert` durch bulma Komponente(n) ersetzen
|
||||||
- api: admin Login case sensitivity (username "admin" == "AdMiN")
|
- api: admin Login case sensitivity (username "admin" == "AdMiN")
|
||||||
- api: `config.solution` - whitespace="IGNORE"->"REMOVE" umbenennen, +Sonderzeichen
|
- api: `config.solution` - whitespace="IGNORE"->"REMOVE" umbenennen, +Sonderzeichen
|
||||||
|
- api: Config-Option "Überspringe leere Türchen" (standard ja)
|
||||||
|
- api: Config-Liste von Extra-Türchen (kein Buchstabe, nur manuelles Bild)
|
||||||
|
|
|
@ -43,6 +43,12 @@ class Puzzle(BaseModel):
|
||||||
# Kalender so viele Tage nach der letzten Türöffnung schließen
|
# Kalender so viele Tage nach der letzten Türöffnung schließen
|
||||||
close_after: int = 90
|
close_after: int = 90
|
||||||
|
|
||||||
|
# Tage, für die kein Buchstabe vorgesehen wird
|
||||||
|
extra_days: set[int] = set()
|
||||||
|
|
||||||
|
# Türchen ohne Buchstabe überspringen
|
||||||
|
skip_empty: bool = True
|
||||||
|
|
||||||
|
|
||||||
class Image(BaseModel):
|
class Image(BaseModel):
|
||||||
# Quadrat, Seitenlänge in px
|
# Quadrat, Seitenlänge in px
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
@ -34,23 +33,6 @@ async def get_all_sorted_days(
|
||||||
return sorted(set(door.day for door in cal_cfg.doors))
|
return sorted(set(door.day for door in cal_cfg.doors))
|
||||||
|
|
||||||
|
|
||||||
async def get_all_event_dates(
|
|
||||||
cfg: Config = Depends(get_config),
|
|
||||||
days: list[int] = Depends(get_all_sorted_days),
|
|
||||||
) -> EventDates:
|
|
||||||
"""
|
|
||||||
Aktueller Kalender-Zeitraum
|
|
||||||
"""
|
|
||||||
|
|
||||||
return EventDates(
|
|
||||||
today=date.today(),
|
|
||||||
begin_month=cfg.puzzle.begin_month,
|
|
||||||
begin_day=cfg.puzzle.begin_day,
|
|
||||||
events=days,
|
|
||||||
close_after=cfg.puzzle.close_after,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_all_parts(
|
async def get_all_parts(
|
||||||
cfg: Config = Depends(get_config),
|
cfg: Config = Depends(get_config),
|
||||||
days: list[int] = Depends(get_all_sorted_days),
|
days: list[int] = Depends(get_all_sorted_days),
|
||||||
|
@ -59,6 +41,11 @@ async def get_all_parts(
|
||||||
Lösung auf vorhandene Tage aufteilen
|
Lösung auf vorhandene Tage aufteilen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# noch keine Buchstaben verteilt
|
||||||
|
result = {day: "" for day in days}
|
||||||
|
# extra-Tage ausfiltern
|
||||||
|
days = [day for day in days if day not in cfg.puzzle.extra_days]
|
||||||
|
|
||||||
solution_length = len(cfg.solution.clean)
|
solution_length = len(cfg.solution.clean)
|
||||||
num_days = len(days)
|
num_days = len(days)
|
||||||
|
|
||||||
|
@ -72,15 +59,33 @@ async def get_all_parts(
|
||||||
*rnd.sample(days, solution_length % num_days),
|
*rnd.sample(days, solution_length % num_days),
|
||||||
]
|
]
|
||||||
|
|
||||||
result: defaultdict[int, str] = defaultdict(str)
|
|
||||||
for day, letter in zip(solution_days, cfg.solution.clean):
|
for day, letter in zip(solution_days, cfg.solution.clean):
|
||||||
result[day] += letter
|
result[day] += letter
|
||||||
|
|
||||||
result |= {missed_day: "" for missed_day in set(days) - set(result.keys())}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def get_all_event_dates(
|
||||||
|
cfg: Config = Depends(get_config),
|
||||||
|
days: list[int] = Depends(get_all_sorted_days),
|
||||||
|
parts: dict[int, str] = Depends(get_all_parts),
|
||||||
|
) -> EventDates:
|
||||||
|
"""
|
||||||
|
Aktueller Kalender-Zeitraum
|
||||||
|
"""
|
||||||
|
|
||||||
|
if cfg.puzzle.skip_empty:
|
||||||
|
days = [day for day in days if parts[day] != "" or day in cfg.puzzle.extra_days]
|
||||||
|
|
||||||
|
return EventDates(
|
||||||
|
today=date.today(),
|
||||||
|
begin_month=cfg.puzzle.begin_month,
|
||||||
|
begin_day=cfg.puzzle.begin_day,
|
||||||
|
events=days,
|
||||||
|
close_after=cfg.puzzle.close_after,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_all_auto_image_names(
|
async def get_all_auto_image_names(
|
||||||
days: list[int] = Depends(get_all_sorted_days),
|
days: list[int] = Depends(get_all_sorted_days),
|
||||||
images: list[str] = Depends(list_images_auto),
|
images: list[str] = Depends(list_images_auto),
|
||||||
|
|
|
@ -46,7 +46,7 @@ async def user_visible_days(
|
||||||
event_dates: EventDates = Depends(get_all_event_dates),
|
event_dates: EventDates = Depends(get_all_event_dates),
|
||||||
) -> list[int]:
|
) -> list[int]:
|
||||||
"""
|
"""
|
||||||
Anzahl der user-sichtbaren Türchen
|
User-sichtbare Türchen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
|
@ -41,6 +41,8 @@ class AdminConfigModel(BaseModel):
|
||||||
last: date
|
last: date
|
||||||
end: date
|
end: date
|
||||||
seed: str
|
seed: str
|
||||||
|
extra_days: list[int]
|
||||||
|
skip_empty: bool
|
||||||
|
|
||||||
class __Calendar(BaseModel):
|
class __Calendar(BaseModel):
|
||||||
config_file: str
|
config_file: str
|
||||||
|
@ -92,6 +94,8 @@ async def get_config_model(
|
||||||
"last": event_dates.last,
|
"last": event_dates.last,
|
||||||
"end": event_dates.end,
|
"end": event_dates.end,
|
||||||
"seed": cfg.random_seed,
|
"seed": cfg.random_seed,
|
||||||
|
"extra_days": sorted(cfg.puzzle.extra_days),
|
||||||
|
"skip_empty": cfg.puzzle.skip_empty,
|
||||||
},
|
},
|
||||||
"calendar": {
|
"calendar": {
|
||||||
"config_file": cfg.calendar,
|
"config_file": cfg.calendar,
|
||||||
|
|
|
@ -89,6 +89,7 @@ export default class extends Vue {
|
||||||
this.store.when_initialized(() => {
|
this.store.when_initialized(() => {
|
||||||
this.toast_timeout = setTimeout(() => {
|
this.toast_timeout = setTimeout(() => {
|
||||||
if (this.store.user_doors.length === 0) return;
|
if (this.store.user_doors.length === 0) return;
|
||||||
|
if (this.store.is_touch_device) return;
|
||||||
|
|
||||||
this.toast!.show({ duration: 600000, type: "is-warning" });
|
this.toast!.show({ duration: 600000, type: "is-warning" });
|
||||||
}, 10e3);
|
}, 10e3);
|
||||||
|
|
|
@ -68,6 +68,23 @@
|
||||||
<dd class="is-family-monospace">
|
<dd class="is-family-monospace">
|
||||||
"{{ admin_config_model.puzzle.seed }}"
|
"{{ admin_config_model.puzzle.seed }}"
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
|
<dt>Extra-Tage</dt>
|
||||||
|
<dd>
|
||||||
|
<template
|
||||||
|
v-for="(day, index) in admin_config_model.puzzle.extra_days"
|
||||||
|
:key="`extra_day-${index}`"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<template v-if="index > 0">, </template>
|
||||||
|
{{ day }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt>Leere Türchen</dt>
|
||||||
|
<dd v-if="admin_config_model.puzzle.skip_empty">Überspringen</dd>
|
||||||
|
<dd v-else>Anzeigen</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -200,6 +217,8 @@ export default class extends Vue {
|
||||||
last: "2023-12-24",
|
last: "2023-12-24",
|
||||||
end: "2024-04-01",
|
end: "2024-04-01",
|
||||||
seed: "",
|
seed: "",
|
||||||
|
extra_days: [],
|
||||||
|
skip_empty: true,
|
||||||
},
|
},
|
||||||
calendar: {
|
calendar: {
|
||||||
config_file: "lorem ipsum",
|
config_file: "lorem ipsum",
|
||||||
|
@ -229,17 +248,20 @@ export default class extends Vue {
|
||||||
|
|
||||||
public fmt_puzzle_date(name: keyof AdminConfigModel["puzzle"]): string {
|
public fmt_puzzle_date(name: keyof AdminConfigModel["puzzle"]): string {
|
||||||
const iso_date = this.admin_config_model.puzzle[name];
|
const iso_date = this.admin_config_model.puzzle[name];
|
||||||
if (iso_date === null) return "-";
|
if (!(typeof iso_date == "string")) return "-";
|
||||||
|
|
||||||
return DateTime.fromISO(iso_date).toLocaleString(DateTime.DATE_SHORT);
|
return DateTime.fromISO(iso_date).toLocaleString(DateTime.DATE_SHORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public on_open(ready: () => void, fail: () => void): void {
|
public on_open(ready: () => void, fail: () => void): void {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
|
this.store.update(),
|
||||||
this.$advent22.api_get<AdminConfigModel>("admin/config_model"),
|
this.$advent22.api_get<AdminConfigModel>("admin/config_model"),
|
||||||
this.$advent22.api_get<DoorSaved[]>("admin/doors"),
|
this.$advent22.api_get<DoorSaved[]>("admin/doors"),
|
||||||
])
|
])
|
||||||
.then(([admin_config_model, doors]) => {
|
.then(([store_update, admin_config_model, doors]) => {
|
||||||
|
store_update; // discard value
|
||||||
|
|
||||||
this.admin_config_model = admin_config_model;
|
this.admin_config_model = admin_config_model;
|
||||||
this.doors = doors;
|
this.doors = doors;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,12 @@
|
||||||
:visible="store.is_touch_device || force_visible"
|
:visible="store.is_touch_device || force_visible"
|
||||||
:rectangle="door.position"
|
:rectangle="door.position"
|
||||||
>
|
>
|
||||||
<div class="has-text-danger">{{ door.day }}</div>
|
<div
|
||||||
|
class="has-text-danger"
|
||||||
|
style="text-shadow: 0 0 10px white, 0 0 20px white"
|
||||||
|
>
|
||||||
|
{{ door.day }}
|
||||||
|
</div>
|
||||||
</SVGRect>
|
</SVGRect>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
xmlns="http://www.w3.org/1999/xhtml"
|
xmlns="http://www.w3.org/1999/xhtml"
|
||||||
:class="`px-4 is-flex is-align-items-center is-justify-content-center is-size-1 has-text-weight-bold ${extra_classes}`"
|
:class="`px-2 is-flex is-align-items-center is-justify-content-center is-size-2 has-text-weight-bold ${extra_classes}`"
|
||||||
style="height: inherit"
|
style="height: inherit"
|
||||||
:title="title"
|
:title="title"
|
||||||
>
|
>
|
||||||
|
@ -72,7 +72,7 @@ foreignObject > div {
|
||||||
|
|
||||||
&.visible,
|
&.visible,
|
||||||
&:hover {
|
&:hover {
|
||||||
border-width: 3px;
|
border-width: 2px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
|
|
||||||
@each $name, $color in $advent22-colors {
|
@each $name, $color in $advent22-colors {
|
||||||
|
|
|
@ -12,6 +12,8 @@ export interface AdminConfigModel {
|
||||||
last: string;
|
last: string;
|
||||||
end: string;
|
end: string;
|
||||||
seed: string;
|
seed: string;
|
||||||
|
extra_days: number[];
|
||||||
|
skip_empty: boolean;
|
||||||
};
|
};
|
||||||
calendar: {
|
calendar: {
|
||||||
config_file: string;
|
config_file: string;
|
||||||
|
|
|
@ -60,53 +60,72 @@ export const advent22Store = defineStore({
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
init(): void {
|
init(): void {
|
||||||
this.update_is_admin();
|
this.update()
|
||||||
|
.then(() => {
|
||||||
this.advent22
|
|
||||||
.api_get_blob("user/favicon")
|
|
||||||
.then((favicon_src) => {
|
|
||||||
const link: HTMLLinkElement =
|
|
||||||
document.querySelector("link[rel*='icon']") ||
|
|
||||||
document.createElement("link");
|
|
||||||
link.rel = "shortcut icon";
|
|
||||||
link.type = "image/x-icon";
|
|
||||||
link.href = favicon_src;
|
|
||||||
|
|
||||||
if (link.parentElement === null)
|
|
||||||
document.getElementsByTagName("head")[0].appendChild(link);
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
Promise.all([
|
|
||||||
this.advent22.api_get<SiteConfigModel>("user/site_config"),
|
|
||||||
this.advent22.api_get_blob("user/background_image"),
|
|
||||||
this.advent22.api_get<DoorSaved[]>("user/doors"),
|
|
||||||
this.advent22.api_get<number | null>("user/next_door"),
|
|
||||||
])
|
|
||||||
.then(([site_config, background_image, user_doors, next_door]) => {
|
|
||||||
document.title = site_config.title;
|
|
||||||
|
|
||||||
if (site_config.subtitle !== "")
|
|
||||||
document.title += " – " + site_config.subtitle;
|
|
||||||
|
|
||||||
this.site_config = site_config;
|
|
||||||
|
|
||||||
this.calendar_background_image = background_image;
|
|
||||||
|
|
||||||
this.user_doors.length = 0;
|
|
||||||
for (const door_saved of user_doors) {
|
|
||||||
this.user_doors.push(Door.load(door_saved));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next_door !== null)
|
|
||||||
this.next_door_target = Date.now() + next_door;
|
|
||||||
|
|
||||||
this.is_initialized = true;
|
this.is_initialized = true;
|
||||||
for (const callback of this.on_initialized) callback();
|
for (const callback of this.on_initialized) callback();
|
||||||
})
|
})
|
||||||
.catch(this.advent22.alert_user_error);
|
.catch(this.advent22.alert_user_error);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
update(): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.advent22
|
||||||
|
.api_get_blob("user/favicon")
|
||||||
|
.then((favicon_src) => {
|
||||||
|
const link: HTMLLinkElement =
|
||||||
|
document.querySelector("link[rel*='icon']") ||
|
||||||
|
document.createElement("link");
|
||||||
|
link.rel = "shortcut icon";
|
||||||
|
link.type = "image/x-icon";
|
||||||
|
link.href = favicon_src;
|
||||||
|
|
||||||
|
if (link.parentElement === null)
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(link);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
this.update_is_admin(),
|
||||||
|
this.advent22.api_get<SiteConfigModel>("user/site_config"),
|
||||||
|
this.advent22.api_get_blob("user/background_image"),
|
||||||
|
this.advent22.api_get<DoorSaved[]>("user/doors"),
|
||||||
|
this.advent22.api_get<number | null>("user/next_door"),
|
||||||
|
])
|
||||||
|
.then(
|
||||||
|
([
|
||||||
|
is_admin,
|
||||||
|
site_config,
|
||||||
|
background_image,
|
||||||
|
user_doors,
|
||||||
|
next_door,
|
||||||
|
]) => {
|
||||||
|
is_admin; // discard value
|
||||||
|
|
||||||
|
document.title = site_config.title;
|
||||||
|
|
||||||
|
if (site_config.subtitle !== "")
|
||||||
|
document.title += " – " + site_config.subtitle;
|
||||||
|
|
||||||
|
this.site_config = site_config;
|
||||||
|
|
||||||
|
this.calendar_background_image = background_image;
|
||||||
|
|
||||||
|
this.user_doors.length = 0;
|
||||||
|
for (const door_saved of user_doors) {
|
||||||
|
this.user_doors.push(Door.load(door_saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_door !== null)
|
||||||
|
this.next_door_target = Date.now() + next_door;
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
when_initialized(callback: () => void): void {
|
when_initialized(callback: () => void): void {
|
||||||
if (this.is_initialized) {
|
if (this.is_initialized) {
|
||||||
callback();
|
callback();
|
||||||
|
|
Loading…
Reference in a new issue