mirror of
https://github.com/pretix/pretix.git
synced 2026-11-09 01:14:25 +00:00
cleanup
This commit is contained in:
1 parent
ff10d9471a
commit
c35e73d821
19 files changed
+559
-783
No files matched your search
@@ -3,56 +3,62 @@ import { computed, inject, ref, watchEffect } from "vue";
|
||||
import StyleSettings from "./style-settings.vue";
|
||||
import Select from "./input/select.vue";
|
||||
import Input from "./input/input.vue";
|
||||
import PassPreview from "./preview/pass-preview.vue";
|
||||
import { StoreKey } from "../walletStore";
|
||||
import Google from "./platform-preview/google.vue";
|
||||
import GoogleEventDefault from "./platform-preview/google-event-default.vue";
|
||||
import GoogleEventCustom from "./platform-preview/google-event-custom.vue";
|
||||
import Apple from "./platform-preview/apple.vue";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
function openForm(url: string, data: Record<string, string|object>) {
|
||||
let form = document.createElement("form");
|
||||
form.target = "_blank";
|
||||
form.method = "POST";
|
||||
form.action = url;
|
||||
form.style.display = "none";
|
||||
function openForm(url: string, data: Record<string, string | object>) {
|
||||
let form = document.createElement("form");
|
||||
form.target = "_blank";
|
||||
form.method = "POST";
|
||||
form.action = url;
|
||||
form.style.display = "none";
|
||||
|
||||
for (var key in data) {
|
||||
var input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = key;
|
||||
let value = data[key];
|
||||
if (value instanceof Object) {
|
||||
value = JSON.stringify(data[key]);
|
||||
}
|
||||
input.value = value
|
||||
form.appendChild(input);
|
||||
}
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
document.body.removeChild(form);
|
||||
for (var key in data) {
|
||||
var input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = key;
|
||||
let value = data[key];
|
||||
if (value instanceof Object) {
|
||||
value = JSON.stringify(data[key]);
|
||||
}
|
||||
input.value = value;
|
||||
form.appendChild(input);
|
||||
}
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
document.body.removeChild(form);
|
||||
}
|
||||
|
||||
async function openPreview(e: Event) {
|
||||
e.preventDefault();
|
||||
openForm("../../preview/", {
|
||||
csrfmiddlewaretoken: store.csrfToken,
|
||||
id: store.layoutId,
|
||||
...(await store.serializeCurrentPlatformLayout())
|
||||
});
|
||||
e.preventDefault();
|
||||
openForm("../../preview/", {
|
||||
csrfmiddlewaretoken: store.csrfToken,
|
||||
id: store.layoutId,
|
||||
...(await store.serializeCurrentPlatformLayout()),
|
||||
});
|
||||
}
|
||||
|
||||
const platformChoices = computed(() => {
|
||||
return [
|
||||
[null, "Do not generate pass"],
|
||||
...Object.values(store.platform.styles).map((x) => [x.identifier, x.name]),
|
||||
];
|
||||
return [
|
||||
[null, "Do not generate pass"],
|
||||
...Object.values(store.platform.styles).map((x) => [x.identifier, x.name]),
|
||||
];
|
||||
});
|
||||
|
||||
const previewRenderers = {
|
||||
google_eventticket_default: GoogleEventDefault,
|
||||
google_eventticket_custom: GoogleEventCustom,
|
||||
apple_eventticket: Apple,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
// TODO: add :key for all `v-for`s
|
||||
// TODO: i18n textfields
|
||||
// TODO: proper spinner
|
||||
|
||||
template(v-if="!store.loaded") {{ gettext("Loading...") }}
|
||||
@@ -69,20 +75,14 @@ const platformChoices = computed(() => {
|
||||
Select.form-group(label="Style" :modelValue="store.style?.identifier || null" @update:modelValue="store.setStyle" :choices="platformChoices")
|
||||
button(type="button" @click="store.setStyle(store.style.identifier)") Reset style
|
||||
StyleSettings(v-if="!!store.style")
|
||||
.col-md-6.col-lg-4
|
||||
.col-md-6.col-lg-4(style="position: sticky; top: 10px;")
|
||||
.panel.panel-default
|
||||
.panel-heading Preview
|
||||
.panel-body
|
||||
div(v-if="!!store.style?.preview_layout")
|
||||
span.text-muted {{ gettext("The preview below is only a rough representation of what the pass might look like. Please check the generated pass.") }}
|
||||
//- div(style="display: grid; gap: 1em; grid-template-columns: repeat(auto-fit, minmax(auto, 360px));")
|
||||
//- PassPreview(v-for="layout in store.style.preview_layout" :layout="layout")
|
||||
Google(v-if="store.activePlatform == 'google'" :data="store.style?.preview_layout")
|
||||
div(v-if="!!store.style?.preview_renderer && store.style?.preview_renderer in previewRenderers")
|
||||
p.text-muted {{ gettext("The preview below is only a rough representation of what the pass might look like. Please check the generated pass.") }}
|
||||
component(:is="previewRenderers[store.style?.preview_renderer]" :data="store.style?.preview_data")
|
||||
div(v-else) Preview not supported
|
||||
//- pre(v-if="store.currentPlatformLayout.style")
|
||||
//- code {{ store.currentPlatformStyles[store.currentPlatformLayout.style] }}
|
||||
//- pre
|
||||
//- code {{ store.walletLayout }}
|
||||
.form-group.submit-group
|
||||
button.btn.btn-lg.btn-default(type="button" @click="openPreview") Preview
|
||||
button.btn.btn-primary.btn-save(type="submit") Submit
|
||||
@@ -91,25 +91,94 @@ const platformChoices = computed(() => {
|
||||
|
||||
<style lang="css">
|
||||
.walletsettings-panel .panel-heading {
|
||||
.checkbox {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
input[type="checkbox"] {
|
||||
margin-top: 0;
|
||||
margin-right: 5px;
|
||||
margin-left: 0px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
label {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
.checkbox {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
input[type="checkbox"] {
|
||||
margin-top: 0;
|
||||
margin-right: 5px;
|
||||
margin-left: 0px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
label {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.walletsettings-panel .panel-body {
|
||||
> :is(h1, h2, h3, h4, h5, h6):first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
> :is(h1, h2, h3, h4, h5, h6):first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pass-container {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
|
||||
border: solid 1px #949494;
|
||||
border-radius: 1em;
|
||||
padding: 1em;
|
||||
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
background-image: var(--background-image);
|
||||
background-color: var(--background-color);
|
||||
color: var(--bg-color);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.blur-bg::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
backdrop-filter: blur(10px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.f-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: inherit;
|
||||
> div {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
.f-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: inherit;
|
||||
}
|
||||
|
||||
.pass-inner {
|
||||
position: relative;
|
||||
gap: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.qrcode-inner {
|
||||
font-weight: bold;
|
||||
background-color: lightgray;
|
||||
color: black;
|
||||
width: 50%;
|
||||
aspect-ratio: 1;
|
||||
align-self: center;
|
||||
|
||||
/* center content */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
</style>
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
const props = defineProps<{ data: any }>();
|
||||
|
||||
const theme = computed(() => {
|
||||
const theme = {
|
||||
bg_color: store.getSettingValue("bg_color"),
|
||||
fg_color: store.getSettingValue("fg_color"),
|
||||
label_color: store.getSettingValue("label_color"),
|
||||
};
|
||||
const bg_image = store.getSettingValue("background");
|
||||
if (bg_image?.url) {
|
||||
theme.bg_image = `url(${encodeURI(store.getSettingValue("background").url)})`;
|
||||
theme.bg_color = "#000";
|
||||
}
|
||||
return theme;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div(style="display: grid; gap: 1em; grid-template-columns: repeat(auto-fit, minmax(auto, 360px)); align-items: start;")
|
||||
.pass-container.blur-bg.apple-pass-front
|
||||
.pass-inner.f-column
|
||||
.f-row.head-row
|
||||
img.logo(:src="store.getSettingValue('logo')?.url")
|
||||
div.logo-text {{ store.renderedFieldGroups['logo_text']?.[0]?.content }}
|
||||
div.header(v-for="entry in store.renderedFieldGroups['header']")
|
||||
div.pass-front-label {{ entry.label }}
|
||||
div.pass-content {{ entry.content }}
|
||||
|
||||
.f-column
|
||||
div.primary(v-for="entry in store.renderedFieldGroups['primary']")
|
||||
div.pass-front-label {{ entry.label }}
|
||||
div.pass-content {{ entry.content }}
|
||||
.f-row
|
||||
div(v-for="entry in store.renderedFieldGroups['secondary']")
|
||||
div.pass-front-label {{ entry.label }}
|
||||
div.pass-content {{ entry.content }}
|
||||
.f-row
|
||||
div(v-for="entry in store.renderedFieldGroups['auxiliary']")
|
||||
div.pass-front-label {{ entry.label }}
|
||||
div.pass-content {{ entry.content }}
|
||||
|
||||
.qrcode-inner(v-for="qrcode in store.renderedFieldGroups['code']") {{ qrcode.label }}
|
||||
|
||||
.pass-container
|
||||
.pass-inner.f-column
|
||||
div(v-for="entry in store.renderedFieldGroups['back']")
|
||||
div.pass-back-label {{ entry.label }}
|
||||
div.pass-content {{ entry.content }}
|
||||
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.apple-pass-front {
|
||||
background-image: v-bind(theme.bg_image);
|
||||
background-color: v-bind(theme.bg_color);
|
||||
color: v-bind(theme.fg_color);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
||||
padding-top: 0.25em;
|
||||
}
|
||||
|
||||
.pass-content {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.head-row {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-height: 3lh;
|
||||
object-fit: contain;
|
||||
flex-grow: 0;
|
||||
}
|
||||
.logo-text {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
min-height: 2lh;
|
||||
flex: 1 1 3;
|
||||
align-content: center;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
.header {
|
||||
flex: 1 0 fit-content;
|
||||
text-align: end;
|
||||
line-height: 1.25;
|
||||
gap: 0.25rem;
|
||||
.pass-content {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
}
|
||||
.primary {
|
||||
.pass-content {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
align-self: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
.pass-front-label {
|
||||
font-weight: bold;
|
||||
font-size: 0.8em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
|
||||
color: v-bind(theme.label_color);
|
||||
}
|
||||
|
||||
.pass-back-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
</style>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import { dateTimeFormat, i18nstringLocalize } from "../../helpers";
|
||||
import Google from "./google.vue";
|
||||
const store = inject(StoreKey)!;
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{ data: any }>();
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
Google(:data="data")
|
||||
template(#card-rows)
|
||||
div.card-row
|
||||
div(v-for="entry in store.renderedFieldGroups['second_row']")
|
||||
div.card-rows-label {{ entry.label }}
|
||||
div.card-rows-content {{ entry.content }}
|
||||
div.card-row
|
||||
div(v-for="entry in store.renderedFieldGroups['third_row']")
|
||||
div.card-rows-label {{ entry.label }}
|
||||
div.card-rows-content {{ entry.content }}
|
||||
|
||||
template(#details)
|
||||
div(v-for="entry in store.renderedFieldGroups['details']")
|
||||
div.pass-details-label {{ entry.label }}
|
||||
div.pass-details-content {{ entry.content }}
|
||||
|
||||
</template>
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import { inject } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import { dateTimeFormat, i18nstringLocalize } from "../../helpers";
|
||||
import Google from "./google.vue";
|
||||
const store = inject(StoreKey)!;
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{ data: any }>();
|
||||
|
||||
function isActive(fieldgroup: string) {
|
||||
return store.layout.fieldgroups[fieldgroup]?.active;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
Google(:data="data")
|
||||
template(#card-rows)
|
||||
div.card-row(v-if="isActive('date')")
|
||||
div
|
||||
div.card-rows-label {{ gettext("Date") }}
|
||||
div.card-rows-content {{ dateTimeFormat(data.date_from, {"dateStyle": "medium"}) }}
|
||||
div
|
||||
div.card-rows-label {{ gettext("Time") }}
|
||||
div.card-rows-content {{ dateTimeFormat(data.date_from, {"timeStyle": "short"}) }}
|
||||
div.card-row(v-if="isActive('seating')")
|
||||
div
|
||||
div.card-rows-label {{ gettext("Section") }}
|
||||
div.card-rows-content {{ gettext("Ground floor") }}
|
||||
div
|
||||
div.card-rows-label {{ gettext("Row") }} / {{ gettext("Seat") }}
|
||||
div.card-rows-content 5 / 2
|
||||
|
||||
template(#details)
|
||||
div.pass-details-row(v-if="isActive('ticket_holder')")
|
||||
div.pass-details-label {{ gettext("Ticket Holder") }}
|
||||
div.pass-details-content {{ gettext("John Doe") }}
|
||||
div.pass-details-row(v-if="isActive('venue')")
|
||||
div.pass-details-label {{ i18nstringLocalize(data.venue?.[0]) }}
|
||||
div.pass-details-content {{ i18nstringLocalize(data.venue?.[1]) }}
|
||||
div.pass-details-row(v-if="isActive('date')")
|
||||
div.pass-details-label {{ gettext("Event Start Time") }}
|
||||
div.pass-details-content {{ i18nstringLocalize(data.venue?.[1]) }}
|
||||
div.pass-details-row
|
||||
div.pass-details-label {{ gettext("Ticket Type") }}
|
||||
div.pass-details-content {{ gettext("Sample product") }}
|
||||
div.pass-details-row
|
||||
div.pass-details-label {{ gettext("Ticket Number") }}
|
||||
div.pass-details-content ABCDE-1
|
||||
div.pass-details-row
|
||||
div.pass-details-label {{ gettext("Order Number") }}
|
||||
div.pass-details-content ABCDE
|
||||
a.pass-details-link.f-row(:href="data.event_page")
|
||||
div
|
||||
i.fa.fa-globe
|
||||
div.pass-details-label
|
||||
| {{ " " + gettext("Website") }}
|
||||
|
||||
</template>
|
||||
+98
-90
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import { i18nstringLocalize } from "../../helpers";
|
||||
import { dateTimeFormat, i18nstringLocalize } from "../../helpers";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
@@ -15,102 +15,110 @@ const qrcode = computed(() => store.renderedFieldGroups["code"]?.[0]);
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div(style="display: grid; gap: 1em; grid-template-columns: repeat(auto-fit, minmax(auto, 360px));")
|
||||
.google-pass-container.f-row
|
||||
img.logo(:src="store.getSettingValue('logo')?.url")
|
||||
div
|
||||
.bold {{ data.event }}
|
||||
div(v-if="isActive('date')") {{ data.date_from }}
|
||||
div(v-else) {{ data.organizer }}
|
||||
|
||||
.google-pass-container(:style="{'--background-color': store.getSettingValue('bg_color')}")
|
||||
.f-row
|
||||
div.pass-preview-google(style="display: grid; gap: 1em; grid-template-columns: repeat(auto-fit, minmax(auto, 360px)); align-items: start;")
|
||||
div.f-column
|
||||
.pass-container.f-row
|
||||
img.logo(:src="store.getSettingValue('logo')?.url")
|
||||
div.logo-text {{ data.organizer }}
|
||||
div
|
||||
div(v-if="isActive('venue')") {{ i18nstringLocalize(data.venue?.[0]) }}
|
||||
div.large {{ data.event }}
|
||||
// TODO: front card
|
||||
.qrcode-inner(v-if="qrcode") {{ qrcode.label }}
|
||||
.f-column
|
||||
img.hero(:src="store.getSettingValue('hero')?.url")
|
||||
div
|
||||
.bold {{ data.event }}
|
||||
div(v-if="isActive('date')") {{ dateTimeFormat(data.date_from, {"dateStyle": "medium"}) }}
|
||||
div(v-else) {{ data.organizer }}
|
||||
|
||||
.pass-container.google-main(:data-background-color="store.getSettingValue('bg_color')")
|
||||
.pass-inner
|
||||
.f-row
|
||||
img.logo(:src="store.getSettingValue('logo')?.url")
|
||||
div.logo-text {{ data.organizer }}
|
||||
|
||||
.top-row
|
||||
div(v-if="isActive('venue')") {{ i18nstringLocalize(data.venue?.[0]) }}
|
||||
.event-name {{ data.event }}
|
||||
|
||||
.f-column
|
||||
slot(name="card-rows")
|
||||
|
||||
.qrcode-inner(v-for="qrcode in store.renderedFieldGroups['code']") {{ qrcode.label }}
|
||||
|
||||
.f-column
|
||||
img.hero(:src="store.getSettingValue('hero')?.url")
|
||||
|
||||
.pass-container
|
||||
.pass-inner
|
||||
slot(name="details")
|
||||
|
||||
.google-pass-container
|
||||
div(v-for="entry in store.renderedFieldGroups['details']")
|
||||
div.pass-details-label {{ entry.label }}
|
||||
div.pass-details-content {{ entry.content }}
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.google-pass-container {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
vertical-align: top;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: solid 1px #949494;
|
||||
border-radius: 1em;
|
||||
padding: 1em;
|
||||
<style lang="css">
|
||||
.pass-preview-google {
|
||||
.google-main {
|
||||
--background-color: attr(data-background-color type(<color>));
|
||||
color: contrast-color(var(--background-color));
|
||||
}
|
||||
|
||||
background-image: var(--background-image);
|
||||
background-color: var(--background-color);
|
||||
color: contrast-color(var(--background-color));
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
.logo {
|
||||
height: 2lh;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
object-fit: fill;
|
||||
}
|
||||
|
||||
gap: 1em;
|
||||
}
|
||||
.logo-text {
|
||||
align-self: center;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.f-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: inherit;
|
||||
}
|
||||
.f-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: inherit;
|
||||
}
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.logo {
|
||||
height: 2lh;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
object-fit: fill;
|
||||
}
|
||||
.logo-text {
|
||||
align-self: center;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.large {
|
||||
font-size: 2em;
|
||||
}
|
||||
.qrcode-inner {
|
||||
font-weight: bold;
|
||||
background-color: lightgray;
|
||||
color: black;
|
||||
width: 50%;
|
||||
aspect-ratio: 1;
|
||||
align-self: center;
|
||||
.top-row > .event-name {
|
||||
font-size: 1.75em;
|
||||
}
|
||||
|
||||
/* center content */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.hero {
|
||||
align-self: center;
|
||||
}
|
||||
.pass-details-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
.pass-details-content {
|
||||
font-size: 0.9em;
|
||||
.card-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: inherit;
|
||||
|
||||
> div {
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
&:has(> :first-child:nth-last-child(2)) {
|
||||
/* twoItems */
|
||||
> :nth-child(2) {
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
&:has(> :first-child:nth-last-child(3)) {
|
||||
/* threeItems */
|
||||
> :nth-child(2) {
|
||||
text-align: center;
|
||||
}
|
||||
> :nth-child(3) {
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-rows-label {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.pass-details-row {
|
||||
border-bottom: 2px lightblue;
|
||||
}
|
||||
.hero {
|
||||
align-self: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
.pass-details-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
.pass-details-content {
|
||||
font-size: 0.9em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.pass-details-link {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, watchEffect } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import { i18nstringLocalize } from "../../helpers";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const props = defineProps<{
|
||||
label?: LocalizableString;
|
||||
content: LocalizableString;
|
||||
content_type: FieldContentType;
|
||||
display: FieldGroupDisplay;
|
||||
display_class?: string | string[];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.fieldgroup-item( :class="display_class")
|
||||
div.fieldgroup-item-qrcode(v-if="display == 'code'") {{ label }}
|
||||
|
||||
template(v-else)
|
||||
div.fieldgroup-label.nowrap(v-if="display == 'with_label'") {{ i18nstringLocalize(label) }}
|
||||
div.nowrap.content(v-if="content_type == 'text'") {{ i18nstringLocalize(content) }}
|
||||
img.fieldgroup-item-image(v-else-if="content_type == 'image' && content" :src="i18nstringLocalize(content)" :class="display_class")
|
||||
</template>
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, watchEffect } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import PlaceholderFieldgroupPreview from "./placeholder-fieldgroup-preview.vue";
|
||||
import PredefinedFieldgroupPreview from "./predefined-fieldgroup-preview.vue";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const props = defineProps<{
|
||||
config: PreviewFieldgroup;
|
||||
}>();
|
||||
|
||||
const style_def = computed(() => {
|
||||
return Object.fromEntries(
|
||||
store.styles[
|
||||
store.layout.style
|
||||
].fieldgroups.map((x) => [x.identifier, x]),
|
||||
)[props.config.fieldgroup];
|
||||
});
|
||||
|
||||
function focusGroup() {
|
||||
// TODO: highlight group? or change ui concept completely
|
||||
const elem = document.getElementById('fieldgroup-' + props.config.fieldgroup)
|
||||
elem && elem.scrollIntoView()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
PlaceholderFieldgroupPreview(v-if="style_def && style_def.type == 'placeholder'" :config="config" :style_def="style_def" @click="focusGroup")
|
||||
PredefinedFieldgroupPreview(v-else-if="style_def && style_def.type == 'predefined'" :config="config" :style_def="style_def" @click="focusGroup")
|
||||
</template>
|
||||
-230
@@ -1,230 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from "vue";
|
||||
import { StoreKey } from "../../walletStore.js";
|
||||
import RowPreview from "./row-preview.vue";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const { layout } = defineProps<{ layout: PreviewLayout }>();
|
||||
|
||||
function isPredefined(
|
||||
row: PreviewFieldgroup,
|
||||
): row is PredefinedFieldgroupPreview {
|
||||
const fieldgroup = store.fieldgroups[row.fieldgroup];
|
||||
return fieldgroup.type == "predefined";
|
||||
}
|
||||
|
||||
function getSettingPreview(setting: string) {
|
||||
const settingDef = store.styles[store.layout.style].settings.find(
|
||||
(x) => x.identifier == setting,
|
||||
);
|
||||
const val = store.settings[setting];
|
||||
|
||||
if (settingDef.type == "image") {
|
||||
if (val && "url" in val && val.url) {
|
||||
return { content: val.url, content_type: "image" };
|
||||
} else if (settingDef.default) {
|
||||
return { content: settingDef.default, content_type: "image" };
|
||||
}
|
||||
} else if (val) {
|
||||
return { content: val, content_type: "text" };
|
||||
} else if (settingDef.default) {
|
||||
return { content: settingDef.default, content_type: "text" };
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateStyle(style: StyleConfig) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(style)
|
||||
.map(([k, v]) => {
|
||||
if (typeof v !== "string" && v.setting) {
|
||||
const settingsVal = getSettingPreview(v.setting);
|
||||
if (!settingsVal) {
|
||||
v = null;
|
||||
} else if (settingsVal.content_type == "image") {
|
||||
v = `url(${encodeURI(settingsVal.content)})`;
|
||||
} else {
|
||||
v = settingsVal.content;
|
||||
}
|
||||
}
|
||||
return [k, v];
|
||||
})
|
||||
.filter(([_k, v]) => !!v),
|
||||
);
|
||||
}
|
||||
|
||||
type EvaluatedPreviewItem = {
|
||||
label?: LocalizableString;
|
||||
content: LocalizableString;
|
||||
content_type?: "text" | "image";
|
||||
};
|
||||
type EvaluatedPreviewRow = (
|
||||
| EvaluatedPreviewItem
|
||||
| { children: EvaluatedPreviewRow[] }
|
||||
) & {
|
||||
display?: string;
|
||||
display_class?: string | string[];
|
||||
style?: Record<string, string | number>;
|
||||
};
|
||||
|
||||
function hasEntries(row) {
|
||||
if ("content" in row) {
|
||||
return true;
|
||||
} else if ("children" in row) {
|
||||
return row.children.some(hasEntries);
|
||||
}
|
||||
}
|
||||
function evaluateRow(row: PreviewRow): EvaluatedPreviewRow {
|
||||
if ("children" in row) {
|
||||
const style = {
|
||||
"flex-direction": row.direction || "row",
|
||||
};
|
||||
const children = row.children.map(evaluateRow).filter((x) => !!x);
|
||||
if (row.max_entries) {
|
||||
children.splice(row.max_entries);
|
||||
}
|
||||
return { children, display_class: row.display_class, style };
|
||||
} else if ("fieldgroup" in row) {
|
||||
// TODO: click
|
||||
const style = {
|
||||
"flex-grow": row.relSize,
|
||||
"flex-direction": row.direction || "row",
|
||||
};
|
||||
if (isPredefined(row)) {
|
||||
if (store.layout.fieldgroups[row.fieldgroup]?.active) {
|
||||
return { style: style, children: row.sample, display_class: row.display_class };
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
style: style,
|
||||
children: store.renderedFieldGroups[row.fieldgroup],
|
||||
display_class: row.display_class
|
||||
};
|
||||
}
|
||||
} else if ("content" in row) {
|
||||
return { label: row.label, content: row.content, display_class: row.display_class };
|
||||
} else if ("setting" in row) {
|
||||
return { ...getSettingPreview(row.setting), display_class: row.display_class };
|
||||
}
|
||||
}
|
||||
|
||||
const renderedLayout = computed(() => {
|
||||
return {
|
||||
style: evaluateStyle(layout.style || {}),
|
||||
rows: layout.rows.map(evaluateRow),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.pass-container(:style="renderedLayout.style")
|
||||
div.pass-content
|
||||
RowPreview(v-for="row of renderedLayout.rows" :config="row")
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.pass-container {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
|
||||
border: solid 1px #949494;
|
||||
border-radius: 1em;
|
||||
padding: 1em;
|
||||
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
background-image: var(--background-image);
|
||||
background-color: var(--background-color);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.pass-container::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
backdrop-filter: blur(10px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pass-content {
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="css">
|
||||
.pass-container {
|
||||
.fieldgroup-container {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
min-height: 2lh;
|
||||
}
|
||||
.fieldgroup-label {
|
||||
font-weight: bold;
|
||||
font-size: 0.8em;
|
||||
color: var(--label-color);
|
||||
}
|
||||
.fieldgroup-item {
|
||||
flex: 0 1 100%;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.fieldgroup-item-image {
|
||||
object-fit: contain;
|
||||
}
|
||||
.fieldgroup-item-qrcode {
|
||||
font-weight: bold;
|
||||
background-color: lightgray;
|
||||
color: black;
|
||||
width: 50%;
|
||||
aspect-ratio: 1;
|
||||
align-self: center;
|
||||
|
||||
/* center content */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.nowrap {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.large {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.tight {
|
||||
line-height: 1;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.img-inline {
|
||||
max-height: 3lh;
|
||||
}
|
||||
.img-circle-sm {
|
||||
max-height: 2lh;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
object-fit: fill;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, watchEffect } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import FieldgroupItemPreview from "./fieldgroup-item-preview.vue";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const props = defineProps<{
|
||||
config: PreviewFieldgroup;
|
||||
style_def: PlaceholderFieldGroupDefinition;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.fieldgroup-container(:style="{'flex-grow': config.relSize, 'flex-direction': config.direction || 'row'}")
|
||||
FieldgroupItemPreview(v-for="{ label, content } of store.renderedFieldGroups[config.fieldgroup]" :label="label" :content="content" :content_type="style_def.content_type" :display_class="config.display" :display="style_def.display")
|
||||
</template>
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, watchEffect } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import FieldgroupItemPreview from "./fieldgroup-item-preview.vue";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const props = defineProps<{
|
||||
style_def: PredefinedFieldGroupDefinition;
|
||||
config: PredefinedFieldgroupPreview;
|
||||
}>();
|
||||
|
||||
const isActive = computed(
|
||||
() =>
|
||||
store.layout.fieldgroups[props.style_def.identifier]?.active,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.fieldgroup-container(v-if="isActive" :style="{'flex-grow': config.relSize, 'flex-direction': config.direction || 'row'}")
|
||||
FieldgroupItemPreview(v-for="{ label, content } of config.sample" :label="label" :content="content" content_type="text" :display="!!label ? 'with_label' : 'plain'")
|
||||
</template>
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import FieldgroupItemPreview from './fieldgroup-item-preview.vue';
|
||||
import FieldgroupPreview from './fieldgroup-preview.vue';
|
||||
import SettingPreview from './setting-preview.vue';
|
||||
const props = defineProps<{
|
||||
config: PreviewRow;
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.preview-row(v-if="'children' in config" :style="{ flexDirection: config.direction || 'row' }" :class="config.display")
|
||||
RowPreview(v-for="child of config.children" :config="child" )
|
||||
FieldgroupPreview(v-else-if="'fieldgroup' in config" :config="config")
|
||||
FieldgroupItemPreview(v-else-if="'value' in config" :label="config.label" :content="config.value" content_type="text" :display="!!config.label ? 'with_label' : 'plain'" :display_class="config.display")
|
||||
SettingPreview(v-else-if="'setting' in config" :config="config")
|
||||
|
||||
</template>
|
||||
<style lang="css" scoped>
|
||||
.preview-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
display: flex
|
||||
}
|
||||
.tight {
|
||||
gap: 0;
|
||||
}
|
||||
</style>
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import FieldgroupItemPreview from './fieldgroup-item-preview.vue';
|
||||
import FieldgroupPreview from './fieldgroup-preview.vue';
|
||||
import SettingPreview from './setting-preview.vue';
|
||||
const props = defineProps<{
|
||||
config: PreviewRow;
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.preview-row(v-if="'children' in config" :style="config.style" :class="config.display_class")
|
||||
RowPreview(v-for="child of config.children" :config="child" )
|
||||
FieldgroupItemPreview(v-else-if="'content' in config" :label="config.label" :content="config.content" :content_type="config.content_type || 'text'" :display="config.display || (!!config.label ? 'with_label' : 'plain')" :display_class="config.display_class")
|
||||
|
||||
</template>
|
||||
<style lang="css" scoped>
|
||||
.preview-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
}
|
||||
</style>
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, toRaw, watchEffect } from "vue";
|
||||
import { StoreKey } from "../../walletStore";
|
||||
import FieldgroupItemPreview from "./fieldgroup-item-preview.vue";
|
||||
import PredefinedFieldgroupPreview from "./predefined-fieldgroup-preview.vue";
|
||||
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
const { config } = defineProps<{
|
||||
config: SettingPreview;
|
||||
}>();
|
||||
|
||||
const settingDef = computed(() => {
|
||||
return Object.fromEntries(
|
||||
store.styles[store.layout.style].settings.map((x) => [x.identifier, x]),
|
||||
)[config.setting];
|
||||
});
|
||||
const settingValue = computed(() => {
|
||||
const val = store.settings[config.setting];
|
||||
|
||||
if (settingDef.value.type == "image") {
|
||||
if (val && "url" in val && val.url) {
|
||||
return val.url;
|
||||
} else if (settingDef.value.type == "image" && settingDef.value.default) {
|
||||
return settingDef.value.default;
|
||||
}
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.fieldgroup-container(:style="{'flex-grow': config.relSize, 'flex-direction': config.direction || 'row'}")
|
||||
FieldgroupItemPreview(:content="settingValue" :content_type="settingDef.type" :display_class="config.display" display="plain")
|
||||
</template>
|
||||
@@ -31,3 +31,8 @@ export function i18nstringLocalize(s: LocalizableString, locale?: string): strin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function dateTimeFormat(d: string, options?: Record<string, string>): string {
|
||||
const locale = document.body.attributes['data-pretixlocale'].value
|
||||
return new Intl.DateTimeFormat(locale, options).format(new Date(d))
|
||||
}
|
||||
@@ -35,7 +35,7 @@ type FieldGroupDefinition =
|
||||
| PlaceholderFieldGroupDefinition
|
||||
| PredefinedFieldGroupDefinition;
|
||||
|
||||
type FieldGroupDisplay = "plain" | "with_label" | "code";
|
||||
type FieldGroupDisplay = "plain" | "with_label";
|
||||
type FieldGroupType = "placeholder" | "predefined";
|
||||
type FieldContentType = "text" | "image";
|
||||
|
||||
@@ -43,7 +43,6 @@ type PlaceholderFieldGroupDefinition = BaseFieldGroupDefinition & {
|
||||
type: "placeholder";
|
||||
content_type: FieldContentType;
|
||||
default_entries: FieldEntry[];
|
||||
display: FieldGroupDisplay;
|
||||
min_entries: number | null;
|
||||
max_entries: number | null;
|
||||
context_args: string[];
|
||||
|
||||
@@ -115,6 +115,9 @@ export function createWalletStore(config: {
|
||||
fieldgroup.identifier
|
||||
] as any as PlaceholderFieldGroupConfig;
|
||||
for (const entry of layout_group.entries) {
|
||||
if (entry.type === "placeholder" && !entry.content) {
|
||||
continue
|
||||
}
|
||||
const placeholder =
|
||||
entry.type === "placeholder"
|
||||
? this.variables[fieldgroup.content_type][entry.content]
|
||||
@@ -134,10 +137,8 @@ export function createWalletStore(config: {
|
||||
`(unknown placeholder: ${entry.content})`;
|
||||
}
|
||||
content[fieldgroup.identifier].push({
|
||||
// entry,
|
||||
label,
|
||||
content: value,
|
||||
display: fieldgroup.display,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
||||
identifier="code",
|
||||
name=_("QR-Code"),
|
||||
max_entries=1,
|
||||
display=FieldGroupDisplay.CODE,
|
||||
display=FieldGroupDisplay.PLAIN,
|
||||
default_entries=[
|
||||
PlaceholderFieldEntry(
|
||||
content="secret",
|
||||
@@ -456,41 +456,8 @@ class AppleWalletEventTicket(AppleWalletStyle):
|
||||
]
|
||||
|
||||
@property
|
||||
def preview_layout(self):
|
||||
return [
|
||||
{
|
||||
"style": {
|
||||
"--background-color": {"setting": "bg_color"},
|
||||
"color": {"setting": "fg_color"},
|
||||
"--label-color": {"setting": "label_color"},
|
||||
"--background-image": {"setting": "background"},
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"children": [
|
||||
{"setting": "logo", "display_class": ["img-inline"]},
|
||||
{
|
||||
"fieldgroup": "logo_text",
|
||||
"relSize": 3,
|
||||
"display_class": ["bold", "large", "centered"],
|
||||
},
|
||||
{
|
||||
"fieldgroup": "header",
|
||||
"relSize": 2,
|
||||
"display_class": ["large", "tight"],
|
||||
},
|
||||
]
|
||||
},
|
||||
{"fieldgroup": "primary", "display_class": "large"},
|
||||
{"fieldgroup": "secondary"},
|
||||
{"fieldgroup": "auxiliary"},
|
||||
{"fieldgroup": "code", "direction": "column"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"rows": [{"fieldgroup": "back", "direction": "column"}],
|
||||
},
|
||||
]
|
||||
def preview_renderer(self) -> str | None:
|
||||
return "apple_eventticket"
|
||||
|
||||
def convert_fields(self, strings, fields, prefix):
|
||||
# TODO: detect if a string-value/string-label is the same as a generated field name
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import enum
|
||||
from functools import cached_property
|
||||
from typing import Any, Literal, TypedDict
|
||||
|
||||
import jsonschema
|
||||
@@ -37,7 +38,6 @@ class FieldGroupType(enum.Enum):
|
||||
class FieldGroupDisplay(enum.Enum):
|
||||
PLAIN = "plain"
|
||||
WITH_LABEL = "with_label"
|
||||
CODE = "code"
|
||||
|
||||
|
||||
class FieldGroup:
|
||||
@@ -478,33 +478,40 @@ class PassStyle:
|
||||
def settings(self) -> list[SettingsField]:
|
||||
return []
|
||||
|
||||
@cached_property
|
||||
def _settings_dict(self):
|
||||
return {x.identifier: x for x in self.settings}
|
||||
|
||||
@property
|
||||
def preview_layout(self) -> list | None:
|
||||
def preview_renderer(self) -> str | None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def cleaned_settings(self):
|
||||
if not self.layout:
|
||||
return {}
|
||||
settings = {}
|
||||
for setting in self.settings:
|
||||
if isinstance(setting, ImageSettingsField):
|
||||
settings[setting.identifier] = (
|
||||
self.file_settings[setting.identifier].url
|
||||
if setting.identifier in self.file_settings
|
||||
else None
|
||||
)
|
||||
if not settings[setting.identifier] and setting.default:
|
||||
settings[setting.identifier] = setting.default
|
||||
else:
|
||||
settings[setting.identifier] = self.layout.get("settings", {}).get(
|
||||
setting.identifier, setting.default
|
||||
)
|
||||
if not settings[setting.identifier] and setting.default:
|
||||
settings[setting.identifier] = setting.default
|
||||
return settings
|
||||
def preview_data(self) -> dict:
|
||||
return {}
|
||||
|
||||
def asdict(self): # -> dict[str, Any]:
|
||||
def get_cleaned_setting(self, setting_identifier: str):
|
||||
if not self.layout:
|
||||
raise ValueError("`layout` needs to be set")
|
||||
|
||||
setting = self._settings_dict.get(setting_identifier)
|
||||
|
||||
if not setting:
|
||||
return None
|
||||
|
||||
if isinstance(setting, ImageSettingsField):
|
||||
if setting.identifier in self.file_settings:
|
||||
return self.file_settings[setting.identifier].url
|
||||
else:
|
||||
if val := self.layout.get("settings", {}).get(
|
||||
setting.identifier, setting.default
|
||||
):
|
||||
return val
|
||||
|
||||
if setting.default:
|
||||
return setting.default
|
||||
|
||||
def asdict(self):
|
||||
context = LayoutContext(
|
||||
placeholders=self.placeholders,
|
||||
placeholder_renderer=get_wallet_placeholder_renderer(),
|
||||
@@ -515,7 +522,8 @@ class PassStyle:
|
||||
"fieldgroups": [
|
||||
x.asdict(self.event.settings.locales, context) for x in self.fieldgroups
|
||||
],
|
||||
"preview_layout": self.preview_layout,
|
||||
"preview_renderer": self.preview_renderer,
|
||||
"preview_data": self.preview_data,
|
||||
"settings": [x.asdict(self.event.settings.locales) for x in self.settings],
|
||||
}
|
||||
|
||||
@@ -611,6 +619,33 @@ class PassStyle:
|
||||
|
||||
return res
|
||||
|
||||
def _render_placeholder_fieldgroup_entries(self, group, context):
|
||||
group_fields = []
|
||||
if group.identifier not in self.layout["fieldgroups"]:
|
||||
return group_fields
|
||||
|
||||
for field in self.layout["fieldgroups"][group.identifier]["entries"]:
|
||||
field_entry: dict[str, MaybeTranslatedString | None] = {}
|
||||
if group.display == FieldGroupDisplay.WITH_LABEL:
|
||||
field_entry["label"] = LazyI18nString(field["label"])
|
||||
if field["type"] == FieldEntryType.PLACEHOLDER.value:
|
||||
label, field_entry["value"] = self.render_placeholder(
|
||||
context, group.content_type.value, field["content"]
|
||||
)
|
||||
if (
|
||||
group.display == FieldGroupDisplay.WITH_LABEL
|
||||
and not str(field_entry["label"])
|
||||
and label
|
||||
):
|
||||
field_entry["label"] = label
|
||||
|
||||
elif field["type"] == FieldEntryType.CUSTOM.value:
|
||||
field_entry["value"] = LazyI18nString(field["content"])
|
||||
if "value" in field_entry and field_entry["value"]:
|
||||
group_fields.append(field_entry)
|
||||
|
||||
return group_fields
|
||||
|
||||
def get_pass_fields(self, op: OrderPosition):
|
||||
if not self.layout:
|
||||
raise ValueError("`layout` needs to be set")
|
||||
@@ -619,46 +654,25 @@ class PassStyle:
|
||||
|
||||
fields = {}
|
||||
for group in self.fieldgroups:
|
||||
if isinstance(group, PredefinedFieldGroup):
|
||||
pass
|
||||
if not isinstance(group, PlaceholderFieldGroup):
|
||||
continue
|
||||
|
||||
elif isinstance(group, PlaceholderFieldGroup):
|
||||
group_fields = fields.get(group.identifier, [])
|
||||
if group.identifier in self.layout["fieldgroups"]:
|
||||
for field in self.layout["fieldgroups"][group.identifier][
|
||||
"entries"
|
||||
]:
|
||||
field_entry: dict[str, MaybeTranslatedString | None] = {}
|
||||
if group.display == FieldGroupDisplay.WITH_LABEL:
|
||||
field_entry["label"] = LazyI18nString(field["label"])
|
||||
if field["type"] == FieldEntryType.PLACEHOLDER.value:
|
||||
label, field_entry["value"] = self.render_placeholder(
|
||||
context, group.content_type.value, field["content"]
|
||||
)
|
||||
if (
|
||||
group.display == FieldGroupDisplay.WITH_LABEL
|
||||
and not str(field_entry["label"])
|
||||
and label
|
||||
):
|
||||
field_entry["label"] = label
|
||||
fields.setdefault(group.identifier, [])
|
||||
fields[group.identifier] += self._render_placeholder_fieldgroup_entries(
|
||||
group, context
|
||||
)
|
||||
if group.min_entries and len(fields[group.identifier]) < group.min_entries:
|
||||
raise ValueError(
|
||||
f"Group {group.identifier} needs at least {group.min_entries} entries, but only {len(fields[group.identifier])} were provided"
|
||||
)
|
||||
|
||||
elif field["type"] == FieldEntryType.CUSTOM.value:
|
||||
field_entry["value"] = LazyI18nString(field["content"])
|
||||
if "value" in field_entry and field_entry["value"]:
|
||||
group_fields.append(field_entry)
|
||||
if group.min_entries and len(group_fields) < group.min_entries:
|
||||
raise ValueError(
|
||||
f"Group {group.identifier} needs at least {group.min_entries} entries, but only {len(group_fields)} were provided"
|
||||
)
|
||||
fields[group.identifier] = group_fields[: group.max_entries]
|
||||
if overflow_group := self.layout["fieldgroups"][group.identifier][
|
||||
"overflow"
|
||||
]:
|
||||
fields.setdefault(overflow_group, [])
|
||||
fields[overflow_group] += group_fields[group.max_entries :]
|
||||
if overflow_group := self.layout["fieldgroups"][group.identifier][
|
||||
"overflow"
|
||||
]:
|
||||
fields.setdefault(overflow_group, [])
|
||||
fields[overflow_group] += fields[group.identifier][group.max_entries :]
|
||||
fields[group.identifier] = fields[group.identifier][: group.max_entries]
|
||||
|
||||
else:
|
||||
raise ValueError("Unknown field group")
|
||||
return fields
|
||||
|
||||
def group_is_active(self, identifier: str):
|
||||
|
||||
@@ -186,7 +186,7 @@ class GoogleWalletEventTicketStyle(PassStyle):
|
||||
identifier="code",
|
||||
name=_("QR-Code"),
|
||||
max_entries=1,
|
||||
display=FieldGroupDisplay.CODE,
|
||||
display=FieldGroupDisplay.PLAIN,
|
||||
default_entries=[
|
||||
PlaceholderFieldEntry(
|
||||
content="secret",
|
||||
@@ -197,106 +197,17 @@ class GoogleWalletEventTicketStyle(PassStyle):
|
||||
]
|
||||
|
||||
@property
|
||||
def preview_layout(self):
|
||||
def preview_data(self):
|
||||
return {
|
||||
"renderer": "google_eventticket",
|
||||
"event": str(self.event),
|
||||
"organizer": str(self.event.organizer),
|
||||
"date_from": date_format(
|
||||
self.event.date_from.astimezone(self.event.timezone),
|
||||
"SHORT_DATE_FORMAT",
|
||||
),
|
||||
"venue": self.venue()
|
||||
"date_from": self.event.date_from.astimezone(
|
||||
self.event.timezone
|
||||
).isoformat(),
|
||||
"venue": self.venue(),
|
||||
"event_page": eventreverse_absolute(self.event, "presale:event.index"),
|
||||
}
|
||||
|
||||
# @property
|
||||
# def preview_layout(self):
|
||||
# return [
|
||||
# {
|
||||
# "rows": [
|
||||
# {
|
||||
# "children": [
|
||||
# {"setting": "logo", "display_class": ["img-circle-sm"]},
|
||||
# {
|
||||
# "direction": "column",
|
||||
# "display_class": ["tight"],
|
||||
# "children": [
|
||||
# {
|
||||
# "content": str(self.event.name),
|
||||
# "display_class": ["bold"],
|
||||
# },
|
||||
# {
|
||||
# "children": [
|
||||
# {
|
||||
# "fieldgroup": "date",
|
||||
# "sample": [
|
||||
# {
|
||||
# "content": date_format(
|
||||
# self.event.date_from.astimezone(
|
||||
# self.event.timezone
|
||||
# ),
|
||||
# "SHORT_DATE_FORMAT",
|
||||
# ),
|
||||
# },
|
||||
# ],
|
||||
# },
|
||||
# {
|
||||
# "content": str(self.event.organizer.name),
|
||||
# },
|
||||
# ],
|
||||
# "max_entries": 1,
|
||||
# },
|
||||
# ],
|
||||
# },
|
||||
# ]
|
||||
# },
|
||||
# ],
|
||||
# },
|
||||
# {
|
||||
# "style": {
|
||||
# "--background-color": {"setting": "bg_color"},
|
||||
# "color": "contrast-color(var(--background-color))",
|
||||
# },
|
||||
# "rows": [
|
||||
# {
|
||||
# "children": [
|
||||
# {"setting": "logo", "display_class": ["img-circle-sm"]},
|
||||
# {
|
||||
# "content": str(self.event.organizer.name),
|
||||
# "relSize": 3,
|
||||
# "display_class": ["large", "centered"],
|
||||
# },
|
||||
# ]
|
||||
# },
|
||||
# {
|
||||
# "children": [
|
||||
# {
|
||||
# "fieldgroup": "venue",
|
||||
# "sample": [
|
||||
# {"content": self.venue()[0], "label": ""},
|
||||
# ],
|
||||
# },
|
||||
# {"content": str(self.event.name), "display_class": "large"},
|
||||
# ],
|
||||
# "direction": "column",
|
||||
# "display_class": ["tight"],
|
||||
# },
|
||||
# *self._preview_layout_front_card,
|
||||
# {"fieldgroup": "code", "direction": "column"},
|
||||
# {"setting": "hero"},
|
||||
# ],
|
||||
# },
|
||||
# {"rows": [*self._preview_layout_back_details]},
|
||||
# ]
|
||||
|
||||
@property
|
||||
def _preview_layout_front_card(self):
|
||||
return []
|
||||
|
||||
@property
|
||||
def _preview_layout_back_details(self):
|
||||
return []
|
||||
|
||||
def venue(self):
|
||||
if self.event.location:
|
||||
name = {}
|
||||
@@ -330,8 +241,8 @@ class GoogleWalletEventTicketStyle(PassStyle):
|
||||
localizable_to_dict("Website", self.event.settings.locales),
|
||||
)
|
||||
|
||||
if (lat := self.cleaned_settings["lat"]) and (
|
||||
long := self.cleaned_settings["long"]
|
||||
if (lat := self.get_cleaned_setting("lat")) and (
|
||||
long := self.get_cleaned_setting("long")
|
||||
):
|
||||
output_class.locations(lat, long)
|
||||
elif self.event.geo_lat and self.event.geo_lon:
|
||||
@@ -339,36 +250,43 @@ class GoogleWalletEventTicketStyle(PassStyle):
|
||||
|
||||
output_class.country_code(self.event.settings.locale)
|
||||
|
||||
if hero := self.cleaned_settings["hero"]:
|
||||
if hero := self.get_cleaned_setting("hero"):
|
||||
output_class.hero_image(
|
||||
urljoin(eventreverse_absolute(self.event, "presale:event.index"), hero),
|
||||
self.event.name,
|
||||
)
|
||||
|
||||
if logo := self.cleaned_settings["logo"]:
|
||||
if logo := self.get_cleaned_setting("logo"):
|
||||
output_class.logo(
|
||||
urljoin(eventreverse_absolute(self.event, "presale:event.index"), logo),
|
||||
self.event.name,
|
||||
)
|
||||
|
||||
output_class.hex_background_color(self.cleaned_settings["bg_color"])
|
||||
output_class.hex_background_color(self.get_cleaned_setting("bg_color"))
|
||||
output_class.confirmation_code_label(ConfirmationCode.orderNumber)
|
||||
|
||||
if self.group_is_active("venue") and all(self.venue()):
|
||||
output_class.venue(*self.venue())
|
||||
|
||||
if self.cleaned_settings.get("shimmer", False):
|
||||
if self.get_cleaned_setting("shimmer"):
|
||||
output_class.securityAnimation(AnimationType.FOIL_SHIMMER)
|
||||
|
||||
if self.group_is_active("date"):
|
||||
output_class.start(self.event.date_from.isoformat())
|
||||
output_class.start(
|
||||
self.event.date_from.astimezone(self.event.timezone).isoformat()
|
||||
)
|
||||
|
||||
if self.event.settings.show_date_to and self.event.date_to:
|
||||
output_class.end(self.event.date_to.isoformat())
|
||||
output_class.end(
|
||||
self.event.date_to.astimezone(self.event.timezone).isoformat()
|
||||
)
|
||||
|
||||
if self.event.date_admission:
|
||||
output_class.doors_open(
|
||||
DoorsOpen.doorsOpen, self.event.date_admission.isoformat()
|
||||
DoorsOpen.doorsOpen,
|
||||
self.event.date_admission.astimezone(
|
||||
self.event.timezone
|
||||
).isoformat(),
|
||||
)
|
||||
|
||||
# TODO: callback url
|
||||
@@ -404,6 +322,10 @@ class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
|
||||
identifier = "event"
|
||||
name = "Default Event Ticket"
|
||||
|
||||
@property
|
||||
def preview_renderer(self):
|
||||
return "google_eventticket_default"
|
||||
|
||||
@property
|
||||
def fieldgroups(self):
|
||||
return [
|
||||
@@ -529,19 +451,8 @@ class GoogleWalletCustomEventTicket(GoogleWalletEventTicketStyle):
|
||||
name = "Custom Event Ticket"
|
||||
|
||||
@property
|
||||
def _preview_layout_front_card(self):
|
||||
return [
|
||||
{
|
||||
"fieldgroup": "second_row",
|
||||
},
|
||||
{
|
||||
"fieldgroup": "third_row",
|
||||
},
|
||||
]
|
||||
|
||||
@property
|
||||
def _preview_layout_back_details(self):
|
||||
return [{"fieldgroup": "details", "direction": "column"}]
|
||||
def preview_renderer(self):
|
||||
return "google_eventticket_custom"
|
||||
|
||||
@property
|
||||
def fieldgroups(self):
|
||||
|
||||
Reference in new issue
Block a user