@webx-ui/module-banners 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BannerEditorPage-j7WlEaek.js +243 -0
- package/dist/BannerEditorPage-j7WlEaek.js.map +1 -0
- package/dist/BannersPage-Dl_BCozz.js +591 -0
- package/dist/BannersPage-Dl_BCozz.js.map +1 -0
- package/dist/index-3RALKrkh.js +1681 -0
- package/dist/index-3RALKrkh.js.map +1 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +28 -2484
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { defineComponent as ue, computed as r, ref as c, watch as I, onMounted as oe, onBeforeUnmount as se, openBlock as h, createElementBlock as N, Fragment as J, createVNode as o, unref as n, withCtx as d, createSlots as ie, createTextVNode as q, toDisplayString as F, createBlock as ce, createCommentVNode as ve } from "vue";
|
|
2
|
+
import { useRoute as de, useRouter as be, onBeforeRouteLeave as fe } from "vue-router";
|
|
3
|
+
import { useLocales as pe, localizedValue as me, confirm as z, WxSkeleton as G, WxCard as H, WxBadge as ye, WxFormItem as ge, WxSelect as ke, WxActionBar as xe, WxButton as we, toast as m } from "@webx-ui/core";
|
|
4
|
+
import { useAdmin as _e, useTranslate as he, useErrorText as Be, useBodyKeys as Se, WxScreenHead as We, WxScreen as Ve, WxSaveState as Ee } from "@webx-ui/module-admin";
|
|
5
|
+
import { c as Ce, u as Le, p as Ne, d as Te, _ as Pe } from "./index-3RALKrkh.js";
|
|
6
|
+
const $e = /* @__PURE__ */ ue({
|
|
7
|
+
name: "WxBannerEditorPage",
|
|
8
|
+
__name: "BannerEditorPage",
|
|
9
|
+
props: {
|
|
10
|
+
base: { default: "/banners" }
|
|
11
|
+
},
|
|
12
|
+
setup(j) {
|
|
13
|
+
const x = j, B = _e(), y = Ce(B), T = de(), S = be(), W = pe();
|
|
14
|
+
Le();
|
|
15
|
+
const a = he("webx-banners"), V = Be(), s = r(() => {
|
|
16
|
+
const e = T.params.id;
|
|
17
|
+
return typeof e == "string" && e !== "" ? Number(e) : null;
|
|
18
|
+
}), v = c(null), u = c({}), g = c(""), t = c(null), f = c(null), E = c([]), w = c(!0), b = c(!1), _ = c({}), p = r(() => B.can("banners.manage")), P = r(() => JSON.stringify(u.value)), k = r(
|
|
19
|
+
() => g.value !== "" && (P.value !== g.value || t.value !== f.value)
|
|
20
|
+
), Q = r(() => b.value ? "saving" : k.value || s.value === null ? "unsaved" : "saved"), X = r(() => {
|
|
21
|
+
const e = u.value.title, l = s.value === null ? a("banner.new") : v.value?.title ?? "";
|
|
22
|
+
return me(e, W.active.value, l) || a("banner.untitled");
|
|
23
|
+
}), $ = r(() => {
|
|
24
|
+
const e = f.value ?? t.value;
|
|
25
|
+
return e === null ? x.base : `${x.base}?place=${encodeURIComponent(e)}`;
|
|
26
|
+
}), Y = r(
|
|
27
|
+
() => B.state.manifest?.modules.find((e) => e.id === "banners")?.title ?? a("module.banners")
|
|
28
|
+
), C = r(() => E.value.find((e) => e.key === t.value) ?? null);
|
|
29
|
+
Ne({ values: u, place: C });
|
|
30
|
+
const Z = r(
|
|
31
|
+
() => C.value?.layers ? [] : [{ op: "remove", target: "layers-card" }]
|
|
32
|
+
);
|
|
33
|
+
function L(e) {
|
|
34
|
+
return !C.value?.layers || e.layers !== null && e.layers !== void 0 ? e : { ...e, layers: Te(W.list.value[0]?.code ?? W.active.value) };
|
|
35
|
+
}
|
|
36
|
+
const ee = r(
|
|
37
|
+
() => E.value.map((e) => ({ value: e.key, label: `${e.title} · ${e.key}` }))
|
|
38
|
+
), R = r({
|
|
39
|
+
get: () => t.value,
|
|
40
|
+
set: (e) => {
|
|
41
|
+
t.value = e == null ? null : String(e);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
function ae() {
|
|
45
|
+
return {
|
|
46
|
+
image: null,
|
|
47
|
+
image_mobile: null,
|
|
48
|
+
video: null,
|
|
49
|
+
title: {},
|
|
50
|
+
text: {},
|
|
51
|
+
buttons: [],
|
|
52
|
+
layers: null,
|
|
53
|
+
enabled: !1
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function D(e) {
|
|
57
|
+
v.value = e.banner, t.value = e.banner.place, f.value = e.banner.place;
|
|
58
|
+
const l = L(e.values);
|
|
59
|
+
u.value = l, g.value = JSON.stringify(l);
|
|
60
|
+
}
|
|
61
|
+
async function K() {
|
|
62
|
+
w.value = !0;
|
|
63
|
+
try {
|
|
64
|
+
const [e, l] = await Promise.all([
|
|
65
|
+
y.places(),
|
|
66
|
+
s.value === null ? Promise.resolve(null) : y.get(s.value)
|
|
67
|
+
]);
|
|
68
|
+
if (E.value = e, l !== null)
|
|
69
|
+
D(l);
|
|
70
|
+
else {
|
|
71
|
+
const i = T.query.place, te = typeof i == "string" && e.some((re) => re.key === i) ? i : null;
|
|
72
|
+
v.value = null, t.value = te ?? e[0]?.key ?? null, f.value = t.value;
|
|
73
|
+
const A = L(ae());
|
|
74
|
+
u.value = A, g.value = JSON.stringify(A);
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
m.danger(V(e)), S.push(x.base);
|
|
78
|
+
} finally {
|
|
79
|
+
w.value = !1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function M() {
|
|
83
|
+
if (!(b.value || !p.value || t.value === null)) {
|
|
84
|
+
b.value = !0, _.value = {};
|
|
85
|
+
try {
|
|
86
|
+
const e = s.value === null, l = e ? await y.create(t.value, u.value) : await y.save(
|
|
87
|
+
s.value,
|
|
88
|
+
u.value,
|
|
89
|
+
t.value !== f.value ? t.value : void 0
|
|
90
|
+
);
|
|
91
|
+
D(l), m.success(a("banner.saved")), e && S.replace(`${x.base}/${l.banner.id}`);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
const l = e.body;
|
|
94
|
+
l?.errors ? (_.value = l.errors, m.danger(a("banner.save-failed"))) : m.danger(V(e));
|
|
95
|
+
} finally {
|
|
96
|
+
b.value = !1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function le() {
|
|
101
|
+
const e = v.value;
|
|
102
|
+
if (!(!e || !await z({
|
|
103
|
+
title: a("banner.delete-title", { name: e.title }),
|
|
104
|
+
message: a("banner.delete-text"),
|
|
105
|
+
confirmText: a("banner.delete"),
|
|
106
|
+
cancelText: a("banner.cancel"),
|
|
107
|
+
tone: "danger"
|
|
108
|
+
})))
|
|
109
|
+
try {
|
|
110
|
+
await y.remove(e.id), m.success(a("banner.deleted")), g.value = P.value, t.value = f.value, S.push($.value);
|
|
111
|
+
} catch (i) {
|
|
112
|
+
m.danger(V(i));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function O(e) {
|
|
116
|
+
k.value && e.preventDefault();
|
|
117
|
+
}
|
|
118
|
+
function U(e) {
|
|
119
|
+
(e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s" && (e.preventDefault(), (k.value || s.value === null) && M());
|
|
120
|
+
}
|
|
121
|
+
Se(U), I(t, () => {
|
|
122
|
+
w.value || (u.value = L(u.value));
|
|
123
|
+
}), I(s, (e) => {
|
|
124
|
+
e !== null && v.value?.id === e || K();
|
|
125
|
+
}), oe(() => {
|
|
126
|
+
K(), window.addEventListener("beforeunload", O);
|
|
127
|
+
}), se(() => {
|
|
128
|
+
window.removeEventListener("beforeunload", O);
|
|
129
|
+
}), fe(async () => !k.value || !p.value ? !0 : await z({
|
|
130
|
+
title: a("banner.leave-title"),
|
|
131
|
+
message: a("banner.leave-text"),
|
|
132
|
+
confirmText: a("banner.leave"),
|
|
133
|
+
cancelText: a("banner.cancel"),
|
|
134
|
+
tone: "danger"
|
|
135
|
+
}));
|
|
136
|
+
const ne = r(
|
|
137
|
+
() => p.value && v.value !== null ? [
|
|
138
|
+
{
|
|
139
|
+
key: "delete",
|
|
140
|
+
label: a("banner.delete"),
|
|
141
|
+
icon: "trash",
|
|
142
|
+
danger: !0,
|
|
143
|
+
menu: !0,
|
|
144
|
+
run: () => {
|
|
145
|
+
le();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
] : []
|
|
149
|
+
);
|
|
150
|
+
return (e, l) => (h(), N("div", {
|
|
151
|
+
class: "wx-banner-editor",
|
|
152
|
+
onKeydown: U
|
|
153
|
+
}, [
|
|
154
|
+
w.value ? (h(), N(J, { key: 0 }, [
|
|
155
|
+
o(n(G), {
|
|
156
|
+
class: "wx-banner-editor__ghost",
|
|
157
|
+
title: "",
|
|
158
|
+
rows: 1
|
|
159
|
+
}),
|
|
160
|
+
o(n(H), null, {
|
|
161
|
+
default: d(() => [
|
|
162
|
+
o(n(G), { rows: 6 })
|
|
163
|
+
]),
|
|
164
|
+
_: 1
|
|
165
|
+
})
|
|
166
|
+
], 64)) : (h(), N(J, { key: 1 }, [
|
|
167
|
+
o(n(We), {
|
|
168
|
+
divider: "",
|
|
169
|
+
back: $.value,
|
|
170
|
+
"back-label": Y.value,
|
|
171
|
+
title: X.value,
|
|
172
|
+
actions: ne.value
|
|
173
|
+
}, ie({ _: 2 }, [
|
|
174
|
+
v.value && !v.value.enabled ? {
|
|
175
|
+
name: "title-after",
|
|
176
|
+
fn: d(() => [
|
|
177
|
+
o(n(ye), { dot: "" }, {
|
|
178
|
+
default: d(() => [
|
|
179
|
+
q(F(n(a)("banner.disabled")), 1)
|
|
180
|
+
]),
|
|
181
|
+
_: 1
|
|
182
|
+
})
|
|
183
|
+
]),
|
|
184
|
+
key: "0"
|
|
185
|
+
} : void 0
|
|
186
|
+
]), 1032, ["back", "back-label", "title", "actions"]),
|
|
187
|
+
o(n(H), { class: "wx-banner-editor__place" }, {
|
|
188
|
+
default: d(() => [
|
|
189
|
+
o(n(ge), {
|
|
190
|
+
label: n(a)("banner.place"),
|
|
191
|
+
help: s.value === null ? void 0 : n(a)("banner.place-help"),
|
|
192
|
+
error: _.value.place?.[0],
|
|
193
|
+
required: ""
|
|
194
|
+
}, {
|
|
195
|
+
default: d(() => [
|
|
196
|
+
o(n(ke), {
|
|
197
|
+
modelValue: R.value,
|
|
198
|
+
"onUpdate:modelValue": l[0] || (l[0] = (i) => R.value = i),
|
|
199
|
+
options: ee.value,
|
|
200
|
+
disabled: !p.value || b.value,
|
|
201
|
+
"aria-label": n(a)("banner.place")
|
|
202
|
+
}, null, 8, ["modelValue", "options", "disabled", "aria-label"])
|
|
203
|
+
]),
|
|
204
|
+
_: 1
|
|
205
|
+
}, 8, ["label", "help", "error"])
|
|
206
|
+
]),
|
|
207
|
+
_: 1
|
|
208
|
+
}),
|
|
209
|
+
o(n(Ve), {
|
|
210
|
+
modelValue: u.value,
|
|
211
|
+
"onUpdate:modelValue": l[1] || (l[1] = (i) => u.value = i),
|
|
212
|
+
name: "banners.form",
|
|
213
|
+
patch: Z.value,
|
|
214
|
+
errors: _.value,
|
|
215
|
+
disabled: !p.value || b.value
|
|
216
|
+
}, null, 8, ["modelValue", "patch", "errors", "disabled"]),
|
|
217
|
+
p.value ? (h(), ce(n(xe), { key: 0 }, {
|
|
218
|
+
state: d(() => [
|
|
219
|
+
o(n(Ee), { state: Q.value }, null, 8, ["state"])
|
|
220
|
+
]),
|
|
221
|
+
default: d(() => [
|
|
222
|
+
o(n(we), {
|
|
223
|
+
type: "primary",
|
|
224
|
+
loading: b.value,
|
|
225
|
+
disabled: !k.value && s.value !== null || t.value === null,
|
|
226
|
+
onClick: M
|
|
227
|
+
}, {
|
|
228
|
+
default: d(() => [
|
|
229
|
+
q(F(n(a)("banner.save")), 1)
|
|
230
|
+
]),
|
|
231
|
+
_: 1
|
|
232
|
+
}, 8, ["loading", "disabled"])
|
|
233
|
+
]),
|
|
234
|
+
_: 1
|
|
235
|
+
})) : ve("", !0)
|
|
236
|
+
], 64))
|
|
237
|
+
], 32));
|
|
238
|
+
}
|
|
239
|
+
}), Ue = /* @__PURE__ */ Pe($e, [["__scopeId", "data-v-b6bd4d3f"]]);
|
|
240
|
+
export {
|
|
241
|
+
Ue as default
|
|
242
|
+
};
|
|
243
|
+
//# sourceMappingURL=BannerEditorPage-j7WlEaek.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BannerEditorPage-j7WlEaek.js","sources":["../src/BannerEditorPage.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'\nimport {\n confirm,\n localizedValue,\n toast,\n useLocales,\n WxActionBar,\n WxBadge,\n WxButton,\n WxCard,\n WxFormItem,\n WxSelect,\n WxSkeleton,\n type LocalizedValue,\n} from '@webx-ui/core'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxSaveState,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n useBodyKeys,\n} from '@webx-ui/module-admin'\nimport type { Patch, ScreenModel } from '@webx-ui/schema'\nimport { createBannersApi } from './api'\nimport { provideBannerEditor } from './editor'\nimport { defaultLayers } from './layers'\nimport { useBannersMessages } from './i18n'\nimport type { BannerDetail, BannerSummary, PlaceRow } from './types'\n\n/**\n * One banner, on a page of its own: a head, the place, the described screen, and one button.\n *\n * A page and not a pane beside the list, because the form is long — three pictures, the words,\n * up to three buttons — and `banners.form` is a described screen a project adds its own fields\n * to (decision 14). One explicit save, no autosave: a banner has no draft (decision 13), so what\n * is saved is on the site at once, if it is switched on.\n *\n * The place is not a field of the screen (decision 9). The list of places is alive — own places\n * come and go — and the options of a described select are patched on when the server boots, so a\n * live list has nowhere to go there. It is a select of its own above the screen, and rides beside\n * the values: `PUT { values, place }`. Changing it marks the form changed like any field does.\n *\n * A new banner is a form first and a record on the first save: pressing \"New banner\" and walking\n * away should not leave an empty, switched-off row at the end of a place.\n */\ndefineOptions({ name: 'WxBannerEditorPage' })\n\nconst props = withDefaults(defineProps<{ base?: string }>(), { base: '/banners' })\n\nconst admin = useAdmin()\nconst api = createBannersApi(admin)\nconst route = useRoute()\nconst router = useRouter()\nconst locales = useLocales()\nuseBannersMessages()\n\nconst t = useTranslate('webx-banners')\n/* Not the server's `message`: the panel says how a request failed in its own words. */\nconst message = useErrorText()\n\n/** `null` — a banner not written yet (`/banners/new`). */\nconst id = computed<number | null>(() => {\n const raw = route.params.id\n\n return typeof raw === 'string' && raw !== '' ? Number(raw) : null\n})\n\nconst banner = ref<BannerSummary | null>(null)\nconst values = ref<ScreenModel>({})\nconst snapshot = ref('')\nconst place = ref<string | null>(null)\n/** The place the banner stands in on the server — what \"changed\" and \"back\" are measured by. */\nconst placeSaved = ref<string | null>(null)\nconst places = ref<PlaceRow[]>([])\n\nconst loading = ref(true)\nconst saving = ref(false)\nconst errors = ref<Record<string, string[]>>({})\n\nconst canManage = computed(() => admin.can('banners.manage'))\n\nconst current = computed(() => JSON.stringify(values.value))\nconst dirty = computed(\n () =>\n snapshot.value !== '' && (current.value !== snapshot.value || place.value !== placeSaved.value),\n)\n\nconst state = computed<'saving' | 'unsaved' | 'saved'>(() => {\n if (saving.value) return 'saving'\n\n return dirty.value || id.value === null ? 'unsaved' : 'saved'\n})\n\n/** The title follows the field rather than the answer: an edit shows at the top as it is typed. */\nconst title = computed(() => {\n const written = values.value.title as LocalizedValue | string | null | undefined\n const fallback = id.value === null ? t('banner.new') : (banner.value?.title ?? '')\n\n return localizedValue(written, locales.active.value, fallback) || t('banner.untitled')\n})\n\n/** Back to the place the banner stands in, and not to the section's first one. */\nconst back = computed(() => {\n const key = placeSaved.value ?? place.value\n\n return key === null ? props.base : `${props.base}?place=${encodeURIComponent(key)}`\n})\n\nconst section = computed(\n () =>\n admin.state.manifest?.modules.find((module) => module.id === 'banners')?.title ??\n t('module.banners'),\n)\n\n/** The place the banner stands in now — the select's, not the server's — with its layers' frame. */\nconst placeRow = computed(() => places.value.find((one) => one.key === place.value) ?? null)\n\nprovideBannerEditor({ values, place: placeRow })\n\n/* The layers' card only where the place prints layers (§4.4 of the layers spec). */\nconst screenPatch = computed<Patch>(() =>\n placeRow.value?.layers ? [] : [{ op: 'remove', target: 'layers-card' }],\n)\n\n/**\n * A banner of a place with layers that was never laid out starts with its title, its text and its\n * buttons as three layers (decision 5). `[]` is somebody having taken them all away, and stays.\n */\nfunction withDefaultLayers(model: ScreenModel): ScreenModel {\n if (!placeRow.value?.layers || (model.layers !== null && model.layers !== undefined)) return model\n\n return { ...model, layers: defaultLayers(locales.list.value[0]?.code ?? locales.active.value) }\n}\n\nconst placeOptions = computed(() =>\n places.value.map((one) => ({ value: one.key, label: `${one.title} · ${one.key}` })),\n)\n\nconst placeModel = computed<string | number | null>({\n get: () => place.value,\n set: (value) => {\n place.value = value === null || value === undefined ? null : String(value)\n },\n})\n\n/** What the server would answer for a banner with nothing in it yet (§5.6). */\nfunction blank(): ScreenModel {\n return {\n image: null,\n image_mobile: null,\n video: null,\n title: {},\n text: {},\n buttons: [],\n layers: null,\n enabled: false,\n }\n}\n\nfunction take(detail: BannerDetail): void {\n banner.value = detail.banner\n place.value = detail.banner.place\n placeSaved.value = detail.banner.place\n\n // The default layers are part of what the banner opens with, not a change somebody made.\n const opened = withDefaultLayers(detail.values)\n\n values.value = opened\n snapshot.value = JSON.stringify(opened)\n}\n\nasync function load(): Promise<void> {\n loading.value = true\n\n try {\n const [list, detail] = await Promise.all([\n api.places(),\n id.value === null ? Promise.resolve(null) : api.get(id.value),\n ])\n\n places.value = list\n\n if (detail !== null) {\n take(detail)\n } else {\n const asked = route.query.place\n const key = typeof asked === 'string' && list.some((one) => one.key === asked) ? asked : null\n banner.value = null\n place.value = key ?? list[0]?.key ?? null\n placeSaved.value = place.value\n\n const fresh = withDefaultLayers(blank())\n\n values.value = fresh\n snapshot.value = JSON.stringify(fresh)\n }\n } catch (error) {\n toast.danger(message(error))\n void router.push(props.base)\n } finally {\n loading.value = false\n }\n}\n\nasync function save(): Promise<void> {\n if (saving.value || !canManage.value || place.value === null) return\n\n saving.value = true\n errors.value = {}\n\n try {\n const made = id.value === null\n const detail = made\n ? await api.create(place.value, values.value)\n : await api.save(\n id.value as number,\n values.value,\n place.value !== placeSaved.value ? place.value : undefined,\n )\n\n take(detail)\n toast.success(t('banner.saved'))\n\n // A record now, with an address of its own; `replace`, so \"back\" does not reopen a blank form.\n if (made) void router.replace(`${props.base}/${detail.banner.id}`)\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]> } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('banner.save-failed'))\n } else {\n toast.danger(message(error))\n }\n } finally {\n saving.value = false\n }\n}\n\nasync function remove(): Promise<void> {\n const record = banner.value\n\n if (!record) return\n\n const agreed = await confirm({\n title: t('banner.delete-title', { name: record.title }),\n message: t('banner.delete-text'),\n confirmText: t('banner.delete'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n\n if (!agreed) return\n\n try {\n await api.remove(record.id)\n toast.success(t('banner.deleted'))\n // Nothing left to protect: the record is in the bin, and asking about its unsaved edits on\n // the way out would be asking about a banner that is gone.\n snapshot.value = current.value\n place.value = placeSaved.value\n void router.push(back.value)\n } catch (error) {\n toast.danger(message(error))\n }\n}\n\n/* The browser's own guard: it cannot wait for a request, so all it can do is ask. */\nfunction leaveGuard(event: BeforeUnloadEvent): void {\n if (dirty.value) event.preventDefault()\n}\n\n/* Ctrl+S saves, as it does everywhere else that has a save button. */\nfunction onKeydown(event: KeyboardEvent): void {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {\n event.preventDefault()\n if (dirty.value || id.value === null) void save()\n }\n}\n\n// And from the page around the form, where a click on empty space leaves the focus.\nuseBodyKeys(onKeydown)\n\n/*\n * `/banners/new` and `/banners/5` are one component on two routes, and the router patches it\n * rather than mounting it again (CLAUDE.md §4) — so a change of banner is watched for. The first\n * save moving the address to the record it just made is not a change: that banner is on screen.\n */\n/* Moved to a place with layers, a banner never laid out gets the default three there too. */\nwatch(place, () => {\n if (!loading.value) values.value = withDefaultLayers(values.value)\n})\n\nwatch(id, (next) => {\n if (next !== null && banner.value?.id === next) return\n\n void load()\n})\n\nonMounted(() => {\n void load()\n window.addEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeUnmount(() => {\n window.removeEventListener('beforeunload', leaveGuard)\n})\n\nonBeforeRouteLeave(async () => {\n if (!dirty.value || !canManage.value) return true\n\n return await confirm({\n title: t('banner.leave-title'),\n message: t('banner.leave-text'),\n confirmText: t('banner.leave'),\n cancelText: t('banner.cancel'),\n tone: 'danger',\n })\n})\n\n/* What leads away from the banner. On a phone the head folds it into the ···. */\nconst actions = computed<ScreenAction[]>(() =>\n canManage.value && banner.value !== null\n ? [\n {\n key: 'delete',\n label: t('banner.delete'),\n icon: 'trash',\n danger: true,\n menu: true,\n run: () => void remove(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-banner-editor\" @keydown=\"onKeydown\">\n <template v-if=\"loading\">\n <!-- Shaped like the screen it stands in for: the head, and a card for what is coming. -->\n <wx-skeleton class=\"wx-banner-editor__ghost\" title :rows=\"1\" />\n <wx-card><wx-skeleton :rows=\"6\" /></wx-card>\n </template>\n\n <template v-else>\n <wx-screen-head divider :back=\"back\" :back-label=\"section\" :title=\"title\" :actions=\"actions\">\n <template v-if=\"banner && !banner.enabled\" #title-after>\n <wx-badge dot>{{ t('banner.disabled') }}</wx-badge>\n </template>\n </wx-screen-head>\n\n <wx-card class=\"wx-banner-editor__place\">\n <wx-form-item\n :label=\"t('banner.place')\"\n :help=\"id === null ? undefined : t('banner.place-help')\"\n :error=\"errors.place?.[0]\"\n required\n >\n <wx-select\n v-model=\"placeModel\"\n :options=\"placeOptions\"\n :disabled=\"!canManage || saving\"\n :aria-label=\"t('banner.place')\"\n />\n </wx-form-item>\n </wx-card>\n\n <wx-screen\n v-model=\"values\"\n name=\"banners.form\"\n :patch=\"screenPatch\"\n :errors=\"errors\"\n :disabled=\"!canManage || saving\"\n />\n\n <wx-action-bar v-if=\"canManage\">\n <template #state>\n <wx-save-state :state=\"state\" />\n </template>\n\n <wx-button\n type=\"primary\"\n :loading=\"saving\"\n :disabled=\"(!dirty && id !== null) || place === null\"\n @click=\"save\"\n >\n {{ t('banner.save') }}\n </wx-button>\n </wx-action-bar>\n </template>\n </div>\n</template>\n\n<style scoped>\n.wx-banner-editor {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n container-type: inline-size;\n}\n\n.wx-banner-editor__ghost {\n max-width: 420px;\n}\n\n/* A select as wide as a place's name needs, not as the page: it is one choice, not a text. */\n.wx-banner-editor__place :deep(.wx-form-item) {\n max-width: 420px;\n margin-bottom: 0;\n}\n</style>\n"],"names":["props","__props","admin","useAdmin","api","createBannersApi","route","useRoute","router","useRouter","locales","useLocales","useBannersMessages","t","useTranslate","message","useErrorText","id","computed","raw","banner","ref","values","snapshot","place","placeSaved","places","loading","saving","errors","canManage","current","dirty","state","title","written","fallback","localizedValue","back","key","section","module","placeRow","one","provideBannerEditor","screenPatch","withDefaultLayers","model","defaultLayers","placeOptions","placeModel","value","blank","take","detail","opened","load","list","asked","fresh","error","toast","save","made","body","remove","record","confirm","leaveGuard","event","onKeydown","useBodyKeys","watch","next","onMounted","onBeforeUnmount","onBeforeRouteLeave","actions","_createElementBlock","_Fragment","_createVNode","_unref","WxSkeleton","WxCard","WxScreenHead","WxBadge","WxFormItem","WxSelect","$event","WxScreen","_createBlock","WxActionBar","WxSaveState","WxButton"],"mappings":";;;;;;;;;;;;AAoDA,UAAMA,IAAQC,GAERC,IAAQC,GAAA,GACRC,IAAMC,GAAiBH,CAAK,GAC5BI,IAAQC,GAAA,GACRC,IAASC,GAAA,GACTC,IAAUC,GAAA;AAChB,IAAAC,GAAA;AAEA,UAAMC,IAAIC,GAAa,cAAc,GAE/BC,IAAUC,GAAA,GAGVC,IAAKC,EAAwB,MAAM;AACvC,YAAMC,IAAMb,EAAM,OAAO;AAEzB,aAAO,OAAOa,KAAQ,YAAYA,MAAQ,KAAK,OAAOA,CAAG,IAAI;AAAA,IAC/D,CAAC,GAEKC,IAASC,EAA0B,IAAI,GACvCC,IAASD,EAAiB,EAAE,GAC5BE,IAAWF,EAAI,EAAE,GACjBG,IAAQH,EAAmB,IAAI,GAE/BI,IAAaJ,EAAmB,IAAI,GACpCK,IAASL,EAAgB,EAAE,GAE3BM,IAAUN,EAAI,EAAI,GAClBO,IAASP,EAAI,EAAK,GAClBQ,IAASR,EAA8B,EAAE,GAEzCS,IAAYZ,EAAS,MAAMhB,EAAM,IAAI,gBAAgB,CAAC,GAEtD6B,IAAUb,EAAS,MAAM,KAAK,UAAUI,EAAO,KAAK,CAAC,GACrDU,IAAQd;AAAA,MACZ,MACEK,EAAS,UAAU,OAAOQ,EAAQ,UAAUR,EAAS,SAASC,EAAM,UAAUC,EAAW;AAAA,IAAA,GAGvFQ,IAAQf,EAAyC,MACjDU,EAAO,QAAc,WAElBI,EAAM,SAASf,EAAG,UAAU,OAAO,YAAY,OACvD,GAGKiB,IAAQhB,EAAS,MAAM;AAC3B,YAAMiB,IAAUb,EAAO,MAAM,OACvBc,IAAWnB,EAAG,UAAU,OAAOJ,EAAE,YAAY,IAAKO,EAAO,OAAO,SAAS;AAE/E,aAAOiB,GAAeF,GAASzB,EAAQ,OAAO,OAAO0B,CAAQ,KAAKvB,EAAE,iBAAiB;AAAA,IACvF,CAAC,GAGKyB,IAAOpB,EAAS,MAAM;AAC1B,YAAMqB,IAAMd,EAAW,SAASD,EAAM;AAEtC,aAAOe,MAAQ,OAAOvC,EAAM,OAAO,GAAGA,EAAM,IAAI,UAAU,mBAAmBuC,CAAG,CAAC;AAAA,IACnF,CAAC,GAEKC,IAAUtB;AAAA,MACd,MACEhB,EAAM,MAAM,UAAU,QAAQ,KAAK,CAACuC,MAAWA,EAAO,OAAO,SAAS,GAAG,SACzE5B,EAAE,gBAAgB;AAAA,IAAA,GAIhB6B,IAAWxB,EAAS,MAAMQ,EAAO,MAAM,KAAK,CAACiB,MAAQA,EAAI,QAAQnB,EAAM,KAAK,KAAK,IAAI;AAE3F,IAAAoB,GAAoB,EAAE,QAAAtB,GAAQ,OAAOoB,EAAA,CAAU;AAG/C,UAAMG,IAAc3B;AAAA,MAAgB,MAClCwB,EAAS,OAAO,SAAS,CAAA,IAAK,CAAC,EAAE,IAAI,UAAU,QAAQ,cAAA,CAAe;AAAA,IAAA;AAOxE,aAASI,EAAkBC,GAAiC;AAC1D,aAAI,CAACL,EAAS,OAAO,UAAWK,EAAM,WAAW,QAAQA,EAAM,WAAW,SAAmBA,IAEtF,EAAE,GAAGA,GAAO,QAAQC,GAActC,EAAQ,KAAK,MAAM,CAAC,GAAG,QAAQA,EAAQ,OAAO,KAAK,EAAA;AAAA,IAC9F;AAEA,UAAMuC,KAAe/B;AAAA,MAAS,MAC5BQ,EAAO,MAAM,IAAI,CAACiB,OAAS,EAAE,OAAOA,EAAI,KAAK,OAAO,GAAGA,EAAI,KAAK,MAAMA,EAAI,GAAG,KAAK;AAAA,IAAA,GAG9EO,IAAahC,EAAiC;AAAA,MAClD,KAAK,MAAMM,EAAM;AAAA,MACjB,KAAK,CAAC2B,MAAU;AACd,QAAA3B,EAAM,QAAQ2B,KAAU,OAA8B,OAAO,OAAOA,CAAK;AAAA,MAC3E;AAAA,IAAA,CACD;AAGD,aAASC,KAAqB;AAC5B,aAAO;AAAA,QACL,OAAO;AAAA,QACP,cAAc;AAAA,QACd,OAAO;AAAA,QACP,OAAO,CAAA;AAAA,QACP,MAAM,CAAA;AAAA,QACN,SAAS,CAAA;AAAA,QACT,QAAQ;AAAA,QACR,SAAS;AAAA,MAAA;AAAA,IAEb;AAEA,aAASC,EAAKC,GAA4B;AACxC,MAAAlC,EAAO,QAAQkC,EAAO,QACtB9B,EAAM,QAAQ8B,EAAO,OAAO,OAC5B7B,EAAW,QAAQ6B,EAAO,OAAO;AAGjC,YAAMC,IAAST,EAAkBQ,EAAO,MAAM;AAE9C,MAAAhC,EAAO,QAAQiC,GACfhC,EAAS,QAAQ,KAAK,UAAUgC,CAAM;AAAA,IACxC;AAEA,mBAAeC,IAAsB;AACnC,MAAA7B,EAAQ,QAAQ;AAEhB,UAAI;AACF,cAAM,CAAC8B,GAAMH,CAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,UACvClD,EAAI,OAAA;AAAA,UACJa,EAAG,UAAU,OAAO,QAAQ,QAAQ,IAAI,IAAIb,EAAI,IAAIa,EAAG,KAAK;AAAA,QAAA,CAC7D;AAID,YAFAS,EAAO,QAAQ+B,GAEXH,MAAW;AACb,UAAAD,EAAKC,CAAM;AAAA,aACN;AACL,gBAAMI,IAAQpD,EAAM,MAAM,OACpBiC,KAAM,OAAOmB,KAAU,YAAYD,EAAK,KAAK,CAACd,OAAQA,GAAI,QAAQe,CAAK,IAAIA,IAAQ;AACzF,UAAAtC,EAAO,QAAQ,MACfI,EAAM,QAAQe,MAAOkB,EAAK,CAAC,GAAG,OAAO,MACrChC,EAAW,QAAQD,EAAM;AAEzB,gBAAMmC,IAAQb,EAAkBM,IAAO;AAEvC,UAAA9B,EAAO,QAAQqC,GACfpC,EAAS,QAAQ,KAAK,UAAUoC,CAAK;AAAA,QACvC;AAAA,MACF,SAASC,GAAO;AACd,QAAAC,EAAM,OAAO9C,EAAQ6C,CAAK,CAAC,GACtBpD,EAAO,KAAKR,EAAM,IAAI;AAAA,MAC7B,UAAA;AACE,QAAA2B,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,mBAAemC,IAAsB;AACnC,UAAI,EAAAlC,EAAO,SAAS,CAACE,EAAU,SAASN,EAAM,UAAU,OAExD;AAAA,QAAAI,EAAO,QAAQ,IACfC,EAAO,QAAQ,CAAA;AAEf,YAAI;AACF,gBAAMkC,IAAO9C,EAAG,UAAU,MACpBqC,IAASS,IACX,MAAM3D,EAAI,OAAOoB,EAAM,OAAOF,EAAO,KAAK,IAC1C,MAAMlB,EAAI;AAAA,YACRa,EAAG;AAAA,YACHK,EAAO;AAAA,YACPE,EAAM,UAAUC,EAAW,QAAQD,EAAM,QAAQ;AAAA,UAAA;AAGvD,UAAA6B,EAAKC,CAAM,GACXO,EAAM,QAAQhD,EAAE,cAAc,CAAC,GAG3BkD,KAAWvD,EAAO,QAAQ,GAAGR,EAAM,IAAI,IAAIsD,EAAO,OAAO,EAAE,EAAE;AAAA,QACnE,SAASM,GAAO;AACd,gBAAMI,IAAQJ,EAA2D;AAEzE,UAAII,GAAM,UACRnC,EAAO,QAAQmC,EAAK,QACpBH,EAAM,OAAOhD,EAAE,oBAAoB,CAAC,KAEpCgD,EAAM,OAAO9C,EAAQ6C,CAAK,CAAC;AAAA,QAE/B,UAAA;AACE,UAAAhC,EAAO,QAAQ;AAAA,QACjB;AAAA;AAAA,IACF;AAEA,mBAAeqC,KAAwB;AACrC,YAAMC,IAAS9C,EAAO;AAYtB,UAVI,GAAC8C,KAUD,CARW,MAAMC,EAAQ;AAAA,QAC3B,OAAOtD,EAAE,uBAAuB,EAAE,MAAMqD,EAAO,OAAO;AAAA,QACtD,SAASrD,EAAE,oBAAoB;AAAA,QAC/B,aAAaA,EAAE,eAAe;AAAA,QAC9B,YAAYA,EAAE,eAAe;AAAA,QAC7B,MAAM;AAAA,MAAA,CACP;AAID,YAAI;AACF,gBAAMT,EAAI,OAAO8D,EAAO,EAAE,GAC1BL,EAAM,QAAQhD,EAAE,gBAAgB,CAAC,GAGjCU,EAAS,QAAQQ,EAAQ,OACzBP,EAAM,QAAQC,EAAW,OACpBjB,EAAO,KAAK8B,EAAK,KAAK;AAAA,QAC7B,SAASsB,GAAO;AACd,UAAAC,EAAM,OAAO9C,EAAQ6C,CAAK,CAAC;AAAA,QAC7B;AAAA,IACF;AAGA,aAASQ,EAAWC,GAAgC;AAClD,MAAIrC,EAAM,SAAOqC,EAAM,eAAA;AAAA,IACzB;AAGA,aAASC,EAAUD,GAA4B;AAC7C,OAAKA,EAAM,WAAWA,EAAM,YAAYA,EAAM,IAAI,YAAA,MAAkB,QAClEA,EAAM,eAAA,IACFrC,EAAM,SAASf,EAAG,UAAU,SAAW6C,EAAA;AAAA,IAE/C;AAGA,IAAAS,GAAYD,CAAS,GAQrBE,EAAMhD,GAAO,MAAM;AACjB,MAAKG,EAAQ,YAAc,QAAQmB,EAAkBxB,EAAO,KAAK;AAAA,IACnE,CAAC,GAEDkD,EAAMvD,GAAI,CAACwD,MAAS;AAClB,MAAIA,MAAS,QAAQrD,EAAO,OAAO,OAAOqD,KAErCjB,EAAA;AAAA,IACP,CAAC,GAEDkB,GAAU,MAAM;AACd,MAAKlB,EAAA,GACL,OAAO,iBAAiB,gBAAgBY,CAAU;AAAA,IACpD,CAAC,GAEDO,GAAgB,MAAM;AACpB,aAAO,oBAAoB,gBAAgBP,CAAU;AAAA,IACvD,CAAC,GAEDQ,GAAmB,YACb,CAAC5C,EAAM,SAAS,CAACF,EAAU,QAAc,KAEtC,MAAMqC,EAAQ;AAAA,MACnB,OAAOtD,EAAE,oBAAoB;AAAA,MAC7B,SAASA,EAAE,mBAAmB;AAAA,MAC9B,aAAaA,EAAE,cAAc;AAAA,MAC7B,YAAYA,EAAE,eAAe;AAAA,MAC7B,MAAM;AAAA,IAAA,CACP,CACF;AAGD,UAAMgE,KAAU3D;AAAA,MAAyB,MACvCY,EAAU,SAASV,EAAO,UAAU,OAChC;AAAA,QACE;AAAA,UACE,KAAK;AAAA,UACL,OAAOP,EAAE,eAAe;AAAA,UACxB,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,KAAK,MAAA;AAAM,YAAKoD,GAAA;AAAA;AAAA,QAAO;AAAA,MACzB,IAEF,CAAA;AAAA,IAAC;2BAKLa,EAqDM,OAAA;AAAA,MArDD,OAAM;AAAA,MAAoB,WAAAR;AAAA,IAAA;MACb3C,EAAA,cAAhBmD,EAIWC,GAAA,EAAA,KAAA,KAAA;AAAA,QAFTC,EAA+DC,EAAAC,CAAA,GAAA;AAAA,UAAlD,OAAM;AAAA,UAA0B,OAAA;AAAA,UAAO,MAAM;AAAA,QAAA;QAC1DF,EAA4CC,EAAAE,CAAA,GAAA,MAAA;AAAA,qBAAnC,MAAyB;AAAA,YAAzBH,EAAyBC,EAAAC,CAAA,GAAA,EAAX,MAAM,GAAC;AAAA,UAAA;;;sBAGhCJ,EA6CWC,GAAA,EAAA,KAAA,KAAA;AAAA,QA5CTC,EAIiBC,EAAAG,EAAA,GAAA;AAAA,UAJD,SAAA;AAAA,UAAS,MAAM9C,EAAA;AAAA,UAAO,cAAYE,EAAA;AAAA,UAAU,OAAON,EAAA;AAAA,UAAQ,SAAS2C,GAAA;AAAA,QAAA;UAClEzD,EAAA,SAAM,CAAKA,EAAA,MAAO;kBAAU;AAAA,kBAC1C,MAAmD;AAAA,cAAnD4D,EAAmDC,EAAAI,EAAA,GAAA,EAAzC,KAAA,MAAG;AAAA,2BAAC,MAA0B;AAAA,sBAAvBJ,EAAApE,CAAA,EAAC,iBAAA,CAAA,GAAA,CAAA;AAAA,gBAAA;;;;;;;QAItBmE,EAcUC,EAAAE,CAAA,GAAA,EAdD,OAAM,6BAAyB;AAAA,qBACtC,MAYe;AAAA,YAZfH,EAYeC,EAAAK,EAAA,GAAA;AAAA,cAXZ,OAAOL,EAAApE,CAAA,EAAC,cAAA;AAAA,cACR,MAAMI,EAAA,UAAE,OAAY,SAAYgE,EAAApE,CAAA,EAAC,mBAAA;AAAA,cACjC,OAAOgB,EAAA,MAAO,QAAK,CAAA;AAAA,cACpB,UAAA;AAAA,YAAA;yBAEA,MAKE;AAAA,gBALFmD,EAKEC,EAAAM,EAAA,GAAA;AAAA,8BAJSrC,EAAA;AAAA,gEAAAA,EAAU,QAAAsC;AAAA,kBAClB,SAASvC,GAAA;AAAA,kBACT,UAAQ,CAAGnB,EAAA,SAAaF,EAAA;AAAA,kBACxB,cAAYqD,EAAApE,CAAA,EAAC,cAAA;AAAA,gBAAA;;;;;;;QAKpBmE,EAMEC,EAAAQ,EAAA,GAAA;AAAA,sBALSnE,EAAA;AAAA,wDAAAA,EAAM,QAAAkE;AAAA,UACf,MAAK;AAAA,UACJ,OAAO3C,EAAA;AAAA,UACP,QAAQhB,EAAA;AAAA,UACR,UAAQ,CAAGC,EAAA,SAAaF,EAAA;AAAA,QAAA;QAGNE,EAAA,cAArB4D,GAagBT,EAAAU,EAAA,GAAA,EAAA,KAAA,KAAA;AAAA,UAZH,SACT,MAAgC;AAAA,YAAhCX,EAAgCC,EAAAW,EAAA,GAAA,EAAhB,OAAO3D,EAAA,SAAK,MAAA,GAAA,CAAA,OAAA,CAAA;AAAA,UAAA;qBAG9B,MAOY;AAAA,YAPZ+C,EAOYC,EAAAY,EAAA,GAAA;AAAA,cANV,MAAK;AAAA,cACJ,SAASjE,EAAA;AAAA,cACT,UAAQ,CAAII,EAAA,SAASf,EAAA,kBAAgBO,EAAA,UAAK;AAAA,cAC1C,SAAOsC;AAAA,YAAA;yBAER,MAAsB;AAAA,oBAAnBmB,EAAApE,CAAA,EAAC,aAAA,CAAA,GAAA,CAAA;AAAA,cAAA;;;;;;;;;;"}
|