zwischenstand

This commit is contained in:
2025-10-02 09:14:58 +00:00
parent f3dc0dc9bd
commit b22d5d5103
8 changed files with 140 additions and 185 deletions

View File

@@ -5,7 +5,6 @@
import Content from "./routes/Content.svelte" import Content from "./routes/Content.svelte"
import Notifications from "./lib/components/widgets/Notifications.svelte" import Notifications from "./lib/components/widgets/Notifications.svelte"
import SSRSkip from "./lib/components/SSRSkip.svelte" import SSRSkip from "./lib/components/SSRSkip.svelte"
import DateModal from "./lib/components/widgets/DateModal.svelte"
import { baseURL } from "./config" import { baseURL } from "./config"
import { isMobile, location, openModal } from "./lib/store" import { isMobile, location, openModal } from "./lib/store"
@@ -106,7 +105,6 @@
</div> </div>
<Notifications /> <Notifications />
<DateModal />
<SSRSkip /> <SSRSkip />
<style lang="less" global> <style lang="less" global>

View File

@@ -1,51 +0,0 @@
<script lang="ts">
import { actionApproval } from "../../store"
import Modal from "../Modal.svelte"
</script>
{#if $actionApproval}
<Modal
show={true}
size="sm"
on:close={() => {
actionApproval.set(null)
}}
>
<svelte:fragment slot="title">{$actionApproval.modalTitle}</svelte:fragment>
<p>
{$actionApproval.modalText}
</p>
<div
slot="footer"
class="action-approval-footer"
>
<button
class="btn cta primary"
on:click={() => {
actionApproval.set(null)
}}
>
Nein
</button>
<button
class="btn cta secondary"
on:click={() => {
$actionApproval.callback()
actionApproval.set(null)
}}>Ja</button
>
</div>
</Modal>
{/if}
<style lang="less">
.action-approval-footer {
display: flex;
justify-content: space-between;
}
h2 {
color: var(--text-invers-100);
padding-bottom: 1px;
}
</style>

View File

@@ -1,50 +0,0 @@
<script lang="ts"></script>
<div id="dateModal"></div>
<style
lang="less"
global
>
#dateModal {
position: absolute;
z-index: 9999;
background-color: white;
display: none;
}
#dateDeleteValidationModal {
position: absolute;
z-index: 9999;
background-color: white;
display: none;
flex-direction: column;
gap: 10px;
padding: 12px;
width: 236px;
.question {
padding: 0px;
margin: 0px;
font-size: 12px;
}
.bottom {
display: flex;
justify-content: space-between;
.cancel,
.delete {
padding: 0px;
cursor: pointer;
background-color: transparent;
border: none;
font-size: 12px;
}
.cancel {
color: var(--stategrey);
}
.delete {
color: var(--renzrot);
}
}
}
</style>

View File

@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
//check with matchMedia api if the slot should be rendered or not.
import { onMount } from "svelte" import { onMount } from "svelte"
export let query: string export let query: string

View File

