mirror of
https://github.com/pretix/pretix.git
synced 2026-08-13 11:17:01 +00:00
WIP
proper fileupload handling, better store
This commit is contained in:
@@ -1,22 +1,24 @@
|
||||
from rest_framework import viewsets, serializers
|
||||
from rest_framework import viewsets
|
||||
from django.db import transaction
|
||||
from .styles import AVAILABLE_STYLES_DICT, AVAILABLE_PLATFORMS
|
||||
from .models import WalletLayout, WalletPlatformLayout
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.api.serializers.fields import UploadedFileField
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
import os
|
||||
|
||||
|
||||
class WalletPlatformLayoutSerializer(I18nAwareModelSerializer):
|
||||
platform = serializers.ChoiceField(
|
||||
choices=[p.identifier for p in AVAILABLE_PLATFORMS]
|
||||
)
|
||||
style = serializers.CharField(allow_null=True, required=False)
|
||||
file_settings = serializers.JSONField(default=dict, required=False)
|
||||
|
||||
class Meta:
|
||||
model = WalletPlatformLayout
|
||||
fields = ("platform", "style", "layout")
|
||||
fields = ("platform", "style", "layout", "file_settings")
|
||||
|
||||
def validate_layout(self, value):
|
||||
if not isinstance(value, dict):
|
||||
@@ -36,20 +38,25 @@ class WalletPlatformLayoutSerializer(I18nAwareModelSerializer):
|
||||
|
||||
style = style(event=self.context["event"], layout=data["layout"])
|
||||
style.validate()
|
||||
data['file_settings'] = style.extract_file_settings(self.context['request'])
|
||||
data["file_settings"] = style.extract_file_settings(
|
||||
self.context["request"], data.get("file_settings", {})
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
def to_representation(self, instance):
|
||||
ret = super().to_representation(instance)
|
||||
ret['file_settings'] = {}
|
||||
ret["file_settings"] = {}
|
||||
for file_setting in instance.file_settings.all():
|
||||
try:
|
||||
url = file_setting.file.url
|
||||
except AttributeError:
|
||||
continue
|
||||
request = self.context['request']
|
||||
ret['file_settings'][file_setting.key] = request.build_absolute_uri(url)
|
||||
request = self.context["request"]
|
||||
ret["file_settings"][file_setting.key] = {
|
||||
"url": request.build_absolute_uri(url),
|
||||
"name": os.path.basename(file_setting.file.name).split('.', 1)[-1]
|
||||
}
|
||||
return ret
|
||||
|
||||
|
||||
@@ -71,7 +78,6 @@ class WalletLayoutSerializer(I18nAwareModelSerializer):
|
||||
platform_layouts = validated_data.pop("platform_layouts")
|
||||
for layout in platform_layouts:
|
||||
if layout["style"]:
|
||||
# TODO: better handling here
|
||||
file_settings = layout.pop("file_settings", {})
|
||||
obj, _ = instance.platform_layouts.update_or_create(
|
||||
platform=layout["platform"], defaults=layout
|
||||
@@ -81,7 +87,10 @@ class WalletLayoutSerializer(I18nAwareModelSerializer):
|
||||
obj.file_settings.filter(key=key).delete()
|
||||
|
||||
elif file != "keep":
|
||||
obj.file_settings.update_or_create(key=key, defaults={"file": file})
|
||||
obj, _ = obj.file_settings.get_or_create(
|
||||
key=key
|
||||
)
|
||||
obj.file.save(os.path.basename(file.name), file)
|
||||
|
||||
instance.platform_layouts.exclude(
|
||||
platform__in={
|
||||
|
||||
@@ -33,24 +33,17 @@ function openPreview(e: Event) {
|
||||
openForm("../../preview/", {
|
||||
csrfmiddlewaretoken: store.csrfToken,
|
||||
platform: store.currentPlatform,
|
||||
style: store.currentPlatformLayout.style,
|
||||
layout: JSON.stringify(store.currentPlatformLayout.layout),
|
||||
style: store.layout.style,
|
||||
layout: JSON.stringify(store.layout.layout),
|
||||
});
|
||||
}
|
||||
|
||||
const platformChoices = computed(() => {
|
||||
return [
|
||||
[null, "Do not generate pass"],
|
||||
...Object.values(store.currentPlatformStyles).map((x) => [
|
||||
x.identifier,
|
||||
x.name,
|
||||
]),
|
||||
...Object.values(store.platform.styles).map((x) => [x.identifier, x.name]),
|
||||
];
|
||||
});
|
||||
|
||||
const preview_layout = computed(() => {
|
||||
return store.currentPlatformStyles[store.currentPlatformLayout.style]?.preview_layout;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
@@ -59,26 +52,26 @@ const preview_layout = computed(() => {
|
||||
// TODO: proper spinner
|
||||
|
||||
template(v-if="!store.loaded") {{ gettext("Loading...") }}
|
||||
form(v-else @submit.prevent="store.saveLayout")
|
||||
form(v-else @submit.prevent="store.save")
|
||||
.form-group
|
||||
Input(label="Name" v-model="store.walletLayout.name")
|
||||
Input(label="Name" v-model="store.name")
|
||||
nav
|
||||
ul.nav.nav-tabs
|
||||
li(v-for="platform in store.platforms" :class="{'active': store.currentPlatform === platform.identifier}")
|
||||
a(role="tab" @click="store.currentPlatform = platform.identifier") {{ platform.name }}
|
||||
li(v-for="platform in store.platforms" :class="{'active': store.platform.identifier === platform.identifier}")
|
||||
a(role="tab" @click="store.setPlatform(platform.identifier)") {{ platform.name }}
|
||||
.tabbed-form.tab-content
|
||||
.tab-pane.active.row
|
||||
.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]")
|
||||
Select.form-group(label="Style" :modelValue="store.style?.identifier || null" @update:modelValue="store.setStyle" :choices="platformChoices")
|
||||
StyleSettings(v-if="!!store.style")
|
||||
.col-md-6.col-lg-4
|
||||
.panel.panel-default
|
||||
.panel-heading Preview
|
||||
.panel-body
|
||||
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(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 preview_layout" :layout="layout")
|
||||
PassPreview(v-for="layout in store.style.preview_layout" :layout="layout")
|
||||
div(v-else) Preview not supported
|
||||
//- pre
|
||||
//- code {{ store.currentPlatformLayout }}
|
||||
@@ -92,23 +85,22 @@ const preview_layout = computed(() => {
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
+18
-13
@@ -1,25 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { useId } from "vue";
|
||||
import { ref, useId, watchEffect } from "vue";
|
||||
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const {
|
||||
label,
|
||||
errors,
|
||||
} = defineProps<{
|
||||
const emit = defineEmits<{change: [File]}>()
|
||||
const props = defineProps<{
|
||||
label?: I18nString;
|
||||
errors?: string[];
|
||||
help_text?: string;
|
||||
filename?: string;
|
||||
current_url?: string;
|
||||
}>();
|
||||
const modelValue = defineModel<string | File | null>();
|
||||
const id = useId();
|
||||
function onChange(e) {
|
||||
modelValue.value = (e.target as HTMLInputElement).files[0]
|
||||
emit("change", (e.target as HTMLInputElement).files[0])
|
||||
}
|
||||
|
||||
// Reset input field if a url is provided
|
||||
const inputRef = ref<HTMLInputElement>();
|
||||
watchEffect(() => {
|
||||
if (props.current_url && inputRef.value?.value) {
|
||||
inputRef.value.value = '';
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
@@ -28,18 +34,17 @@ function onChange(e) {
|
||||
br(v-if="!$attrs.required")
|
||||
span.optional(v-if="!$attrs.required") {{ gettext("Optional") }}
|
||||
div.col-md-9
|
||||
template(v-if="typeof modelValue == 'string'")
|
||||
template(v-if="!!current_url")
|
||||
| {{ gettext("Currently") + ': ' }}
|
||||
//- TODO: preview filename
|
||||
a(:href="modelValue") FILENAME
|
||||
a(:href="current_url") {{ filename }}
|
||||
| {{ " " }}
|
||||
button.btn.btn-sm(@click.prevent="() => {console.log('clear'); modelValue = null}") {{ gettext("Clear") }}
|
||||
button.btn.btn-sm(@click.prevent="() => {console.log('clear'); emit('change', null)}") {{ gettext("Clear") }}
|
||||
//- br
|
||||
//- a(:href="modelValue" data-lightbox="input")
|
||||
//- img.thumb-img(:src="modelValue")
|
||||
br
|
||||
| {{ gettext("Change") + ': ' }}
|
||||
input(:id="id" @change="onChange" v-bind="$attrs" type="file" style="display: inline")
|
||||
input(:id="id" @change="onChange" v-bind="$attrs" type="file" style="display: inline" ref="inputRef")
|
||||
.help-block(v-if="!!help_text") {{ help_text }}
|
||||
.help-block(v-if="!!errors" v-for="error in errors") {{ error }}
|
||||
</template>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, reactive, watchEffect } from "vue";
|
||||
import { computed, inject, watchEffect } from "vue";
|
||||
import Select from "./input/select.vue";
|
||||
import Checkbox from "./input/checkbox.vue";
|
||||
import I18nInput from "./input/i18ninput.vue";
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@ const props = defineProps<{
|
||||
|
||||
const style_def = computed(() => {
|
||||
return Object.fromEntries(
|
||||
store.currentPlatformStyles[
|
||||
store.currentPlatformLayout.style
|
||||
store.styles[
|
||||
store.layout.style
|
||||
].fieldgroups.map((x) => [x.identifier, x]),
|
||||
)[props.config.fieldgroup];
|
||||
});
|
||||
|
||||
+1
-1
@@ -13,5 +13,5 @@ const props = defineProps<{
|
||||
|
||||
<template lang="pug">
|
||||
div.fieldgroup-container(:style="{'flex-grow': config.relSize, 'flex-direction': config.direction || 'row'}")
|
||||
FieldgroupItemPreview(v-for="{ label, content } of store.currentLayoutFieldContent[config.fieldgroup]" :label="label" :content="content" :content_type="style_def.content_type" :display_class="config.display" :display="style_def.display")
|
||||
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>
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const props = defineProps<{
|
||||
|
||||
const isActive = computed(
|
||||
() =>
|
||||
store.currentPlatformLayout.layout.fieldgroups[props.style_def.identifier]?.active,
|
||||
store.layout.fieldgroups[props.style_def.identifier]?.active,
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
+2
-2
@@ -12,13 +12,13 @@ const { config } = defineProps<{
|
||||
|
||||
const settingDef = computed(() => {
|
||||
return Object.fromEntries(
|
||||
store.currentPlatformStyles[store.currentPlatformLayout.style].settings.map(
|
||||
store.styles[store.layout.style].settings.map(
|
||||
(x) => [x.identifier, x],
|
||||
),
|
||||
)[config.setting];
|
||||
});
|
||||
const settingValue = computed(() => {
|
||||
const val = store.currentLayoutSettings[config.setting];
|
||||
const val = store.settings[config.setting];
|
||||
if (settingDef.value.type == "image" && val instanceof File) {
|
||||
return URL.createObjectURL(val);
|
||||
} else {
|
||||
|
||||
+2
-21
@@ -4,33 +4,14 @@ import FileInput from "./input/file-input.vue";
|
||||
import { inject } from "vue";
|
||||
import { StoreKey } from "../walletStore";
|
||||
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{
|
||||
field?: Setting;
|
||||
}>();
|
||||
const store = inject(StoreKey)!;
|
||||
|
||||
// function uploadFile(e: Event) {
|
||||
// // TODO: only store ref, upload when saving, proper handling in store
|
||||
// const [file] = (e.target as HTMLInputElement).files;
|
||||
// store.setSetting(props.field.identifier, file)
|
||||
// // const settings = store.currentPlatformLayout.layout.settings
|
||||
// // const identifier = props.field.identifier;
|
||||
// // fetch("/api/v1/upload", {
|
||||
// // method: "POST",
|
||||
// // body: file,
|
||||
// // headers: {
|
||||
// // "content-disposition": `attachment; filename="${encodeURI(file.name)}"`,
|
||||
// // "content-type": file.type || "application/octet-stream",
|
||||
// // "X-CSRFToken": store.csrfToken,
|
||||
// // },
|
||||
// // }).then(x => x.json()).then(x=>settings[identifier] = x.id);
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
div.form-group
|
||||
Input(v-if='field.type == "text"' :label="field.label" :type="field.type" :required="field.required" @update:modelValue="(v) => store.setSetting(field.identifier, v)" :modelValue="store.currentLayoutSettings[field.identifier]" :help_text="field.help_text")
|
||||
FileInput(v-if='field.type == "image"' :label="field.label" :required="field.required" :help_text="field.help_text" @update:modelValue="(v) => store.setSetting(field.identifier, v)" :modelValue="store.currentLayoutSettings[field.identifier]")
|
||||
Input(v-if='field.type == "text"' :label="field.label" :type="field.type" :required="field.required" @update:modelValue="(v) => store.setSetting(field.identifier, v)" :modelValue="store.settings[field.identifier]" :help_text="field.help_text")
|
||||
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]?.name")
|
||||
</template>
|
||||
|
||||
+8
-12
@@ -1,31 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, watchEffect } from "vue";
|
||||
import { inject } from "vue";
|
||||
import PlaceholderFieldSettings from "./placeholder-field-settings.vue";
|
||||
import PredefinedFieldSettings from "./predefined-field-settings.vue";
|
||||
import SettingsField from "./settings-field.vue";
|
||||
import { StoreKey } from "../walletStore.js";
|
||||
const gettext = (window as any).gettext;
|
||||
|
||||
const props = defineProps<{
|
||||
style?: Style;
|
||||
}>();
|
||||
|
||||
const layout = defineModel<LayoutData>();
|
||||
const store = inject(StoreKey)!;
|
||||
</script>
|
||||
|
||||
<template lang="pug">
|
||||
h2.h3 {{ gettext("Settings") }}
|
||||
SettingsField(v-for="field of style.settings" :field="field" :key="field.identifier")
|
||||
SettingsField(v-for="field of store.style.settings" :field="field" :key="field.identifier")
|
||||
h2.h3 {{ gettext("Field Groups") }}
|
||||
div(v-if="props.style && layout.fieldgroups"
|
||||
v-for="(fieldgroup, fieldgroupId) in props.style.fieldgroups" :id="'fieldgroup-' + fieldgroup.identifier")
|
||||
div(v-for="(fieldgroup, fieldgroupId) in store.style.fieldgroups" :id="'fieldgroup-' + fieldgroup.identifier")
|
||||
PlaceholderFieldSettings(
|
||||
v-if="fieldgroup.type == 'placeholder'"
|
||||
v-model="layout.fieldgroups[fieldgroup.identifier]"
|
||||
v-model="store.layout.fieldgroups[fieldgroup.identifier]"
|
||||
:fieldgroup="fieldgroup"
|
||||
:overflows="props.style.fieldgroups.slice(fieldgroupId + 1) \
|
||||
:overflows="store.style.fieldgroups.slice(fieldgroupId + 1) \
|
||||
.filter(x => x.type == 'placeholder' && x.content_type === fieldgroup.content_type)"
|
||||
)
|
||||
PredefinedFieldSettings(v-else-if="fieldgroup.type == 'predefined'"
|
||||
v-model="layout.fieldgroups[fieldgroup.identifier]"
|
||||
v-model="store.layout.fieldgroups[fieldgroup.identifier]"
|
||||
:fieldgroup="fieldgroup")
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
type Platform = {
|
||||
identifier: string;
|
||||
name: string;
|
||||
styles: Styles;
|
||||
};
|
||||
|
||||
type Style = {
|
||||
identifier: string;
|
||||
name: string;
|
||||
fieldgroups: FieldGroupDefinition[];
|
||||
settings: Setting[];
|
||||
};
|
||||
|
||||
type Variable = {
|
||||
label: string;
|
||||
sample: string;
|
||||
required_context: string[];
|
||||
};
|
||||
|
||||
type Styles = Record<string, Style>;
|
||||
type Variables = Record<string, Variable>;
|
||||
type VariableConfig = Record<string, Variables>;
|
||||
type Platforms = Platform[];
|
||||
|
||||
//
|
||||
|
||||
type BaseFieldGroupDefinition = {
|
||||
type: string;
|
||||
identifier: string;
|
||||
@@ -46,38 +72,15 @@ type FieldEntry = PlaceholderFieldEntry | CustomFieldEntry;
|
||||
|
||||
type Setting = {
|
||||
identifier: string;
|
||||
label: I18nString;
|
||||
label: string;
|
||||
type: "text" | "image";
|
||||
required: boolean;
|
||||
help_text: string;
|
||||
};
|
||||
type Style = {
|
||||
identifier: string;
|
||||
name: string;
|
||||
fieldgroups: FieldGroupDefinition[];
|
||||
settings: Setting[]
|
||||
};
|
||||
|
||||
type Variable = {
|
||||
label: string;
|
||||
sample: string;
|
||||
required_context: string[];
|
||||
};
|
||||
|
||||
type Platform = {
|
||||
identifier: string;
|
||||
name: string;
|
||||
styles: Styles;
|
||||
};
|
||||
|
||||
type Styles = Record<string, Style>;
|
||||
type Variables = Record<string, Variable>;
|
||||
type VariableConfig = Record<string, Variables>;
|
||||
type Platforms = Platform[];
|
||||
|
||||
type PlaceholderFieldGroupConfig = {
|
||||
entries: Array<FieldEntry>;
|
||||
overflow: string | null;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
type PredefinedFieldGroupConfig = {
|
||||
@@ -90,7 +93,7 @@ type FieldGroupConfig =
|
||||
|
||||
type LayoutData = {
|
||||
fieldgroups?: Record<string, FieldGroupConfig>;
|
||||
settings?: Record<string, any>
|
||||
settings?: Record<string, any>;
|
||||
};
|
||||
|
||||
type PlatformLayout = {
|
||||
@@ -113,7 +116,11 @@ type WalletStore = {
|
||||
|
||||
type PreviewLayout = Array<PreviewRow>;
|
||||
type PreviewRow =
|
||||
| { children: Array<PreviewRow>; direction?: "row" | "column"; display?: Array<string>;}
|
||||
| {
|
||||
children: Array<PreviewRow>;
|
||||
direction?: "row" | "column";
|
||||
display?: Array<string>;
|
||||
}
|
||||
| PreviewFieldgroup
|
||||
| FixedPreview
|
||||
| SettingPreview;
|
||||
@@ -122,13 +129,15 @@ type PreviewProps = {
|
||||
relSize?: number;
|
||||
direction?: "row" | "column";
|
||||
display?: Array<string>;
|
||||
}
|
||||
};
|
||||
type SettingPreview = {
|
||||
setting: string;
|
||||
} & PreviewProps
|
||||
} & PreviewProps;
|
||||
|
||||
type PreviewFieldgroup = PredefinedFieldgroupPreview | PlaceholderFieldGroupPreview;
|
||||
type FixedPreview = {value: I18nString; label?: I18nString; } & PreviewProps
|
||||
type PreviewFieldgroup =
|
||||
| PredefinedFieldgroupPreview
|
||||
| PlaceholderFieldGroupPreview;
|
||||
type FixedPreview = { value: I18nString; label?: I18nString } & PreviewProps;
|
||||
|
||||
type PlaceholderFieldGroupPreview = {
|
||||
fieldgroup: string;
|
||||
@@ -142,4 +151,14 @@ type PredefinedFieldgroupPreview = {
|
||||
type PreviewSample = {
|
||||
content: I18nString;
|
||||
label: I18nString;
|
||||
}
|
||||
};
|
||||
|
||||
type NewPlatformLayout = {
|
||||
style: string;
|
||||
fieldgroups: {};
|
||||
settings: {};
|
||||
file_settings: Record<string, WalletFile>;
|
||||
};
|
||||
type ServerSideFile = { url: string; name: string };
|
||||
type ClientSideFile = { file: File | null; identifier?: string };
|
||||
type WalletFile = ServerSideFile | ClientSideFile;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { serialize } from "node:v8";
|
||||
import { i18nstringLocalize } from "./helpers.js";
|
||||
import { createStore } from "./lib/store.ts";
|
||||
import { toRaw, type InjectionKey } from "vue";
|
||||
@@ -5,6 +6,43 @@ import { toRaw, type InjectionKey } from "vue";
|
||||
export type WidgetStore = ReturnType<typeof createWalletStore>;
|
||||
export const StoreKey: InjectionKey<WidgetStore> = Symbol("WidgetStore");
|
||||
|
||||
function getDefaultFieldgroupState(
|
||||
fieldgroup: FieldGroupDefinition,
|
||||
): FieldGroupConfig {
|
||||
if (fieldgroup.type == "predefined") {
|
||||
return { active: fieldgroup.required };
|
||||
} else if (fieldgroup.type == "placeholder") {
|
||||
return {
|
||||
overflow: null,
|
||||
entries: JSON.parse(JSON.stringify(fieldgroup.default_entries)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function parseExistingFieldgroupState(
|
||||
fieldgroup: FieldGroupDefinition,
|
||||
existing?: FieldGroupConfig,
|
||||
): FieldGroupConfig {
|
||||
if (!existing) {
|
||||
return getDefaultFieldgroupState(fieldgroup);
|
||||
}
|
||||
if (fieldgroup.type == "predefined") {
|
||||
return {
|
||||
active: "active" in existing ? existing.active : fieldgroup.required,
|
||||
};
|
||||
} else if (fieldgroup.type == "placeholder") {
|
||||
return {
|
||||
overflow: "overflow" in existing ? existing.overflow : null,
|
||||
// TODO: check that placeholders are possible
|
||||
entries: structuredClone(
|
||||
toRaw(
|
||||
"entries" in existing ? existing.entries : fieldgroup.default_entries,
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function createWalletStore(config: {
|
||||
platforms: Platforms;
|
||||
variables: VariableConfig;
|
||||
@@ -15,53 +53,51 @@ export function createWalletStore(config: {
|
||||
return createStore({
|
||||
state: () => ({
|
||||
...config,
|
||||
walletLayout: null as WalletLayout | null,
|
||||
currentPlatform: config.platforms[0].identifier,
|
||||
loaded: false,
|
||||
files: {} as Record<string, File>
|
||||
activePlatform: config.platforms[0].identifier,
|
||||
name: null as string | null,
|
||||
platformLayouts: {} as Record<string, NewPlatformLayout>,
|
||||
}),
|
||||
getters: {
|
||||
currentPlatformStyles() {
|
||||
for (const platform of this.platforms) {
|
||||
if (platform.identifier === this.currentPlatform) {
|
||||
return platform.styles;
|
||||
platform() {
|
||||
return this.getPlatform(this.activePlatform);
|
||||
},
|
||||
layout() {
|
||||
if (!this.loaded) {
|
||||
return;
|
||||
}
|
||||
return this.platformLayouts[this.activePlatform] || null;
|
||||
},
|
||||
style(): Style {
|
||||
if (this.layout) {
|
||||
return this.platform.styles[this.layout.style];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
styles() {
|
||||
return this.platform.styles;
|
||||
},
|
||||
settings() {
|
||||
const settings = {};
|
||||
for (const setting of this.style.settings) {
|
||||
if (setting.type == "text") {
|
||||
settings[setting.identifier] =
|
||||
this.layout.settings[setting.identifier];
|
||||
} else if (setting.type == "image") {
|
||||
settings[setting.identifier] =
|
||||
this.layout.file_settings[setting.identifier];
|
||||
}
|
||||
}
|
||||
throw "Unknown platform";
|
||||
return settings;
|
||||
},
|
||||
currentPlatformLayout(): PlatformLayout {
|
||||
if (!this.walletLayout) {
|
||||
throw "currentPlatformLayout access before store was loaded";
|
||||
}
|
||||
for (const layout of this.walletLayout.platform_layouts) {
|
||||
if (layout.platform === this.currentPlatform) {
|
||||
if (!("fieldgroups" in layout.layout)) {
|
||||
layout.layout.fieldgroups = {};
|
||||
}
|
||||
if (!("settings" in layout.layout)) {
|
||||
layout.layout.settings = {};
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
const newLayout = {
|
||||
platform: this.currentPlatform,
|
||||
style: null,
|
||||
layout: { fieldgroups: {}, settings: {} },
|
||||
};
|
||||
this.walletLayout.platform_layouts.push(newLayout);
|
||||
return newLayout;
|
||||
},
|
||||
|
||||
currentLayoutFieldContent() {
|
||||
renderedFieldGroups() {
|
||||
const content = {};
|
||||
const group_defs =
|
||||
this.currentPlatformStyles[this.currentPlatformLayout.style]
|
||||
.fieldgroups;
|
||||
const group_defs = this.style.fieldgroups;
|
||||
for (const fieldgroup of group_defs) {
|
||||
if (fieldgroup.type == "placeholder") {
|
||||
content[fieldgroup.identifier] = [];
|
||||
const layout_group = this.currentPlatformLayout.layout.fieldgroups[
|
||||
const layout_group = this.layout.fieldgroups[
|
||||
fieldgroup.identifier
|
||||
] as any as PlaceholderFieldGroupConfig;
|
||||
for (const entry of layout_group.entries) {
|
||||
@@ -94,7 +130,7 @@ export function createWalletStore(config: {
|
||||
for (const fieldgroup of group_defs) {
|
||||
if (fieldgroup.type == "placeholder") {
|
||||
const layout_group: PlaceholderFieldGroupConfig =
|
||||
this.currentPlatformLayout.layout.fieldgroups[
|
||||
this.layout.fieldgroups[
|
||||
fieldgroup.identifier
|
||||
];
|
||||
if (
|
||||
@@ -116,11 +152,15 @@ export function createWalletStore(config: {
|
||||
|
||||
return content;
|
||||
},
|
||||
currentLayoutSettings() {
|
||||
return {...this.currentPlatformLayout.file_settings, ...this.currentPlatformLayout.layout.settings}
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
getPlatform(identifier: string): Platform {
|
||||
for (const platform of this.platforms) {
|
||||
if (platform.identifier === identifier) {
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
},
|
||||
load() {
|
||||
// TODO: error handling / proper api client
|
||||
fetch(
|
||||
@@ -128,44 +168,134 @@ export function createWalletStore(config: {
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.then((x) => {
|
||||
this.walletLayout = x;
|
||||
this.parseServerLayout(x);
|
||||
this.loaded = true;
|
||||
});
|
||||
})
|
||||
.catch(alert);
|
||||
},
|
||||
async uploadFile(file: File) {
|
||||
parseServerLayout(serverLayout) {
|
||||
this.name = serverLayout.name;
|
||||
for (const layout of serverLayout.platform_layouts) {
|
||||
const clientLayout = {
|
||||
style: layout.style,
|
||||
fieldgroups: {},
|
||||
settings: layout.layout.settings,
|
||||
file_settings: layout.file_settings,
|
||||
};
|
||||
const styleDefinition = this.getPlatform(layout.platform).styles[
|
||||
layout.style
|
||||
];
|
||||
console.log(styleDefinition);
|
||||
for (const fieldgroup of styleDefinition.fieldgroups) {
|
||||
clientLayout.fieldgroups[fieldgroup.identifier] =
|
||||
parseExistingFieldgroupState(
|
||||
fieldgroup,
|
||||
layout.layout.fieldgroups[fieldgroup.identifier],
|
||||
);
|
||||
}
|
||||
this.platformLayouts[layout.platform] = clientLayout;
|
||||
}
|
||||
},
|
||||
setPlatform(platform: string) {
|
||||
this.activePlatform = platform;
|
||||
},
|
||||
setStyle(style: string | null) {
|
||||
if (style === null) {
|
||||
delete this.platformLayouts[this.activePlatform];
|
||||
} else if (Object.keys(this.platform.styles).includes(style)) {
|
||||
const newLayout = {
|
||||
style,
|
||||
fieldgroups: {},
|
||||
settings: {},
|
||||
file_settings: {},
|
||||
};
|
||||
for (const fieldgroup of this.platform.styles[style].fieldgroups) {
|
||||
newLayout.fieldgroups[fieldgroup.identifier] =
|
||||
getDefaultFieldgroupState(fieldgroup);
|
||||
}
|
||||
this.platformLayouts[this.activePlatform] = newLayout;
|
||||
// TODO: keep old fieldgroups & settings if matching
|
||||
}
|
||||
},
|
||||
getSetting(identifier: string): Setting {
|
||||
for (const setting of this.style.settings) {
|
||||
if (setting.identifier === identifier) {
|
||||
return setting;
|
||||
}
|
||||
}
|
||||
},
|
||||
setSetting(identifier: string, value: string | File) {
|
||||
const setting = this.getSetting(identifier);
|
||||
if (!setting) return;
|
||||
|
||||
if (
|
||||
setting.type === "image" &&
|
||||
(value instanceof File || value === null)
|
||||
) {
|
||||
this.layout.file_settings[setting.identifier] = {
|
||||
file: value as File | null,
|
||||
};
|
||||
} else if (setting.type == "text" && typeof value == "string") {
|
||||
this.layout.settings[setting.identifier] = value;
|
||||
}
|
||||
},
|
||||
async uploadFile(file: ClientSideFile) {
|
||||
return await fetch("/api/v1/upload", {
|
||||
method: "POST",
|
||||
body: file,
|
||||
body: file.file,
|
||||
headers: {
|
||||
"content-disposition": `attachment; filename="${encodeURI(file.name)}"`,
|
||||
"content-type": file.type || "application/octet-stream",
|
||||
"content-disposition": `attachment; filename="${encodeURI(file.file.name)}"`,
|
||||
"content-type": file.file.type || "application/octet-stream",
|
||||
"X-CSRFToken": this.csrfToken,
|
||||
},
|
||||
})
|
||||
.then((x) => x.json())
|
||||
.then((x) => x.id);
|
||||
.then((x) => {
|
||||
file.identifier = x.id;
|
||||
return x.id;
|
||||
})
|
||||
.catch(alert);
|
||||
},
|
||||
async saveLayout() {
|
||||
const layoutToSave = structuredClone(toRaw(this.walletLayout))
|
||||
// TODO: error handling, parallelization
|
||||
for (const platformLayout of layoutToSave.platform_layouts) {
|
||||
const platformStyle = this.platforms.filter(
|
||||
(x) => x.identifier == platformLayout.platform,
|
||||
)[0].styles[platformLayout.style];
|
||||
for (const setting of platformStyle.settings) {
|
||||
console.log(setting, platformLayout.layout?.settings[setting.identifier])
|
||||
if (
|
||||
setting.type === "image" &&
|
||||
platformLayout.layout?.settings[setting.identifier] instanceof
|
||||
File
|
||||
) {
|
||||
platformLayout.layout.settings[setting.identifier] = await
|
||||
this.uploadFile(
|
||||
platformLayout.layout.settings[setting.identifier],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
async serializePlatformLayout(platform, layout: NewPlatformLayout) {
|
||||
const uploadedFiles = Object.fromEntries(
|
||||
(
|
||||
await Promise.all(
|
||||
Object.entries(layout.file_settings).map(async ([k, v]) => {
|
||||
if ("url" in v) {
|
||||
return;
|
||||
} else if ("identifier" in v) {
|
||||
return [k, v.identifier];
|
||||
} else if ("file" in v && v.file instanceof File) {
|
||||
return [k, await this.uploadFile(v)];
|
||||
} else if ("file" in v && v.file == null) {
|
||||
return [k, null];
|
||||
}
|
||||
}),
|
||||
)
|
||||
).filter((x) => !!x),
|
||||
);
|
||||
return {
|
||||
platform,
|
||||
style: layout.style,
|
||||
file_settings: uploadedFiles,
|
||||
layout: {
|
||||
fieldgroups: layout.fieldgroups,
|
||||
settings: layout.settings,
|
||||
},
|
||||
};
|
||||
},
|
||||
async serializeLayout() {
|
||||
const layoutPromises = Object.entries(this.platformLayouts).map(
|
||||
([platform, layout]) =>
|
||||
this.serializePlatformLayout(platform, layout),
|
||||
);
|
||||
return {
|
||||
name: this.name,
|
||||
platform_layouts: await Promise.all(layoutPromises),
|
||||
};
|
||||
},
|
||||
async save() {
|
||||
const serializedLayout = await this.serializeLayout();
|
||||
// TODO: error handling / proper api client
|
||||
fetch(
|
||||
`/api/v1/organizers/demo/events/wallet/walletlayouts/${this.layoutId}/`,
|
||||
@@ -175,78 +305,15 @@ export function createWalletStore(config: {
|
||||
"content-type": "application/json",
|
||||
"X-CSRFToken": this.csrfToken,
|
||||
},
|
||||
body: JSON.stringify(layoutToSave),
|
||||
body: JSON.stringify(serializedLayout),
|
||||
},
|
||||
)
|
||||
.then((x) => x.json())
|
||||
.catch((x) => alert(x))
|
||||
.then((x) => {
|
||||
this.walletLayout = x;
|
||||
this.parseServerLayout(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),
|
||||
),
|
||||
active:
|
||||
newFieldGroups[key].required ||
|
||||
newFieldGroups[key].default_entries.length > 0,
|
||||
};
|
||||
} else {
|
||||
this.currentPlatformLayout.layout.fieldgroups[key] = {
|
||||
active: newFieldGroups[key].required,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.currentPlatformLayout.style = style;
|
||||
}
|
||||
},
|
||||
setSetting(identifier: string, value: string | File | null) {
|
||||
this.currentPlatformLayout.layout.settings[identifier] = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
import { i18nstringLocalize } from "./helpers.js";
|
||||
import { createStore } from "./lib/store.ts";
|
||||
import { toRaw, 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;
|
||||
}) {
|
||||
return createStore({
|
||||
state: () => ({
|
||||
...config,
|
||||
walletLayout: null as WalletLayout | null,
|
||||
currentPlatform: config.platforms[0].identifier,
|
||||
loaded: false,
|
||||
files: {} as Record<string, File>
|
||||
}),
|
||||
getters: {
|
||||
currentPlatformStyles() {
|
||||
for (const platform of this.platforms) {
|
||||
if (platform.identifier === this.currentPlatform) {
|
||||
return platform.styles;
|
||||
}
|
||||
}
|
||||
throw "Unknown platform";
|
||||
},
|
||||
currentPlatformLayout(): PlatformLayout {
|
||||
if (!this.walletLayout) {
|
||||
throw "currentPlatformLayout access before store was loaded";
|
||||
}
|
||||
for (const layout of this.walletLayout.platform_layouts) {
|
||||
if (layout.platform === this.currentPlatform) {
|
||||
if (!("fieldgroups" in layout.layout)) {
|
||||
layout.layout.fieldgroups = {};
|
||||
}
|
||||
if (!("settings" in layout.layout)) {
|
||||
layout.layout.settings = {};
|
||||
}
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
const newLayout = {
|
||||
platform: this.currentPlatform,
|
||||
style: null,
|
||||
layout: { fieldgroups: {}, settings: {} },
|
||||
};
|
||||
this.walletLayout.platform_layouts.push(newLayout);
|
||||
return newLayout;
|
||||
},
|
||||
|
||||
currentLayoutFieldContent() {
|
||||
const content = {};
|
||||
const group_defs =
|
||||
this.currentPlatformStyles[this.currentPlatformLayout.style]
|
||||
.fieldgroups;
|
||||
for (const fieldgroup of group_defs) {
|
||||
if (fieldgroup.type == "placeholder") {
|
||||
content[fieldgroup.identifier] = [];
|
||||
const layout_group = this.currentPlatformLayout.layout.fieldgroups[
|
||||
fieldgroup.identifier
|
||||
] as any as PlaceholderFieldGroupConfig;
|
||||
for (const entry of layout_group.entries) {
|
||||
const placeholder =
|
||||
entry.type === "placeholder"
|
||||
? this.variables[fieldgroup.content_type][entry.content]
|
||||
: null;
|
||||
|
||||
let label = i18nstringLocalize(entry.label);
|
||||
if (placeholder && !label) {
|
||||
label = i18nstringLocalize(placeholder.label);
|
||||
}
|
||||
|
||||
let value = null;
|
||||
if (entry.type == "custom") {
|
||||
value = i18nstringLocalize(entry.content);
|
||||
} else if (entry.type == "placeholder") {
|
||||
value =
|
||||
placeholder?.sample ||
|
||||
`(unknown placeholder: ${entry.content})`;
|
||||
}
|
||||
content[fieldgroup.identifier].push({
|
||||
entry,
|
||||
label,
|
||||
content: value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const fieldgroup of group_defs) {
|
||||
if (fieldgroup.type == "placeholder") {
|
||||
const layout_group: PlaceholderFieldGroupConfig =
|
||||
this.currentPlatformLayout.layout.fieldgroups[
|
||||
fieldgroup.identifier
|
||||
];
|
||||
if (
|
||||
fieldgroup.max_entries &&
|
||||
content[fieldgroup.identifier].length > fieldgroup.max_entries
|
||||
) {
|
||||
const overflow = content[fieldgroup.identifier].slice(
|
||||
fieldgroup.max_entries,
|
||||
);
|
||||
content[fieldgroup.identifier] = content[
|
||||
fieldgroup.identifier
|
||||
].slice(0, fieldgroup.max_entries);
|
||||
if (layout_group.overflow) {
|
||||
content[layout_group.overflow].splice(0, 0, ...overflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
},
|
||||
currentLayoutSettings() {
|
||||
return {...this.currentPlatformLayout.file_settings, ...this.currentPlatformLayout.layout.settings}
|
||||
},
|
||||
},
|
||||
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;
|
||||
});
|
||||
},
|
||||
async uploadFile(file: File) {
|
||||
return await fetch("/api/v1/upload", {
|
||||
method: "POST",
|
||||
body: file,
|
||||
headers: {
|
||||
"content-disposition": `attachment; filename="${encodeURI(file.name)}"`,
|
||||
"content-type": file.type || "application/octet-stream",
|
||||
"X-CSRFToken": this.csrfToken,
|
||||
},
|
||||
})
|
||||
.then((x) => x.json())
|
||||
.then((x) => x.id);
|
||||
},
|
||||
async saveLayout() {
|
||||
const layoutToSave = structuredClone(toRaw(this.walletLayout))
|
||||
// TODO: error handling, parallelization
|
||||
for (const platformLayout of layoutToSave.platform_layouts) {
|
||||
const platformStyle = this.platforms.filter(
|
||||
(x) => x.identifier == platformLayout.platform,
|
||||
)[0].styles[platformLayout.style];
|
||||
for (const setting of platformStyle.settings) {
|
||||
console.log(setting, platformLayout.layout?.settings[setting.identifier])
|
||||
if (
|
||||
setting.type === "image" &&
|
||||
platformLayout.layout?.settings[setting.identifier] instanceof
|
||||
File
|
||||
) {
|
||||
platformLayout.layout.settings[setting.identifier] = await
|
||||
this.uploadFile(
|
||||
platformLayout.layout.settings[setting.identifier],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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(layoutToSave),
|
||||
},
|
||||
)
|
||||
.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),
|
||||
),
|
||||
active:
|
||||
newFieldGroups[key].required ||
|
||||
newFieldGroups[key].default_entries.length > 0,
|
||||
};
|
||||
} else {
|
||||
this.currentPlatformLayout.layout.fieldgroups[key] = {
|
||||
active: newFieldGroups[key].required,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.currentPlatformLayout.style = style;
|
||||
}
|
||||
},
|
||||
setSetting(identifier: string, value: string | File | null) {
|
||||
this.currentPlatformLayout.layout.settings[identifier] = value;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -384,21 +384,18 @@ class PassStyle:
|
||||
except jsonschema.ValidationError as e:
|
||||
raise ValidationError("Invalid layout: {}".format(str(e)))
|
||||
|
||||
def extract_file_settings(self, request):
|
||||
file_settings = {}
|
||||
def extract_file_settings(self, request, file_settings):
|
||||
res = {}
|
||||
for setting in self.settings:
|
||||
if setting.type == "image":
|
||||
if self.layout.get("settings", {}).get(setting.identifier) == "file:keep":
|
||||
file_settings[setting.identifier] = "keep"
|
||||
elif data := self.layout.get("settings", {}).get(setting.identifier):
|
||||
file_settings[setting.identifier] = handle_file_upload(data, request.user, request.auth, {"image/png", "image/jpeg"})
|
||||
del self.layout["settings"][setting.identifier]
|
||||
print(self.layout['settings'])
|
||||
elif setting.identifier in self.layout.get("settings", {}):
|
||||
file_settings[setting.identifier] = None
|
||||
del self.layout["settings"][setting.identifier]
|
||||
if file_settings.get(setting.identifier) == "file:keep":
|
||||
res[setting.identifier] = "keep"
|
||||
elif data := file_settings.get(setting.identifier):
|
||||
res[setting.identifier] = handle_file_upload(data, request.user, request.auth, {"image/png", "image/jpeg"})
|
||||
elif setting.identifier in file_settings:
|
||||
res[setting.identifier] = None
|
||||
|
||||
return file_settings
|
||||
return res
|
||||
|
||||
def get_pass_fields(self, op: OrderPosition):
|
||||
context = WalletPlaceholderContext(
|
||||
|
||||
Reference in New Issue
Block a user