zwischenstand

This commit is contained in:
2024-02-13 16:36:09 +00:00
parent 0b4a474180
commit 49e55a90f7
173 changed files with 15832 additions and 1359 deletions

View File

@@ -1,5 +1,17 @@
<script lang="ts">
import { location } from "./lib/store"
import { onDestroy } from "svelte"
import { getEventCallbacks, registerEventCallback, unregisterEventCallback } from "./lib/functions/eventBus"
import { loadModulesAndSetStore } from "./lib/functions/fetch/loadModules"
import { location, pages, rerender } from "./lib/store"
import { Route, Router, links } from "../../vendor/svelte-routing"
import Footer from "./lib/components/Footer.svelte"
import Header from "./lib/components/Header.svelte"
import Page from "./routes/Page.svelte"
import ScrollTop from "./lib/components/ScrollTop.svelte"
import ScrollBottom from "./lib/components/ScrollBottom.svelte"
import { loadContentAndSetStores } from "./lib/functions/fetch/loadContentAndSetStores"
import { loadNavigationAndSetStores } from "./lib/functions/fetch/loadNavigationAndSetStores"
import { loadLibraryAndSetStore } from "./lib/functions/fetch/loadLibraryAndSetStores"
export let url = ""
@@ -15,8 +27,102 @@
pop: false,
}
}
function registerEvent() {
let callbacks = getEventCallbacks("navigate")
if (callbacks && callbacks["rerender"]) return
registerEventCallback("navigate", "rerender", () => {
$rerender = !$rerender
return true
})
}
registerEvent()
onDestroy(() => {
let callbacks = getEventCallbacks("navigate")
if (callbacks["rerender"]) unregisterEventCallback("navigate", "rerender")
})
loadModulesAndSetStore()
if (typeof window !== "undefined") console.log("App initialized")
loadNavigationAndSetStores()
loadLibraryAndSetStore()
loadContentAndSetStores()
</script>
<h1>Hello World</h1>
<main use:links>
<Header></Header>
<Router {url}>
<Route path="/" component="{Page}" />
<Route path="/*path" let:params>
{#key $rerender}
<Page path="/{params?.path}" isHomepage="{false}"></Page>
{/key}
</Route>
</Router>
<Footer></Footer>
</main>
<ScrollTop />
<ScrollBottom />
<style lang="less" global>
@import "./lib/assets/css/variables.less";
@import "./lib/assets/css/main.less";
@import "swiper/swiper-bundle.min.css";
@import "swiper/modules/effect-fade/effect-fade";
@import "swiper/modules/navigation/navigation";
@import "swiper/modules/pagination/pagination";
.swiper-button-prev,
.swiper-button-next {
transform-origin: left;
color: #333;
transform: scale(0.3);
background-color: rgba(255, 255, 255, 0.6);
top: 50%;
padding: 10px;
height: 70px;
width: 70px;
border-radius: 50px;
}
.swiper-button-prev {
left: 6%;
}
.swiper-button-next {
right: 6%;
transform-origin: right;
}
html,
body {
font-family: "Libre Franklin", sans-serif;
button {
font-family: "Libre Franklin", sans-serif;
}
@media @mobile {
font-size: @bodyfontsize;
line-height: 1.4;
}
@media @tablet {
font-size: @bodyfontsize_desktop;
line-height: 1.6;
}
}
main {
display: flex;
flex-direction: column;
min-height: 100vh;
align-items: center;
}
@font-face {
font-display: swap;
font-family: "Libre Franklin";
font-style: normal;
font-weight: 400;
src: url("./lib/assets/fonts/libre-franklin-v13-latin-regular.woff2") format("woff2");
}
@font-face {
font-display: swap;
font-family: "Libre Caslon Text";
font-style: normal;
font-weight: 400;
src: url("./lib/assets/fonts/LibreCaslonText-Regular.woff2") format("woff2");
}
</style>

View File

@@ -2,6 +2,7 @@ import * as sentry from "./sentry"
import configClient from "../../api/hooks/config-client"
export const apiBaseURL = "/api/"
export const baseURL = "https://tibi_starter.code.testversion.online.de"
export const release = configClient.release
export const sentryDSN = "https://5063f9b5564d0fdece4e47a8e2e63672@sentry.basehosts.de/3"

View File

@@ -0,0 +1,252 @@
@import "../../../variables.less";
.my-form {
input,
select,
textarea,
.data-protection {
margin: 5px 0px;
box-shadow: 0 0 25px 10px rgba(24, 24, 24, 0.05);
}
.success-message {
h1 {
color: @main-color;
font-size: 36px;
margin-top: 50px;
}
p {
font-size: 20px;
margin-top: 20px;
}
}
.invalidBlocks {
border: 2px solid rgb(255, 0, 0) !important;
position: relative;
&::after {
font-size: 0.9rem !important;
color: red !important;
position: absolute;
bottom: 2px;
content: "Bitte wähle entweder eine Kartenanzahl oder einen Wunschbetrag aus.";
}
}
.border-red {
border-color: red !important;
}
.no-margin {
margin-top: 15px !important;
}
.invalid {
border: 2px solid red !important;
}
.error-message {
font-size: 0.9rem !important;
color: red !important;
position: absolute;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
height: 100%;
}
input[type="number"] {
-moz-appearance: textfield;
appearance: textfield;
}
.checkit {
display: none;
}
.checkit-span {
height: 20px;
width: 20px;
border: 1px solid grey;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
[type="checkbox"]:checked + span:before {
content: "\2714";
position: absolute;
transform-origin: bottom;
}
.form-rows {
display: flex;
flex-direction: column;
width: 100%;
h3 {
font-weight: bold !important;
margin-bottom: 0px !important;
}
@media @tablet {
gap: 1.5rem;
}
.form-row {
display: flex;
flex-direction: column;
width: 100%;
gap: 1.5rem;
.date {
max-width: 100%;
box-sizing: border-box;
}
label {
width: 100% !important;
font-size: inherit;
input,
select,
textarea {
width: 100%;
font-size: inherit;
}
}
.form-cols {
display: flex;
gap: 1.5rem;
width: 100%;
}
}
.form-column {
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-start;
width: 100%;
gap: 1rem;
border-radius: 4px;
}
h3 {
font-size: 1.2rem;
font-weight: bold;
}
p {
margin-bottom: 0.5rem;
color: var(--hover-color);
}
input,
select,
textarea,
.data-protection {
padding: 10px 20px;
border: 0px solid white;
border-bottom: 3px solid @main-color;
outline: 0px solid white;
color: black;
background-color: @background-color;
resize: none;
}
.data-protection {
display: flex;
flex-wrap: nowrap;
gap: 5px !important;
justify-content: start;
flex-direction: row !important;
}
input[type="date"] {
font-family: inherit;
width: 100% !important;
position: relative;
}
select {
padding: 10px 20px;
border: 0;
border-bottom: 3px solid @main-color;
outline: none;
}
select:focus {
border-bottom-color: @main-color;
}
#time-select {
appearance: none;
background-repeat: no-repeat;
background-position: right 20px center;
background-size: 18px;
option {
padding: 10px 20px;
background-color: @background-color;
}
}
@media @mobile {
.date {
width: 100vw !important;
}
}
@media @tablet {
.date {
width: 100% !important;
}
.form-cols {
flex-direction: row;
}
}
}
i .max-width {
max-width: @body-maxwidth !important;
}
.datasec {
display: flex;
align-items: center !important;
.link {
height: 100%;
display: flex;
align-items: flex-end !important;
text-decoration: underline;
margin-right: 3px;
color: rgb(14, 91, 146);
}
}
.additional {
display: flex;
@media @mobile {
flex-direction: column !important;
}
@media @tablet {
flex-direction: row !important;
}
gap: 2.5rem;
div {
flex: 1;
display: flex;
align-items: center;
justify-content: start;
}
.submit-request {
flex: 1;
box-shadow: 0 0 25px 10px rgba(0, 0, 0, 0.05);
width: 100%;
display: flex;
align-items: center;
justify-content: start;
background-color: @main-color;
color: @background-color;
padding: 10px 20px;
}
}
}

View File

@@ -0,0 +1,99 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
overflow-wrap: break-word;
}
html {
background-color: black;
}
body {
color: #343a40 !important;
height: 100%;
background-color: #f9f9f9;
}
ul {
list-style-type: none;
}
.boxes {
.content {
ul {
padding-left: 20px;
list-style-type: disc;
}
}
}
ol {
list-style-type: decimal;
}
/* Links */
a {
text-decoration: none;
font-weight: 700;
color: inherit;
font-weight: normal;
cursor: pointer;
}
/* Tabellen */
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
* {
transition:
background-color 0.5s ease,
border-color 0.5s ease,
color 0.5s ease,
max-height 0.5s,
height 0.5s ease,
width 0.5s ease,
flex 0.5s ease,
opacity 0.5s ease,
top 0.5s ease,
bottom 0.5s ease,
left 0.5s ease,
right 0.5s ease,
transform 0.5s ease;
}
button {
background-color: inherit;
border: none;
cursor: pointer;
font-size: inherit;
color: #343a40;
}
input,
select {
color: #343a40;
width: 100%;
}
*::-webkit-scrollbar {
width: 10px; /* width of the entire scrollbar */
height: 10px;
}
*::-webkit-scrollbar-track {
background: rgb(0, 0, 0); /* color of the tracking area */
}
*::-webkit-scrollbar-thumb {
background-color: rgb(255, 255, 255); /* color of the scroll thumb */
border-radius: 20px; /* roundness of the scroll thumb */
//border: 3px solid black; /* creates padding around scroll thumb */
}
* {
scrollbar-width: thin; /* "auto" or "thin" */
scrollbar-color: rgb(255, 255, 255) rgb(0, 0, 0); /* scroll thumb and track */
}
a {
color: black;
text-decoration: underline;
}

View File

@@ -0,0 +1,18 @@
@main-color: #b5103f;
@hover-color: #920a31;
@background-color: white;
@bodyfontsize: 16px;
@bodyfontsize_desktop: 18px;
@headingfontsize: 24px;
@headingfontsize_desktop: 32px;
@font-family: "Open Sans", sans-serif;
@desktop: ~"only screen and (min-width: 1024px)";
@tablet: ~"only screen and (min-width: 768px)";
@mobile: ~"only screen and (min-width: 100px)";
@body-maxwidth: 1300px;
:root {
}

View File

