merge newui branch
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
import { settingsSidebar } from "../settingsSidebar";
|
||||
import { batchAccordion } from "./batchAccordion";
|
||||
import { rateLimitAccordion, sortRules } from "./rateLimitAccordion";
|
||||
import { trustedProxyAccordion } from "./trustedProxyAccordion";
|
||||
|
||||
export function pageApplicationSettings() {
|
||||
app.store.title = "Application settings";
|
||||
|
||||
const data = store({
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
formSettings: null,
|
||||
originalFormSettings: null,
|
||||
get originalFormSettingsHash() {
|
||||
return JSON.stringify(data.originalFormSettings);
|
||||
},
|
||||
get hasChanges() {
|
||||
return data.originalFormSettingsHash != JSON.stringify(data.formSettings);
|
||||
},
|
||||
});
|
||||
|
||||
loadSettings();
|
||||
|
||||
async function loadSettings() {
|
||||
data.isLoading = true;
|
||||
|
||||
try {
|
||||
const settings = await app.pb.settings.getAll();
|
||||
init(settings);
|
||||
|
||||
data.isLoading = false;
|
||||
} catch (err) {
|
||||
if (!err.isAbort) {
|
||||
app.checkApiError(err);
|
||||
// data.isLoading = false; don't reset in case of a server error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (data.isSaving || !data.hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.isSaving = true;
|
||||
|
||||
data.formSettings.rateLimits.rules = sortRules(data.formSettings.rateLimits.rules);
|
||||
|
||||
try {
|
||||
const redacted = app.utils.filterRedactedProps(data.formSettings);
|
||||
const settings = await app.pb.settings.update(redacted);
|
||||
init(settings);
|
||||
|
||||
app.toasts.success("Successfully saved application settings.");
|
||||
} catch (err) {
|
||||
app.checkApiError(err);
|
||||
}
|
||||
|
||||
data.isSaving = false;
|
||||
}
|
||||
|
||||
function init(settings = {}) {
|
||||
// refresh local app settings
|
||||
app.store.settings = JSON.parse(JSON.stringify(settings));
|
||||
|
||||
// load from the css style as fallback
|
||||
if (!settings.meta?.accentColor) {
|
||||
const cssColor = window.getComputedStyle(document.documentElement)?.getPropertyValue("--accentColor");
|
||||
if (cssColor?.startsWith("#")) {
|
||||
settings.meta = settings.meta || {};
|
||||
settings.meta.accentColor = cssColor.toLowerCase() || "";
|
||||
}
|
||||
}
|
||||
|
||||
data.originalFormSettings = {
|
||||
meta: settings.meta || {},
|
||||
batch: settings.batch || {},
|
||||
trustedProxy: settings.trustedProxy || { headers: [] },
|
||||
rateLimits: settings.rateLimits || { rules: [] },
|
||||
};
|
||||
|
||||
sortRules(data.originalFormSettings.rateLimits.rules);
|
||||
|
||||
data.formSettings = JSON.parse(JSON.stringify(data.originalFormSettings));
|
||||
}
|
||||
|
||||
function reset() {
|
||||
data.formSettings = JSON.parse(data.originalFormSettingsHash);
|
||||
}
|
||||
|
||||
return t.div(
|
||||
{
|
||||
pbEvent: "pageApplicationSettings",
|
||||
className: "page page-application-settings",
|
||||
},
|
||||
settingsSidebar(),
|
||||
t.div(
|
||||
{ className: "page-content full-height" },
|
||||
t.header(
|
||||
{ className: "page-header" },
|
||||
t.nav(
|
||||
{ className: "breadcrumbs" },
|
||||
t.div({ className: "breadcrumb-item" }, "Settings"),
|
||||
t.div({ className: "breadcrumb-item" }, "Application"),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "wrapper m-b-base" },
|
||||
() => {
|
||||
if (data.isLoading) {
|
||||
return t.div({ className: "block txt-center" }, t.span({ className: "loader lg" }));
|
||||
}
|
||||
|
||||
return t.form(
|
||||
{
|
||||
pbEvent: "applicationSettingsForm",
|
||||
className: "grid application-settings-form",
|
||||
inert: () => data.isSaving,
|
||||
onsubmit: (e) => {
|
||||
e.preventDefault();
|
||||
save();
|
||||
},
|
||||
},
|
||||
t.div(
|
||||
{ className: "col-md-5" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: "meta.appName" }, "Application name"),
|
||||
t.input({
|
||||
id: "meta.appName",
|
||||
name: "meta.appName",
|
||||
type: "text",
|
||||
required: true,
|
||||
value: () => data.formSettings.meta.appName || "",
|
||||
oninput: (e) => (data.formSettings.meta.appName = e.target.value),
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-md-5" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.label({ htmlFor: "meta.appURL" }, "Application URL"),
|
||||
t.input({
|
||||
id: "meta.appURL",
|
||||
name: "meta.appURL",
|
||||
type: "url",
|
||||
required: true,
|
||||
value: () => data.formSettings.meta.appURL || "",
|
||||
oninput: (e) => (data.formSettings.meta.appURL = e.target.value),
|
||||
}),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-md-2" },
|
||||
// pass isSaving to ensure that it will be rerendered after save
|
||||
() => accentColorField(data, data.isSaving),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-lg-12" },
|
||||
() => trustedProxyAccordion(data),
|
||||
() => rateLimitAccordion(data),
|
||||
() => batchAccordion(data),
|
||||
),
|
||||
t.div(
|
||||
{ className: "col-lg-12" },
|
||||
t.div(
|
||||
{ className: "field" },
|
||||
t.input({
|
||||
id: "meta.hideControls",
|
||||
name: "meta.hideControls",
|
||||
type: "checkbox",
|
||||
className: "switch",
|
||||
checked: () => data.formSettings.meta.hideControls,
|
||||
onchange: (e) => (data.formSettings.meta.hideControls = e.target.checked),
|
||||
}),
|
||||
t.label(
|
||||
{ htmlFor: "meta.hideControls" },
|
||||
t.span({ className: "txt" }, "Hide collection create and edit controls"),
|
||||
),
|
||||
),
|
||||
),
|
||||
t.div({ className: "col-lg-12" }, t.hr()),
|
||||
t.div(
|
||||
{ className: "col-lg-12" },
|
||||
t.div(
|
||||
{ className: "flex" },
|
||||
t.div({ className: "m-r-auto" }),
|
||||
t.button(
|
||||
{
|
||||
type: "button",
|
||||
className: "btn transparent secondary",
|
||||
disabled: () => data.isSaving,
|
||||
hidden: () => !data.hasChanges,
|
||||
onclick: reset,
|
||||
},
|
||||
t.span({ className: "txt" }, "Cancel"),
|
||||
),
|
||||
t.button(
|
||||
{
|
||||
className: () => `btn expanded ${data.isSaving ? "loading" : ""}`,
|
||||
disabled: () => !data.hasChanges || data.isSaving,
|
||||
},
|
||||
t.span({ className: "txt" }, "Save changes"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
t.footer({ className: "page-footer" }, app.components.credits()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function accentColorField(pageData) {
|
||||
const uniqueId = "accent_" + app.utils.randomString();
|
||||
|
||||
const local = store({
|
||||
isTooLight: false,
|
||||
});
|
||||
|
||||
let colorChangeTimeoutId;
|
||||
let tempNoAnimationTimeoutId;
|
||||
|
||||
function changeAccentColor(color) {
|
||||
// temporary disable animations to minimize flickering
|
||||
clearTimeout(tempNoAnimationTimeoutId);
|
||||
document.documentElement.style.setProperty("--animationSpeed", "0");
|
||||
|
||||
if (color) {
|
||||
document.documentElement.style.setProperty("--accentColor", color.toLowerCase());
|
||||
} else {
|
||||
document.documentElement.style.removeProperty("--accentColor");
|
||||
}
|
||||
|
||||
// restore animation
|
||||
tempNoAnimationTimeoutId = setTimeout(() => {
|
||||
document.documentElement.style.removeProperty("--animationSpeed");
|
||||
}, 100);
|
||||
}
|
||||
|
||||
const watchers = [
|
||||
watch(() => pageData.formSettings?.meta?.accentColor, (newColor) => {
|
||||
clearTimeout(colorChangeTimeoutId);
|
||||
colorChangeTimeoutId = setTimeout(() => {
|
||||
changeAccentColor(newColor);
|
||||
}, 100);
|
||||
}),
|
||||
];
|
||||
|
||||
return t.div(
|
||||
{
|
||||
className: "field",
|
||||
ariaDescription: app.attrs.tooltip(() => local.isTooLight ? "Invalid - color is too light" : ""),
|
||||
onunmount: () => {
|
||||
clearTimeout(colorChangeTimeoutId);
|
||||
changeAccentColor(pageData.formSettings.meta.accentColor);
|
||||
watchers.forEach((w) => w?.unwatch());
|
||||
},
|
||||
},
|
||||
t.label(
|
||||
{ htmlFor: uniqueId },
|
||||
t.span({ className: "txt" }, "Accent"),
|
||||
t.i({
|
||||
hidden: () => !local.isTooLight,
|
||||
className: "txt-warning ri-alert-line",
|
||||
}),
|
||||
),
|
||||
app.components.colorPicker({
|
||||
id: uniqueId,
|
||||
name: "meta.accentColor",
|
||||
predefinedColors: () => app.store.predefinedAccentColors,
|
||||
value: () => pageData.formSettings.meta.accentColor,
|
||||
onchange: (color) => {
|
||||
// @todo consider removing the constraint once contrast-color is implemented
|
||||
local.isTooLight = false;
|
||||
if (!app.utils.isDarkEnoughForWhiteText(color)) {
|
||||
local.isTooLight = true;
|
||||
return;
|
||||
}
|
||||
|
||||
pageData.formSettings.meta.accentColor = color;
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user