mirror of
https://code.lenaisten.de/Lenaisten/advent22.git
synced 2024-11-23 00:03:07 +00:00
Merge branch 'feature/days-rework' into develop
This commit is contained in:
commit
3ccd3da28a
21 changed files with 394 additions and 332 deletions
|
@ -5,76 +5,95 @@ from fastapi import Depends
|
||||||
from PIL import Image, ImageFont
|
from PIL import Image, ImageFont
|
||||||
|
|
||||||
from .advent_image import _XY, AdventImage
|
from .advent_image import _XY, AdventImage
|
||||||
|
from .calendar_config import CalendarConfig, get_calendar_config
|
||||||
from .config import Config, get_config
|
from .config import Config, get_config
|
||||||
from .image_helpers import list_images_auto, load_image
|
from .helpers import Random, list_images_auto, load_image, set_len
|
||||||
from .sequence_helpers import Random, set_len, shuffle
|
|
||||||
from .webdav import WebDAV
|
from .webdav import WebDAV
|
||||||
|
|
||||||
|
|
||||||
async def shuffle_solution(
|
async def get_days(
|
||||||
|
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
||||||
|
) -> list[int]:
|
||||||
|
"""
|
||||||
|
Alle Tage, für die es ein Türchen gibt
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sorted(set(door.day for door in cal_cfg.doors))
|
||||||
|
|
||||||
|
|
||||||
|
async def get_parts(
|
||||||
cfg: Config = Depends(get_config),
|
cfg: Config = Depends(get_config),
|
||||||
) -> str:
|
days: list[int] = Depends(get_days),
|
||||||
|
) -> dict[int, str]:
|
||||||
"""
|
"""
|
||||||
Lösung: Reihenfolge zufällig bestimmen
|
Lösung auf vorhandene Tage aufteilen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return "".join(await shuffle(cfg.puzzle.solution))
|
solution_length = len(cfg.puzzle.solution)
|
||||||
|
num_days = len(days)
|
||||||
|
|
||||||
|
rnd = await Random.get()
|
||||||
|
solution_days = [
|
||||||
|
*rnd.shuffled(days * (solution_length // num_days)),
|
||||||
|
*rnd.sample(days, solution_length % num_days),
|
||||||
|
]
|
||||||
|
|
||||||
|
result: dict[int, str] = {}
|
||||||
|
for day, letter in zip(solution_days, cfg.puzzle.solution):
|
||||||
|
result[day] = result.get(day, "")
|
||||||
|
result[day] += letter
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def shuffle_images_auto(
|
async def get_day_part(
|
||||||
images: list[str] = Depends(list_images_auto),
|
|
||||||
) -> list[str]:
|
|
||||||
"""
|
|
||||||
Bilder: Reihenfolge zufällig bestimmen
|
|
||||||
"""
|
|
||||||
|
|
||||||
ls = set_len(images, 24)
|
|
||||||
return await shuffle(ls)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_part(
|
|
||||||
day: int,
|
day: int,
|
||||||
shuffled_solution: str = Depends(shuffle_solution),
|
parts: dict[int, str] = Depends(get_parts),
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Heute angezeigter Teil der Lösung
|
Heute angezeigter Teil der Lösung
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return shuffled_solution[day]
|
return parts[day]
|
||||||
|
|
||||||
|
|
||||||
async def get_random(
|
async def get_auto_image_names(
|
||||||
day: int,
|
days: list[int] = Depends(get_days),
|
||||||
) -> Random:
|
images: list[str] = Depends(list_images_auto),
|
||||||
|
) -> dict[int, str]:
|
||||||
"""
|
"""
|
||||||
Tagesabhängige Zufallszahlen
|
Bilder: Reihenfolge zufällig bestimmen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await Random.get(day)
|
rnd = await Random.get()
|
||||||
|
ls = set_len(images, len(days))
|
||||||
|
|
||||||
|
return dict(zip(days, rnd.shuffled(ls)))
|
||||||
|
|
||||||
|
|
||||||
async def gen_auto_image(
|
async def gen_day_auto_image(
|
||||||
day: int,
|
day: int,
|
||||||
auto_images: list[str] = Depends(shuffle_images_auto),
|
|
||||||
cfg: Config = Depends(get_config),
|
cfg: Config = Depends(get_config),
|
||||||
rnd: Random = Depends(get_random),
|
auto_image_names: list[str] = Depends(get_auto_image_names),
|
||||||
part: str = Depends(get_part),
|
day_part: str = Depends(get_day_part),
|
||||||
) -> Image.Image:
|
) -> Image.Image:
|
||||||
"""
|
"""
|
||||||
Automatisch generiertes Bild erstellen
|
Automatisch generiertes Bild erstellen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Datei existiert garantiert!
|
# Datei existiert garantiert!
|
||||||
img = await load_image(auto_images[day])
|
img = await load_image(auto_image_names[day])
|
||||||
image = await AdventImage.from_img(img)
|
image = await AdventImage.from_img(img)
|
||||||
|
|
||||||
|
rnd = await Random.get(day)
|
||||||
|
|
||||||
font = ImageFont.truetype(
|
font = ImageFont.truetype(
|
||||||
font=BytesIO(await WebDAV.read_bytes(f"files/{cfg.server.font}")),
|
font=BytesIO(await WebDAV.read_bytes(f"files/{cfg.server.font}")),
|
||||||
size=50,
|
size=50,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Buchstaben verstecken
|
# Buchstaben verstecken
|
||||||
for letter in part:
|
for letter in day_part:
|
||||||
await image.hide_text(
|
await image.hide_text(
|
||||||
xy=cast(_XY, tuple(rnd.choices(range(30, 470), k=2))),
|
xy=cast(_XY, tuple(rnd.choices(range(30, 470), k=2))),
|
||||||
text=letter,
|
text=letter,
|
||||||
|
@ -84,12 +103,11 @@ async def gen_auto_image(
|
||||||
return image.img
|
return image.img
|
||||||
|
|
||||||
|
|
||||||
async def get_image(
|
async def get_day_image(
|
||||||
day: int,
|
day: int,
|
||||||
auto_images: list[str] = Depends(shuffle_images_auto),
|
|
||||||
cfg: Config = Depends(get_config),
|
cfg: Config = Depends(get_config),
|
||||||
rnd: Random = Depends(get_random),
|
auto_image_names: list[str] = Depends(get_auto_image_names),
|
||||||
part: str = Depends(get_part),
|
day_part: str = Depends(get_day_part),
|
||||||
) -> Image.Image:
|
) -> Image.Image:
|
||||||
"""
|
"""
|
||||||
Bild für einen Tag abrufen
|
Bild für einen Tag abrufen
|
||||||
|
@ -105,6 +123,6 @@ async def get_image(
|
||||||
|
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
# Erstelle automatisch generiertes Bild
|
# Erstelle automatisch generiertes Bild
|
||||||
return await gen_auto_image(
|
return await gen_day_auto_image(
|
||||||
day=day, auto_images=auto_images, cfg=cfg, rnd=rnd, part=part
|
day=day, cfg=cfg, auto_image_names=auto_image_names, day_part=day_part
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,11 +1,35 @@
|
||||||
|
import itertools
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Any, Self, Sequence, TypeVar
|
||||||
|
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from .config import get_config
|
||||||
from .webdav import WebDAV
|
from .webdav import WebDAV
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class Random(random.Random):
|
||||||
|
@classmethod
|
||||||
|
async def get(cls, bonus_salt: Any = "") -> Self:
|
||||||
|
cfg = await get_config()
|
||||||
|
return cls(f"{cfg.puzzle.solution}{cfg.puzzle.random_seed}{bonus_salt}")
|
||||||
|
|
||||||
|
def shuffled(self, population: Sequence[T]) -> Sequence[T]:
|
||||||
|
return self.sample(population, k=len(population))
|
||||||
|
|
||||||
|
|
||||||
|
def set_len(seq: Sequence[T], len: int) -> Sequence[T]:
|
||||||
|
# `seq` unendlich wiederholen
|
||||||
|
infinite = itertools.cycle(seq)
|
||||||
|
|
||||||
|
# Die ersten `length` einträge nehmen
|
||||||
|
return list(itertools.islice(infinite, len))
|
||||||
|
|
||||||
|
|
||||||
async def list_images_auto() -> list[str]:
|
async def list_images_auto() -> list[str]:
|
||||||
"""
|
"""
|
|
@ -1,28 +0,0 @@
|
||||||
import itertools
|
|
||||||
import random
|
|
||||||
from typing import Any, Self, Sequence
|
|
||||||
|
|
||||||
from .config import get_config
|
|
||||||
|
|
||||||
|
|
||||||
class Random(random.Random):
|
|
||||||
@classmethod
|
|
||||||
async def get(cls, bonus_salt: Any = "") -> Self:
|
|
||||||
cfg = await get_config()
|
|
||||||
return cls(f"{cfg.puzzle.solution}{cfg.puzzle.random_seed}{bonus_salt}")
|
|
||||||
|
|
||||||
|
|
||||||
async def shuffle(seq: Sequence, rnd: random.Random | None = None) -> list:
|
|
||||||
# Zufallsgenerator
|
|
||||||
rnd = rnd or await Random.get()
|
|
||||||
|
|
||||||
# Elemente mischen
|
|
||||||
return rnd.sample(seq, len(seq))
|
|
||||||
|
|
||||||
|
|
||||||
def set_len(seq: Sequence, length: int) -> list:
|
|
||||||
# `seq` unendlich wiederholen
|
|
||||||
infinite = itertools.cycle(seq)
|
|
||||||
|
|
||||||
# Die ersten `length` einträge nehmen
|
|
||||||
return list(itertools.islice(infinite, length))
|
|
|
@ -1,10 +1,8 @@
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from . import admin, days, general, user
|
from . import admin, images
|
||||||
|
|
||||||
router = APIRouter(prefix="/api")
|
router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
router.include_router(admin.router)
|
router.include_router(admin.router)
|
||||||
router.include_router(days.router)
|
router.include_router(images.router)
|
||||||
router.include_router(general.router)
|
|
||||||
router.include_router(user.router)
|
|
||||||
|
|
|
@ -50,14 +50,14 @@ async def user_visible_doors() -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
async def user_can_view_door(
|
async def user_can_view_day(
|
||||||
day: int,
|
day: int,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
True iff das Türchen von Tag `day` user-sichtbar ist
|
True iff das Türchen von Tag `day` user-sichtbar ist
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if day < 0:
|
if day < 1:
|
||||||
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY)
|
raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||||
|
|
||||||
return day < await user_visible_doors()
|
return day <= await user_visible_doors()
|
||||||
|
|
|
@ -3,9 +3,9 @@ from datetime import date
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ..core.calendar_config import CalendarConfig, get_calendar_config
|
from ..core.calendar_config import CalendarConfig, DoorsSaved, get_calendar_config
|
||||||
from ..core.config import Config, get_config
|
from ..core.config import Config, get_config
|
||||||
from ..core.depends import shuffle_solution
|
from ..core.depends import get_parts
|
||||||
from ..core.settings import SETTINGS
|
from ..core.settings import SETTINGS
|
||||||
from ._security import require_admin, user_is_admin
|
from ._security import require_admin, user_is_admin
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ async def is_admin(
|
||||||
class ConfigModel(BaseModel):
|
class ConfigModel(BaseModel):
|
||||||
class __Puzzle(BaseModel):
|
class __Puzzle(BaseModel):
|
||||||
solution: str
|
solution: str
|
||||||
shuffled: str
|
|
||||||
begin: date
|
begin: date
|
||||||
end: date
|
end: date
|
||||||
closing: date
|
closing: date
|
||||||
|
@ -57,13 +56,12 @@ async def get_config_model(
|
||||||
_: None = Depends(require_admin),
|
_: None = Depends(require_admin),
|
||||||
cfg: Config = Depends(get_config),
|
cfg: Config = Depends(get_config),
|
||||||
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
||||||
shuffled_solution: str = Depends(shuffle_solution),
|
parts: dict[int, str] = Depends(get_parts),
|
||||||
) -> ConfigModel:
|
) -> ConfigModel:
|
||||||
return ConfigModel.model_validate(
|
return ConfigModel.model_validate(
|
||||||
{
|
{
|
||||||
"puzzle": {
|
"puzzle": {
|
||||||
"solution": cfg.puzzle.solution,
|
"solution": cfg.puzzle.solution,
|
||||||
"shuffled": shuffled_solution,
|
|
||||||
"begin": date.today(), # TODO
|
"begin": date.today(), # TODO
|
||||||
"end": date.today(), # TODO
|
"end": date.today(), # TODO
|
||||||
"closing": date.today(), # TODO
|
"closing": date.today(), # TODO
|
||||||
|
@ -85,3 +83,49 @@ async def get_config_model(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DayPartModel(BaseModel):
|
||||||
|
day: int
|
||||||
|
part: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/day_parts")
|
||||||
|
async def get_day_parts(
|
||||||
|
_: None = Depends(require_admin),
|
||||||
|
parts: dict[int, str] = Depends(get_parts),
|
||||||
|
) -> list[DayPartModel]:
|
||||||
|
return [
|
||||||
|
DayPartModel.model_validate({"day": day, "part": part})
|
||||||
|
for day, part in sorted(parts.items())
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/doors")
|
||||||
|
async def get_doors(
|
||||||
|
_: None = Depends(require_admin),
|
||||||
|
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
||||||
|
) -> DoorsSaved:
|
||||||
|
"""
|
||||||
|
Türchen lesen
|
||||||
|
"""
|
||||||
|
|
||||||
|
return cal_cfg.doors
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/doors")
|
||||||
|
async def put_doors(
|
||||||
|
doors: DoorsSaved,
|
||||||
|
_: None = Depends(require_admin),
|
||||||
|
cfg: Config = Depends(get_config),
|
||||||
|
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Türchen ändern
|
||||||
|
"""
|
||||||
|
|
||||||
|
cal_cfg.doors = sorted(
|
||||||
|
doors,
|
||||||
|
key=lambda door: door.day,
|
||||||
|
)
|
||||||
|
await cal_cfg.change(cfg)
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
|
||||||
from fastapi.responses import StreamingResponse
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
from ..core.config import get_config
|
|
||||||
from ..core.depends import get_image, get_part, shuffle_solution
|
|
||||||
from ..core.image_helpers import api_return_image
|
|
||||||
from ._security import user_can_view_door, user_is_admin, user_visible_doors
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/days", tags=["days"])
|
|
||||||
|
|
||||||
|
|
||||||
@router.on_event("startup")
|
|
||||||
async def startup() -> None:
|
|
||||||
cfg = await get_config()
|
|
||||||
print(cfg.puzzle.solution)
|
|
||||||
|
|
||||||
shuffled_solution = await shuffle_solution(cfg)
|
|
||||||
print(shuffled_solution)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/date")
|
|
||||||
async def get_date() -> str:
|
|
||||||
"""
|
|
||||||
Aktuelles Server-Datum
|
|
||||||
"""
|
|
||||||
|
|
||||||
return date.today().isoformat()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/visible_days")
|
|
||||||
async def get_visible_days(
|
|
||||||
visible_doors: int = Depends(user_visible_doors),
|
|
||||||
) -> int:
|
|
||||||
"""
|
|
||||||
Sichtbare Türchen
|
|
||||||
"""
|
|
||||||
|
|
||||||
return visible_doors
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/part/{day}")
|
|
||||||
async def get_part_for_day(
|
|
||||||
part: str = Depends(get_part),
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
Heutiger Lösungsteil
|
|
||||||
"""
|
|
||||||
|
|
||||||
return part
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/image/{day}",
|
|
||||||
response_class=StreamingResponse,
|
|
||||||
)
|
|
||||||
async def get_image_for_day(
|
|
||||||
image: Image.Image = Depends(get_image),
|
|
||||||
can_view: bool = Depends(user_can_view_door),
|
|
||||||
is_admin: bool = Depends(user_is_admin),
|
|
||||||
) -> StreamingResponse:
|
|
||||||
"""
|
|
||||||
Bild für einen Tag erstellen
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not (can_view or is_admin):
|
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Wie unhöflich!!!")
|
|
||||||
|
|
||||||
return await api_return_image(image)
|
|
|
@ -1,50 +0,0 @@
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
from fastapi.responses import StreamingResponse
|
|
||||||
|
|
||||||
from ..core.calendar_config import CalendarConfig, DoorsSaved, get_calendar_config
|
|
||||||
from ..core.config import Config, get_config
|
|
||||||
from ..core.image_helpers import api_return_image, load_image
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/general", tags=["general"])
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
|
||||||
"/background",
|
|
||||||
response_class=StreamingResponse,
|
|
||||||
)
|
|
||||||
async def get_image_for_day(
|
|
||||||
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
|
||||||
) -> StreamingResponse:
|
|
||||||
"""
|
|
||||||
Hintergrundbild laden
|
|
||||||
"""
|
|
||||||
|
|
||||||
return await api_return_image(await load_image(f"files/{cal_cfg.background}"))
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/doors")
|
|
||||||
async def get_doors(
|
|
||||||
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
|
||||||
) -> DoorsSaved:
|
|
||||||
"""
|
|
||||||
Türchen lesen
|
|
||||||
"""
|
|
||||||
|
|
||||||
return cal_cfg.doors
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/doors")
|
|
||||||
async def put_doors(
|
|
||||||
doors: DoorsSaved,
|
|
||||||
cfg: Config = Depends(get_config),
|
|
||||||
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Türchen setzen
|
|
||||||
"""
|
|
||||||
|
|
||||||
cal_cfg.doors = sorted(
|
|
||||||
doors,
|
|
||||||
key=lambda door: door.day,
|
|
||||||
)
|
|
||||||
await cal_cfg.change(cfg)
|
|
50
api/advent22_api/routers/images.py
Normal file
50
api/advent22_api/routers/images.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from ..core.calendar_config import CalendarConfig, get_calendar_config
|
||||||
|
from ..core.config import get_config
|
||||||
|
from ..core.depends import get_day_image
|
||||||
|
from ..core.helpers import api_return_image, load_image
|
||||||
|
from ._security import user_can_view_day, user_is_admin
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/images", tags=["images"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.on_event("startup")
|
||||||
|
async def startup() -> None:
|
||||||
|
cfg = await get_config()
|
||||||
|
print(cfg.puzzle.solution)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/background",
|
||||||
|
response_class=StreamingResponse,
|
||||||
|
)
|
||||||
|
async def get_background(
|
||||||
|
cal_cfg: CalendarConfig = Depends(get_calendar_config),
|
||||||
|
) -> StreamingResponse:
|
||||||
|
"""
|
||||||
|
Hintergrundbild laden
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await api_return_image(await load_image(f"files/{cal_cfg.background}"))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/{day}",
|
||||||
|
response_class=StreamingResponse,
|
||||||
|
)
|
||||||
|
async def get_image_for_day(
|
||||||
|
image: Image.Image = Depends(get_day_image),
|
||||||
|
can_view: bool = Depends(user_can_view_day),
|
||||||
|
is_admin: bool = Depends(user_is_admin),
|
||||||
|
) -> StreamingResponse:
|
||||||
|
"""
|
||||||
|
Bild für einen Tag erstellen
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not (can_view or is_admin):
|
||||||
|
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Wie unhöflich!!!")
|
||||||
|
|
||||||
|
return await api_return_image(image)
|
|
@ -1,12 +0,0 @@
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
|
|
||||||
from ._security import require_admin
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/user", tags=["user"])
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/admin")
|
|
||||||
def check_admin(
|
|
||||||
_: None = Depends(require_admin),
|
|
||||||
) -> bool:
|
|
||||||
return True
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<div class="image is-unselectable">
|
<div class="image is-unselectable">
|
||||||
<img :src="$advent22.api_url('general/background')" />
|
<img :src="$advent22.api_url('images/background')" />
|
||||||
<ThouCanvas>
|
<ThouCanvas>
|
||||||
<CalendarDoor
|
<CalendarDoor
|
||||||
v-for="(door, index) in doors"
|
v-for="(door, index) in doors"
|
||||||
|
@ -50,8 +50,8 @@ export default class extends Vue {
|
||||||
multi_modal: MultiModal;
|
multi_modal: MultiModal;
|
||||||
};
|
};
|
||||||
|
|
||||||
public door_hover(index: number) {
|
public door_hover(day: number) {
|
||||||
this.figure_caption = `Türchen ${index + 1}`;
|
this.figure_caption = `Türchen ${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public door_unhover() {
|
public door_unhover() {
|
||||||
|
|
|
@ -1,37 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<MultiModal ref="multi_modal" />
|
<MultiModal ref="multi_modal" />
|
||||||
|
|
||||||
<BulmaDrawer header="Kalender-Assistent">
|
<BulmaDrawer
|
||||||
|
header="Kalender-Assistent"
|
||||||
|
:ready="is_loaded"
|
||||||
|
@open="on_open"
|
||||||
|
refreshable
|
||||||
|
>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h4>Alle Türchen</h4>
|
<h4>Alle Türchen</h4>
|
||||||
<div class="tags are-medium">
|
<div class="tags are-medium">
|
||||||
<span
|
<BulmaButton
|
||||||
v-for="index in 24"
|
v-for="(day_part, index) in day_parts"
|
||||||
:key="index"
|
:key="`btn-${index}`"
|
||||||
class="tag button is-primary"
|
class="tag button is-primary"
|
||||||
@click.left="door_click(index - 1)"
|
icon="fa-solid fa-door-open"
|
||||||
>
|
:text="`${day_part.day}`"
|
||||||
<span class="icon">
|
@click.left="door_click(day_part.day)"
|
||||||
<font-awesome-icon icon="fa-solid fa-door-open" />
|
/>
|
||||||
</span>
|
|
||||||
<span>{{ index }}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4>Buchstaben-Zuordnung</h4>
|
<h4>Buchstaben-Zuordnung</h4>
|
||||||
<div class="tags are-medium">
|
<div class="tags are-medium">
|
||||||
<span class="tag is-info">1: A</span>
|
<span
|
||||||
<span class="tag is-info">2: G</span>
|
v-for="(day_part, index) in day_parts"
|
||||||
<span class="tag is-info">3: F</span>
|
:key="`part-${index}`"
|
||||||
<span class="tag is-info">4: C</span>
|
class="tag is-info"
|
||||||
<span class="tag is-info">5: I</span>
|
>
|
||||||
<span class="tag is-info">6: N</span>
|
{{ day_part.day }}: {{ day_part.part.split("").join(", ") }}
|
||||||
<span class="tag is-info">7: B</span>
|
</span>
|
||||||
<span class="tag is-info">…</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4>Bilder-Zuordnung</h4>
|
<h4>Bilder-Zuordnung</h4>
|
||||||
|
<!-- TODO -->
|
||||||
<div class="tags are-medium">
|
<div class="tags are-medium">
|
||||||
<span class="tag is-primary">1: images_auto/1.jpg</span>
|
<span class="tag is-primary">1: images_auto/1.jpg</span>
|
||||||
<span class="tag is-primary">2: images_manual/1.jpg</span>
|
<span class="tag is-primary">2: images_manual/1.jpg</span>
|
||||||
|
@ -48,6 +50,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { DayPartModel } from "@/lib/api";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import BulmaButton from "./bulma/Button.vue";
|
import BulmaButton from "./bulma/Button.vue";
|
||||||
|
@ -62,15 +65,29 @@ import MultiModal from "./calendar/MultiModal.vue";
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
|
public is_loaded = true;
|
||||||
|
public day_parts: DayPartModel[] = [];
|
||||||
|
|
||||||
declare $refs: {
|
declare $refs: {
|
||||||
multi_modal: MultiModal;
|
multi_modal: MultiModal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public on_open(): void {
|
||||||
|
this.is_loaded = false;
|
||||||
|
|
||||||
|
Promise.all([this.$advent22.api_get<DayPartModel[]>("admin/day_parts")])
|
||||||
|
.then(([day_parts]) => {
|
||||||
|
this.day_parts = day_parts;
|
||||||
|
this.is_loaded = true;
|
||||||
|
})
|
||||||
|
.catch(console.log);
|
||||||
|
}
|
||||||
|
|
||||||
public door_click(day: number) {
|
public door_click(day: number) {
|
||||||
this.$refs.multi_modal.show_progress();
|
this.$refs.multi_modal.show_progress();
|
||||||
|
|
||||||
this.$advent22
|
this.$advent22
|
||||||
.api_get_blob(`days/image/${day}`)
|
.api_get_blob(`images/${day}`)
|
||||||
.then((data) => this.$refs.multi_modal.show_image(data))
|
.then((data) => this.$refs.multi_modal.show_image(data))
|
||||||
.catch(() => this.$refs.multi_modal.set_active(false));
|
.catch(() => this.$refs.multi_modal.set_active(false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<BulmaDrawer header="Konfiguration">
|
<BulmaDrawer
|
||||||
|
header="Konfiguration"
|
||||||
|
:ready="is_loaded"
|
||||||
|
@open="on_open"
|
||||||
|
refreshable
|
||||||
|
>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-one-third">
|
<div class="column is-one-third">
|
||||||
|
@ -7,32 +12,46 @@
|
||||||
<h4>Rätsel</h4>
|
<h4>Rätsel</h4>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Titel</dt>
|
<dt>Titel</dt>
|
||||||
|
<!-- TODO -->
|
||||||
<dd>Adventskalender 2023</dd>
|
<dd>Adventskalender 2023</dd>
|
||||||
|
|
||||||
<dt>Lösung</dt>
|
<dt>Lösung</dt>
|
||||||
<dd>{{ admin_config_model.puzzle.solution }}</dd>
|
<dd>{{ config_model.puzzle.solution }}</dd>
|
||||||
|
|
||||||
<dt>Reihenfolge</dt>
|
<dt>Reihenfolge</dt>
|
||||||
<dd>{{ admin_config_model.puzzle.shuffled }}</dd>
|
<dd>
|
||||||
|
<template
|
||||||
|
v-for="(day_part, index) in day_parts"
|
||||||
|
:key="`part-${index}`"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<template v-if="index > 0"> – </template>
|
||||||
|
{{ day_part.day }}:
|
||||||
|
</span>
|
||||||
|
<span class="is-family-monospace">{{ day_part.part }}</span>
|
||||||
|
</template>
|
||||||
|
</dd>
|
||||||
|
|
||||||
<dt>Offene Türchen</dt>
|
<dt>Offene Türchen</dt>
|
||||||
|
<!-- TODO -->
|
||||||
<dd>10</dd>
|
<dd>10</dd>
|
||||||
|
|
||||||
<dt>Nächstes Türchen in</dt>
|
<dt>Nächstes Türchen in</dt>
|
||||||
|
<!-- TODO -->
|
||||||
<dd>dd-hh-mm-ss</dd>
|
<dd>dd-hh-mm-ss</dd>
|
||||||
|
|
||||||
<dt>Erstes Türchen</dt>
|
<dt>Erstes Türchen</dt>
|
||||||
<dd>{{ admin_config_model.puzzle.begin }}</dd>
|
<dd>{{ config_model.puzzle.begin }}</dd>
|
||||||
|
|
||||||
<dt>Letztes Türchen</dt>
|
<dt>Letztes Türchen</dt>
|
||||||
<dd>{{ admin_config_model.puzzle.end }}</dd>
|
<dd>{{ config_model.puzzle.end }}</dd>
|
||||||
|
|
||||||
<dt>Rätsel schließt</dt>
|
<dt>Rätsel schließt</dt>
|
||||||
<dd>{{ admin_config_model.puzzle.closing }}</dd>
|
<dd>{{ config_model.puzzle.closing }}</dd>
|
||||||
|
|
||||||
<dt>Zufalls-Seed</dt>
|
<dt>Zufalls-Seed</dt>
|
||||||
<dd class="is-family-monospace">
|
<dd class="is-family-monospace">
|
||||||
"{{ admin_config_model.puzzle.seed }}"
|
"{{ config_model.puzzle.seed }}"
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,35 +61,37 @@
|
||||||
<h4>Kalender</h4>
|
<h4>Kalender</h4>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Definition</dt>
|
<dt>Definition</dt>
|
||||||
<dd>{{ admin_config_model.calendar.config_file }}</dd>
|
<dd>{{ config_model.calendar.config_file }}</dd>
|
||||||
|
|
||||||
<dt>Hintergrundbild</dt>
|
<dt>Hintergrundbild</dt>
|
||||||
<dd>{{ admin_config_model.calendar.background }}</dd>
|
<dd>{{ config_model.calendar.background }}</dd>
|
||||||
|
|
||||||
<dt>Türchen</dt>
|
<dt>Türchen</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<!-- <span>{{ admin_config_model.calendar.doors.join(", ") }}</span> -->
|
<template
|
||||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
|
v-for="(day_part, index) in day_parts"
|
||||||
<span class="tag is-danger ml-2">
|
:key="`door-${index}`"
|
||||||
<span class="icon">
|
>
|
||||||
<font-awesome-icon icon="fa-solid fa-bolt" />
|
<span>
|
||||||
|
<template v-if="index > 0">, </template>
|
||||||
|
{{ day_part.day }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</template>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h4>Bilder</h4>
|
<h4>Bilder</h4>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>Größe</dt>
|
<dt>Größe</dt>
|
||||||
<dd>{{ admin_config_model.image.size }} px</dd>
|
<dd>{{ config_model.image.size }} px</dd>
|
||||||
|
|
||||||
<dt>Rand</dt>
|
<dt>Rand</dt>
|
||||||
<dd>{{ admin_config_model.image.border }} px</dd>
|
<dd>{{ config_model.image.border }} px</dd>
|
||||||
|
|
||||||
<dt>Schriftarten</dt>
|
<dt>Schriftarten</dt>
|
||||||
<dd
|
<dd
|
||||||
v-for="(font, idx) in admin_config_model.image.fonts"
|
v-for="(font, index) in config_model.image.fonts"
|
||||||
:key="`font-${idx}`"
|
:key="`font-${index}`"
|
||||||
>
|
>
|
||||||
{{ font.file }} (Größe {{ font.size }})
|
{{ font.file }} (Größe {{ font.size }})
|
||||||
</dd>
|
</dd>
|
||||||
|
@ -82,32 +103,30 @@
|
||||||
<h4>WebDAV</h4>
|
<h4>WebDAV</h4>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>URL</dt>
|
<dt>URL</dt>
|
||||||
<dd>{{ admin_config_model.webdav.url }}</dd>
|
<dd>{{ config_model.webdav.url }}</dd>
|
||||||
|
|
||||||
<dt>Zugangsdaten</dt>
|
<dt>Zugangsdaten</dt>
|
||||||
|
<!-- TODO -->
|
||||||
<dd>
|
<dd>
|
||||||
<span>***</span>
|
<span>***</span>
|
||||||
<span class="tag button is-primary ml-2">
|
<button class="tag button icon is-primary ml-2">
|
||||||
<span class="icon">
|
<font-awesome-icon icon="fa-solid fa-eye" />
|
||||||
<font-awesome-icon icon="fa-solid fa-eye" />
|
</button>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt>Cache-Dauer</dt>
|
<dt>Cache-Dauer</dt>
|
||||||
<dd>{{ admin_config_model.webdav.cache_ttl }} s</dd>
|
<dd>{{ config_model.webdav.cache_ttl }} s</dd>
|
||||||
|
|
||||||
<dt>Konfigurationsdatei</dt>
|
<dt>Konfigurationsdatei</dt>
|
||||||
<dd>{{ admin_config_model.webdav.config_file }}</dd>
|
<dd>{{ config_model.webdav.config_file }}</dd>
|
||||||
|
|
||||||
<dt>UI-Admin</dt>
|
<dt>UI-Admin</dt>
|
||||||
|
<!-- TODO -->
|
||||||
<dd>
|
<dd>
|
||||||
<span>***</span>
|
<span>***</span>
|
||||||
<span class="tag button is-primary ml-2">
|
<button class="tag button icon is-primary ml-2">
|
||||||
<span class="icon">
|
<font-awesome-icon icon="fa-solid fa-eye" />
|
||||||
<font-awesome-icon icon="fa-solid fa-eye" />
|
</button>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,45 +137,21 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { ConfigModel, DayPartModel } from "@/lib/api";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import BulmaDrawer from "./bulma/Drawer.vue";
|
import BulmaDrawer from "./bulma/Drawer.vue";
|
||||||
|
|
||||||
interface ConfigModel {
|
|
||||||
puzzle: {
|
|
||||||
solution: string;
|
|
||||||
shuffled: string;
|
|
||||||
begin: string;
|
|
||||||
end: string;
|
|
||||||
closing: string;
|
|
||||||
seed: string;
|
|
||||||
};
|
|
||||||
calendar: {
|
|
||||||
config_file: string;
|
|
||||||
background: string;
|
|
||||||
};
|
|
||||||
image: {
|
|
||||||
size: number;
|
|
||||||
border: number;
|
|
||||||
fonts: { file: string; size: number }[];
|
|
||||||
};
|
|
||||||
webdav: {
|
|
||||||
url: string;
|
|
||||||
cache_ttl: number;
|
|
||||||
config_file: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Options({
|
@Options({
|
||||||
components: {
|
components: {
|
||||||
BulmaDrawer,
|
BulmaDrawer,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
public admin_config_model: ConfigModel = {
|
public is_loaded = false;
|
||||||
|
public config_model: ConfigModel = {
|
||||||
puzzle: {
|
puzzle: {
|
||||||
solution: "ABCDEFGHIJKLMNOPQRSTUVWX",
|
solution: "ABCDEFGHIJKLMNOPQRSTUVWX",
|
||||||
shuffled: "AGFCINBEWLKQMXDURPOSJVHT",
|
|
||||||
begin: "01.12.2023",
|
begin: "01.12.2023",
|
||||||
end: "24.12.2023",
|
end: "24.12.2023",
|
||||||
closing: "01.04.2024",
|
closing: "01.04.2024",
|
||||||
|
@ -177,11 +172,20 @@ export default class extends Vue {
|
||||||
config_file: "config.toml",
|
config_file: "config.toml",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
public day_parts: DayPartModel[] = [];
|
||||||
|
|
||||||
public mounted(): void {
|
public on_open(): void {
|
||||||
this.$advent22
|
this.is_loaded = false;
|
||||||
.api_get<ConfigModel>("admin/config_model")
|
|
||||||
.then((data) => (this.admin_config_model = data))
|
Promise.all([
|
||||||
|
this.$advent22.api_get<ConfigModel>("admin/config_model"),
|
||||||
|
this.$advent22.api_get<DayPartModel[]>("admin/day_parts"),
|
||||||
|
])
|
||||||
|
.then(([config_model, day_parts]) => {
|
||||||
|
this.config_model = config_model;
|
||||||
|
this.day_parts = day_parts;
|
||||||
|
this.is_loaded = true;
|
||||||
|
})
|
||||||
.catch(console.log);
|
.catch(console.log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<BulmaDrawer header="Türchen bearbeiten">
|
<BulmaDrawer header="Türchen bearbeiten" :ready="is_loaded" @open="on_open">
|
||||||
<div class="is-flex is-align-items-center is-justify-content-space-between">
|
<div class="is-flex is-align-items-center is-justify-content-space-between">
|
||||||
<BulmaButton
|
<BulmaButton
|
||||||
:disabled="current_step === 0"
|
:disabled="current_step === 0"
|
||||||
|
@ -46,7 +46,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Door, DoorsSaved } from "@/lib/door";
|
import { DoorsSaved } from "@/lib/api";
|
||||||
|
import { Door } from "@/lib/door";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import Calendar from "./Calendar.vue";
|
import Calendar from "./Calendar.vue";
|
||||||
|
@ -67,6 +68,8 @@ import DoorPlacer from "./editor/DoorPlacer.vue";
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
|
public is_loaded = true;
|
||||||
|
|
||||||
public readonly steps: Step[] = [
|
public readonly steps: Step[] = [
|
||||||
{ label: "Platzieren", icon: "fa-solid fa-crosshairs" },
|
{ label: "Platzieren", icon: "fa-solid fa-crosshairs" },
|
||||||
{ label: "Ordnen", icon: "fa-solid fa-list-ol" },
|
{ label: "Ordnen", icon: "fa-solid fa-list-ol" },
|
||||||
|
@ -76,7 +79,7 @@ export default class extends Vue {
|
||||||
public doors: Door[] = [];
|
public doors: Door[] = [];
|
||||||
|
|
||||||
private load_doors(): Promise<void | DoorsSaved> {
|
private load_doors(): Promise<void | DoorsSaved> {
|
||||||
return this.$advent22.api_get<DoorsSaved>("general/doors").then((data) => {
|
return this.$advent22.api_get<DoorsSaved>("admin/doors").then((data) => {
|
||||||
this.doors.length = 0;
|
this.doors.length = 0;
|
||||||
|
|
||||||
for (const value of data) {
|
for (const value of data) {
|
||||||
|
@ -89,14 +92,20 @@ export default class extends Vue {
|
||||||
const data: DoorsSaved = [];
|
const data: DoorsSaved = [];
|
||||||
|
|
||||||
for (const door of this.doors) {
|
for (const door of this.doors) {
|
||||||
if (door.day === -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.push(door.save());
|
data.push(door.save());
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$advent22.api_put("general/doors", data);
|
return this.$advent22.api_put("admin/doors", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public on_open(): void {
|
||||||
|
this.is_loaded = false;
|
||||||
|
|
||||||
|
this.load_doors()
|
||||||
|
.then(() => (this.is_loaded = true))
|
||||||
|
.catch(([reason, endpoint]) => {
|
||||||
|
alert(`Fehler: ${reason} in ${endpoint}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public mounted(): void {
|
public mounted(): void {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<header
|
<header class="card-header has-background-grey-lighter is-unselectable">
|
||||||
class="card-header has-background-grey-lighter is-unselectable"
|
<p class="card-header-title" @click="toggle">{{ header }}</p>
|
||||||
@click="is_open = !is_open"
|
<p v-if="refreshable" class="card-header-icon px-0">
|
||||||
>
|
<button class="tag button icon is-info" @click="refresh">
|
||||||
<p class="card-header-title">{{ header }}</p>
|
<font-awesome-icon icon="fa-solid fa-arrows-rotate" />
|
||||||
<button class="card-header-icon">
|
</button>
|
||||||
|
</p>
|
||||||
|
<button class="card-header-icon" @click="toggle">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
:icon="'fa-solid fa-angle-' + (is_open ? 'down' : 'right')"
|
:icon="'fa-solid fa-angle-' + (is_open ? 'down' : 'right')"
|
||||||
|
@ -14,7 +16,12 @@
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<slot v-if="is_open" name="default" />
|
<template v-if="is_open">
|
||||||
|
<slot v-if="ready" name="default" />
|
||||||
|
<div v-else class="card-content">
|
||||||
|
<progress class="progress is-info" max="100" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -28,11 +35,31 @@ import { Options, Vue } from "vue-class-component";
|
||||||
required: false,
|
required: false,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
|
ready: Boolean,
|
||||||
|
refreshable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
emits: ["open"],
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
public is_open = false;
|
|
||||||
public header!: string;
|
public header!: string;
|
||||||
|
public ready!: boolean;
|
||||||
|
public refreshable!: boolean;
|
||||||
|
|
||||||
|
public is_open = false;
|
||||||
|
|
||||||
|
public toggle() {
|
||||||
|
this.is_open = !this.is_open;
|
||||||
|
if (this.is_open) this.$emit("open");
|
||||||
|
}
|
||||||
|
|
||||||
|
public refresh() {
|
||||||
|
this.is_open = false;
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default class extends Vue {
|
||||||
this.$emit("doorClick");
|
this.$emit("doorClick");
|
||||||
|
|
||||||
this.$advent22
|
this.$advent22
|
||||||
.api_get_blob(`days/image/${this.door.day}`)
|
.api_get_blob(`images/${this.door.day}`)
|
||||||
.then((data) => this.$emit("doorSuccess", data))
|
.then((data) => this.$emit("doorSuccess", data))
|
||||||
.catch(([reason]) => {
|
.catch(([reason]) => {
|
||||||
let msg = "Unbekannter Fehler, bitte wiederholen!";
|
let msg = "Unbekannter Fehler, bitte wiederholen!";
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<figure class="image is-unselectable">
|
<figure class="image is-unselectable">
|
||||||
<img :src="$advent22.api_url('general/background')" />
|
<img :src="$advent22.api_url('images/background')" />
|
||||||
<ThouCanvas>
|
<ThouCanvas>
|
||||||
<PreviewDoor
|
<PreviewDoor
|
||||||
v-for="(_, index) in doors"
|
v-for="(_, index) in doors"
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<figure class="image is-unselectable">
|
<figure class="image is-unselectable">
|
||||||
<img :src="$advent22.api_url('general/background')" />
|
<img :src="$advent22.api_url('images/background')" />
|
||||||
<RectangleCanvas
|
<RectangleCanvas
|
||||||
:rectangles="rectangles"
|
:rectangles="rectangles"
|
||||||
@draw="on_draw"
|
@draw="on_draw"
|
||||||
|
|
|
@ -18,12 +18,12 @@
|
||||||
ref="day_input"
|
ref="day_input"
|
||||||
class="input is-large"
|
class="input is-large"
|
||||||
type="number"
|
type="number"
|
||||||
min="-1"
|
:min="MIN_DAY"
|
||||||
placeholder="Tag"
|
placeholder="Tag"
|
||||||
@keydown="on_keydown"
|
@keydown="on_keydown"
|
||||||
/>
|
/>
|
||||||
<div v-else class="is-size-1 has-text-weight-bold">
|
<div v-else-if="door.day > 0" class="is-size-1 has-text-weight-bold">
|
||||||
<template v-if="door.day >= 0">{{ door.day }}</template>
|
{{ door.day }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</foreignObject>
|
</foreignObject>
|
||||||
|
@ -46,6 +46,7 @@ import SVGRect from "../calendar/SVGRect.vue";
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
public door!: Door;
|
public door!: Door;
|
||||||
|
public readonly MIN_DAY = Door.MIN_DAY;
|
||||||
|
|
||||||
public day_str = "";
|
public day_str = "";
|
||||||
public editing = false;
|
public editing = false;
|
||||||
|
|
38
ui/src/lib/api.ts
Normal file
38
ui/src/lib/api.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
export interface ConfigModel {
|
||||||
|
puzzle: {
|
||||||
|
solution: string;
|
||||||
|
begin: string;
|
||||||
|
end: string;
|
||||||
|
closing: string;
|
||||||
|
seed: string;
|
||||||
|
};
|
||||||
|
calendar: {
|
||||||
|
config_file: string;
|
||||||
|
background: string;
|
||||||
|
};
|
||||||
|
image: {
|
||||||
|
size: number;
|
||||||
|
border: number;
|
||||||
|
fonts: { file: string; size: number }[];
|
||||||
|
};
|
||||||
|
webdav: {
|
||||||
|
url: string;
|
||||||
|
cache_ttl: number;
|
||||||
|
config_file: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DayPartModel {
|
||||||
|
day: number;
|
||||||
|
part: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DoorSaved {
|
||||||
|
day: number;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DoorsSaved = DoorSaved[];
|
|
@ -1,23 +1,16 @@
|
||||||
|
import { DoorSaved } from "./api";
|
||||||
import { Rectangle } from "./rectangle";
|
import { Rectangle } from "./rectangle";
|
||||||
import { Vector2D } from "./vector2d";
|
import { Vector2D } from "./vector2d";
|
||||||
|
|
||||||
export interface DoorSaved {
|
|
||||||
day: number;
|
|
||||||
x1: number;
|
|
||||||
y1: number;
|
|
||||||
x2: number;
|
|
||||||
y2: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DoorsSaved = DoorSaved[];
|
|
||||||
|
|
||||||
export class Door {
|
export class Door {
|
||||||
private _day = -1;
|
public static readonly MIN_DAY = 1;
|
||||||
|
|
||||||
|
private _day = Door.MIN_DAY;
|
||||||
public position: Rectangle;
|
public position: Rectangle;
|
||||||
|
|
||||||
constructor(position: Rectangle);
|
constructor(position: Rectangle);
|
||||||
constructor(position: Rectangle, day: number);
|
constructor(position: Rectangle, day: number);
|
||||||
constructor(position: Rectangle, day = -1) {
|
constructor(position: Rectangle, day = Door.MIN_DAY) {
|
||||||
this.day = day;
|
this.day = day;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
}
|
}
|
||||||
|
@ -31,9 +24,9 @@ export class Door {
|
||||||
const result = Number(day);
|
const result = Number(day);
|
||||||
|
|
||||||
if (isNaN(result)) {
|
if (isNaN(result)) {
|
||||||
this._day = -1;
|
this._day = Door.MIN_DAY;
|
||||||
} else {
|
} else {
|
||||||
this._day = Math.max(Math.floor(result), -1);
|
this._day = Math.max(Math.floor(result), Door.MIN_DAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue