wallet wip

This commit is contained in:
Kara Engelhardt
2026-07-07 18:09:47 +02:00
parent 6ff3f55e68
commit 9f7a3664b2
15 changed files with 400 additions and 143 deletions
+2
View File
@@ -7,6 +7,7 @@ on:
- 'src/pretix/static/pretixpresale/widget/**'
- 'src/pretix/static/pretixcontrol/js/ui/checkinrules/**'
- 'src/pretix/plugins/webcheckin/**'
- 'src/pretix/plugins/wallet/**'
- 'eslint.config.mjs'
- 'package.json'
- 'package-lock.json'
@@ -16,6 +17,7 @@ on:
- 'src/pretix/static/pretixpresale/widget/**'
- 'src/pretix/static/pretixcontrol/js/ui/checkinrules/**'
- 'src/pretix/plugins/webcheckin/**'
- 'src/pretix/plugins/wallet/**'
- 'eslint.config.mjs'
- 'package.json'
- 'package-lock.json'
@@ -1,67 +1,19 @@
<script setup lang="ts">
import { computed, ref, watchEffect } from "vue";
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 { StoreKey } from "../walletStore";
const gettext = (window as any).gettext;
const isLoading = ref<boolean>(true);
const wallet_layout = ref<Layout | null>(null);
const store = inject(StoreKey)!
const PLATFORMS: Platforms = JSON.parse(
document.querySelector("#platforms")?.textContent ?? "{}",
);
const VARIABLES: VariableConfig = JSON.parse(
document.querySelector("#variables")?.textContent ?? "{}",
);
const LOCALES: Record<string, string> = JSON.parse(
document.querySelector("#locales")?.textContent ?? "{}",
);
const CSRF_TOKEN =
document.querySelector<HTMLInputElement>("input[name=csrfmiddlewaretoken]")
?.value ?? "";
const props = defineProps<{
layoutId: string;
}>();
watchEffect(() => {
// TODO: error handling / proper api client
isLoading.value = true;
fetch(
`/api/v1/organizers/demo/events/wallet/walletlayouts/${props.layoutId}/`,
)
.then((x) => x.json())
.then((x) => {
wallet_layout.value = x;
isLoading.value = false;
});
const platformChoices = computed(() => {
return [[null, "Do not generate pass"], ...Object.values(store.currentPlatformStyles).map(x => [x.identifier, x.name])]
});
function saveLayout(e: SubmitEvent) {
e.preventDefault();
isLoading.value = true;
// TODO: error handling / proper api client
fetch(
`/api/v1/organizers/demo/events/wallet/walletlayouts/${props.layoutId}/`,
{
method: "PUT",
headers: {
"content-type": "application/json",
"X-CSRFToken": CSRF_TOKEN,
},
body: JSON.stringify(wallet_layout.value),
},
)
.then((x) => x.json())
.catch((x) => alert(x))
.then((x) => {
wallet_layout.value = x;
isLoading.value = false;
});
}
function openForm(url: string, data: Record<string, string>) {
let form = document.createElement("form");
@@ -82,33 +34,9 @@ function openForm(url: string, data: Record<string, string>) {
document.body.removeChild(form);
}
const currentPlatform = ref(PLATFORMS[0].identifier);
const currentLayout = computed(() => ({}));
const platformStyles = computed(() => {
for (const platform of PLATFORMS) {
if (platform.identifier === currentPlatform.value) {
return platform.styles
}
}
});
const platformLayout = computed(() => {
for (const layout of wallet_layout.value.platform_layouts) {
if (layout.platform === currentPlatform.value) {
return layout
}
}
const newLayout = {platform: currentPlatform, style: null, layout: {}};
wallet_layout.value.platform_layouts.push(newLayout);
return newLayout
});
const platformChoices = computed(() => {
return [[null, "Do not generate pass"], ...Object.values(platformStyles.value).map(x => [x.identifier, x.name])]
});
function openPreview(e: SubmitEvent) {
e.preventDefault();
openForm("../../preview/", {"csrfmiddlewaretoken": CSRF_TOKEN, "platform": currentPlatform.value, "style": platformLayout.value.style, "layout": JSON.stringify(platformLayout.value.layout)})
openForm("../../preview/", {"csrfmiddlewaretoken": store.csrfToken, "platform": store.currentPlatform, "style": store.currentPlatformLayout.style, "layout": JSON.stringify(store.currentPlatformLayout.layout)})
}
</script>
@@ -116,31 +44,41 @@ function openPreview(e: SubmitEvent) {
// TODO: add :key for all `v-for`s
// TODO: i18n textfields
// TODO: proper spinner
template(v-if="isLoading") {{ gettext("Loading...") }}
form(v-else @submit="saveLayout")
template(v-if="!store.loaded") {{ gettext("Loading...") }}
form(v-else @submit.prevent="store.saveLayout")
.form-group
Input(label="Name" v-model="wallet_layout.name")
Input(label="Name" v-model="store.walletLayout.name")
nav
ul.nav.nav-tabs
li(v-for="platform in PLATFORMS" :class="{'active': currentPlatform === platform.identifier}")
a(role="tab" @click="currentPlatform = platform.identifier") {{ platform.name }}
li(v-for="platform in store.platforms" :class="{'active': store.currentPlatform === platform.identifier}")
a(role="tab" @click="store.currentPlatform = platform.identifier") {{ platform.name }}
.tabbed-form.tab-content
.tab-pane.active.row
.col-md-8
Select.form-group(label="Style" v-model="platformLayout.style" :choices="platformChoices")
Select.form-group(label="Style" :modelValue="store.currentPlatformLayout.style" @update:modelValue="store.setCurrentPlatformStyle" :choices="platformChoices")
StyleSettings(v-if="platformLayout.style" v-model="platformLayout.layout" :style="platformStyles[platformLayout.style]" :variables="VARIABLES" :locales="LOCALES")
StyleSettings(v-if="store.currentPlatformLayout.style" v-model="store.currentPlatformLayout.layout" :style="store.currentPlatformStyles[store.currentPlatformLayout.style]")
.col-md-4
.panel.panel-default
.panel-heading Preview
.panel-body
span.text-muted The preview below is only a rough representation of what the pass might look like. Please check the generated pass.
div(style="margin-top: 1em; width: 10cm; height: 15cm; position: relative; border: solid 1px gray; border-radius: 1em;")
div(style="position: absolute; background-color: red; top: 0.5cm; left: 0cm; height: 1cm; width: 2cm;") Logo
div(style="position: absolute; background-color: red; top: 0.5cm; left: 2.25cm; height: 1cm; width: 5.5cm;") Logo Text
div(style="position: absolute; background-color: red; top: 0.5cm; left: 8cm; height: 1cm; width: 2cm;") Header
div(style="position: absolute; background-color: red; top: 2.5cm; left: 0cm; height: 2cm; width: 10cm;") Primary
div(style="position: absolute; background-color: red; top: 4.75cm; left: 0cm; height: 1cm; width: 10cm;") Secondary
div(style="position: absolute; background-color: red; top: 7cm; left: 0cm; height: 1cm; width: 10cm;") Auxiliary
div(style="position: absolute; background-color: red; top: 10cm; left: 3cm; height: 4cm; width: 4cm;") Barcode
// TODO: Preview
pre
code {{ platformLayout }}
pre(v-if="wallet_layout.style")
code {{ platformStyles[wallet_layout.style] }}
code {{ store.currentPlatformLayout }}
pre(v-if="store.currentPlatformLayout.style")
code {{ store.currentPlatformStyles[store.currentPlatformLayout.style] }}
pre
code {{ wallet_layout }}
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
@@ -0,0 +1,148 @@
<script setup lang="ts">
import { computed, ref, watchEffect } from "vue";
import StyleSettings from "./style-settings.vue";
import Select from "./input/select.vue";
import Input from "./input/input.vue";
const gettext = (window as any).gettext;
const isLoading = ref<boolean>(true);
const wallet_layout = ref<PlatformLayout | null>(null);
const PLATFORMS: Platforms = JSON.parse(
document.querySelector("#platforms")?.textContent ?? "{}",
);
const VARIABLES: VariableConfig = JSON.parse(
document.querySelector("#variables")?.textContent ?? "{}",
);
const LOCALES: Record<string, string> = JSON.parse(
document.querySelector("#locales")?.textContent ?? "{}",
);
const CSRF_TOKEN =
document.querySelector<HTMLInputElement>("input[name=csrfmiddlewaretoken]")
?.value ?? "";
const props = defineProps<{
layoutId: string;
}>();
watchEffect(() => {
// TODO: error handling / proper api client
isLoading.value = true;
fetch(
`/api/v1/organizers/demo/events/wallet/walletlayouts/${props.layoutId}/`,
)
.then((x) => x.json())
.then((x) => {
wallet_layout.value = x;
isLoading.value = false;
});
});
function saveLayout(e: SubmitEvent) {
e.preventDefault();
isLoading.value = true;
// TODO: error handling / proper api client
fetch(
`/api/v1/organizers/demo/events/wallet/walletlayouts/${props.layoutId}/`,
{
method: "PUT",
headers: {
"content-type": "application/json",
"X-CSRFToken": CSRF_TOKEN,
},
body: JSON.stringify(wallet_layout.value),
},
)
.then((x) => x.json())
.catch((x) => alert(x))
.then((x) => {
wallet_layout.value = x;
isLoading.value = false;
});
}
function openForm(url: string, data: Record<string, string>) {
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;
input.value = data[key];
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}
const currentPlatform = ref(PLATFORMS[0].identifier);
const currentLayout = computed(() => ({}));
const platformStyles = computed(() => {
for (const platform of PLATFORMS) {
if (platform.identifier === currentPlatform.value) {
return platform.styles
}
}
});
const platformLayout = computed(() => {
for (const layout of wallet_layout.value.platform_layouts) {
if (layout.platform === currentPlatform.value) {
return layout
}
}
const newLayout = {platform: currentPlatform, style: null, layout: {}};
wallet_layout.value.platform_layouts.push(newLayout);
return newLayout
});
const platformChoices = computed(() => {
return [[null, "Do not generate pass"], ...Object.values(platformStyles.value).map(x => [x.identifier, x.name])]
});
function openPreview(e: SubmitEvent) {
e.preventDefault();
openForm("../../preview/", {"csrfmiddlewaretoken": CSRF_TOKEN, "platform": currentPlatform.value, "style": platformLayout.value.style, "layout": JSON.stringify(platformLayout.value.layout)})
}
</script>
<template lang="pug">
// TODO: add :key for all `v-for`s
// TODO: i18n textfields
// TODO: proper spinner
template(v-if="isLoading") {{ gettext("Loading...") }}
form(v-else @submit="saveLayout")
.form-group
Input(label="Name" v-model="wallet_layout.name")
nav
ul.nav.nav-tabs
li(v-for="platform in PLATFORMS" :class="{'active': currentPlatform === platform.identifier}")
a(role="tab" @click="currentPlatform = platform.identifier") {{ platform.name }}
.tabbed-form.tab-content
.tab-pane.active.row
.col-md-8
Select.form-group(label="Style" v-model="platformLayout.style" :choices="platformChoices")
StyleSettings(v-if="platformLayout.style" v-model="platformLayout.layout" :style="platformStyles[platformLayout.style]" :variables="VARIABLES" :locales="LOCALES")
.col-md-4
.panel.panel-default
.panel-heading Preview
.panel-body
// TODO: Preview
pre
code {{ platformLayout }}
pre(v-if="wallet_layout.style")
code {{ platformStyles[wallet_layout.style] }}
pre
code {{ wallet_layout }}
.form-group.submit-group
button.btn.btn-lg.btn-default(type="button" @click="openPreview") Preview
button.btn.btn-primary.btn-save(type="submit") Submit
</template>
@@ -1,5 +1,8 @@
<script setup lang="ts">
import { watchEffect } from 'vue'
import { inject, watchEffect } from 'vue'
import { StoreKey } from "../../walletStore";
const store = inject(StoreKey)!
defineOptions({
inheritAttrs: false
@@ -7,19 +10,18 @@ defineOptions({
const props = defineProps<{
errors?: string[],
locales: Record<string, string>
}>();
const modelValue = defineModel<Record<string, string> | string>();
watchEffect(() => {
if (typeof modelValue.value === "string") {
const oldVal = modelValue.value;
modelValue.value = Object.fromEntries(Object.keys(props.locales).map((x): [string, string] => [x, oldVal]))
modelValue.value = Object.fromEntries(Object.keys(store.locales).map((x): [string, string] => [x, oldVal]))
}
})
</script>
<template lang="pug">
input.form-control(v-for="(human_readable, locale) in locales" v-model="modelValue[locale]" v-bind="$attrs" :lang="locale" :title="human_readable" :placeholder="human_readable")
input.form-control(v-for="(human_readable, locale) in store.locales" v-model="modelValue[locale]" v-bind="$attrs" :lang="locale" :title="human_readable" :placeholder="human_readable")
.help-block(v-if="props.errors" v-for="error in props.errors") {{ error }}
</template>
@@ -9,7 +9,7 @@ const props = defineProps<{
label?: string
choices: Array<[string, string]>
errors?: string[],
class: string
class?: string
}>()
const modelValue = defineModel<string|null>();
const id = useId()
@@ -1,17 +1,18 @@
<script setup lang="ts">
import { computed, reactive, watchEffect } from "vue";
import { computed, inject, reactive, watchEffect } from "vue";
import Select from "./input/select.vue";
import Input from "./input/input.vue";
import I18nInput from "./input/i18ninput.vue";
import TextContent from "./text-content.vue";
import { StoreKey } from "../walletStore";
const store = inject(StoreKey)!
const gettext = (window as any).gettext;
const props = defineProps<{
fieldgroup: PlaceholderFieldGroupDefinition;
overflows: FieldGroupDefinition[];
variables: Variables;
locales: Record<string, string>;
}>();
const fieldConfig = defineModel<PlaceholderFieldGroupConfig>({ required: true });
@@ -58,15 +59,13 @@ watchEffect(() => {
tr(v-for="n,i in fieldConfig.entries.length" :key="i")
td(v-if="fieldgroup.labels")
.i18n-form-group
I18nInput(v-model="fieldConfig.entries[n-1].label" :locales="locales")
I18nInput(v-model="fieldConfig.entries[n-1].label")
td
TextContent(v-if='fieldgroup.content_type == "text"'
v-model="fieldConfig.entries[n-1]"
:variables="props.variables"
:locales="locales")
v-model="fieldConfig.entries[n-1]")
Select(v-else-if='fieldgroup.content_type == "image"'
v-model="fieldConfig.entries[n-1].content"
:choices="Object.entries(props.variables).map(([k,v]) => [k, v.label])"
:choices="Object.entries(store.variables.image).map(([k,v]) => [k, v.label])"
)
td.text-right
button.btn.btn-danger.form-control-static(type="button" @click="fieldConfig.entries.splice(n-1, 1)")
@@ -1,14 +1,12 @@
<script setup lang="ts">
import { computed, watchEffect } from "vue";
import { computed, inject, watchEffect } from "vue";
import PlaceholderFieldSettings from "./placeholder-field-settings.vue";
import PredefinedFieldSettings from "./predefined-field-settings.vue";
const gettext = (window as any).gettext;
const props = defineProps<{
variables: VariableConfig
style?: Style;
locales: Record<string, string>;
}>();
const layout = defineModel<LayoutData>();
@@ -32,8 +30,6 @@ watchEffect(() => {
v-model="layout.fieldgroups[fieldgroup.identifier]"
:fieldgroup="fieldgroup"
:overflows="props.style.fieldgroups.slice(fieldgroupId + 1).filter(x => x.type == 'placeholder' && x.content_type === fieldgroup.content_type)"
:variables="variables[fieldgroup.content_type]"
:locales="locales"
)
PredefinedFieldSettings(v-else-if="fieldgroup.type == 'predefined'"
v-model="layout.fieldgroups[fieldgroup.identifier]"
@@ -1,18 +1,16 @@
<script setup lang="ts">
import { computed, reactive } from 'vue'
import { computed, inject, reactive } from 'vue'
import Select from './input/select.vue'
import I18nInput from './input/i18ninput.vue'
import { StoreKey } from "../walletStore";
const store = inject(StoreKey)!
const gettext = (window as any).gettext
const props = defineProps<{
variables: Variables
locales: Record<string, string>;
}>()
const entry = defineModel<FieldEntry>({ required: true })
const selectChoices = computed(() =>{
const choices = Object.entries(props.variables).map(([k,v]): [string, string] => [k, v.label])
const choices = Object.entries(store.variables.text).map(([k,v]): [string, string] => [k, v.label])
choices.push(["other", gettext("Other…")])
return choices
});
@@ -23,11 +21,9 @@ const selection = computed({
return entry.value.content
} else if (entry.value.type === 'custom') {
return "other"
} else {
throw new Error(`Unknown entry type "${entry.value.type}"`);
}
},
set(newValue) {
set(newValue) {
if (newValue == "other") {
entry.value.type = "custom"
entry.value.content = {};
@@ -44,11 +40,9 @@ const textContent = computed({
return ""
} else if (entry.value.type === 'custom') {
return entry.value.content
} else {
throw new Error(`Unknown entry type "${entry.value.type}"`);
}
},
set(newValue) {
set(newValue) {
entry.value.content = newValue
}
})
@@ -61,5 +55,5 @@ const textContent = computed({
v-model="selection"
:choices="selectChoices"
)
I18nInput(v-model="textContent" v-if="selection === 'other'" :locales="locales")
I18nInput(v-model="textContent" v-if="selection === 'other'" :locales="store.locales")
</template>
@@ -30,13 +30,13 @@ type PlaceholderFieldEntry = {
content?: string;
}
type ContentFieldEntry = {
type: FieldContentType;
type CustomFieldEntry = {
type: 'custom';
label?: I18nString;
content?: I18nString;
}
type FieldEntry = PlaceholderFieldEntry | ContentFieldEntry;
type FieldEntry = PlaceholderFieldEntry | CustomFieldEntry;
type Style = {
identifier: string;
@@ -57,7 +57,7 @@ type Platform = {
type Styles = Record<string, Style>;
type Variables = Record<string, Variable>;
type VariableConfig = Record<string, Variables>;
type Platforms = Record<string, Platform>;
type Platforms = Platform[];
type PlaceholderFieldGroupConfig = {
@@ -73,9 +73,20 @@ type LayoutData = {
fieldgroups: Record<string, FieldGroupConfig>;
};
type Layout = {
name?: string;
style?: string;
layout?: LayoutData;
type PlatformLayout = {
platform: string;
style: string | null;
layout: LayoutData;
};
type WalletLayout = {
name?: string;
platform_layouts: PlatformLayout[];
}
type WalletStore = {
platforms: Platforms,
variables: VariableConfig,
locales: Record<string, string>,
csrfToken: String,
walletLayout: WalletLayout | null
}
@@ -0,0 +1 @@
../../../../../../../pretix/static/pretixpresale/widget/src/lib/store.ts
@@ -1,13 +1,28 @@
import { createApp } from 'vue'
import App from './components/app.vue'
const mountEl = document.querySelector<HTMLElement>('#editor')!
const app = createApp(App, mountEl.dataset)
app.mount(mountEl)
import { createApp } from "vue";
import App from "./components/app.vue";
import { createWalletStore, StoreKey } from "./walletStore";
const mountEl = document.querySelector<HTMLElement>("#editor")!;
const store = createWalletStore({
platforms: JSON.parse(
document.querySelector("#platforms")?.textContent ?? "{}",
),
variables: JSON.parse(
document.querySelector("#variables")?.textContent ?? "{}",
),
locales: JSON.parse(document.querySelector("#locales")?.textContent ?? "{}"),
csrfToken: document.querySelector<HTMLInputElement>(
"input[name=csrfmiddlewaretoken]",
)?.value!!,
layoutId: mountEl.dataset.layoutId!
});
store.load();
const app = createApp(App);
app.provide(StoreKey, store);
app.mount(mountEl);
app.config.errorHandler = (error, _vm, info) => {
// vue fatals on errors by default, which is a weird choice
// https://github.com/vuejs/core/issues/3525
// https://github.com/vuejs/router/discussions/2435
console.error('[VUE]', info, error)
}
console.error("[VUE]", info, error);
};
@@ -0,0 +1,144 @@
import { createStore } from "./lib/store.ts";
import { nextTick, type InjectionKey } from "vue";
export type WidgetStore = ReturnType<typeof createWalletStore>;
export const StoreKey: InjectionKey<WidgetStore> = Symbol("WidgetStore");
export function createWalletStore(config: {
platforms: Platforms;
variables: VariableConfig;
locales: Record<string, string>;
csrfToken: string;
layoutId: string;
// platform_layouts: Record<string, PlatformLayout>;
}) {
return createStore({
state: () => ({
...config,
walletLayout: null as WalletLayout | null,
currentPlatform: config.platforms[0].identifier,
loaded: false,
}),
getters: {
currentPlatformStyles() {
for (const platform of this.platforms) {
if (platform.identifier === this.currentPlatform) {
return platform.styles;
}
}
throw "Unknown platform";
},
currentPlatformLayout() {
if (!this.walletLayout) {
throw "currentPlatformLayout access before store was loaded";
}
for (const layout of this.walletLayout.platform_layouts) {
if (layout.platform === this.currentPlatform) {
return layout;
}
}
const newLayout = {
platform: this.currentPlatform,
style: null,
layout: { fieldgroups: {} },
};
this.walletLayout.platform_layouts.push(newLayout);
return newLayout;
},
},
actions: {
load() {
// TODO: error handling / proper api client
fetch(
`/api/v1/organizers/demo/events/wallet/walletlayouts/${this.layoutId}/`,
)
.then((x) => x.json())
.then((x) => {
this.walletLayout = x;
this.loaded = true;
});
},
saveLayout() {
// TODO: error handling / proper api client
fetch(
`/api/v1/organizers/demo/events/wallet/walletlayouts/${this.layoutId}/`,
{
method: "PUT",
headers: {
"content-type": "application/json",
"X-CSRFToken": this.csrfToken,
},
body: JSON.stringify(this.walletLayout),
},
)
.then((x) => x.json())
.catch((x) => alert(x))
.then((x) => {
this.walletLayout = x;
});
},
setCurrentPlatformStyle(style: string | null) {
if (style === null) {
this.currentPlatformLayout.style = null;
this.currentPlatformLayout.layout.fieldgroups = {};
} else if (Object.keys(this.currentPlatformStyles).includes(style)) {
const oldStyle =
this.currentPlatformLayout.style !== null
? this.currentPlatformStyles[this.currentPlatformLayout.style]
: { fieldgroups: [] };
const newStyle = this.currentPlatformStyles[style];
const oldFieldGroups = Object.fromEntries(
oldStyle.fieldgroups.map((x) => [x.identifier, x]),
);
const newFieldGroups = Object.fromEntries(
newStyle.fieldgroups.map((x) => [x.identifier, x]),
);
const keysToKeep = new Set(
Object.keys(this.currentPlatformLayout.layout.fieldgroups).filter(
(x) =>
oldFieldGroups[x]?.type === "placeholder" &&
newFieldGroups[x]?.type === "placeholder" &&
oldFieldGroups[x]?.content_type ===
newFieldGroups[x]?.content_type,
),
);
const keysToDefault = new Set(Object.keys(newFieldGroups)).difference(
keysToKeep,
);
const keysToRemove = new Set(
Object.keys(this.currentPlatformLayout.layout.fieldgroups),
)
.difference(keysToKeep)
.difference(keysToDefault);
for (const key of keysToRemove) {
delete this.currentPlatformLayout.layout.fieldgroups[key];
}
for (const key of keysToDefault) {
if (newFieldGroups[key].type == "placeholder") {
this.currentPlatformLayout.layout.fieldgroups[key] = {overflow: null, entries: JSON.parse(JSON.stringify(newFieldGroups[key].default_entries))};
} else {
this.currentPlatformLayout.layout.fieldgroups[key] = {};
}
}
this.currentPlatformLayout.style = style;
}
// else if (this.currentPlatformLayout.style === null && Object.keys(this.currentPlatformStyles).includes(style)) {
// this.currentPlatformLayout.style = style;
// this.currentPlatformLayout.layout.fieldgroups = {}
// } else if (this.currentPlatformLayout.style !== null && Object.keys(this.currentPlatformStyles).includes(style)) {
// const oldStyle = this.currentPlatformStyles[this.currentPlatformLayout.style];
// const newStyle = this.currentPlatformStyles[style];
// this.currentPlatformLayout.style = style;
// console.log(oldStyle, newStyle)
// }
// this.currentPlatformLayout.style = style;
// }
// if (style == null) { }
},
},
});
}
+4 -1
View File
@@ -15,13 +15,16 @@ AVAILABLE_STYLES_DICT = {
plat: {s.identifier: s for s in styls} for plat, styls in AVAILABLE_STYLES.items()
}
# TODO? move to models?
def get_platform(identifier: str):
for platform in AVAILABLE_PLATFORMS:
if platform.identifier == identifier:
return platform
def get_style(platform: str, identifier: str):
return AVAILABLE_STYLES_DICT.get(platform, {}).get(identifier)
__all__ = ["AVAILABLE_PLATFORMS", "AVAILABLE_STYLES", "PassLayout"]
+6 -4
View File
@@ -44,7 +44,7 @@ class ApplePlatform(WalletPlatform):
order.code,
op.pk,
)
breakpoint()
context = {
"placeholders": get_layout_variables(op.order.event),
"evaluation_context": [op, order, order.event],
@@ -255,8 +255,9 @@ class AppleWalletEventTicket(AppleWalletStyle):
TextFieldGroup(
identifier="headers", name=_("Header"), max_entries=3
),
TextFieldGroup(identifier="auxillary", name=_("Auxillary"), max_entries=4),
TextFieldGroup(identifier="auxiliary", name=_("Auxiliary"), max_entries=4),
TextFieldGroup(identifier="back", name=_("Back")),
TextFieldGroup(identifier="code", name=_("QR-Code"), max_entries=1),
]
# preview_image = "apple/event_ticket.svg"
@@ -285,9 +286,10 @@ class AppleWalletEventTicket(AppleWalletStyle):
"secondaryFields": self.convert_fields(
strings, fields["secondary"], "secondary"
),
"auxillaryFields": self.convert_fields(
strings, fields["auxillary"], "auxillary"
"auxiliaryFields": self.convert_fields(
strings, fields["auxiliary"], "auxiliary"
),
"backFields": self.convert_fields(strings, fields["back"], "back"),
}
}
+4 -2
View File
@@ -3,7 +3,9 @@
"src/pretix/static/**/*",
"src/pretix/static/**/*.vue",
"src/pretix/plugins/webcheckin/**/*",
"src/pretix/plugins/webcheckin/**/*.vue"
"src/pretix/plugins/webcheckin/**/*.vue",
"src/pretix/plugins/wallet/**/*",
"src/pretix/plugins/wallet/**/*.vue"
],
"compilerOptions": {
"baseUrl": ".",
@@ -11,7 +13,7 @@
"strict": false,
"allowJs": true,
"checkJs": true,
"target": "esnext",
"target": "es2025",
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,