@@ -1,13 +1,9 @@
<script <script module lang="ts">
context="module"
lang="ts"
>
import pDebounce from "p-debounce" import pDebounce from "p-debounce"
import { getDBEntries, getDBEntry } from "../../../api"
// TODO: check ssr import { apiBaseURL } from "../../configs/config"
import { getCachedEntries, getCachedEntry } from "../../api"
const medialibCache: { [id: string]: MedialibEntry } ={} const medialibCache: { [id: string]: MedialibEntry } = {}
let loadQueue: string[] = [] let loadQueue: string[] = []
export async function loadMedialibEntry(id: string): Promise<MedialibEntry> { export async function loadMedialibEntry(id: string): Promise<MedialibEntry> {
@@ -21,7 +17,7 @@
if (loadQueue.length) { if (loadQueue.length) {
const _ids = loadQueue const _ids = loadQueue
loadQueue = [] loadQueue = []
const entries = await getDBEntries("medialib", { _id: { $in: _ids } }) const entries = await getCachedEntries("medialib", { _id: { $in: _ids } })
entries.forEach((entry) => { entries.forEach((entry) => {
medialibCache[entry.id] = entry medialibCache[entry.id] = entry
}) })
@@ -32,14 +28,24 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { apiBaseURL } from "../../../config" let {
import { apiBaseOverride } from "../../store" id,
noPlaceholder,
loading,
childrenSnippet,
loadingSnippet,
notFoundSnippet,
}: {
id: string
noPlaceholder?: boolean
loading?: boolean
loadingSnippet: ({ entry, src }: { entry: MedialibEntry; src: string }) => any
childrenSnippet: ({ entry, src }: { entry: MedialibEntry; src: string }) => any
notFoundSnippet: () => any
} = $props()
export let id: string, let entry: MedialibEntry = $state(),
noPlaceholder: boolean = false, fileSrc: string = $state()
loading = true
let entry: MedialibEntry, fileSrc: string
async function loadFile() { async function loadFile() {
loading = true loading = true
@@ -47,33 +53,31 @@
fileSrc = null fileSrc = null
try { try {
entry = entry =
typeof window !== "undefined" ? await loadMedialibEntry(id) : await getDBEntry("medialib", { _id: id }) typeof window !== "undefined"
if (entry?.file?.src) ? await loadMedialibEntry(id)
fileSrc = ($apiBaseOverride ? $apiBaseOverride : apiBaseURL) + "medialib/" + id + "/" + entry.file.src : await getCachedEntry("medialib", { _id: id })
if (entry?.file?.src) {
fileSrc = `${apiBaseURL}${window?.location.host.includes("tibi") ? "_/renz_shop_2024/" : ""}medialib/${id}/${entry.file.src}`
}
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
loading = false loading = false
} }
$: if (id) loadFile() $effect(() => {
if (id) loadFile()
})
</script> </script>
{#if id} {#if id}
{#if loading} {#if loading}
{#if !noPlaceholder} {#if !noPlaceholder}
<slot {@render loadingSnippet({ entry, src: fileSrc })}
name="loading"
entry={entry}
src={fileSrc}
/>
{/if} {/if}
{:else if entry} {:else if entry}
<slot {@render childrenSnippet({ entry, src: fileSrc })}
entry={entry}
src={fileSrc}
/>
{:else if !noPlaceholder} {:else if !noPlaceholder}
<slot name="not-found" /> {@render notFoundSnippet()}
{/if} {/if}
{/if} {/if}

View File

@@ -1,83 +1,138 @@
<script lang="ts"> <script lang="ts">
import { onDestroy } from "svelte"
import MedialibFile from "./MedialibFile.svelte" import MedialibFile from "./MedialibFile.svelte"
export let id: string, interface Props {
filter: null | "xs" | "s" | "m" | "l" | "xl" | "xxl" = null, id: string
minWidth: number = null, filter?:
noPlaceholder: boolean = false | null
| "xs"
| "s"
| "m"
| "l"
| "xl"
| "xxl"
| "xs-webp"
| "s-webp"
| "m-webp"
| "l-webp"
| "xl-webp"
| "xxl-webp"
minWidth?: number
noPlaceholder?: boolean
style?: string
widthMultiplier?: number
lazy?: boolean
}
let imgElement: HTMLImageElement let {
id,
filter,
minWidth = null,
noPlaceholder = false,
style,
widthMultiplier = 1,
lazy = false,
}: Props = $props()
let imgElement: HTMLImageElement = $state()
function getSrcWithFilter(_imgElement: HTMLImageElement, entry: MedialibEntry, src: string) { function getSrcWithFilter(_imgElement: HTMLImageElement, entry: MedialibEntry, src: string) {
if (typeof window === "undefined") { if (typeof window === "undefined") {
return src + `?filter=${filter || "xs"}` return src + `?filter=${filter || "xs-webp"}`
} }
let internalFilter = filter
if (imgElement) { if (imgElement) {
if (!entry.file?.type?.match(/^image\/(png|jpe?g)/)) return src // no filter for svg if (!entry.file?.type?.match(/^image\/(png|jpe?g|webp)/)) return src // no filter for svg
if (!filter) { if (!internalFilter) {
let imgWidth = _imgElement.width
if (widthMultiplier) {
imgWidth *= widthMultiplier
}
// get the width of the image element // get the width of the image element
const width = minWidth const width = minWidth ? (imgWidth < minWidth ? minWidth : imgWidth) : imgWidth
? _imgElement.width < minWidth
? minWidth
: _imgElement.width
: _imgElement.width
switch (true) { switch (true) {
case width <= 90: case width <= 90:
filter = "xs" internalFilter = "xs-webp"
break break
case width <= 300: case width <= 300:
filter = "s" internalFilter = "s-webp"
break break
case width <= 600: case width <= 600:
filter = "m" internalFilter = "m-webp"
break break
case width <= 1200: case width <= 1200:
filter = "l" internalFilter = "l-webp"
break break
case width <= 2000: case width <= 2000:
filter = "xl" internalFilter = "xl-webp"
break break
case width <= 4000: case width <= 4000:
filter = "xxl" internalFilter = "xxl-webp"
break break
} }
} }
return src + (filter ? `?filter=${filter}` : "") return src + (internalFilter ? `?filter=${internalFilter}` : "")
} else { } else {
// placeholder // placeholder
return "/assets/img/placeholder-image.png" return "/assets/img/placeholder-image.png"
} }
} }
let resizeObserver: ResizeObserver
function initResizeObserver(node: HTMLImageElement, { entry, src }: { entry: MedialibEntry; src: string }) {
if (!filter && typeof ResizeObserver !== "undefined") {
resizeObserver?.disconnect()
resizeObserver = new ResizeObserver(() => {
const oldWidth = parseInt(node.getAttribute("data-width")) || 0
const newWidth = node.width
if (oldWidth >= newWidth) return
const newSrc = getSrcWithFilter(imgElement, entry, src)
if (newSrc !== node.getAttribute("src")) {
node.setAttribute("src", newSrc)
}
node.setAttribute("data-width", newWidth.toString())
})
resizeObserver.observe(node)
}
return {
destroy() {
resizeObserver?.disconnect()
},
}
}
onDestroy(() => {
resizeObserver?.disconnect()
})
let properties = {}
if (lazy) {
properties = { loading: "lazy" }
}
</script> </script>
{#if id} {#if id}
<MedialibFile <MedialibFile {id} {noPlaceholder}>
id={id} {#snippet childrenSnippet({ entry, src }: { entry: MedialibEntry; src: string })}
let:entry <img
let:src {style}
noPlaceholder={noPlaceholder} bind:this={imgElement}
> src={getSrcWithFilter(imgElement, entry, src)}
<img alt={entry.alt || "icon"}
bind:this={imgElement} data-entry-id={id}
src={getSrcWithFilter(imgElement, entry, src)} use:initResizeObserver={{ entry, src }}
alt={entry.alt || 'icon'} loading="lazy"
data-entry-id={id} {...properties}
/> />
<img {/snippet}
slot="loading" {#snippet loadingSnippet({ entry, src }: { entry: MedialibEntry; src: string })}{/snippet}
let:entry
let:src {#snippet notFoundSnippet()}{/snippet}
src="/assets/img/placeholder-image.png"
alt="placeholder"
data-entry-id={id}
data-entry={JSON.stringify(entry)}
data-entry-src={src}
/>
<img
slot="not-found"
src="/assets/img/placeholder-image.png"
alt="not found"
data-entry-id={id}
/>
</MedialibFile> </MedialibFile>
{/if} {/if}

View File

@@ -20,7 +20,7 @@
class="container" class="container"
class:inModal={force} class:inModal={force}
> >
{#each $notifications as n, i (n)} {#each $notifications as n (n)}
{#if !$openModal || ($openModal && force)} {#if !$openModal || ($openModal && force)}
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role --> <!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
<li <li

View File

@@ -2,8 +2,8 @@
import { getCachedEntry } from "../api" import { getCachedEntry } from "../api"
import NotFound from "./NotFound.svelte" import NotFound from "./NotFound.svelte"
export let location: LocationStore | undefined export let location: LocationStore | undefined = undefined
export let id: string | undefined export let id: string | null = null
let loading = true let loading = true
let contentEntry: ContentEntry | null = null let contentEntry: ContentEntry | null = null