preview from server, qrcode in apple event pass

This commit is contained in:
Kara Engelhardt
2026-07-21 13:58:37 +02:00
parent 536e1a24a0
commit 2a2f87a4ea
5 changed files with 108 additions and 94 deletions
@@ -6,65 +6,53 @@ import Input from "./input/input.vue";
import PassPreview from "./preview/pass-preview.vue";
import { StoreKey } from "../walletStore";
const store = inject(StoreKey)!;
const gettext = (window as any).gettext;
const store = inject(StoreKey)!;
const platformChoices = computed(() => {
return [
[null, "Do not generate pass"],
...Object.values(store.currentPlatformStyles).map((x) => [
x.identifier,
x.name,
]),
];
});
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";
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);
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);
}
function openPreview(e: SubmitEvent) {
e.preventDefault();
openForm("../../preview/", {
csrfmiddlewaretoken: store.csrfToken,
platform: store.currentPlatform,
style: store.currentPlatformLayout.style,
layout: JSON.stringify(store.currentPlatformLayout.layout),
});
e.preventDefault();
openForm("../../preview/", {
csrfmiddlewaretoken: store.csrfToken,
platform: store.currentPlatform,
style: store.currentPlatformLayout.style,
layout: JSON.stringify(store.currentPlatformLayout.layout),
});
}
const preview_layouts = [
[
{
children: [
{ fieldgroup: "logo", relSize: 1 },
{ fieldgroup: "logo_text", relSize: 3 },
{ fieldgroup: "header", relSize: 2 },
],
},
{ fieldgroup: "primary" },
{ fieldgroup: "secondary" },
{ fieldgroup: "auxiliary" },
{ fieldgroup: "code" },
],
[{ fieldgroup: "back", mode: "column" }],
];
const platformChoices = computed(() => {
return [
[null, "Do not generate pass"],
...Object.values(store.currentPlatformStyles).map((x) => [
x.identifier,
x.name,
]),
];
});
const preview_layout = computed(() => {
return JSON.parse(store.currentPlatformStyles[store.currentPlatformLayout.style].preview_layout)
})
</script>
<template lang="pug">
@@ -82,23 +70,25 @@ const preview_layouts = [
a(role="tab" @click="store.currentPlatform = platform.identifier") {{ platform.name }}
.tabbed-form.tab-content
.tab-pane.active.row
.col-md-8
.col-md-6.col-lg-8
Select.form-group(label="Style" :modelValue="store.currentPlatformLayout.style" @update:modelValue="store.setCurrentPlatformStyle" :choices="platformChoices")
StyleSettings(v-if="store.currentPlatformLayout.style" v-model="store.currentPlatformLayout.layout" :style="store.currentPlatformStyles[store.currentPlatformLayout.style]")
.col-md-4
.col-md-6.col-lg-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.
PassPreview(v-for="layout in preview_layouts" :layout="layout")
pre
code {{ store.currentPlatformLayout }}
pre(v-if="store.currentPlatformLayout.style")
code {{ store.currentPlatformStyles[store.currentPlatformLayout.style] }}
pre
code {{ store.walletLayout }}
div(v-if="preview_layout")
span.text-muted The preview below is only a rough representation of what the pass might look like. Please check the generated pass.
div
PassPreview(v-for="layout in preview_layout" :layout="layout")
div(v-else) Preview not supported
//- pre
//- code {{ store.currentPlatformLayout }}
//- 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
@@ -3,40 +3,42 @@ type BaseFieldGroupDefinition = {
identifier: string;
name: string;
required: boolean;
}
};
type FieldGroupDefinition = PlaceholderFieldGroupDefinition | PredefinedFieldGroupDefinition;
type FieldGroupDefinition =
| PlaceholderFieldGroupDefinition
| PredefinedFieldGroupDefinition;
type FieldGroupDisplay = 'plain' | 'with_label' | 'code';
type FieldGroupDisplay = "plain" | "with_label" | "code";
type PlaceholderFieldGroupDefinition = BaseFieldGroupDefinition & {
type: 'placeholder';
type: "placeholder";
content_type: FieldContentType;
default_entries: FieldEntry[];
display: FieldGroupDisplay;
min_entries: number|null;
max_entries: number|null;
}
min_entries: number | null;
max_entries: number | null;
};
type PredefinedFieldGroupDefinition = BaseFieldGroupDefinition & {
type: 'predefined';
}
type: "predefined";
};
type I18nString = string | Record<string, string>
type I18nString = null | string | Record<string, string>;
type FieldContentType = 'text' | 'image';
type FieldContentType = "text" | "image";
type PlaceholderFieldEntry = {
type: 'placeholder';
label?: I18nString;
content?: string;
}
type: "placeholder";
label?: I18nString;
content?: string;
};
type CustomFieldEntry = {
type: 'custom';
label?: I18nString;
content?: I18nString;
}
type: "custom";
label?: I18nString;
content?: I18nString;
};
type FieldEntry = PlaceholderFieldEntry | CustomFieldEntry;
@@ -47,7 +49,7 @@ type Style = {
};
type Variable = {
label: string
label: string;
editor_sample: I18nString;
};
@@ -62,7 +64,6 @@ type Variables = Record<string, Variable>;
type VariableConfig = Record<string, Variables>;
type Platforms = Platform[];
type PlaceholderFieldGroupConfig = {
entries: Array<FieldEntry>;
overflow: string | null;
@@ -70,7 +71,9 @@ type PlaceholderFieldGroupConfig = {
type PredefinedFieldGroupConfig = {};
type FieldGroupConfig = PlaceholderFieldGroupConfig | PredefinedFieldGroupConfig;
type FieldGroupConfig =
| PlaceholderFieldGroupConfig
| PredefinedFieldGroupConfig;
type LayoutData = {
fieldgroups: Record<string, FieldGroupConfig>;
@@ -85,11 +88,20 @@ type PlatformLayout = {
type WalletLayout = {
name?: string;
platform_layouts: PlatformLayout[];
}
};
type WalletStore = {
platforms: Platforms,
variables: VariableConfig,
locales: Record<string, string>,
csrfToken: String,
walletLayout: WalletLayout | null
}
platforms: Platforms;
variables: VariableConfig;
locales: Record<string, string>;
csrfToken: String;
walletLayout: WalletLayout | null;
};
type PreviewLayout = Array<PreviewRow>;
type PreviewRow = { children: Array<PreviewRow> } | PreviewFieldgroup;
type PreviewFieldgroup = {
fieldgroup: string;
relSize?: number;
direction?: 'row' | 'column',
display?: Array<string>
};
+13 -3
View File
@@ -1,3 +1,5 @@
from typing import Any
from .base import (
FieldEntryType,
FieldGroupDisplay,
@@ -268,7 +270,6 @@ class AppleWalletEventTicket(AppleWalletStyle):
), # TODO: validation of max field count if combined "Coupons, store cards, and generic passes with a square barcode can have a total of up to four secondary and auxiliary fields, combined."
TextFieldGroup(identifier="header", name=_("Header"), max_entries=3),
TextFieldGroup(identifier="auxiliary", name=_("Auxiliary"), max_entries=4),
TextFieldGroup(identifier="back", name=_("Back")),
TextFieldGroup(
identifier="code",
name=_("QR-Code"),
@@ -280,8 +281,9 @@ class AppleWalletEventTicket(AppleWalletStyle):
)
],
),
TextFieldGroup(identifier="back", name=_("Back")),
]
# preview_image = "apple/event_ticket.svg"
preview_layout = '[[{"children":[{"fieldgroup":"logo","relSize":1},{"fieldgroup":"logo_text","relSize":3,"display":["bold","large","centered"]},{"fieldgroup":"header","relSize":2,"display":["large", "tight"]}]},{"fieldgroup":"primary","display":"large"},{"fieldgroup":"secondary"},{"fieldgroup":"auxiliary"},{"fieldgroup":"code"}],[{"fieldgroup":"back","direction":"column"}]]'
def convert_fields(self, strings, fields, prefix):
converted = []
@@ -300,7 +302,7 @@ class AppleWalletEventTicket(AppleWalletStyle):
return converted
def pass_content(self, fields, strings):
content = {
content: dict[str, Any] = {
"eventTicket": {
"primaryFields": self.convert_fields(
strings, fields["primary"], "primary"
@@ -319,4 +321,12 @@ class AppleWalletEventTicket(AppleWalletStyle):
content["logoText"] = self.convert_fields(
strings, fields["logo_text"], "logo_text"
)[0]["value"]
if fields['code']:
content["barcodes"] = [{
"format": "PKBarcodeFormatQR",
"message": str(fields["code"][0]["value"]),
"messageEncoding": "utf-8",
"altText": str(fields["code"][0]["value"])
}]
return content
+2 -1
View File
@@ -231,14 +231,15 @@ class PassStyle:
name: str
# order here limits in what order users can configure field "overspilling" (if too many fields are defined, where should the rest go) -> can only go down in the list
# we evaluate the fields in this order, so they overspill in this order as well (fields from primary are appended to the overspilling field before fields from secondary are etc)
fieldgroups: list[FieldGroup]
preview_layout: str | None
def asdict(self):
return {
"identifier": self.identifier,
"name": self.name,
"fieldgroups": [x.asdict() for x in self.fieldgroups],
"preview_layout": self.preview_layout
}
def layout_schema(self, context):
@@ -18,3 +18,4 @@ class GoogleWalletEventTicket(PassStyle):
PredefinedFieldGroup(identifier="seating", name=_("Seating")),
TextFieldGroup(identifier="qrcode", name=_("QR-Code"), display=FieldGroupDisplay.PLAIN),
]
preview_layout = None