@@ -0,0 +1,60 @@
<script lang="ts">
export let cookieName: string
export let backgroundUrl = ""
export let textPosition = "center"
export let background = ""
let contentShown = false
const positions = {
oben: "flex-start",
mitte: "center",
unten: "flex-end",
}
window.addEventListener("ccAccept", (e) => {
if ((e as CustomEvent).detail[1] == cookieName) contentShown = true
})
//isCookieSet isnt really precise
function checkCookie(cookieName: string) {
// Get all cookies
var allCookies = decodeURIComponent(document.cookie)
// Split into individual cookies
var cookies = allCookies.split(";")
var ccTagCookies: string[] = []
cookies.forEach((e) => {
e.includes("ccTags") ? (ccTagCookies = e.split(",")) : void 0
})
for (var i = 0; i < ccTagCookies.length; i++) {
var c = ccTagCookies[i]
// Trim whitespace
while (c.charAt(0) == " ") c = c.substring(1)
// If the cookie's name matches the given name
if (c == cookieName) return true
}
return false
}
// Verwendung
if (checkCookie(cookieName)) contentShown = true
</script>
{#if contentShown}
<slot />
{:else}
<div
style="display: flex;
justify-content: center;
align-items: {positions[textPosition]};
background-image: url({backgroundUrl});
background-size: cover;
width: 100%;
align-self: stretch;
flex-grow: 1;
"
>
<div style="background-color: rgba(255,255,255,0.7); padding: 20px; background: {background}; width: 100%;">
<p>Cookie ist nicht aktiviert. Bitte aktivieren Sie ihn.</p>
</div>
</div>
{/if}
<style lang="less"></style>

View File

@@ -0,0 +1,123 @@
<script lang="ts">
import { serviceNavigation, content } from "../store"
import { navigateWrapper } from "../functions/utils"
</script>
<div class="footer">
<div class="infos">
<h3>webmakers Erfurt</h3>
<div class="infos-inner">
<div class="upper">
<p>Inh. Marc Oschmann</p>
<p>Marktstraße 34</p>
<p>99084 Erfurt</p>
</div>
<div class="lower">
<p>Tel.: 0361 6021502</p>
<p>Fax.: 0361 6021502</p>
<p>Email: webmakers@gmail.de</p>
</div>
</div>
<div class="social">
<a href="">
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M11.7 3h12.6c4.8 0 8.7 3.9 8.7 8.7v12.6a8.7 8.7 0 0 1-8.7 8.7H11.7C6.9 33 3 29.1 3 24.3V11.7A8.7 8.7 0 0 1 11.7 3zm-.3 3A5.4 5.4 0 0 0 6 11.4v13.2c0 2.985 2.415 5.4 5.4 5.4h13.2a5.4 5.4 0 0 0 5.4-5.4V11.4C30 8.415 27.585 6 24.6 6H11.4zm14.475 2.25a1.875 1.875 0 1 1 0 3.75 1.875 1.875 0 0 1 0-3.75zM18 10.5a7.5 7.5 0 1 1 0 15 7.5 7.5 0 0 1 0-15zm0 3a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9z"
fill="#333333"
></path>
</svg>
</a>
<a href="">
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M18 3.06c-8.25 0-15 6.735-15 15.03 0 7.5 5.49 13.725 12.66 14.85v-10.5h-3.81v-4.35h3.81v-3.315c0-3.765 2.235-5.835 5.67-5.835 1.635 0 3.345.285 3.345.285v3.705h-1.89c-1.86 0-2.445 1.155-2.445 2.34v2.82h4.17l-.675 4.35H20.34v10.5A15 15 0 0 0 33 18.09c0-8.295-6.75-15.03-15-15.03z"
fill="#333333"
></path>
</svg>
</a>
</div>
</div>
<nav class="services">
<ul>
{#each $serviceNavigation || [] as service, i (i)}
<li>
<button
on:click="{() => {
navigateWrapper(`${$content[service?.page || '']?.path}`)
}}"
><a class="nav-title-m" href="{$content?.[service?.page || '']?.path}" on:click|preventDefault
>{service?.name}</a
></button
>
</li>
{/each}
</ul>
</nav>
</div>
<style lang="less">
@import "../assets/css/variables.less";
a {
text-decoration: none;
}
.social {
margin-top: 20px;
}
.footer {
margin-top: 60px;
width: 100vw;
min-height: fit-content;
font-size: 16px;
display: flex;
justify-content: space-between;
max-width: calc(@body-maxwidth - min(9vw, 200px));
background-color: @hover-color;
padding: min(4.5vw, 100px) min(4.5vw, 100px) 0px min(4.5vw, 100px);
}
.infos {
display: flex;
height: 70%;
.infos-inner {
gap: 10px;
display: flex;
}
}
.services {
display: flex;
flex-direction: column;
button {
font-size: inherit;
padding: 6px 0px;
text-align: start;
}
}
@media @mobile {
.infos {
flex-direction: column;
justify-content: start;
@media @mobile {
gap: 20px;
}
@media @tablet {
gap: 40px;
}
.infos-inner {
flex-direction: column;
}
}
.footer {
}
}
@media @desktop {
.footer {
height: 350px;
.infos-inner {
gap: 5vw;
flex-direction: row;
}
}
}
</style>

View File

