@webx-ui/module-settings 0.3.27 → 0.4.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/index.js +114 -68
- package/dist/index.js.map +1 -1
- package/dist/settingOptions.d.ts +8 -0
- package/dist/style.css +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,18 +1,50 @@
|
|
|
1
|
-
import { defineComponent as
|
|
2
|
-
import { useAdmin as
|
|
3
|
-
import { toast as
|
|
4
|
-
function
|
|
5
|
-
const
|
|
1
|
+
import { defineComponent as E, ref as v, computed as x, onMounted as P, openBlock as m, createElementBlock as C, createVNode as h, unref as l, createBlock as w, withCtx as S, createTextVNode as M, toDisplayString as O, createCommentVNode as T } from "vue";
|
|
2
|
+
import { useAdmin as B, useTranslate as $, useErrorText as j, WxScreenHead as I, WxScreen as L, WxBackupNote as R } from "@webx-ui/module-admin";
|
|
3
|
+
import { toast as y, WxSkeleton as D, WxActionBar as H, WxButton as K } from "@webx-ui/core";
|
|
4
|
+
function U(e) {
|
|
5
|
+
const n = `${e.apiPath}/settings`;
|
|
6
6
|
return {
|
|
7
7
|
async load() {
|
|
8
|
-
return (await e.http.get(
|
|
8
|
+
return (await e.http.get(n)).data.values;
|
|
9
9
|
},
|
|
10
|
-
async save(
|
|
11
|
-
return (await e.http.put(
|
|
10
|
+
async save(a) {
|
|
11
|
+
return (await e.http.put(n, { values: a })).data.values;
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
const
|
|
15
|
+
const Y = /* @__PURE__ */ new Set(["wx-input", "wx-textarea", "wx-input-number", "wx-autocomplete"]), q = "shortcodes.data", _ = 40;
|
|
16
|
+
function z(e, n, a) {
|
|
17
|
+
const s = [], r = (o, u) => {
|
|
18
|
+
for (const t of o) {
|
|
19
|
+
if (t.type === "wx-repeater") continue;
|
|
20
|
+
if (t.name !== void 0) {
|
|
21
|
+
if (Y.has(t.type) && t.name !== q) {
|
|
22
|
+
const f = [...u, t.label ?? t.name].join(" → "), p = F(n[t.name], a);
|
|
23
|
+
s.push({
|
|
24
|
+
value: t.name,
|
|
25
|
+
label: f,
|
|
26
|
+
description: p === "" ? t.name : `${t.name} · ${p}`
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const d = (t.type === "wx-tab" || t.type === "wx-card") && t.label;
|
|
32
|
+
r(t.children ?? [], d ? [...u, t.label] : u);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return r(e, []), s;
|
|
36
|
+
}
|
|
37
|
+
function F(e, n) {
|
|
38
|
+
let a = e;
|
|
39
|
+
if (e !== null && typeof e == "object" && !Array.isArray(e)) {
|
|
40
|
+
const r = e;
|
|
41
|
+
a = r[n] || Object.values(r).find((o) => typeof o == "string" && o !== "");
|
|
42
|
+
}
|
|
43
|
+
if (typeof a == "number" && (a = String(a)), typeof a != "string") return "";
|
|
44
|
+
const s = a.replace(/\s+/g, " ").trim();
|
|
45
|
+
return s.length > _ ? `${s.slice(0, _ - 1)}…` : s;
|
|
46
|
+
}
|
|
47
|
+
const G = {
|
|
16
48
|
module: {
|
|
17
49
|
title: "Settings"
|
|
18
50
|
},
|
|
@@ -21,107 +53,121 @@ const j = {
|
|
|
21
53
|
saved: "Settings saved.",
|
|
22
54
|
failed: "Some values were not accepted. Check the highlighted fields."
|
|
23
55
|
}
|
|
24
|
-
},
|
|
25
|
-
function
|
|
56
|
+
}, W = /* @__PURE__ */ new WeakSet();
|
|
57
|
+
function J() {
|
|
26
58
|
try {
|
|
27
|
-
const { i18n: e } =
|
|
28
|
-
|
|
59
|
+
const { i18n: e } = B();
|
|
60
|
+
W.has(e) || (e.defaults("webx-settings", G), W.add(e));
|
|
29
61
|
} catch {
|
|
30
62
|
}
|
|
31
63
|
}
|
|
32
|
-
const
|
|
64
|
+
const Q = { class: "wx-settings" }, X = /* @__PURE__ */ E({
|
|
33
65
|
__name: "SettingsPage",
|
|
34
66
|
setup(e) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
() =>
|
|
67
|
+
const n = B(), a = U(n);
|
|
68
|
+
J();
|
|
69
|
+
const s = $("webx-settings"), r = j(), o = v({}), u = v({}), t = v(!0), d = v(!1), f = n.can("settings.manage"), p = v([]), N = x(
|
|
70
|
+
() => b(p.value, "shortcode-key") ? [
|
|
71
|
+
{
|
|
72
|
+
op: "set",
|
|
73
|
+
target: "shortcode-key",
|
|
74
|
+
props: { options: z(p.value, o.value, n.i18n.state.locale) }
|
|
75
|
+
}
|
|
76
|
+
] : []
|
|
77
|
+
);
|
|
78
|
+
function b(c, i) {
|
|
79
|
+
return c.some((g) => g.id === i || b(g.children ?? [], i));
|
|
80
|
+
}
|
|
81
|
+
const V = x(
|
|
82
|
+
() => n.state.manifest?.modules.find((c) => c.id === "settings")?.title ?? s("module.title")
|
|
39
83
|
);
|
|
40
|
-
|
|
84
|
+
P(async () => {
|
|
41
85
|
try {
|
|
42
|
-
|
|
43
|
-
} catch (
|
|
44
|
-
|
|
86
|
+
o.value = await a.load();
|
|
87
|
+
} catch (c) {
|
|
88
|
+
y.danger(r(c));
|
|
45
89
|
} finally {
|
|
46
|
-
|
|
90
|
+
t.value = !1;
|
|
47
91
|
}
|
|
48
92
|
});
|
|
49
|
-
async function
|
|
50
|
-
|
|
93
|
+
async function k() {
|
|
94
|
+
d.value = !0, u.value = {};
|
|
51
95
|
try {
|
|
52
|
-
|
|
53
|
-
} catch (
|
|
54
|
-
const i =
|
|
55
|
-
i?.errors ? (
|
|
96
|
+
o.value = await a.save(o.value), y.success(s("page.saved")), n.refreshManifest();
|
|
97
|
+
} catch (c) {
|
|
98
|
+
const i = c.body;
|
|
99
|
+
i?.errors ? (u.value = i.errors, y.danger(s("page.failed"))) : y.danger(r(c, s("page.failed")));
|
|
56
100
|
} finally {
|
|
57
|
-
|
|
101
|
+
d.value = !1;
|
|
58
102
|
}
|
|
59
103
|
}
|
|
60
|
-
const
|
|
104
|
+
const A = x(
|
|
61
105
|
() => f ? [
|
|
62
106
|
{
|
|
63
107
|
key: "save",
|
|
64
|
-
label:
|
|
108
|
+
label: s("page.save"),
|
|
65
109
|
primary: !0,
|
|
66
|
-
loading:
|
|
110
|
+
loading: d.value,
|
|
67
111
|
run: () => {
|
|
68
|
-
|
|
112
|
+
k();
|
|
69
113
|
}
|
|
70
114
|
}
|
|
71
115
|
] : []
|
|
72
116
|
);
|
|
73
|
-
return (
|
|
74
|
-
|
|
75
|
-
title:
|
|
76
|
-
actions:
|
|
117
|
+
return (c, i) => (m(), C("div", Q, [
|
|
118
|
+
h(l(I), {
|
|
119
|
+
title: V.value,
|
|
120
|
+
actions: A.value
|
|
77
121
|
}, null, 8, ["title", "actions"]),
|
|
78
|
-
|
|
122
|
+
t.value ? (m(), w(l(D), {
|
|
79
123
|
key: 0,
|
|
80
124
|
rows: 4
|
|
81
|
-
})) : (
|
|
125
|
+
})) : (m(), w(l(L), {
|
|
82
126
|
key: 1,
|
|
83
|
-
modelValue:
|
|
84
|
-
"onUpdate:modelValue": i[0] || (i[0] = (
|
|
127
|
+
modelValue: o.value,
|
|
128
|
+
"onUpdate:modelValue": i[0] || (i[0] = (g) => o.value = g),
|
|
85
129
|
name: "settings.index",
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
130
|
+
patch: N.value,
|
|
131
|
+
errors: u.value,
|
|
132
|
+
disabled: !l(f),
|
|
133
|
+
onLoaded: i[1] || (i[1] = (g) => p.value = g)
|
|
134
|
+
}, null, 8, ["modelValue", "patch", "errors", "disabled"])),
|
|
135
|
+
h(l(R)),
|
|
136
|
+
l(f) && !t.value ? (m(), w(l(H), { key: 2 }, {
|
|
137
|
+
default: S(() => [
|
|
138
|
+
h(l(K), {
|
|
93
139
|
type: "primary",
|
|
94
|
-
loading:
|
|
95
|
-
onClick:
|
|
140
|
+
loading: d.value,
|
|
141
|
+
onClick: k
|
|
96
142
|
}, {
|
|
97
|
-
default:
|
|
98
|
-
M(
|
|
143
|
+
default: S(() => [
|
|
144
|
+
M(O(l(s)("page.save")), 1)
|
|
99
145
|
]),
|
|
100
146
|
_: 1
|
|
101
147
|
}, 8, ["loading"])
|
|
102
148
|
]),
|
|
103
149
|
_: 1
|
|
104
|
-
})) :
|
|
150
|
+
})) : T("", !0)
|
|
105
151
|
]));
|
|
106
152
|
}
|
|
107
|
-
}),
|
|
108
|
-
const
|
|
109
|
-
for (const [
|
|
110
|
-
|
|
111
|
-
return
|
|
112
|
-
},
|
|
113
|
-
function
|
|
114
|
-
const
|
|
153
|
+
}), Z = (e, n) => {
|
|
154
|
+
const a = e.__vccOpts || e;
|
|
155
|
+
for (const [s, r] of n)
|
|
156
|
+
a[s] = r;
|
|
157
|
+
return a;
|
|
158
|
+
}, ee = /* @__PURE__ */ Z(X, [["__scopeId", "data-v-75a3a47c"]]);
|
|
159
|
+
function se(e = {}) {
|
|
160
|
+
const n = e.path ?? "/settings";
|
|
115
161
|
return {
|
|
116
162
|
id: "settings",
|
|
117
|
-
path:
|
|
118
|
-
routes: [{ path:
|
|
163
|
+
path: n,
|
|
164
|
+
routes: [{ path: n, name: "webx.settings", component: ee }]
|
|
119
165
|
};
|
|
120
166
|
}
|
|
121
167
|
export {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
168
|
+
ee as WxSettingsPage,
|
|
169
|
+
U as createSettingsApi,
|
|
170
|
+
se as settings,
|
|
171
|
+
G as settingsMessages
|
|
126
172
|
};
|
|
127
173
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/api.ts","../src/messages.ts","../src/i18n.ts","../src/SettingsPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { SettingsValues } from './types'\n\nexport interface SettingsApi {\n /** Every value the screen describes, as stored. */\n load(): Promise<SettingsValues>\n /** Sends the values back; answers with what the server kept. A 422 lands as `HttpError`. */\n save(values: SettingsValues): Promise<SettingsValues>\n}\n\n/** `GET` and `PUT` on `/settings`, under the panel's API path. */\nexport function createSettingsApi(admin: AdminContext): SettingsApi {\n const base = `${admin.apiPath}/settings`\n\n return {\n async load() {\n const body = await admin.http.get<{ data: { values: SettingsValues } }>(base)\n\n return body.data.values\n },\n async save(values) {\n const body = await admin.http.put<{ data: { values: SettingsValues } }>(base, { values })\n\n return body.data.values\n },\n }\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * What this package says, in English — the same keys `webx-ui/module-settings` ships as\n * `lang/en/*.php`, so a key never reaches the screen when a translation is missing.\n */\nexport const settingsMessages: Record<string, Messages> = {\n module: {\n title: 'Settings',\n },\n page: {\n save: 'Save',\n saved: 'Settings saved.',\n failed: 'Some values were not accepted. Check the highlighted fields.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { settingsMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useSettingsMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-settings', settingsMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxBackupNote,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { toast, WxActionBar, WxButton, WxSkeleton } from '@webx-ui/core'\nimport type { ScreenModel } from '@webx-ui/schema'\nimport { createSettingsApi } from './api'\nimport { useSettingsMessages } from './i18n'\n\n/**\n * The settings section: the screen the server describes, and the saving that is the page's own\n * — in the head, and again in the bar along the bottom, because the form is taller than a\n * window and the head goes with the scroll. The screen does not know how it is saved; this does.\n */\nconst context = useAdmin()\nconst api = createSettingsApi(context)\nuseSettingsMessages()\n\nconst t = useTranslate('webx-settings')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst values = ref<ScreenModel>({})\nconst errors = ref<Record<string, string[]>>({})\nconst loading = ref(true)\nconst saving = ref(false)\n\nconst canManage = context.can('settings.manage')\n\n/** The section's name, as the server translated it; the built-in English until it arrives. */\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'settings')?.title ??\n t('module.title'),\n)\n\nonMounted(async () => {\n try {\n values.value = await api.load()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n})\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n try {\n values.value = await api.save(values.value)\n toast.success(t('page.saved'))\n\n // Some of what this screen holds is also what the panel is wearing — the logo in the\n // corner, the mark on the rail, the name of the site. The manifest is where the shell\n // reads those, so saving them here has to hand the shell a new one; otherwise the logo\n // somebody has just replaced goes on being the old one until the page is reloaded.\n void context.refreshManifest()\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]>; message?: string } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('page.failed'))\n } else {\n toast.danger(message(error, t('page.failed')))\n }\n } finally {\n saving.value = false\n }\n}\n/* Saving, and again in the bar below: the head goes with the scroll on a form this long (§3.3). */\nconst actions = computed<ScreenAction[]>(() =>\n canManage\n ? [\n {\n key: 'save',\n label: t('page.save'),\n primary: true,\n loading: saving.value,\n run: () => void save(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-settings\">\n <wx-screen-head :title=\"title\" :actions=\"actions\" />\n\n <wx-skeleton v-if=\"loading\" :rows=\"4\" />\n <wx-screen\n v-else\n v-model=\"values\"\n name=\"settings.index\"\n :errors=\"errors\"\n :disabled=\"!canManage\"\n />\n\n <!--\n Where the panel says when the database was last dumped (§6 of the backups spec). It is\n the section this belongs in — System is Settings and Administrators, and of those two\n this is the one somebody opens to ask how the installation is doing — and `settings.view`\n is the right the line itself asks for. A footnote under the form rather than a field in\n it: the form is what the site is, and this is what the server did last night.\n -->\n <wx-backup-note />\n\n <!-- The settings are longer than a window, and the button in the head is off the top of it\n by the second group of fields. This is the same button, where the eye already is. -->\n <wx-action-bar v-if=\"canManage && !loading\">\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">{{ t('page.save') }}</wx-button>\n </wx-action-bar>\n </div>\n</template>\n\n<style scoped>\n/* A column, because the bar along the bottom is pushed there by an auto margin — and it is\n `WxMain` that gives a screen carrying one the height to push it down through. */\n.wx-settings {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport SettingsPage from './SettingsPage.vue'\n\nexport interface SettingsOptions {\n /** Where the section lives inside the panel. */\n path?: string\n}\n\n/**\n * The settings, as a section of the panel.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: a section shows up when both halves are installed.\n */\nexport function settings(options: SettingsOptions = {}): AdminModule {\n const path = options.path ?? '/settings'\n\n return {\n id: 'settings',\n path,\n routes: [{ path, name: 'webx.settings', component: SettingsPage }],\n }\n}\n"],"names":["createSettingsApi","admin","base","values","settingsMessages","seeded","useSettingsMessages","i18n","useAdmin","context","api","t","useTranslate","message","useErrorText","ref","errors","loading","saving","canManage","title","computed","module","onMounted","error","toast","save","body","actions","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","WxScreenHead","_createBlock","WxSkeleton","WxScreen","$event","WxBackupNote","WxActionBar","WxButton","settings","options","path","SettingsPage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/api.ts","../src/settingOptions.ts","../src/messages.ts","../src/i18n.ts","../src/SettingsPage.vue","../src/module.ts"],"sourcesContent":["import type { AdminContext } from '@webx-ui/module-admin'\nimport type { SettingsValues } from './types'\n\nexport interface SettingsApi {\n /** Every value the screen describes, as stored. */\n load(): Promise<SettingsValues>\n /** Sends the values back; answers with what the server kept. A 422 lands as `HttpError`. */\n save(values: SettingsValues): Promise<SettingsValues>\n}\n\n/** `GET` and `PUT` on `/settings`, under the panel's API path. */\nexport function createSettingsApi(admin: AdminContext): SettingsApi {\n const base = `${admin.apiPath}/settings`\n\n return {\n async load() {\n const body = await admin.http.get<{ data: { values: SettingsValues } }>(base)\n\n return body.data.values\n },\n async save(values) {\n const body = await admin.http.put<{ data: { values: SettingsValues } }>(base, { values })\n\n return body.data.values\n },\n }\n}\n","import type { AutocompleteOption } from '@webx-ui/core'\nimport type { ScreenModel, ScreenNode } from '@webx-ui/schema'\n\n/**\n * The types whose value a shortcode can print as it is: a line of text or a number. A picture, a\n * list or a switch would print as nothing, so they are not offered.\n */\nconst PRINTABLE = new Set(['wx-input', 'wx-textarea', 'wx-input-number', 'wx-autocomplete'])\n\n/** The list the shortcodes keep themselves in — reading it would print nothing. */\nconst OWN_KEY = 'shortcodes.data'\n\n/** Long enough to recognise an address by, short enough for one line of the dropdown. */\nconst PREVIEW = 40\n\n/**\n * The settings a shortcode can read, for the autocomplete of its key: labelled the way the form\n * shows them («Contacts → Phone»), with the key and what the setting holds now underneath, so the\n * one being picked is the one meant.\n */\nexport function settingOptions(\n root: ScreenNode[],\n values: ScreenModel,\n locale: string,\n): AutocompleteOption[] {\n const options: AutocompleteOption[] = []\n\n const walk = (nodes: ScreenNode[], trail: string[]): void => {\n for (const node of nodes) {\n // A repeater's children are fields of its rows, not settings.\n if (node.type === 'wx-repeater') continue\n\n if (node.name !== undefined) {\n if (PRINTABLE.has(node.type) && node.name !== OWN_KEY) {\n const label = [...trail, node.label ?? node.name].join(' → ')\n const now = preview(values[node.name], locale)\n\n options.push({\n value: node.name,\n label,\n description: now === '' ? node.name : `${node.name} · ${now}`,\n })\n }\n continue\n }\n\n // Tabs and cards name a group; the rest of the layout does not.\n const named = (node.type === 'wx-tab' || node.type === 'wx-card') && node.label\n walk(node.children ?? [], named ? [...trail, node.label as string] : trail)\n }\n }\n\n walk(root, [])\n\n return options\n}\n\n/** A value as a line: the panel's language of a translated one, or the first language filled. */\nfunction preview(value: unknown, locale: string): string {\n let text = value\n\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n const languages = value as Record<string, unknown>\n text =\n languages[locale] ||\n Object.values(languages).find((one) => typeof one === 'string' && one !== '')\n }\n\n if (typeof text === 'number') text = String(text)\n if (typeof text !== 'string') return ''\n\n const line = text.replace(/\\s+/g, ' ').trim()\n\n return line.length > PREVIEW ? `${line.slice(0, PREVIEW - 1)}…` : line\n}\n","import type { Messages } from '@webx-ui/module-admin'\n\n/**\n * What this package says, in English — the same keys `webx-ui/module-settings` ships as\n * `lang/en/*.php`, so a key never reaches the screen when a translation is missing.\n */\nexport const settingsMessages: Record<string, Messages> = {\n module: {\n title: 'Settings',\n },\n page: {\n save: 'Save',\n saved: 'Settings saved.',\n failed: 'Some values were not accepted. Check the highlighted fields.',\n },\n}\n","import { useAdmin } from '@webx-ui/module-admin'\nimport { settingsMessages } from './messages'\n\nconst seeded = new WeakSet<object>()\n\n/** Puts this package's English under the panel's dictionary, once per panel. */\nexport function useSettingsMessages(): void {\n try {\n const { i18n } = useAdmin()\n\n if (!seeded.has(i18n)) {\n i18n.defaults('webx-settings', settingsMessages)\n seeded.add(i18n)\n }\n } catch {\n // Outside a panel there is no dictionary to seed, and `useTranslate` falls back on its own.\n }\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\nimport {\n useAdmin,\n useErrorText,\n useTranslate,\n WxBackupNote,\n WxScreen,\n WxScreenHead,\n type ScreenAction,\n} from '@webx-ui/module-admin'\nimport { toast, WxActionBar, WxButton, WxSkeleton } from '@webx-ui/core'\nimport type { Patch, ScreenModel, ScreenNode } from '@webx-ui/schema'\nimport { createSettingsApi } from './api'\nimport { settingOptions } from './settingOptions'\nimport { useSettingsMessages } from './i18n'\n\n/**\n * The settings section: the screen the server describes, and the saving that is the page's own\n * — in the head, and again in the bar along the bottom, because the form is taller than a\n * window and the head goes with the scroll. The screen does not know how it is saved; this does.\n */\nconst context = useAdmin()\nconst api = createSettingsApi(context)\nuseSettingsMessages()\n\nconst t = useTranslate('webx-settings')\n/* Not the server's `message`: the panel says how a request failed in its own words (§13.3). */\nconst message = useErrorText()\n\nconst values = ref<ScreenModel>({})\nconst errors = ref<Record<string, string[]>>({})\nconst loading = ref(true)\nconst saving = ref(false)\n\nconst canManage = context.can('settings.manage')\n\n/** The tree as drawn, for what the shortcodes can read; it arrives with the screen. */\nconst tree = ref<ScreenNode[]>([])\n\n/*\n * A shortcode that reads a setting names it by key, and nobody remembers keys: the list to pick\n * from is every text setting on this screen, with where it lives and what it holds right now.\n * Built here because only the page has both the tree and the values.\n */\nconst patch = computed<Patch>(() =>\n hasNode(tree.value, 'shortcode-key')\n ? [\n {\n op: 'set',\n target: 'shortcode-key',\n props: { options: settingOptions(tree.value, values.value, context.i18n.state.locale) },\n },\n ]\n : [],\n)\n\nfunction hasNode(nodes: ScreenNode[], id: string): boolean {\n return nodes.some((node) => node.id === id || hasNode(node.children ?? [], id))\n}\n\n/** The section's name, as the server translated it; the built-in English until it arrives. */\nconst title = computed(\n () =>\n context.state.manifest?.modules.find((module) => module.id === 'settings')?.title ??\n t('module.title'),\n)\n\nonMounted(async () => {\n try {\n values.value = await api.load()\n } catch (error) {\n toast.danger(message(error))\n } finally {\n loading.value = false\n }\n})\n\nasync function save(): Promise<void> {\n saving.value = true\n errors.value = {}\n\n try {\n values.value = await api.save(values.value)\n toast.success(t('page.saved'))\n\n // Some of what this screen holds is also what the panel is wearing — the logo in the\n // corner, the mark on the rail, the name of the site. The manifest is where the shell\n // reads those, so saving them here has to hand the shell a new one; otherwise the logo\n // somebody has just replaced goes on being the old one until the page is reloaded.\n void context.refreshManifest()\n } catch (error) {\n const body = (error as { body?: { errors?: Record<string, string[]>; message?: string } }).body\n\n if (body?.errors) {\n errors.value = body.errors\n toast.danger(t('page.failed'))\n } else {\n toast.danger(message(error, t('page.failed')))\n }\n } finally {\n saving.value = false\n }\n}\n/* Saving, and again in the bar below: the head goes with the scroll on a form this long (§3.3). */\nconst actions = computed<ScreenAction[]>(() =>\n canManage\n ? [\n {\n key: 'save',\n label: t('page.save'),\n primary: true,\n loading: saving.value,\n run: () => void save(),\n },\n ]\n : [],\n)\n</script>\n\n<template>\n <div class=\"wx-settings\">\n <wx-screen-head :title=\"title\" :actions=\"actions\" />\n\n <wx-skeleton v-if=\"loading\" :rows=\"4\" />\n <wx-screen\n v-else\n v-model=\"values\"\n name=\"settings.index\"\n :patch=\"patch\"\n :errors=\"errors\"\n :disabled=\"!canManage\"\n @loaded=\"tree = $event\"\n />\n\n <!--\n Where the panel says when the database was last dumped (§6 of the backups spec). It is\n the section this belongs in — System is Settings and Administrators, and of those two\n this is the one somebody opens to ask how the installation is doing — and `settings.view`\n is the right the line itself asks for. A footnote under the form rather than a field in\n it: the form is what the site is, and this is what the server did last night.\n -->\n <wx-backup-note />\n\n <!-- The settings are longer than a window, and the button in the head is off the top of it\n by the second group of fields. This is the same button, where the eye already is. -->\n <wx-action-bar v-if=\"canManage && !loading\">\n <wx-button type=\"primary\" :loading=\"saving\" @click=\"save\">{{ t('page.save') }}</wx-button>\n </wx-action-bar>\n </div>\n</template>\n\n<style scoped>\n/* A column, because the bar along the bottom is pushed there by an auto margin — and it is\n `WxMain` that gives a screen carrying one the height to push it down through. */\n.wx-settings {\n display: flex;\n flex-direction: column;\n gap: var(--wx-gap, var(--wx-space-16));\n}\n</style>\n","import type { AdminModule } from '@webx-ui/module-admin'\nimport SettingsPage from './SettingsPage.vue'\n\nexport interface SettingsOptions {\n /** Where the section lives inside the panel. */\n path?: string\n}\n\n/**\n * The settings, as a section of the panel.\n *\n * The id matches the module the server reports, which is what makes the entry appear in the\n * navigation: a section shows up when both halves are installed.\n */\nexport function settings(options: SettingsOptions = {}): AdminModule {\n const path = options.path ?? '/settings'\n\n return {\n id: 'settings',\n path,\n routes: [{ path, name: 'webx.settings', component: SettingsPage }],\n }\n}\n"],"names":["createSettingsApi","admin","base","values","PRINTABLE","OWN_KEY","PREVIEW","settingOptions","root","locale","options","walk","nodes","trail","node","label","now","preview","named","value","text","languages","one","line","settingsMessages","seeded","useSettingsMessages","i18n","useAdmin","context","api","t","useTranslate","message","useErrorText","ref","errors","loading","saving","canManage","tree","patch","computed","hasNode","id","title","module","onMounted","error","toast","save","body","actions","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","WxScreenHead","_createBlock","WxSkeleton","WxScreen","$event","_cache","WxBackupNote","WxActionBar","WxButton","settings","path","SettingsPage"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AutocompleteOption } from '@webx-ui/core';
|
|
2
|
+
import { ScreenModel, ScreenNode } from '@webx-ui/schema';
|
|
3
|
+
/**
|
|
4
|
+
* The settings a shortcode can read, for the autocomplete of its key: labelled the way the form
|
|
5
|
+
* shows them («Contacts → Phone»), with the key and what the setting holds now underneath, so the
|
|
6
|
+
* one being picked is the one meant.
|
|
7
|
+
*/
|
|
8
|
+
export declare function settingOptions(root: ScreenNode[], values: ScreenModel, locale: string): AutocompleteOption[];
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.wx-settings[data-v-
|
|
1
|
+
.wx-settings[data-v-75a3a47c]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16))}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webx-ui/module-settings",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Site settings for the WebX UI admin panel: the section that draws the settings screen and saves it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"vue-router": "^4.5.0"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@webx-ui/core": "^0.
|
|
46
|
-
"@webx-ui/module-admin": "^0.
|
|
47
|
-
"@webx-ui/schema": "^0.
|
|
45
|
+
"@webx-ui/core": "^0.40.0",
|
|
46
|
+
"@webx-ui/module-admin": "^0.25.0",
|
|
47
|
+
"@webx-ui/schema": "^0.9.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^24.10.1",
|