File settings handling; nicer file upload button

This commit is contained in:
Kara Engelhardt committed 2026-11-09 12:57:55 +01:00
1 parent 0f1f492397
commit f018d32804
9 files changed
+52 -69

No files matched your search

@@ -1,14 +1,12 @@
<script setup lang="ts">
import { ref, useId, watchEffect } from "vue";
import { computed, ref, useId, watchEffect } from "vue";
const gettext = (window as any).gettext;
defineOptions({
inheritAttrs: false,
});
const emit = defineEmits<{change: [File]}>()
const selectedFile = defineModel<{"name": string; "url": string;}>()
const props = defineProps<{
label?: I18nString;
label?: LocalizableString;
errors?: string[];
help_text?: string;
filename?: string;
@@ -17,28 +15,16 @@ const props = defineProps<{
}>();
const id = useId();
const selectedFile = ref(null);
function onChange(e) {
selectedFile.value = (e.target as HTMLInputElement).files[0] || null
emit("change", selectedFile.value)
}
// Reset input field if a url is provided
const inputRef = ref<HTMLInputElement>();
watchEffect(() => {
if (props.current_url && inputRef.value?.value) {
inputRef.value.value = '';
}
})
const clearFile = () => {
if (!inputRef.value) {
return
}
var event = new Event('change');
inputRef.value.value = '';
inputRef.value.dispatchEvent(event)
function clearFile() {
selectedFile.value = null;
}
</script>
<template lang="pug">
@@ -48,16 +34,14 @@ const clearFile = () => {
span.optional(v-if="!$attrs.required") {{ gettext("Optional") }}
div.col-md-9
template(v-if="!!current_url")
div(v-if="!!selectedFile")
| {{ gettext("Currently") + ': ' }}
a(:href="current_url" data-lightbox="input") {{ filename }}
| {{ " " }}
button.btn.btn-sm(@click.prevent="() => emit('change', null)") {{ gettext("Clear") }}
br
| {{ gettext("Change") + ': ' }}
input(:id="id" @change="onChange" v-bind="$attrs" :required="$attrs.required && !current_url" type="file" style="display: inline" ref="inputRef")
| {{ " " }}
button.btn.btn-sm(v-if="selectedFile" @click.prevent="clearFile") {{ gettext("Clear") }}
a(:href="selectedFile.url" data-lightbox="input") {{ selectedFile.name }}
.btn-group
button.btn.btn-default(type="button" @click="inputRef.click()") Select file
button.btn.btn-default(v-if="!!selectedFile || !!current_url" type="button" @click="clearFile") {{ gettext("Clear") }}
input(:id="id" @change="onChange" v-bind="$attrs" :required="$attrs.required && !current_url" type="file" style="display: none" ref="inputRef")
.help-block(v-if="!!help_text || !!props.default")
template(v-if="!!help_text") {{ help_text }}
br(v-if="!!help_text && !!props.default")
@@ -102,12 +102,12 @@ const placeholders = computed(() => {
.panel-body(v-if="fieldConfig")
span.text-muted(v-if="fieldgroup.description") {{ fieldgroup.description }}
h4 {{ gettext("Content") }}
.form-group()
.form-group
table.table.table-hover
thead
tr
th.col-md-5(v-if="fieldgroup.display == 'with_label'") {{ gettext('Label') }}
th(:class="'col-md-' + (fieldgroup.display == 'with_label' ? '6' : '11')") {{ gettext('Content') }}
th.col-5(v-if="fieldgroup.display == 'with_label'") {{ gettext('Label') }}
th(:class="'col-' + (fieldgroup.display == 'with_label' ? '6' : '11')") {{ gettext('Content') }}
th.col-xs-1
tbody
tr(v-for="n, i in fieldConfig.entries.length" :key="i")
@@ -123,9 +123,10 @@ const placeholders = computed(() => {
:choices="Object.entries(store.variables.image).map(([k, v]) => [k, i18nstringLocalize(v.label)])"
)
td.text-right
button.btn.btn-danger.form-control-static(type="button" @click="fieldConfig.entries.splice(n - 1, 1)")
button.btn.btn-danger.form-control-static(type="button" @click="fieldConfig.entries.splice(n - 1, 1)" v-if="fieldConfig.entries.length > (fieldgroup.min_entries||0)")
i.fa.fa-trash
span.sr-only {{ gettext('Delete') }}
.text-left.text-muted(v-else) {{ gettext("At least one field in this group is required") }}
button.btn.btn-default(type="button" @click="addVariable")
i.fa.fa-plus
@@ -12,24 +12,17 @@ const layoutStyle = computed(() => {
return Object.fromEntries(
Object.entries(layout.style)
.map(([k, v]) => {
if (v.setting) {
if (typeof v !== "string" && v.setting) {
const settingDef = store.styles[store.layout.style].settings.find(
(x) => x.identifier == v.setting,
);
const val = store.settings[v.setting];
if (!settingDef) {
v = null;
} else if (
settingDef.type == "image" &&
val &&
"file" in val &&
val.file
) {
v = `url(${URL.createObjectURL(val.file)})`; // TODO: encode
} else if (settingDef.type == "image" && val && val.url) {
v = `url(${val.url})`;
} else if (settingDef.type == "image" && val) {
v = `url(${val.url})`; // TODO: encode
} else if (settingDef.type == "image" && settingDef.default) {
v = `url(${settingDef.default})`;
v = `url(${settingDef.default})`; // TODO: encode
} else {
v = val;
}
@@ -53,8 +46,8 @@ const layoutStyle = computed(() => {
<style lang="css" scoped>
.pass-container {
width: 100%;
max-width: 360px;
width: 100%;
max-width: 360px;
vertical-align: top;
display: inline-block;
@@ -19,9 +19,6 @@ const settingValue = computed(() => {
const val = store.settings[config.setting];
if (settingDef.value.type == "image") {
if (val && "file" in val && val.file) {
return URL.createObjectURL(val.file);
}
if (val && "url" in val && val.url) {
return val.url;
} else if (settingDef.value.type == "image" && settingDef.value.default) {
@@ -18,6 +18,6 @@ const store = inject(StoreKey)!;
:label="field.label" :type="field.type" :help_text="field.help_text"
:min="field.min" :max="field.max" :defaultVal="field.default")
FileInput(v-if='field.type == "image"' :label="field.label" :required="field.required"
:help_text="field.help_text" @change="(v) => store.setSetting(field.identifier, v)"
:filename="store.settings[field.identifier]?.name" :current_url="store.settings[field.identifier]?.url" :default="field.default")
:help_text="field.help_text" @update:modelValue="(v) => store.setSetting(field.identifier, v)"
:modelValue="store.settings[field.identifier]" :default="field.default")
</template>
@@ -51,6 +51,7 @@ type PlaceholderFieldGroupDefinition = BaseFieldGroupDefinition & {
type PredefinedFieldGroupDefinition = BaseFieldGroupDefinition & {
type: "predefined";
default_active: boolean;
};
type LocalizableString = null | string | Record<string, string>;
@@ -97,7 +98,7 @@ type LayoutData = {
settings?: Record<string, any>;
};
type PreviewLayout = {"style": Record<string, string | {settting: string}>, "rows": PreviewRow};
type PreviewLayout = {"style": Record<string, string | {setting: string}>, "rows": PreviewRow};
type PreviewRow =
| {
@@ -9,7 +9,7 @@ function getDefaultFieldgroupState(
fieldgroup: FieldGroupDefinition,
): FieldGroupConfig {
if (fieldgroup.type == "predefined") {
return { active: fieldgroup.required };
return { active: fieldgroup.required || fieldgroup.default_active };
} else if (fieldgroup.type == "placeholder") {
return {
overflow: null,
@@ -84,8 +84,14 @@ export function createWalletStore(config: {
settings[setting.identifier] =
this.layout.settings[setting.identifier];
} else if (setting.type == "image") {
settings[setting.identifier] =
this.layout.file_settings[setting.identifier];
const val = this.layout.file_settings[setting.identifier];
if (val && "file" in val && val.file) {
settings[setting.identifier] = {"name": val.file.name, "url": URL.createObjectURL(val.file)};
} else if (val && "url" in val && val.url) {
settings[setting.identifier] = val;
} else {
settings[setting.identifier] = null;
}
}
}
return settings;
+13
View File
@@ -121,6 +121,14 @@ class CustomFieldEntry(FieldEntry[MaybeTranslatedString]):
class PredefinedFieldGroup(FieldGroup):
type = FieldGroupType.PREDEFINED
default_active: bool
def __init__(self, identifier: str, name: str, description=None, required=False, default_active=True):
self.identifier = identifier
self.name = name
self.required = required
self.description = description or ""
self.default_active = default_active
def layout_schema(
self,
@@ -129,6 +137,11 @@ class PredefinedFieldGroup(FieldGroup):
):
return {"type": "object", "properties": {"active": {"type": "boolean"}}}
def asdict(self, locales: list[str], context: LayoutContext):
return {
**super().asdict(locales, context),
"default_active": self.default_active,
}
class PlaceholderFieldGroup(FieldGroup):
type = FieldGroupType.PLACEHOLDER
@@ -29,7 +29,6 @@ from pretix.plugins.wallet.styles.base import (
ColorSettingsField,
FieldGroupDisplay,
FloatSettingsField,
ImageFieldGroup,
ImageSettingsField,
PassStyle,
PlaceholderFieldEntry,
@@ -232,17 +231,6 @@ class GoogleWalletDefaultEventTicket(GoogleWalletEventTicketStyle):
@property
def fieldgroups(self):
return [
ImageFieldGroup(
identifier="logo",
name=_("Logo"),
min_entries=0,
max_entries=1,
default_entries=[
PlaceholderFieldEntry(
content="poweredby",
)
],
),
PredefinedFieldGroup(identifier="venue", name=_("Venue")),
PredefinedFieldGroup(identifier="date", name=_("Date")),
PredefinedFieldGroup(identifier="seating", name=_("Seating")),