@@ -0,0 +1,382 @@
<script lang="ts">
import { navigateWrapper } from "../functions/utils"
import { content, navigation } from "../store"
let opened = -1
let openMenu = false
$: {
if (openMenu) document.body.style.overflow = "hidden"
else document.body.style.overflow = "auto"
}
</script>
<header class="header">
<div class="container">
<button class="logo" on:keydown on:click="{() => navigateWrapper('/')}">
<img src="/media/logoquer.png" alt="Logo Quer" />
</button>
<nav class="navigation">
{#each $navigation as nav, i}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<ul class="nav-element">
<li>
<button
class="nav-title"
on:click="{(e) => {
if (nav.page) navigateWrapper($content?.[nav.page]?.path)
}}"
>
<a href="{$content?.[nav?.page || '']?.path}" on:click|preventDefault> {nav.name}</a>
</button>
</li>
{#if !nav?.endpoint}
<ul class="subNav" style="{$navigation.length - 1 == i ? 'right: 0px;' : ''}'}">
{#each nav.elements || [] as subNav}
<li>
<button
class="nav-subtitle"
on:click="{(e) => {
if (subNav.page) navigateWrapper($content?.[subNav.page]?.path)
}}"
>
<a href="{$content?.[subNav?.page || '']?.path}" on:click|preventDefault>
{subNav.name}</a
>
</button>
</li>
{/each}
</ul>
{/if}
</ul>
{/each}
</nav>
<button
class="button-three"
id="button-burger"
on:click="{(e) => {
let button = document.getElementsByClassName('button-three')[0]
const currentState = button.getAttribute('data-state')
if (!currentState || currentState === 'closed') {
button.setAttribute('data-state', 'opened')
button.setAttribute('aria-expanded', 'true')
} else {
button.setAttribute('data-state', 'closed')
button.setAttribute('aria-expanded', 'false')
}
openMenu = !openMenu
}}"
aria-controls="primary-navigation"
aria-expanded="false"
>
<svg stroke="white" fill="none" class="hamburger" viewBox="-10 -10 120 120" width="45">
<path
class="line"
stroke-width="10"
stroke-linecap="round"
stroke-linejoin="round"
d="m 20 40 h 60 a 1 1 0 0 1 0 20 h -60 a 1 1 0 0 1 0 -40 h 30 v 70"
>
</path>
</svg>
</button>
<nav class="navigation-m" class:open="{openMenu}">
{#each $navigation as nav, i}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<ul class="nav-element-m" style="padding-top: {i == 0 ? '15px' : '0px'}">
<li>
<button
on:click="{(e) => {
if (opened == i) opened = -1
else opened = i
if (nav.endpoint) {
const burger = document.getElementById('button-burger')
if (burger) burger.click()
if (nav.page) navigateWrapper($content?.[nav.page]?.path)
}
}}"
><a class="nav-title-m" href="{$content?.[nav?.page || '']?.path}" on:click|preventDefault>
<div>{@html nav.name}</div>
{#if !nav?.endpoint}
<div class="">
{#if i == opened}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-chevron-up"
viewBox="0 0 16 16"
>
<path
fill-rule="evenodd"
d="M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708l6-6z"
></path>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-chevron-down"
viewBox="0 0 16 16"
>
<path
fill-rule="evenodd"
d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
></path>
</svg>
{/if}
</div>
{/if}</a
>
</button>
</li>
{#if !nav?.endpoint}
<ul class="subNav-m {i == opened ? 'visible' : 'hidden'}">
{#each nav.elements || [] as subNav}
<li>
<button
class="nav-subtitle-m"
on:click="{(e) => {
const burger = document.getElementById('button-burger')
if (burger) burger.click()
if (subNav.page) navigateWrapper($content?.[subNav.page]?.path)
}}"
>
<a href="{$content?.[subNav?.page || '']?.path}" on:click|preventDefault>
{subNav.name}</a
>
</button>
</li>
{/each}
</ul>
{/if}
</ul>
{/each}
</nav>
</div>
</header>
<style lang="less">
@import "../assets/css/variables.less";
@switchit: 1000px;
a {
text-decoration: none;
color: white;
}
.header {
min-height: 60px;
height: 100%;
background-color: @main-color;
width: 100%;
color: white;
position: relative;
button {
color: white;
}
display: flex;
justify-content: center;
.desktop-nav {
display: flex;
align-items: center;
gap: 2vw;
}
@media @mobile {
font-size: @headingfontsize;
}
@media (min-width: 1000px) and (max-width: 1500px) {
font-size: 15px;
padding: 10px;
gap: 10px;
}
@media (min-width: 1250px) and (max-width: 1500px) {
font-size: 17px;
padding: 10px;
gap: 10px;
}
.button-three {
--button-color: #333;
overflow: hidden;
background: transparent;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
margin-top: 8px;
@media (min-width: 1001px) {
display: none;
}
}
.button-three .hamburger {
transition:
translate 400ms,
rotate 400ms;
}
.button-three[aria-expanded="true"] .hamburger {
translate: 1px -1px;
rotate: 0.125turn;
}
.button-three .line {
transition: 1s;
stroke-dasharray: 60 31 60 300;
}
.button-three[aria-expanded="true"] .line {
stroke-dasharray: 60 105 60 300;
stroke-dashoffset: -90;
}
.container {
cursor: pointer;
height: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: 100%;
max-width: @body-maxwidth;
align-items: center;
@media @mobile {
padding: 0px 20px;
}
@media @tablet {
padding: 0px 10px;
}
.logo {
display: flex;
height: 100%;
align-items: center;
.symbol {
margin-right: min(35px, 2vw);
}
}
.navigation {
display: flex;
@media (max-width: @switchit) {
display: none;
}
align-items: center;
justify-content: flex-end;
gap: 20px;
flex-grow: 1;
height: 85px;
padding: 0px 20px;
@media (min-width: 1000px) and (max-width: 1500px) {
font-size: 14px;
padding: 10px;
gap: 10px;
}
@media (min-width: 1250px) and (max-width: 1500px) {
font-size: 17px;
padding: 10px;
gap: 10px;
}
.nav-element {
display: flex;
height: 100%;
flex-direction: column;
position: relative;
.nav-title {
padding: 20px 0px;
height: 100%;
display: flex;
align-items: center;
}
.subNav {
opacity: 0;
top: 99%;
height: 0px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 10px;
position: absolute;
background: #333e52c0;
max-width: 450px;
width: fit-content;
min-width: 100%;
z-index: 9999;
.nav-subtitle {
width: fit-content;
max-width: 450px;
white-space: nowrap;
}
}
&:hover {
.subNav {
opacity: 1;
height: fit-content;
}
}
}
}
.navigation-m {
position: absolute;
z-index: 9999;
background-color: @main-color;
top: 100%;
left: -100vw;
width: 100%;
height: 0px;
display: flex;
flex-direction: column;
transition:
height 0ms,
left 400ms;
@media @mobile {
padding: 0px 20px;
}
@media @tablet {
padding: 0px 10px;
}
&.open {
left: 0px;
height: 100vh;
}
button {
width: 100%;
.nav-title-m {
cursor: pointer;
display: flex;
width: 100%;
padding: 15px 0px;
justify-content: space-between;
font-weight: 700;
font-size: 1.5rem;
padding-bottom: 10px;
}
}
.subNav-m {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 5px;
.nav-subtitle-m {
display: flex;
justify-content: flex-start;
font-size: 1rem;
}
&.hidden {
max-height: 0px;
}
&.visible {
max-height: 300px;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,71 @@
<script>
import { onMount, onDestroy } from "svelte"
import { rerender } from "../store"
let showButton = true
const checkScroll = () => {
// Change the visibility of the button based on the scroll position
showButton = window.pageYOffset < 100
}
const jumpDown = () => {
if (typeof window !== "undefined") {
// Jump down by 100vh
window.scrollTo({ top: window.innerHeight, behavior: "smooth" })
}
}
onMount(() => {
if (typeof window !== "undefined") {
// Attach scroll event listener when component is mounted
window.addEventListener("scroll", checkScroll)
}
})
onDestroy(() => {
if (typeof window !== "undefined") {
// Remove scroll event listener when component is destroyed
window.removeEventListener("scroll", checkScroll)
}
})
let force = true
if (typeof window !== "undefined") {
setInterval(() => {
if (location.pathname != "/") {
force = false
} else force = true
}, 1000)
}
$: {
if (typeof window !== "undefined") {
if ($rerender) {
if (location.pathname != "/") {
force = false
} else force = true
}
}
}
</script>
{#if showButton && force}
<button on:click="{jumpDown}" class="jump-down"
><span> SCROLL </span>
<img src="/media/chev-d.svg" alt="arrow" />
</button>
{/if}
<style>
.jump-down {
display: flex;
flex-direction: column;
align-items: center;
position: fixed;
color: #4f4f4f;
z-index: 100;
gap: 5px;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
</style>

View File

@@ -0,0 +1,52 @@
<script>
import { onMount, onDestroy } from "svelte"
let showButton = false
const checkScroll = () => {
// Change the visibility of the button based on the scroll position
showButton = window.pageYOffset > 200
}
const scrollToTop = () => {
if (typeof window !== "undefined") {
// Scroll smoothly to the top
window.scrollTo({ top: 0, behavior: "smooth" })
}
}
onMount(() => {
if (typeof window !== "undefined") {
// Attach scroll event listener when component is mounted
window.addEventListener("scroll", checkScroll)
}
})
onDestroy(() => {
if (typeof window !== "undefined") {
// Remove scroll event listener when component is destroyed
window.removeEventListener("scroll", checkScroll)
}
})
</script>
{#if showButton}
<button on:click="{scrollToTop}" class="scroll-to-top"><img src="/media/ToTop.svg" alt="toTop" /> </button>
{/if}
<style lang="less">
@import "../assets/css/variables.less";
.scroll-to-top {
/* Place your styles here */
position: fixed;
bottom: 5px;
z-index: 9999;
transform: scale(0.5);
right: 5px;
@media @tablet {
bottom: 60px;
right: 60px;
transform: scale(1);
}
}
</style>

View File

@@ -0,0 +1,27 @@
<script lang="ts">
import CookieSet from "../CookieSet.svelte"
const setHeight = (element: HTMLElement) => {
element.style.height = (element.offsetWidth / 16) * 9 + "px"
}
</script>
<CookieSet cookieName="{'googleMaps'}" textPosition="{'unten'}" background="{'rgba(44, 44, 44, 0.4)'}">
<iframe
title="Google Maps"
use:setHeight
id="iframe"
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2512.077224708092!2d11.023318016007138!3d50.97776317955154!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x47a4729650047377%3A0xca0e03f621729448!2sWebmakers%20GmbH!5e0!3m2!1sde!2sde!4v1571866765252!5m2!1sde!2sde"
style="border:0;"
allowfullscreen="{true}"
loading="lazy"
referrerpolicy="no-referrer-when-downgrade"
></iframe>
</CookieSet>
<style>
iframe {
width: 100%;
max-width: 100%;
border: 0;
}
</style>

View File

@@ -0,0 +1,96 @@
<script lang="ts">
import { onMount } from "svelte"
import { register } from "swiper/element/bundle"
import { apiBaseURL } from "../../../config"
import { mediaLibrary } from "../../store"
export let images: string[]
register(false)
let swiper: any
onMount(async () => {
if (swiper !== undefined) {
const response = await fetch("/dist/index.css")
const cssText = await response.text()
const params = {
injectStyles: [cssText],
}
Object.assign(swiper, params)
swiper.initialize()
}
})
let image = images[0]
</script>
{#if images.length > 1}
<div class="flex">
<swiper-container
bind:this="{swiper}"
slides-per-view="1"
loop="{true}"
direction="horizontal"
effect="slide"
autoplay-delay="2500"
mousewheel="{true}"
navigation="{true}"
init="{false}"
speed="600"
class="relative"
>
{#each images as image, i (i)}
<swiper-slide class="relative">
<div class="image-container">
<img
src="{`${apiBaseURL}medialib/${image}/${$mediaLibrary?.[image]?.file?.src}?filter=${
window.innerWidth > 500 ? 'xl' : 'm'
}`}"
alt="Bild"
/>
</div>
</swiper-slide>
{/each}
</swiper-container>
</div>
{:else if image}
<div class="image-container single flex">
<img
src="{`${apiBaseURL}medialib/${image}/${$mediaLibrary?.[image]?.file?.src}?filter=${
window.innerWidth > 500 ? 'xl' : 'm'
}`}"
alt="Bild"
/>
</div>
{/if}
<style lang="less">
@import "../../assets/css/variables.less";
swiper-container {
height: auto;
width: 100%;
}
swiper-slide {
width: 100% !important;
}
.flex {
flex: 2 !important;
}
.image-container {
max-width: 100%;
width: 100%;
overflow: hidden;
max-width: @body-maxwidth;
&:hover img {
transform: scale(1.05);
transform-origin: center;
}
img {
width: 100%;
height: auto;
object-fit: cover;
}
}
</style>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { modules } from "../../store"
import Formular from "./form/Formular.svelte"
export let col: { contentType: "moduleImport"; moduleImport: string }
export let pageId: string
let module = $modules[col.moduleImport] || {}
$: module = $modules[col.moduleImport] || {}
</script>
{#if module.type == "form"}
<Formular {col} {pageId} form="{module}" />
{/if}

View File

@@ -0,0 +1,158 @@
<script lang="ts">
import { navigateWrapper } from "../../functions/utils"
import Image from "./Image.svelte"
import Text from "./Text.svelte"
import Modules from "./Modules.svelte"
import GoogleMaps from "./GoogleMaps.svelte"
export let row: Row
export let pageId: string
export let bright: boolean
export let isHP: boolean
export let i: number
export let page: Content
</script>
{#if Object.keys(row).length}
{#if page.pageTitle && i == 0}
<h1>{page.pageTitle}</h1>
{/if}
{#if row.title}
<h2 class="">{row.title}</h2>
{/if}
{#if row?.columns?.length}
<div class="row">
{#each row?.columns as col}
<div class="col">
{#if col.contentType == "images"}
<Image images="{col.images}" />
{:else if col.contentType == "text"}
<Text text="{col.text}" />
{:else if col.contentType == "form" || col.contentType == "moduleImport"}
<Modules {col} {pageId} />
{:else if col.contentType == "googleMaps"}
<GoogleMaps />
{/if}
</div>
{/each}
</div>{/if}
{/if}
<style lang="less">
@import "../../assets/css/variables.less";
h3 {
font-weight: 500;
margin-bottom: 15px;
@media @tablet {
margin-bottom: 0px;
}
}
h2 {
font-weight: 500;
font-size: 1.7rem !important;
}
h1 {
font-weight: 500;
font-size: 2rem;
}
.top-header {
img {
width: 20px;
}
font-size: 0.7rem;
@media @tablet {
font-size: 1.2rem;
img {
width: 48px;
}
}
}
@media @tablet {
h3 {
font-size: 1.3rem;
display: flex;
align-items: center;
gap: 10px;
img {
width: 40px;
margin-right: 10px;
}
&.subheading {
font-size: 1.8rem;
}
}
h2 {
font-size: 2rem !important;
font-weight: 500;
}
h1 {
font-size: 3.5rem;
font-weight: 500;
}
}
@media @desktop {
h2 {
font-size: 2.3rem !important;
}
h1 {
font-size: 5rem;
}
}
.row {
padding-top: 40px;
display: flex;
align-items: flex-start;
flex-wrap: wrap;
gap: 20px;
&.dominant {
@media (max-width: 1024px) {
flex-direction: row-reverse;
}
}
@media (max-width: 640px) {
flex-direction: column-reverse;
&.normalWrap {
flex-direction: column;
}
}
& > .col {
max-width: 100%;
&.dominant {
@media (max-width: 1024px) {
min-width: 80% !important;
}
flex: 5 !important;
}
min-width: 40% !important;
@media @desktop {
min-width: 30% !important;
}
flex: 1;
min-width: 0;
}
&.twoToThree {
& > .col:nth-child(1) {
flex: 2 !important;
}
& > .col:nth-child(2) {
flex: 3 !important;
}
}
}
.hph3 {
text-transform: uppercase;
font-size: 0.7rem;
}
.nmh3 {
font-size: 0.9rem;
@media @tablet {
font-size: 1.6rem;
}
}
</style>

View File

@@ -0,0 +1,10 @@
<script lang="ts">
export let text: string
</script>
<div class="text">
{@html text}
</div>
<style lang="less">
</style>

View File

@@ -12,7 +12,3 @@
<input type="checkbox" bind:checked="{bindValue}" {id} {name} bind:this="{formValues[name]}" />
<span>{label}</span>
</label>
<style>
/* Sie können hier den Stil für Ihre Checkbox anpassen */
</style>

View File

@@ -1,7 +1,8 @@
<script lang="ts">
import type { Writable } from "svelte/store"
export let groupTitle: string
export let checkboxes: { name: string; emailName: string }[]
export let checkboxes: { textTitle: string; emailTitle: string }[]
export let formValues: Writable<FormValues>
export let rowNr: number
</script>
@@ -14,10 +15,10 @@
<input
type="checkbox"
class="checkbox-input checkit"
bind:this="{$formValues[`checkbox_${groupTitle}_${rowNr}_${checkbox.emailName}`]}"
bind:this="{$formValues[`checkbox_${groupTitle}_${rowNr}_${checkbox.emailTitle}`]}"
/>
<span class="checkit-span"></span>
{checkbox.name}
{checkbox.textTitle}
</label>
{/each}
</div>

View File

@@ -2,7 +2,6 @@
import { CalendarView } from "fluent-svelte"
import type { Writable } from "svelte/store"
export let groupTitle: string
export let datePickerProps: DatePickerProps
export let formValues: Writable<FormValues>
@@ -112,22 +111,410 @@
</div>
<style lang="less">
@import url("https://unpkg.com/fluent-svelte/theme.css");
/* Global Variables */
:root {
/* Accent Colors */
--fds-accent-light-3: 191, 98%, 80%;
--fds-accent-light-2: 199, 99%, 69%;
--fds-accent-light-1: 205, 100%, 49%;
--fds-accent-base: 206, 100%, 42%;
--fds-accent-dark-1: 209, 100%, 36%;
--fds-accent-dark-2: 215, 100%, 29%;
--fds-accent-dark-3: 226, 100%, 20%;
/* Font Families */
--fds-font-family-fallback: "Segoe UI", -apple-system, ui-sans-serif, system-ui, BlinkMacSystemFont, Helvetica,
Arial, sans-serif;
--fds-font-family-text: "Segoe UI Variable Text", "Seoge UI Variable Static Text",
var(--fds-font-family-fallback);
--fds-font-family-small: "Segoe UI Variable Small", "Seoge UI Variable Static Small",
var(--fds-font-family-fallback);
--fds-font-family-display: "Segoe UI Variable Display", "Seoge UI Variable Static Display",
var(--fds-font-family-fallback);
/* Font Size */
--fds-caption-font-size: 12px;
--fds-body-font-size: 14px;
--fds-body-large-font-size: 18px;
--fds-subtitle-font-size: 20px;
--fds-title-font-size: 28px;
--fds-title-large-font-size: 40px;
--fds-display-font-size: 68px;
/* Roundness */
--fds-control-corner-radius: 4px;
--fds-overlay-corner-radius: 8px;
/* Duration */
--fds-control-slow-duration: 333ms;
--fds-control-normal-duration: 250ms;
--fds-control-fast-duration: 167ms;
/* --fds-control-fast-after-duration: 168ms; */
--fds-control-faster-duration: 83ms;
/* Easing */
--fds-control-fast-out-slow-in-easing: cubic-bezier(0, 0, 0, 1);
/* Focus Stroke */
--fds-focus-stroke: 0 0 0 1px var(--fds-focus-stroke-inner), 0 0 0 3px var(--fds-focus-stroke-outer);
/* Acrylic */
--fds-acrylic-noise-asset: url("data:image/png;base64,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");
--fds-acrylic-blur-factor: blur(60px);
}
/* Reduced Motion Support */
@media (prefers-reduced-motion: reduce) {
:root {
--fds-control-slow-duration: 0ms;
--fds-control-normal-duration: 0ms;
--fds-control-fast-duration: 0ms;
/* --fds-control-fast-after-duration: 0ms; */
--fds-control-faster-duration: 0ms;
}
}
/* Light Theme Colors */
@media (prefers-color-scheme: light) {
:root {
/* Text */
--fds-text-primary: hsla(0, 0%, 0%, 89.56%);
--fds-text-secondary: hsla(0, 0%, 0%, 60.63%);
--fds-text-tertiary: hsla(0, 0%, 0%, 44.58%);
--fds-text-disabled: hsla(0, 0%, 0%, 36.14%);
/* Accent */
--fds-accent-default: hsl(var(--fds-accent-dark-1));
--fds-accent-secondary: hsla(var(--fds-accent-dark-1), 90%);
--fds-accent-tertiary: hsla(var(--fds-accent-dark-1), 80%);
--fds-accent-disabled: hsla(0, 0%, 0%, 21.69%);
/* Accent Text */
--fds-accent-text-primary: hsl(var(--fds-accent-dark-2));
--fds-accent-text-secondary: hsl(var(--fds-accent-dark-3));
--fds-accent-text-tertiary: hsl(var(--fds-accent-dark-1));
--fds-accent-text-disabled: hsla(0, 0%, 0%, 36.14%);
/* Text on Accent */
--fds-text-on-accent-primary: hsl(0, 0%, 100%);
--fds-text-on-accent-secondary: hsla(0, 0%, 100%, 70%);
--fds-text-on-accent-disabled: var(--fds-text-on-accent-secondary);
--fds-text-on-accent-selected: var(--fds-text-on-accent-primary);
/* Control Fill */
--fds-control-fill-transparent: transparent;
--fds-control-fill-default: hsla(0, 0%, 100%, 70%);
--fds-control-fill-secondary: hsla(0, 0%, 98%, 50%);
--fds-control-fill-tertiary: hsla(0, 0%, 98%, 30%);
--fds-control-fill-disabled: var(--fds-control-fill-tertiary);
--fds-control-fill-input-active: hsl(0, 0%, 100%);
/* Control Strong Fill */
--fds-control-strong-fill-default: hsla(0, 0%, 0%, 44.58%);
--fds-control-strong-fill-disabled: hsla(0, 0%, 0%, 31.73%);
/* Control Solid Fill */
--fds-control-solid-fill-default: hsl(0, 0%, 100%);
/* Control Alt Fill */
--fds-control-alt-fill-transparent: transparent;
--fds-control-alt-fill-secondary: hsla(0, 0%, 0%, 2.41%);
--fds-control-alt-fill-tertiary: hsla(0, 0%, 0%, 5.78%);
--fds-control-alt-fill-quarternary: hsla(0, 0%, 0%, 9.24%);
--fds-control-alt-fill-disabled: var(--fds-control-alt-fill-transparent);
/* Control on Image Fill */
--fds-control-on-image-fill-default: hsla(0, 0%, 100%, 79%);
--fds-control-on-image-fill-secondary: hsl(0, 0%, 95%);
--fds-control-on-image-fill-tertiary: hsl(0, 0%, 92%);
--fds-control-on-image-fill-disabled: transparent;
/* Subtle Fill */
--fds-subtle-fill-transparent: transparent;
--fds-subtle-fill-secondary: hsla(0, 0%, 0%, 3.73%);
--fds-subtle-fill-tertiary: hsla(0, 0%, 0%, 2.41%);
--fds-subtle-fill-disabled: var(--fds-subtle-fill-transparent);
/* Control Stroke */
--fds-control-stroke-default: hsla(0, 0%, 0%, 5.78%);
--fds-control-stroke-secondary: hsla(0, 0%, 0%, 16.22%);
/* Control Strong Stroke */
--fds-control-strong-stroke-default: hsla(0, 0%, 0%, 44.58%);
--fds-control-strong-stroke-disabled: hsla(0, 0%, 0%, 21.69%);
/* Control Stroke on Accent */
--fds-control-stroke-on-accent-default: hsla(0, 0%, 100%, 8%);
--fds-control-stroke-on-accent-secondary: hsla(0, 0%, 0%, 40%);
--fds-control-stroke-on-accent-tertiary: hsla(0, 0%, 0%, 21.69%);
--fds-control-stroke-on-accent-disabled: var(--fds-control-stroke-on-accent-default);
/* Control Strong Stroke on Image */
--fds-control-strong-stroke-on-image-default: hsla(0, 0%, 100%, 35%);
/* Card Stroke */
--fds-card-stroke-default: hsla(0, 0%, 0%, 5.78%);
--fds-card-stroke-default-solid: hsl(0, 0%, 92%);
/* Surface Stroke */
--fds-surface-stroke-default: hsla(0, 0%, 46%, 40%);
--fds-surface-stroke-flyout: hsla(0, 0%, 0%, 5.78%);
/* Divider Stroke */
--fds-divider-stroke-default: hsla(0, 0%, 0%, 8.03%);
/* Focus Stroke */
--fds-focus-stroke-outer: hsla(0, 0%, 0%, 89.56%);
--fds-focus-stroke-inner: hsl(0, 0%, 100%);
/* Card Background */
--fds-card-background-default: hsla(0, 0%, 100%, 70%);
--fds-card-background-secondary: hsla(0, 0%, 96%, 50%);
/* --fds-card-background-tertiary: hsl(0, 0%, 100%); */
/* Smoke Background */
--fds-smoke-background-default: hsla(0, 0%, 0%, 30%);
/* Layer */
--fds-layer-background-default: hsla(0, 0%, 100%, 50%);
--fds-layer-background-alt: hsl(0, 0%, 100%);
/* Layer on Acrylic */
--fds-layer-on-acrylic-background-default: hsla(0, 0%, 100%, 25%);
--fds-layer-on-accent-acrylic-background-default: var(--fds-layer-on-acrylic-background-default);
/* Solid Background */
--fds-solid-background-base: hsl(0, 0%, 95%);
--fds-solid-background-secondary: hsl(0, 0%, 93%);
--fds-solid-background-tertiary: hsl(0, 0%, 98%);
--fds-solid-background-quarternary: hsl(0, 0%, 100%);
/* Mica Background */
/* --fds-mica-background-base: linear-gradient(0deg, hsla(0, 0%, 95%, 0.5), hsla(0, 0%, 95%, 0.5)), hsl(0, 0%, 95%); */
/* Acrylic Background */
--fds-acrylic-background-default: transparent, rgba(252, 252, 252, 85%);
--fds-acrylic-background-base: transparent, rgba(243, 243, 243, 90%);
/* Accent Acrylic Background */
/* --fds-accent-acrylic-background-base: url("NoiseAsset_256.png"), linear-gradient(0deg, rgba(153, 235, 255, 80%), rgba(153, 235, 255, 80%)), rgba(153, 235, 255, 90%); */
/* --fds-accent-acrylic-background-default: url("NoiseAsset_256.png"), linear-gradient(0deg, rgba(153, 235, 255, 80%), rgba(153, 235, 255, 80%)), rgba(153, 235, 255, 90%); */
/* System */
--fds-system-attention: hsl(209, 100%, 36%);
--fds-system-success: hsl(120, 78%, 27%);
--fds-system-caution: hsl(36, 100%, 31%);
--fds-system-critical: hsl(5, 75%, 44%);
--fds-system-neutral: hsla(0, 0%, 0%, 44.58%);
/* System Solid */
--fds-system-solid-neutral: hsl(0, 0%, 54%);
/* System Background */
--fds-system-background-attention: hsla(0, 0%, 96%, 50%);
--fds-system-background-success: hsl(115, 58%, 92%);
--fds-system-background-caution: hsl(47, 100%, 90%);
--fds-system-background-critical: hsl(355, 85%, 95%);
/* System Background Solid */
--fds-system-background-solid-attention: hsl(0, 0%, 97%);
--fds-system-background-solid-neutral: hsl(0, 0%, 95%);
/* Borders */
--fds-control-border-default: var(--fds-control-stroke-default) var(--fds-control-stroke-default)
var(--fds-control-stroke-secondary) var(--fds-control-stroke-default);
/* Shadows */
--fds-card-shadow: 0px 2px 4px hsla(0, 0%, 0%, 4%);
--fds-tooltip-shadow: 0px 4px 8px hsla(0, 0%, 0%, 14%);
--fds-flyout-shadow: 0px 8px 16px hsla(0, 0%, 0%, 14%);
--fds-dialog-shadow: 0px 32px 64px hsla(0, 0%, 0%, 18.76%), 0px 2px 21px hsl(0, 0%, 0%, 14.74%);
/* Shell Shadows */
--fds-inactive-window-shadow: 0px 16px 32px hsla(0, 0%, 0%, 18%), 0px 2px 10.67px hsla(0, 0%, 0%, 0.1474);
--fds-active-window-shadow: 0px 32px 64px hsla(0, 0%, 0%, 28%), 0px 2px 21px hsla(0, 0%, 0%, 22%);
}
}
/* Dark Theme Colors */
@media (prefers-color-scheme: dark) {
:root {
/* Text */
--fds-text-primary: hsla(0, 0%, 100%, 100%);
--fds-text-secondary: hsla(0, 0%, 100%, 78.6%);
--fds-text-tertiary: hsla(0, 0%, 100%, 54.42%);
--fds-text-disabled: hsla(0, 0%, 100%, 36.28%);
/* Accent */
--fds-accent-default: hsla(var(--fds-accent-light-2));
--fds-accent-secondary: hsla(var(--fds-accent-light-2), 90%);
--fds-accent-tertiary: hsla(var(--fds-accent-light-2), 80%);
--fds-accent-disabled: hsla(0, 0%, 100%, 15.81%);
/* Accent Text */
--fds-accent-text-primary: hsl(var(--fds-accent-light-3));
--fds-accent-text-secondary: hsl(var(--fds-accent-light-3));
--fds-accent-text-tertiary: hsl(var(--fds-accent-light-2));
--fds-accent-text-disabled: hsla(0, 0%, 100%, 36.28%);
/* Text on Accent */
--fds-text-on-accent-primary: hsl(0, 0%, 0%);
--fds-text-on-accent-secondary: hsla(0, 0%, 0%, 0.5);
--fds-text-on-accent-disabled: hsla(0, 0%, 100%, 0.53);
--fds-text-on-accent-selected: hsl(0, 0%, 100%);
/* Control Fill */
--fds-control-fill-transparent: transparent;
--fds-control-fill-default: hsla(0, 0%, 100%, 0.061);
--fds-control-fill-secondary: hsla(0, 0%, 100%, 0.084);
--fds-control-fill-tertiary: hsla(0, 0%, 100%, 0.033);
--fds-control-fill-disabled: hsla(0, 0%, 100%, 0.042);
--fds-control-fill-input-active: hsla(0, 0%, 12%, 70%);
/* Control Strong Fill */
--fds-control-strong-fill-default: hsla(0, 0%, 100%, 54.42%);
--fds-control-strong-fill-disabled: hsla(0, 0%, 100%, 24.65%);
/* Control Solid Fill */
--fds-control-solid-fill-default: hsl(0, 0%, 27%);
/* Control Alt Fill */
--fds-control-alt-fill-transparent: transparent;
--fds-control-alt-fill-secondary: hsla(0, 0%, 0%, 0.1);
--fds-control-alt-fill-tertiary: hsla(0, 0%, 100%, 0.042);
--fds-control-alt-fill-quarternary: hsla(0, 0%, 100%, 0.07);
--fds-control-alt-fill-disabled: var(--fds-control-fill-transparent);
/* Control on Image Fill */
--fds-control-on-image-fill-default: hsla(0, 0%, 11%, 70%);
--fds-control-on-image-fill-secondary: hsl(0, 0%, 10%);
--fds-control-on-image-fill-tertiary: hsl(0, 0%, 7%);
--fds-control-on-image-fill-disabled: transparent;
/* Subtle Fill */
--fds-subtle-fill-transparent: transparent;
--fds-subtle-fill-secondary: hsla(0, 0%, 100%, 6.05%);
--fds-subtle-fill-tertiary: hsla(0, 0%, 100%, 4.19%);
--fds-subtle-fill-disabled: transparent;
/* Control Stroke */
--fds-control-stroke-default: hsla(0, 0%, 100%, 6.98%);
--fds-control-stroke-secondary: hsla(0, 0%, 100%, 9.3%);
/* Control Strong Stroke */
--fds-control-strong-stroke-default: hsla(0, 0%, 100%, 54.42%);
--fds-control-strong-stroke-disabled: hsla(0, 0%, 100%, 15.81%);
/* Control Stroke on Accent */
--fds-control-stroke-on-accent-default: hsla(0, 0%, 100%, 8%);
--fds-control-stroke-on-accent-secondary: hsla(0, 0%, 0%, 14%);
--fds-control-stroke-on-accent-tertiary: hsla(0, 0%, 0%, 21.69%);
--fds-control-stroke-on-accent-disabled: hsla(0, 0%, 0%, 20%);
/* Control Strong Stroke on Image */
--fds-control-strong-stroke-on-image-default: hsla(0, 0%, 0%, 42%);
/* Card Stroke */
--fds-card-stroke-default: hsla(0, 0%, 0%, 10%);
--fds-card-stroke-default-solid: hsl(0, 0%, 11%);
/* Surface Stroke */
--fds-surface-stroke-default: hsla(0, 0%, 46%, 40%);
--fds-surface-stroke-flyout: hsla(0, 0%, 0%, 20%);
/* Divider Stroke */
--fds-divider-stroke-default: hsla(0, 0%, 100%, 8.37%);
/* Focus Stroke */
--fds-focus-stroke-outer: hsl(0, 0%, 100%);
--fds-focus-stroke-inner: hsla(0, 0%, 0%, 70%);
/* Card Background */
--fds-card-background-default: hsla(0, 0%, 100%, 5.12%);
--fds-card-background-secondary: hsla(0, 0%, 100%, 3.26%);
/* --fds-card-background-tertiary: unset; */
/* Smoke Background */
--fds-smoke-background-default: hsla(0, 0%, 0%, 30%);
/* Layer */
--fds-layer-background-default: hsla(0, 0%, 23%, 30%);
--fds-layer-background-alt: hsla(0, 0%, 100%, 5.38%);
/* Layer on Acrylic */
--fds-layer-on-acrylic-background-default: hsla(0, 0%, 100%, 3.59%);
--fds-layer-on-accent-background-default: var(--fds-layer-on-acrylic-background-default);
/* Solid Background */
--fds-solid-background-base: hsl(0, 0%, 13%);
--fds-solid-background-secondary: hsl(0, 0%, 11%);
--fds-solid-background-tertiary: hsl(0, 0%, 16%);
--fds-solid-background-quarternary: hsl(0, 0%, 17%);
/* Mica Background */
/* --fds-mica-background-base: linear-gradient(0deg, rgb(32, 32, 32, 0.8), rgb(32, 32, 32, 0.8)), #202020; */
/* Acrylic Background */
--fds-acrylic-background-default: linear-gradient(0deg, rgb(44, 44, 44, 15%), rgb(44, 44, 44, 15%)),
rgba(44, 44, 44, 96%);
--fds-acrylic-background-base: linear-gradient(0deg, rgb(32, 32, 32, 50%), rgb(32, 32, 32, 50%)),
rgba(32, 32, 32, 96%);
/* Accent Acrylic Background */
/* --fds-accent-acrylic-background-default: url("NoiseAsset_256.png"), linear-gradient(0deg, rgb(0, 120, 212, 80%), rgb(0, 120, 212, 80%)), rgb(0, 120, 212, 80%); */
/* --fds-accent-acrylic-background-base: url("NoiseAsset_256.png"), linear-gradient(0deg, rgba(0, 63, 255, 80%), rgba(0, 63, 255, 80%)), rgb(0, 63, 255, 80%); */
/* System */
--fds-system-attention: hsl(199, 100%, 69%);
--fds-system-success: hsl(113, 51%, 58%);
--fds-system-caution: hsl(54, 100%, 49%);
--fds-system-critical: hsl(354, 100%, 80%);
--fds-system-neutral: hsla(0, 0%, 100%, 54.42%);
/* System Solid */
--fds-system-solid-neutral: hsl(0, 0%, 62%);
/* System Background */
--fds-system-background-attention: hsla(0, 0%, 100%, 3.26%);
--fds-system-background-success: hsl(67, 39%, 17%);
--fds-system-background-caution: hsl(40, 46%, 18%);
--fds-system-background-critical: hsl(2, 28%, 21%);
/* System Background Solid */
--fds-system-background-solid-attention: hsl(0, 0%, 18%);
--fds-system-background-solid-neutral: hsl(0, 0%, 62%);
/* Borders */
--fds-control-border-default: var(--fds-control-stroke-secondary) var(--fds-control-stroke-default)
var(--fds-control-stroke-default) var(--fds-control-stroke-default);
/* Shadows */
--fds-card-shadow: 0px 2px 4px hsla(0, 0%, 0%, 0.13);
--fds-tooltip-shadow: 0px 4px 8px hsla(0, 0%, 0%, 0.26);
--fds-flyout-shadow: 0px 8px 16px hsla(0, 0%, 0%, 0.14);
--fds-dialog-shadow: 0px 32px 64px hsla(0, 0%, 0%, 0.37), 0px 2px 21px hsla(0, 0%, 0%, 0.37);
/* Shell Shadows */
--fds-inactive-window-shadow: 0px 16px 32px hsla(0, 0%, 0%, 0.37), 0px 2px 10.67px hsla(0, 0%, 0%, 0.37);
--fds-active-window-shadow: 0px 32px 64px hsla(0, 0%, 0%, 0.56), 0px 2px 21px hsla(0, 0%, 0%, 0.55);
}
}
.container {
width: 100%;
display: flex;
justify-content: flex-start;
--fds-solid-background-quarternary: var(--background-color);
--fds-text-primary: var(--normal-font-color);
--fds-text-secondary: var(--normal-font-color-80);
--fds-text-tertiary: var(--normal-font-color-50);
--fds-text-disabled: var(--normal-font-color-30);
--fds-accent-disabled: var(--opposite-bg-color-5);
--fds-control-strong-stroke-default: var(--opposite-bg-color-80);
--fds-solid-background-quarternary: rgb(235, 221, 221);
--fds-text-primary: #333333;
--fds-text-secondary: rgba(51, 51, 51, 0.8);
--fds-text-tertiary: rgba(51, 51, 51, 0.5);
--fds-text-disabled: rgba(51, 51, 51, 0.3);
--fds-accent-disabled: rgba(24, 24, 24, 0.05);
--fds-control-strong-stroke-default: rgba(24, 24, 24, 0.8);
.datepicker {
width: fit-content;
margin: 5px 0px;
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
box-shadow: 0 0 25px 10px rgba(24, 24, 24, 0.05);
}
}
</style>

View File

@@ -1,200 +0,0 @@
<script lang="ts">
import CheckboxGroup from "./CheckboxGroup.svelte"
import Datepicker from "./Datepicker.svelte"
import FormLabelNumberBlock from "./FormLabelNumberBlock.svelte"
import type { Writable } from "svelte/store"
import Select from "./Select.svelte"
export let formRow: FormRow
export let index: number
export let formValues: Writable<FormValues>
/* function getSortedFields(column: FormColumn) {
const fields = [
...(column.text.length ? [{ text: column.text, type: "text", order: column.textfieldOrder ?? 3 }] : []),
...(column.showTimes ? [{ type: "times", order: column.timesfieldOrder ?? 3, times: column.times }] : []),
...(column.showDate
? [{ type: "date", order: column.datefieldOrder ?? 3, datePlaceholder: column.datePlaceholder }]
: []),
]
return fields.sort((a, b) => a.order - b.order)
}*/
//formRow.columns = formRow.columns.map((e) => getSortedFields(e))
function removeInvalid(e: Event) {
let element = e.currentTarget as HTMLElement
if (element) element.classList.remove("invalid")
}
</script>
<div class="form-row">
<h3 style="margin-bottom: -0.5rem;">
{#if formRow.showRowName}{formRow.rowName || ""}{/if}
</h3>
<div class="form-cols">
{#each formRow.columns as column, columnIndex}
<div class="form-column">
<h3>{column?.title ?? ""}</h3>
{#if column?.annotation}
<p>{column?.annotation}</p>
{/if}
{#if column.showLabelNumber}
<FormLabelNumberBlock {column} {formValues} rowIndex="{index}" />
{/if}
{#if column?.showTimes}
<label bind:this="{$formValues[`times_${column?.title ? column.title + '_' : ''}label`]}">
<select
id="time-select"
bind:this="{$formValues[
`times_${column?.title ?? 'Zeiten'}_${column.timesfieldOrder}_${index}_${
column.emailNameTimes || columnIndex + 'invalidtimes'
}`
]}"
required="{column?.timeNotRequired !== true}"
on:change="{removeInvalid}"
>
<option value="" disabled selected>Bitte Uhrzeit wählen</option>
{#each column?.times ?? [] as time}
<option value="{time?.timeFrom}-{time?.timeTo}">
{time?.timeFrom} - {time?.timeTo}
</option>
{/each}
</select>
</label>
{/if}
{#if column?.showSelect}
<label bind:this="{$formValues[`select_${column?.title ? column.title + '_' : ''}label`]}">
<select
required="{column?.dateSelectNotRequired !== true}"
bind:this="{$formValues[
`select_${column?.title ?? 'Auswahl'}_${column.datefieldOrder}_${index}_${
column?.emailNameTime || columnIndex + 'invalidtime'
}`
]}"
on:change="{removeInvalid}"
>
<option value="" disabled selected>{column.selectTitle}</option>
{#each column?.selectEntries as entry}
<option value="{entry?.leftSide}-{entry?.rightSide}">
{entry?.leftSide}-{entry?.rightSide}
</option>
{/each}
</select>
</label>
{/if}
{#if column.showDate}
<label bind:this="{$formValues[`date_${column?.title ? column.title + '_' : ''}label`]}">
<input
class="date"
type="date"
required="{column?.dateNotRequired !== true}"
on:change="{removeInvalid}"
bind:this="{$formValues[
`date_${column?.title ?? 'Datum'}_${column.datefieldOrder}_${index}_${
column?.emailNameDate || columnIndex + 'invaliddate'
}`
]}"
/>
</label>
{/if}
{#if column.showNumber}
<label
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_label`
]}"
>
<input
type="number"
step="any"
required="{column?.numberNotRequired !== true}"
on:change="{removeInvalid}"
placeholder="{column?.numberPlaceholder}"
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_${
column.numberfieldOrder
}_${index}_${column?.emailNameNumber || columnIndex + 'invalidnumber'}`
]}"
/>
</label>
{/if}
{#if column.showCheckboxGroup}
<CheckboxGroup
checkboxes="{column.checkboxes}"
groupTitle="{column.groupTitle}"
{formValues}
rowNr="{index}"
/>
{/if}
{#if column.showDatePicker}
<Datepicker
datePickerProps="{column.datePickerProps}"
groupTitle="{column.groupTitle}"
{formValues}
rowNr="{index}"
formCol="{column}"
/>
{/if}
{#if column.showMultiSelect}
<Select
groupTitle="{column.groupTitle}"
{formValues}
rowNr="{index}"
formCol="{column}"
options="{column.multiSelectOptions}"
/>
{/if}
{#each column.text ?? [] as textField, textFieldIndex}
<div>
<h3 class="textTitle">{textField.textTitle || ""}</h3>
{#if textField?.textArea}
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
<textarea
placeholder="{textField?.textPlaceholder}"
required="{textField?.notRequired !== true}"
on:change="{removeInvalid}"
bind:this="{$formValues[
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"
></textarea>
</label>
{:else}
<label
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
]}"
>
<input
type="text"
placeholder="{textField?.textPlaceholder}"
on:change="{removeInvalid}"
required="{textField?.notRequired !== true}"
bind:this="{$formValues[
`${
textField?.telValidation
? 'Telefon'
: textField?.emailValidation
? 'Email'
: 'input'
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
textField.textfieldOrder
}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"
/>
</label>
{/if}
</div>
{/each}
</div>
{/each}
</div>
</div>

View File

@@ -0,0 +1,469 @@
<script lang="ts">
import CheckboxGroup from "./CheckboxGroup.svelte"
import Datepicker from "./Datepicker.svelte"
import FormLabelNumberBlock from "./FormLabelNumberBlock.svelte"
import type { Writable } from "svelte/store"
import Select from "./Select.svelte"
export let column: FormColumn
export let index: number
export let columnIndex: number
export let formValues: Writable<FormValues>
function removeInvalid(e: Event) {
let element = e.currentTarget as HTMLElement
if (element) element.classList.remove("invalid")
}
// @ts-ignore
const widgets: Widget[] = column.inputWidgets.map((e) => {
if (e === "text") {
return {
text: column.textInput?.map((input) => {
return {
...input.standardInputProperties,
...input,
}
}),
}
}
if (e === "number") {
return {
number: {
...column.numberInput.standardInputProperties,
},
}
}
if (e === "defaultCalendar") {
return {
date: {
...column.dateInput.standardInputProperties,
},
}
}
if (e === "times") {
return {
times: {
...column.timesInput.standardInputProperties,
times: column.timesInput.times,
},
}
}
if (e === "checkboxGroup") {
return {
checkboxGroup: {
groupTitle: column.checkboxGroupInput.groupTitle,
checkboxes: column.checkboxGroupInput.checkboxes.map((checkbox) => {
return {
...checkbox.standardInputProperties,
}
}),
},
}
}
if (e === "customCalendar") {
return {
datepicker: {
...column.datePickerInput.standardInputProperties,
props: column.datePickerInput.props,
},
}
}
if (e === "multiSelect") {
return {
multiselect: {
...column.datePickerInput.standardInputProperties,
props: column.datePickerInput.props,
},
}
}
if (e == "labelNumber") {
return { labelNumber: column.labelNumberInput }
}
if (e == "timeSelect") {
return {
timeSelect: {
...column?.timeSelect?.standardInputProperties,
...column.timeSelect,
},
}
}
if (e == "standardSelect") {
return {
standardSelect: {
...column?.standardSelect?.standardInputProperties,
...column.standardSelect,
},
}
}
})
function getPosition(column: FormColumn, pos: number, i = 0) {
let position = 0
if (pos == 0) return
if (column.inputWidgets.includes("labelNumber")) position++
if (pos == 1) return position
if (column.inputWidgets.includes("times")) position++
if (pos == 2) return position
if (column.inputWidgets.includes("timeSelect")) position++
if (pos == 3) return position
if (column.inputWidgets.includes("standardSelect")) position++
if (pos == 4) return position
if (column.inputWidgets.includes("defaultCalendar")) position++
if (pos == 5) return position
if (column.inputWidgets.includes("number")) position++
if (pos == 6) return position
if (column.inputWidgets.includes("checkboxGroup")) position++
if (pos == 7) return position
if (column.inputWidgets.includes("customCalendar")) position++
if (pos == 8) return position
if (column.inputWidgets.includes("multiSelect")) position++
return position + i
}
</script>
{#each widgets as widget}
{#if column?.title?.trim()}
<h3>{column?.title ?? ""}</h3>{/if}
{#if column?.annotation}
<p>{column?.annotation}</p>
{/if}
{#if widget?.labelNumber}
<div class="{`column-${columnIndex} position-${getPosition(column, 0)}`}">
<FormLabelNumberBlock widget="{widget.labelNumber}" {formValues} rowIndex="{index}" />
</div>
{/if}
{#if widget?.times}
<div class="column-{columnIndex} position-{getPosition(column, 1)}">
<label bind:this="{$formValues[`times_${column.emailTitle ? column.emailTitle + '_' : ''}label`]}">
<select
id="time-select"
bind:this="{$formValues[
`times_${column.emailTitle ?? 'Zeiten'}_${widget.times.fieldOrder}_${index}_${
widget.times?.emailTitle || columnIndex + 'invalidtimes'
}`
]}"
required="{widget.times.notRequired !== true}"
on:change="{removeInvalid}"
>
<option value="" disabled selected>Bitte Uhrzeit wählen</option>
{#each widget?.times.times ?? [] as time}
<option value="{time?.from}-{time?.to}">
{time?.from} - {time?.to}
</option>
{/each}
</select>
</label>
</div>
{/if}
{#if widget?.timeSelect}
<div class="column-{columnIndex} position-{getPosition(column, 2)}">
<label bind:this="{$formValues[`select_${column?.emailTitle ? column.emailTitle + '_' : ''}label`]}">
<select
required="{widget.timeSelect.notRequired !== true}"
bind:this="{$formValues[
`select_${column?.emailTitle ?? 'Auswahl'}_${widget?.timeSelect?.fieldOrder}_${index}_${
widget.timeSelect.emailTitle || columnIndex + 'invalidtime'
}`
]}"
on:change="{removeInvalid}"
>
<option value="" disabled selected
>{widget.timeSelect.textTitle || widget.timeSelect.placeholder || ""}</option
>
{#each widget.timeSelect.selectEntries as entry}
<option value="{entry?.leftSide}-{entry?.rightSide}">
{entry?.leftSide}-{entry?.rightSide}
</option>
{/each}
</select>
</label>
</div>
{/if}
{#if widget?.standardSelect}
<div class="column-{columnIndex} position-{getPosition(column, 3)}">
<label
bind:this="{$formValues[`standardSelect_${column?.emailTitle ? column.emailTitle + '_' : ''}label`]}"
>
<select
required="{widget.standardSelect.notRequired !== true}"
bind:this="{$formValues[
`select_${column?.emailTitle ?? 'Auswahl'}_${widget?.standardSelect?.fieldOrder}_${index}_${
widget.standardSelect.emailTitle || columnIndex + 'invalidtime'
}`
]}"
on:change="{removeInvalid}"
>
{#each widget.standardSelect.selectEntries as entry}
<option value="{entry.value}" selected="{entry.defaultValue}">
{entry.shownValue}
</option>
{/each}
</select>
</label>
</div>
{/if}
{#if widget?.date}
<div class="column-{columnIndex} position-{getPosition(column, 4)}">
<label bind:this="{$formValues[`date_${column.emailTitle ? column.emailTitle + '_' : ''}label`]}">
<input
class="date"
type="date"
required="{widget.date.notRequired !== true}"
on:change="{removeInvalid}"
bind:this="{$formValues[
`date_${column?.emailTitle ?? 'Datum'}_${widget.date.fieldOrder}_${index}_${
widget.date.emailTitle || columnIndex + 'invaliddate'
}`
]}"
/>
</label>
</div>
{/if}
{#if widget?.number}
<div class="column-{columnIndex} position-{getPosition(column, 5)}">
<label
bind:this="{$formValues[
`input_${column.emailTitle ? column.emailTitle + '_' : ''}${widget.number.placeholder}_label`
]}"
>
<input
type="number"
step="any"
required="{widget.number.notRequired !== true}"
on:change="{removeInvalid}"
placeholder="{widget.number.placeholder}"
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${widget.number.placeholder}_${
widget.number.fieldOrder
}_${index}_${widget.number.emailTitle || columnIndex + 'invalidnumber'}`
]}"
/>
</label>
</div>
{/if}
{#if widget?.checkboxGroup}
<div class="column-{columnIndex} position-{getPosition(column, 6)}">
<CheckboxGroup
checkboxes="{widget.checkboxGroup.checkboxes}"
groupTitle="{widget.checkboxGroup.groupTitle}"
{formValues}
rowNr="{index}"
/>
</div>
{/if}
{#if widget?.datepicker}
<div class="column-{columnIndex} position-{getPosition(column, 7)}">
<Datepicker
datePickerProps="{widget.datepicker.props}"
groupTitle="{widget.datepicker.groupTitle}"
{formValues}
rowNr="{index}"
formCol="{column}"
/>
</div>
{/if}
{#if widget?.multiselect}
<div class="column-{columnIndex} position-{getPosition(column, 8)}">
<Select
groupTitle="{widget.multiselect.groupTitle}"
{formValues}
rowNr="{index}"
formCol="{column}"
options="{widget.multiselect.options}"
/>
</div>
{/if}
{#if widget?.text}
{#each widget.text ?? [] as textField, textFieldIndex}
<div class="column-{columnIndex} position-{getPosition(column, 8 + textFieldIndex, textFieldIndex)}">
<h3 class="textTitle">{textField.textTitle || ""}</h3>
<div class="column-{columnIndex} position-{getPosition(column, 8 + textFieldIndex, textFieldIndex)}">
{#if textField?.textArea}
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
<textarea
placeholder="{textField?.placeholder}"
required="{textField?.notRequired !== true}"
on:change="{removeInvalid}"
bind:this="{$formValues[
`textarea_Nachricht_${textField.fieldOrder}_${index}_${
textField.emailTitle || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"
></textarea>
</label>
{:else}
<label
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${textField?.placeholder}_label`
]}"
>
<input
type="text"
placeholder="{textField?.placeholder}"
on:change="{removeInvalid}"
required="{textField?.notRequired !== true}"
bind:this="{$formValues[
`${
textField?.telValidation
? 'Telefon'
: textField?.emailValidation
? 'Email'
: 'input'
}_${column.title ? column.title + '_' : ''}${textField?.placeholder}_${
textField.fieldOrder
}_${index}_${textField.emailTitle || columnIndex + 'invalidtext' + textFieldIndex}`
]}"
/>
</label>
{/if}
</div>
</div>
{/each}
{/if}
{/each}
<style lang="less">
@media (max-width: 768px) {
.mobile-fields {
display: flex;
flex-direction: column;
}
.form-field {
order: 1;
}
/* Order for first column */
.column-0.position-0 {
order: 1;
}
.column-0.position-1 {
order: 5;
}
.column-0.position-2 {
order: 9;
}
.column-0.position-3 {
order: 13;
}
.column-0.position-4 {
order: 17;
}
.column-0.position-5 {
order: 21;
}
.column-0.position-6 {
order: 25;
}
.column-0.position-7 {
order: 29;
}
.column-0.position-8 {
order: 33;
}
.column-0.position-9 {
order: 37;
}
/* Order for second column */
.column-1.position-0 {
order: 2;
}
.column-1.position-1 {
order: 6;
}
.column-1.position-2 {
order: 10;
}
.column-1.position-3 {
order: 14;
}
.column-1.position-4 {
order: 18;
}
.column-1.position-5 {
order: 22;
}
.column-1.position-6 {
order: 26;
}
.column-1.position-7 {
order: 30;
}
.column-1.position-8 {
order: 34;
}
.column-1.position-9 {
order: 38;
}
/* Order for third column */
.column-2.position-0 {
order: 3;
}
.column-2.position-1 {
order: 7;
}
.column-2.position-2 {
order: 11;
}
.column-2.position-3 {
order: 15;
}
.column-2.position-4 {
order: 19;
}
.column-2.position-5 {
order: 23;
}
.column-2.position-6 {
order: 27;
}
.column-2.position-7 {
order: 31;
}
.column-2.position-8 {
order: 35;
}
.column-2.position-9 {
order: 39;
}
/* Order for fourth column */
.column-3.position-0 {
order: 4;
}
.column-3.position-1 {
order: 8;
}
.column-3.position-2 {
order: 12;
}
.column-3.position-3 {
order: 16;
}
.column-3.position-4 {
order: 20;
}
.column-3.position-5 {
order: 24;
}
.column-3.position-6 {
order: 28;
}
.column-3.position-7 {
order: 32;
}
.column-3.position-8 {
order: 36;
}
.column-3.position-9 {
order: 40;
}
}
</style>

View File

@@ -0,0 +1,56 @@
<script lang="ts">
import type { Writable } from "svelte/store"
import FormColumn from "./FormColumn.svelte"
import { onMount } from "svelte"
export let formRow: DBFormRow
export let index: number
export let formValues: Writable<FormValues>
let innerWidth = typeof window !== "undefined" ? window?.innerWidth || 0 : 0
if (typeof window !== "undefined") {
onMount(() => {
const handleResize = () => {
innerWidth = window.innerWidth
}
window.addEventListener("resize", handleResize)
// Cleanup function
return () => {
window.removeEventListener("resize", handleResize)
}
})
}
</script>
<div class="form-row">
<h3 style="margin-bottom: -0.5rem;">
{#if formRow.title}{formRow.title || ""}{/if}
</h3>
{#if innerWidth < 768}
<div class="form-cols mobile-fields">
{#each formRow.columns as column, columnIndex}
<FormColumn {column} {index} {columnIndex} {formValues} />
{/each}
</div>
{/if}
{#if innerWidth >= 768}
<div class="form-cols desktop-fields">
{#each formRow.columns as column, columnIndex}
<div class="form-column">
<FormColumn {column} {index} {columnIndex} {formValues} />
</div>
{/each}
</div>{/if}
</div>
<style lang="less">
.mobile-fields {
display: flex !important;
flex-direction: column;
}
.desktop-fields {
display: flex !important;
}
</style>

View File

@@ -1,15 +1,15 @@
<script lang="ts">
import type { Writable } from "svelte/store"
export let column: FormColumn
export let widget: LabelNumberInput[]
export let formValues: Writable<FormValues>
export let rowIndex: number
let blockContainer: HTMLDivElement
$formValues["blockGroups"] = new Set(column.labelNumber.map((e) => e.group))
$formValues["blockGroups"] = new Set(widget.map((e) => e.group))
</script>
<div class="blockContainer" bind:this="{blockContainer}">
{#each column.labelNumber as outerblock, i}
{#each widget as outerblock, i}
<div class="{`block`}" bind:this="{$formValues['blockGroups'][i]}">
<h3>{outerblock.title}</h3>
<div class="innterBlockContainer">

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import Form from "./Form.svelte"
import MobileForm from "./MobileForm.svelte"
import { writable } from "svelte/store"
import { validateFields } from "../../../functions/validateFields"
import { sendForm } from "../../../functions/sendForm"
import { navigate } from "svelte-routing"
import { onMount } from "svelte"
import { validateFields } from "../../../functions/pagebuilder/components/form/validateFields"
import { sendForm } from "../../../functions/pagebuilder/components/form/sendForm"
export let col: Column
export let siteId: string
export let rowNr: number
export let row: Row
export let rows: Row[]
import { modules } from "../../../store"
import FormColumnWrapper from "./FormColumnWrapper.svelte"
import { navigateWrapper } from "../../../functions/utils"
export let col: { contentType: "form"; moduleImport: string }
export let pageId: string
// @ts-ignore
let form: DBFormObj = $modules[col?.moduleImport]?.form
$: form = $modules[col?.moduleImport]?.form
let formSend = false
let formValues = writable<FormValues>({})
@@ -20,8 +20,9 @@
const values: Array<ValueEntry> = Object.entries($formValues).map((entry: ObjectEntry) => {
const key: string = entry[0]
const value: CustomHTMLElement = entry[1]
if (!key.includes("numberLabel")) {
return [key, [value.checked || value.value, value.required]] as ValueEntry
return [key, [value.type == "checkbox" ? value.checked : value.value, value.required]] as ValueEntry
} else {
return [key, [value.value, value, value.getAttribute("name"), value.required]] as ValueEntry
}
@@ -30,7 +31,7 @@
const fields: Array<ValueEntry> = values.filter((entry: ValueEntry) => !entry[0].includes("label"))
const validation = validateFields(fields)
if (validation.length) {
validation.forEach((error) => {
validation.forEach((error: string[]) => {
// @ts-ignore
if (error[0].includes("block")) {
// @ts-ignore
@@ -58,344 +59,75 @@
formObj[entry[0]] = entry[1][0]
}
})
let form: any
row.column.forEach((col) => {
if (col.contentType == "form") form = col
})
if (!form) return
formObj["formRows"] = form.formRows.map((r: FormRow) => r.rowName)
formObj["formTitle"] = form.formEmailTitle
if (!form) {
return
}
formObj["formRows"] = form.rows.map((r) => r.emailTitle)
formObj["formTitle"] = form.emailSubject
formSend = true
const hny = document.getElementById("hny") as HTMLInputElement
if (hny) formObj["honey"] = hny.checked
sendForm(formObj)
}
}
let innerWidth = typeof window !== "undefined" ? window?.innerWidth || 0 : 0
if (typeof window !== "undefined") {
onMount(() => {
const handleResize = () => {
innerWidth = window.innerWidth
}
window.addEventListener("resize", handleResize)
// Cleanup function
return () => {
window.removeEventListener("resize", handleResize)
}
})
}
</script>
{#if formSend}
<div class="success-message">
<h1>Formular erfolgreich gesendet!</h1>
<p>Vielen Dank für Ihre Anfrage. Wir werden uns in Kürze bei Ihnen melden.</p>
</div>
{:else}
<form
class="form-rows"
on:submit="{(e) => {
e.preventDefault()
submitForm()
}}"
>
{#each col.formRows ?? [] as formRow, i}
{#if innerWidth < 768}
<MobileForm {formRow} {formValues} index="{i}" />
{:else}
<Form {formRow} {formValues} index="{i}" />
{/if}
{/each}
<div class="row additional" style="padding: 0px;">
<div class="data-protection">
<label bind:this="{$formValues[`agreement_label`]}">
<input
required="{true}"
class="checkit"
type="checkbox"
on:change="{(e) => {
let element = e.currentTarget
element.classList.remove('invalid')
}}"
bind:this="{$formValues['agreement']}"
/>
<span class="checkit-span"></span>
</label>
<div class="datasec">
<button on:click|preventDefault="{() => navigate('/datenschutz')}" class="link">
Datenschutz
</button>
akzeptieren
</div>
</div>
<input
type="checkbox"
name="contact_me_by_fax_only"
id="hny"
value="1"
style="display:none !important"
tabindex="-1"
autocomplete="off"
/>
<button class="submit-request" type="submit">Anfrage absenden</button>
<div class="my-form">
{#if formSend}
<div class="success-message">
<h1>Formular erfolgreich gesendet!</h1>
<p>Vielen Dank für Ihre Anfrage. Wir werden uns in Kürze bei Ihnen melden.</p>
</div>
</form>
{/if}
{:else if form}
<form
class="form-rows"
novalidate
on:submit="{(e) => {
e.preventDefault()
submitForm()
}}"
>
{#each form.rows ?? [] as formRow, i}
<FormColumnWrapper {formRow} {formValues} index="{i}" />
{/each}
<div class="row additional" style="padding: 0px;">
<input
type="checkbox"
name="contact_me_by_fax_only"
id="hny"
value="1"
style="position: absolute; left: -9999px;"
autocomplete="off"
/>
<div class="data-protection">
<label bind:this="{$formValues[`agreement_label`]}">
<input
class="checkit"
required
type="checkbox"
on:change="{(e) => {
let element = e.currentTarget
element.classList.remove('invalid')
}}"
bind:this="{$formValues['agreement']}"
/>
<span class="checkit-span"></span>
</label>
<div class="datasec">
<button on:click|preventDefault="{() => navigateWrapper('/datenschutz')}" class="link">
Datenschutz
</button>
akzeptieren
</div>
</div>
<button class="submit-request" type="submit">{form.sendFormBtnText || "Formular absenden"}</button>
</div>
</form>
{/if}
</div>
<style lang="less" global>
@import "../../../assets/css/variables.less";
input,
select,
textarea,
.data-protection {
margin: 5px 0px;
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
}
.success-message {
h1 {
color: var(--heading-font-color);
font-size: 36px;
margin-top: 50px;
}
p {
font-size: 20px;
margin-top: 20px;
}
}
.invalidBlocks {
border: 2px solid rgb(255, 0, 0) !important;
position: relative;
&::after {
font-size: 0.9rem !important;
color: red !important;
position: absolute;
bottom: 2px;
content: "Bitte wähle entweder eine Kartenanzahl oder einen Wunschbetrag aus.";
}
}
.border-red {
border-color: red !important;
}
.no-margin {
margin-top: 15px !important;
}
.invalid {
border: 2px solid red !important;
}
.error-message {
font-size: 0.9rem !important;
color: red !important;
position: absolute;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
height: 100%;
}
input[type="number"] {
-moz-appearance: textfield;
appearance: textfield;
}
.checkit {
display: none;
}
.checkit-span {
height: 20px;
width: 20px;
border: 1px solid grey;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
[type="checkbox"]:checked + span:before {
content: "\2714";
position: absolute;
transform-origin: bottom;
}
.form-rows {
display: flex;
flex-direction: column;
gap: 1.5rem;
width: 100%;
h3 {
font-weight: bold !important;
margin-bottom: 0px !important;
}
.form-row {
display: flex;
flex-direction: column;
width: 100%;
gap: 1.5rem;
.date {
max-width: 100%;
box-sizing: border-box;
}
label {
width: 100% !important;
font-size: inherit;
input,
select,
textarea {
width: 100%;
font-size: inherit;
}
}
.form-cols {
display: flex;
gap: 1.5rem;
width: 100%;
}
}
.form-column {
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-start;
width: 100%;
gap: 1rem;
border-radius: 4px;
}
h3 {
font-size: 1.2rem;
font-weight: bold;
}
p {
margin-bottom: 0.5rem;
color: var(--hover-color);
}
input,
select,
textarea,
.data-protection {
padding: 10px 20px;
border: 0px solid var(--opposite-bg-color);
border-bottom: 3px solid var(--heading-font-color);
outline: 0px solid var(--opposite-bg-color);
color: var(--opposite-bg-color);
background-color: var(--background-color);
resize: none;
}
.data-protection {
display: flex;
flex-wrap: nowrap;
gap: 5px !important;
justify-content: start;
flex-direction: row !important;
}
input[type="date"] {
font-family: inherit;
width: 100% !important;
position: relative;
}
select {
padding: 10px 20px;
border: 0;
border-bottom: 3px solid var(--heading-font-color);
outline: none;
}
select:focus {
border-bottom-color: var(--heading-font-color);
}
#time-select {
appearance: none;
background-image: url("../../../../../media/clock.svg");
background-repeat: no-repeat;
background-position: right 20px center;
background-size: 18px;
option {
padding: 10px 20px;
background-color: var(--background-color);
}
}
@media @mobile {
.date {
width: 100vw !important;
}
}
@media @tablet {
.date {
width: 100% !important;
}
.form-cols {
flex-direction: row;
}
}
}
i .max-width {
max-width: @body-maxwidth !important;
}
.datasec {
display: flex;
align-items: center !important;
.link {
height: 100%;
display: flex;
align-items: flex-end !important;
text-decoration: underline;
margin-right: 3px;
color: rgb(14, 91, 146);
}
}
.additional {
display: flex;
@media @mobile {
flex-direction: column !important;
}
@media @tablet {
flex-direction: row !important;
}
gap: 2.5rem;
div {
flex: 1;
display: flex;
align-items: center;
justify-content: start;
}
.submit-request {
flex: 1;
box-shadow: 0 0 25px 10px var(--opposite-bg-color-5);
width: 100%;
display: flex;
align-items: center;
justify-content: start;
background-color: var(--heading-font-color);
color: var(--background-color);
padding: 10px 20px;
}
}
@import "../../../assets/css/components/pagebuilder/form/form.less";
</style>

View File

@@ -1,351 +0,0 @@
<script lang="ts">
import FormLabelNumberBlock from "./FormLabelNumberBlock.svelte"
import type { Writable } from "svelte/store"
import CheckboxGroup from "./CheckboxGroup.svelte"
import Datepicker from "./Datepicker.svelte"
import Select from "./Select.svelte"
export let formRow: FormRow
export let formValues: Writable<FormValues>
export let index: number
function removeInvalid(e: Event) {
let element = e.currentTarget as HTMLElement
if (element) element.classList.remove("invalid")
}
function getPosition(column: FormColumn, pos: number, i = 0) {
let position = 0
if (pos == 0) return
if (column.showLabelNumber) position++
if (pos == 1) return position
if (column.showTimes) position++
if (pos == 2) return position
if (column.showSelect) position++
if (pos == 3) return position
if (column.showDate) position++
if (pos == 4) return position
if (column.showNumber) position++
return position + i
}
</script>
<div class="form-row">
<h3 style="margin-bottom: -0.5rem;">
{#if formRow.showRowName}{formRow.rowName || ""}{/if}
</h3>
<div class="form-cols mobile-fields">
{#each formRow.columns as column, columnIndex}
<h3>{column?.title ?? ""}</h3>
{#if column?.annotation}
<p>{column?.annotation}</p>
{/if}
{#if column.showLabelNumber}
<div class="{`column-${columnIndex} position-${getPosition(column, 0)}`}">
<FormLabelNumberBlock {column} {formValues} rowIndex="{index}" />
</div>
{/if}
{#if column?.showTimes}
<div class="column-{columnIndex} position-{getPosition(column, 1)}">
<label bind:this="{$formValues[`times_${column?.title ? column.title + '_' : ''}label`]}">
<select
id="time-select"
bind:this="{$formValues[
`times_${column?.title ?? 'Zeiten'}_${column.timesfieldOrder}_${index}_${
column.emailNameTimes || columnIndex + 'invalidtimes'
}`
]}"
required="{column?.timeNotRequired !== true}"
on:change="{removeInvalid}"
>
<option value="" disabled selected>Bitte Uhrzeit wählen</option>
{#each column?.times ?? [] as time}
<option value="{time?.timeFrom}-{time?.timeTo}">
{time?.timeFrom} - {time?.timeTo}
</option>
{/each}
</select>
</label>
</div>
{/if}
{#if column?.showSelect}
<div class="column-{columnIndex} position-{getPosition(column, 2)}">
<label bind:this="{$formValues[`select_${column?.title ? column.title + '_' : ''}label`]}">
<select
required="{column?.dateNotRequired !== true}"
bind:this="{$formValues[
`select_${column?.title ?? 'Auswahl'}_${column.datefieldOrder}_${index}_${
column?.emailNameTime || columnIndex + 'invalidtime'
}`
]}"
on:change="{removeInvalid}"
>
<option value="" disabled selected>{column.selectTitle}</option>
{#each column?.selectEntries as entry}
<option value="{entry?.leftSide}-{entry?.rightSide}">
{entry?.leftSide}-{entry?.rightSide}
</option>
{/each}
</select>
</label>
</div>
{/if}
{#if column.showDate}
<div class="column-{columnIndex} position-{getPosition(column, 3)}">
<label bind:this="{$formValues[`date_${column?.title ? column.title + '_' : ''}label`]}">
<input
class="date"
type="date"
required="{column?.dateNotRequired !== true}"
on:change="{removeInvalid}"
bind:this="{$formValues[
`date_${column?.title ?? 'Datum'}_${column.datefieldOrder}_${index}_${
column?.emailNameDate || columnIndex + 'invaliddate'
}`
]}"
/>
</label>
</div>
{/if}
{#if column.showNumber}
<div class=" column-{columnIndex} position-{getPosition(column, 4)}">
<label
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_label`
]}"
>
<input
type="number"
step="any"
required="{column?.numberNotRequired !== true}"
on:change="{removeInvalid}"
placeholder="{column?.numberPlaceholder}"
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${column?.numberPlaceholder}_${
column.numberfieldOrder
}_${index}_${column?.emailNameNumber || columnIndex + 'invalidnumber'}`
]}"
/>
</label>
</div>
{/if}
{#if column.showCheckboxGroup}
<CheckboxGroup
checkboxes="{column.checkboxes}"
groupTitle="{column.groupTitle}"
{formValues}
rowNr="{index}"
/>
{/if}
{#if column.showDatePicker}
<Datepicker
datePickerProps="{column.datePickerProps}"
groupTitle="{column.groupTitle}"
{formValues}
rowNr="{index}"
formCol="{column}"
/>
{/if}
{#if column.showMultiSelect}
<Select
groupTitle="{column.groupTitle}"
{formValues}
rowNr="{index}"
formCol="{column}"
options="{column.multiSelectOptions}"
/>
{/if}
{#each column.text ?? [] as textField, textFieldIndex}
<div class="column-{columnIndex} position-{getPosition(column, 5 + textFieldIndex, textFieldIndex)}">
<h3 class="textTitle">{textField.textTitle || ""}</h3>
<div
class="column-{columnIndex} position-{getPosition(column, 5 + textFieldIndex, textFieldIndex)}"
>
{#if textField?.textArea}
<label bind:this="{$formValues[`textarea_Nachricht_label`]}">
<textarea
placeholder="{textField?.textPlaceholder}"
required="{textField?.notRequired !== true}"
on:change="{removeInvalid}"
bind:this="{$formValues[
`textarea_Nachricht_${textField.textfieldOrder}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"
></textarea>
</label>
{:else}
<label
bind:this="{$formValues[
`input_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_label`
]}"
>
<input
type="text"
placeholder="{textField?.textPlaceholder}"
on:change="{removeInvalid}"
required="{textField?.notRequired !== true}"
bind:this="{$formValues[
`${
textField?.telValidation
? 'Telefon'
: textField?.emailValidation
? 'Email'
: 'input'
}_${column.title ? column.title + '_' : ''}${textField?.textPlaceholder}_${
textField.textfieldOrder
}_${index}_${
textField.emailName || columnIndex + 'invalidtext' + textFieldIndex
}`
]}"
/>
</label>
{/if}
</div>
</div>
{/each}
{/each}
</div>
</div>
<style lang="less">
.mobile-fields {
display: flex;
flex-direction: column;
}
.form-field {
order: 1;
}
/* Order for first column */
.column-0.position-0 {
order: 1;
}
.column-0.position-1 {
order: 5;
}
.column-0.position-2 {
order: 9;
}
.column-0.position-3 {
order: 13;
}
.column-0.position-4 {
order: 17;
}
.column-0.position-5 {
order: 21;
}
.column-0.position-6 {
order: 25;
}
.column-0.position-7 {
order: 29;
}
.column-0.position-8 {
order: 33;
}
.column-0.position-9 {
order: 37;
}
/* Order for second column */
.column-1.position-0 {
order: 2;
}
.column-1.position-1 {
order: 6;
}
.column-1.position-2 {
order: 10;
}
.column-1.position-3 {
order: 14;
}
.column-1.position-4 {
order: 18;
}
.column-1.position-5 {
order: 22;
}
.column-1.position-6 {
order: 26;
}
.column-1.position-7 {
order: 30;
}
.column-1.position-8 {
order: 34;
}
.column-1.position-9 {
order: 38;
}
/* Order for third column */
.column-2.position-0 {
order: 3;
}
.column-2.position-1 {
order: 7;
}
.column-2.position-2 {
order: 11;
}
.column-2.position-3 {
order: 15;
}
.column-2.position-4 {
order: 19;
}
.column-2.position-5 {
order: 23;
}
.column-2.position-6 {
order: 27;
}
.column-2.position-7 {
order: 31;
}
.column-2.position-8 {
order: 35;
}
.column-2.position-9 {
order: 39;
}
/* Order for fourth column */
.column-3.position-0 {
order: 4;
}
.column-3.position-1 {
order: 8;
}
.column-3.position-2 {
order: 12;
}
.column-3.position-3 {
order: 16;
}
.column-3.position-4 {
order: 20;
}
.column-3.position-5 {
order: 24;
}
.column-3.position-6 {
order: 28;
}
.column-3.position-7 {
order: 32;
}
.column-3.position-8 {
order: 36;
}
.column-3.position-9 {
order: 40;
}
</style>

View File

@@ -3,7 +3,7 @@
import { onMount } from "svelte"
import type { Writable } from "svelte/store"
export let options: MultiSelectOptions[] = []
export let options: { name: string }[] = []
export let groupTitle: string
export let formValues: Writable<FormValues>
@@ -19,7 +19,7 @@
// @ts-ignore
$formValues[`selectMultiple_${groupTitle}_${rowNr}_${formCol.multiSelectEmailTitle}`] = {
value: value.toString(),
required: !formCol.multiSelectNotRequired,
required: !formCol?.multiSelectInput?.standardInputProperties?.notRequired,
}
}
@@ -80,5 +80,69 @@
<style lang="less" global>
@import "../../../assets/css/variables.less";
@import "../../../assets/css/svelte-select.less";
.svelte-select {
height: 55px !important;
margin-bottom: 20px;
margin: 5px 0px !important;
box-shadow: 0 0 25px 10px rgba(0, 0, 0, 0.05) !important;
padding: 10px 20px !important;
border: 0px solid black !important;
border-bottom: 3px solid @main-color !important;
outline: 0px solid black !important;
color: black !important;
background-color: @background-color !important;
resize: none !important;
.value-container {
input {
padding-top: 20px;
}
.selected-item {
padding-top: 19px;
}
}
.multi-item {
background-color: rgba(0, 0, 0, 0.1) !important;
border-radius: 3px !important;
}
.selected-item {
margin-left: 3px;
height: auto;
line-height: initial;
}
&.focused {
border-color: black !important;
}
input {
height: 100%;
font-size: 1rem !important;
}
.list-item {
.item {
&.active {
background-color: transparent !important;
color: black !important;
}
}
}
.indicators {
color: #ccc;
& > * {
border-left: 1px solid #ccc !important;
height: 40px !important;
cursor: pointer !important;
}
.clear-select {
color: @hover-color !important;
font-weight: 700 !important;
}
}
}
</style>

View File

@@ -0,0 +1,13 @@
const eventCallbacks: { [key: string]: any } = {}
export const registerEventCallback = (event: string | number, id: string | number, callback: any) => {
if (!eventCallbacks[event]) eventCallbacks[event] = {}
eventCallbacks[event][id] = callback
}
export const unregisterEventCallback = (event: string | number, id: string | number) => {
delete eventCallbacks[event][id]
}
export const getEventCallbacks = (event: string) => {
return eventCallbacks[event]
}

View File

@@ -0,0 +1,16 @@
import { api } from "../../../api"
import { pages, content, rerender } from "../../store"
export async function loadContentAndSetStores(): Promise<null> {
const contentRes = await api<Content[]>("content", {})
const contentStore: { [id: string]: Content } = {}
const pagesStore: { [path: string]: Content } = {}
contentRes.data.forEach((e: Content) => {
contentStore[e.id] = e
if (e.type === "page") {
pagesStore[e.path] = e
}
})
content.set(contentStore)
pages.set(pagesStore)
return null
}

View File

@@ -0,0 +1,12 @@
import { api } from "../../../api"
import { mediaLibrary } from "../../store"
export async function loadLibraryAndSetStore(): Promise<null> {
const libraryRes = await api<MediaLibrary[]>("medialib", {})
const libStore: { [id: string]: MediaLibrary } = {}
libraryRes.data.forEach((el: MediaLibrary) => {
libStore[el.id] = el
})
mediaLibrary.set(libStore)
return null
}

View File

@@ -0,0 +1,11 @@
import { api } from "../../../api"
import { modules } from "../../store"
export async function loadModulesAndSetStore(): Promise<{ [id: string]: Module }> {
const module = await api<Module[]>("module", {})
const moduleStore: { [id: string]: Module } = {}
module.data.forEach((e: Module) => {
moduleStore[e.id] = e
})
modules.set(moduleStore)
return moduleStore
}

View File

@@ -0,0 +1,15 @@
import { api } from "../../../api"
import { navigation, serviceNavigation } from "../../store"
export async function loadNavigationAndSetStores(): Promise<null> {
const navigations = await api<Navigation[]>("navigation", {})
let navigationStore: NavElement[] = []
let serviceNavigationStore: NavElement[] = []
navigations.data.forEach((nav: Navigation) => {
if (nav.tree == 0) navigationStore = nav.elements
else if (nav.tree == 1) serviceNavigationStore = nav.elements
})
navigation.set(navigationStore)
serviceNavigation.set(serviceNavigationStore)
return null
}

View File

@@ -20,7 +20,10 @@ export function validateFields(fieldsArray: ValueEntry[]): (string | (() => void
}
fieldsArray.forEach(([field, value]) => {
if (field === "blockGroups" || (typeof field === "string" && field.includes("numberLabel"))) {
// number label benötigt gesonderte Validierung
if (!field.includes("numberLabel")) return
// @ts-ignore
let [elementValue, element, group, boolean] = value

View File

@@ -0,0 +1,14 @@
import { getEventCallbacks } from "./eventBus"
import { navigate } from "../../../../vendor/svelte-routing"
export function navigateWrapper(path: string, props: any = null) {
if (getEventCallbacks("navigate")) {
let callbacks = Object.values(getEventCallbacks("navigate"))
for (let i = 0; i < callbacks?.length; i++) {
// do not navigate if false is returned
if ((callbacks[i] as Function)(path, props) === false) return
}
}
if (props) navigate(path, props)
else navigate(path)
}

View File

@@ -8,4 +8,11 @@ const initLoc = {
pop: false,
categoryPath: "",
}
export const location = writable(initLoc)
export const location = writable(initLoc)
export let content = writable<{ [id: string]: Content }>({})
export let pages = writable<{ [path: string]: Content }>({})
export let modules = writable<{ [id: string]: Module }>({})
export let mediaLibrary = writable<{ [id: string]: MediaLibrary }>({})
export let navigation = writable<NavElement[]>([])
export let serviceNavigation = writable<NavElement[]>([])
export let rerender = writable(false)

View File

@@ -0,0 +1,56 @@
<script>
import { navigateWrapper } from "../lib/functions/utils"
</script>
<div class="not-found">
<div class="content">
<h1>404</h1>
<h2>Seite nicht gefunden</h2>
<p>
Die gesuchte Seite wurde möglicherweise entfernt, ihr Name wurde geändert oder sie ist vorübergehend nicht
verfügbar.
</p>
<button on:click="{() => navigateWrapper('/')}" class="back-home">Zurück zur Startseite</button>
</div>
</div>
<style lang="less">
@import "../lib/assets/css/variables.less";
.not-found {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
.content {
text-align: center;
padding: 2rem;
background-color: rgba(255, 255, 255, 0.9);
h1 {
font-size: 6rem;
color: @main-color;
margin-bottom: 2rem;
}
h2 {
font-size: 2rem;
margin-bottom: 1rem;
}
p {
margin-bottom: 2rem;
}
.back-home {
text-decoration: none;
border: 1px solid black;
padding: 0.5rem 1rem;
transition:
background-color 0.2s,
color 0.2s;
}
}
}
</style>

View File

@@ -0,0 +1,119 @@
<script lang="ts">
import { mediaLibrary, pages } from "../lib/store"
import Pagebuilder from "../lib/components/pagebuilder/Pagebuilder.svelte"
import { apiBaseURL, baseURL } from "../config"
import { onMount } from "svelte"
import NotFound from "./NotFound.svelte"
export let path: string = "/"
export let isHomepage = true
let page: Content = $pages[path]
$: page = $pages[path]
let personPage = false
</script>
<svelte:head>
{#key page}
<!-- Title -->
{#if page?.pageTitle}
<title>{page.pageTitle}</title>
{:else if page?.meta?.title}
<title>{page.meta.title}</title>
{/if}
<!-- Description -->
{#if page?.meta?.description}
<meta name="description" content="{page.meta.description}" />
{/if}
<!-- Keywords -->
{#if page?.meta?.keywords}
<meta name="keywords" content="{page.meta.keywords}" />
{/if}
{#if page?.active === false}
<meta name="robots" content="noindex" />
{/if}
<link rel="canonical" href="{baseURL + page?.path}" />
{/key}
</svelte:head>
<div class="rows" class:HP="{path == '/'}">
{#if page}
{#each page.rows as row, i}
<div class="row" id="row-{i}">
{#if row.backgroundImage && $mediaLibrary[row.backgroundImage]}
<div class="background-image">
<img
src="{`${apiBaseURL}medialib/${row?.backgroundImage}/${
$mediaLibrary?.[row?.backgroundImage]?.file?.src
}`}"
alt="img"
/>
</div>
{/if}
<div class="content" class:bright="{row.backgroundImage}">
<Pagebuilder
isHP="{path == '/'}"
{i}
{row}
{page}
pageId="{page.id}"
bright="{!!row.backgroundImage}"
/>
</div>
</div>
{/each}
{:else}
<NotFound />
{/if}
</div>
<style lang="less">
@import "../lib/assets/css/variables.less";
.rows {
display: flex;
flex-direction: column;
justify-content: flex-start;
width: 100%;
position: relative;
max-width: 100vw;
overflow: hidden;
gap: 10px;
& > .row {
margin: 15px 0px;
padding: 15px 10px;
@media @tablet {
margin: 40px 0px;
padding: 30px 10px;
}
width: 100%;
position: relative;
display: flex;
justify-content: center;
.background-image {
position: absolute;
width: 100%;
z-index: 1;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
img {
object-fit: cover;
width: 100%;
height: 100%;
}
}
& > .content {
width: 100%;
max-width: 1400px;
margin: 0px 2.5vw;
position: relative;
z-index: 2;
}
}
}
</style>