Saving success messages betteR

This commit is contained in:
Simon Martens
2026-01-23 20:00:55 +01:00
parent 7ef2611537
commit 0beb5a2c79
20 changed files with 1209 additions and 811 deletions

View File

@@ -18,6 +18,7 @@ export class AlmanachEditPage extends HTMLElement {
this._preferredSeriesRelationId = "";
this._preferredSeriesSeriesId = "";
this._handleSaveClick = this._handleSaveClick.bind(this);
this._handleSaveViewClick = this._handleSaveViewClick.bind(this);
this._handleResetClick = this._handleResetClick.bind(this);
this._handleDeleteClick = this._handleDeleteClick.bind(this);
this._handleDeleteConfirmClick = this._handleDeleteConfirmClick.bind(this);
@@ -145,6 +146,7 @@ export class AlmanachEditPage extends HTMLElement {
this._teardownSaveHandling();
this._form = this.querySelector("#changealmanachform");
this._saveButton = this.querySelector("[data-role='almanach-save']");
this._saveViewButton = this.querySelector("[data-role='almanach-save-view']");
this._resetButton = this.querySelector("[data-role='almanach-reset']");
this._deleteButton = this.querySelector("[data-role='almanach-delete']");
this._deleteDialog = this.querySelector("[data-role='almanach-delete-dialog']");
@@ -157,6 +159,9 @@ export class AlmanachEditPage extends HTMLElement {
this._saveEndpoint = this._form.getAttribute("data-save-endpoint") || this._deriveSaveEndpoint();
this._deleteEndpoint = this._form.getAttribute("data-delete-endpoint") || "";
this._saveButton.addEventListener("click", this._handleSaveClick);
if (this._saveViewButton) {
this._saveViewButton.addEventListener("click", this._handleSaveViewClick);
}
if (this._resetButton) {
this._resetButton.addEventListener("click", this._handleResetClick);
}
@@ -188,6 +193,9 @@ export class AlmanachEditPage extends HTMLElement {
if (this._saveButton) {
this._saveButton.removeEventListener("click", this._handleSaveClick);
}
if (this._saveViewButton) {
this._saveViewButton.removeEventListener("click", this._handleSaveViewClick);
}
if (this._resetButton) {
this._resetButton.removeEventListener("click", this._handleResetClick);
}
@@ -204,6 +212,7 @@ export class AlmanachEditPage extends HTMLElement {
this._deleteDialog.removeEventListener("cancel", this._handleDeleteCancelClick);
}
this._saveButton = null;
this._saveViewButton = null;
this._resetButton = null;
this._deleteButton = null;
this._deleteDialog = null;
@@ -258,13 +267,54 @@ export class AlmanachEditPage extends HTMLElement {
throw new Error(message);
}
if (data?.redirect) {
window.location.assign(data.redirect);
return;
await this._reloadForm(data?.message || "Änderungen gespeichert.");
} catch (error) {
this._showStatus(error instanceof Error ? error.message : "Speichern fehlgeschlagen.", "error");
} finally {
this._setSavingState(false);
}
}
async _handleSaveViewClick(event) {
event.preventDefault();
if (this._isSaving) {
return;
}
const redirectUrl = this._saveViewButton?.getAttribute("data-redirect-url");
if (!redirectUrl) {
return;
}
this._clearStatus();
let payload;
try {
payload = this._buildPayload();
} catch (error) {
this._showStatus(error instanceof Error ? error.message : String(error), "error");
return;
}
this._setSavingState(true);
try {
const response = await fetch(this._saveEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
let message = `Speichern fehlgeschlagen (${response.status}).`;
try {
const data = await response.clone().json();
message = data?.error || message;
} catch {
// ignore parsing error
}
throw new Error(message);
}
await this._reloadForm(data?.message || "Änderungen gespeichert.");
this._clearStatus();
window.location.assign(redirectUrl);
} catch (error) {
this._showStatus(error instanceof Error ? error.message : "Speichern fehlgeschlagen.", "error");
} finally {

View File

@@ -134,6 +134,44 @@
@apply w-full inline-flex justify-center py-2 px-4 border border-transparent rounded-md text-sm font-medium text-gray-800 bg-stone-200 hover:bg-stone-300 cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 no-underline;
}
.form-with-action-bar {
@apply pb-0;
}
.form-action-bar {
@apply sticky bottom-0 z-30 border-t border-transparent bg-stone-50;
box-shadow: none;
width: calc(100% + 120px);
margin-left: -60px;
margin-right: -60px;
}
.form-action-bar-inner {
@apply w-full max-w-(--breakpoint-xl) mx-auto px-8 py-3 flex items-center justify-between gap-4 flex-wrap;
}
.form-action-bar-actions {
@apply ml-auto flex items-center gap-3 flex-wrap justify-end w-full md:w-auto;
}
.form-action-bar-message {
@apply text-left mr-auto w-full md:w-auto;
}
.form-action-bar .submitbutton,
.form-action-bar .resetbutton {
@apply !w-auto px-4;
}
.form-action-bar.is-stuck {
@apply border-t border-l border-r border-transparent;
background-color: rgb(250 250 249);
border-color: rgba(15, 23, 42, 0.06);
box-shadow: 0 -1px 6px rgba(15, 23, 42, 0.06);
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.content-action-button {
@apply inline-flex items-center justify-center gap-2 rounded-xs border border-slate-300 bg-white px-3 py-1.5 text-base font-semibold text-gray-800 shadow-sm transition-all duration-75 hover:bg-stone-50 focus:outline-none focus:ring-2 focus:ring-slate-400/30;
}

View File

@@ -616,6 +616,32 @@ function FormLoad(form) {
// Update on change
checkbox.addEventListener("change", updateHiddenInput);
});
}
function InitStickyActionBars() {
if (InitStickyActionBars._initialized) {
return;
}
InitStickyActionBars._initialized = true;
const update = () => {
const bars = document.querySelectorAll(".form-action-bar");
if (!bars.length) {
return;
}
const viewportBottom = window.innerHeight || document.documentElement.clientHeight;
bars.forEach((bar) => {
const rect = bar.getBoundingClientRect();
const stuck = rect.bottom >= viewportBottom - 1;
bar.classList.toggle("is-stuck", stuck);
});
};
update();
window.addEventListener("scroll", update, { passive: true });
window.addEventListener("resize", update);
document.addEventListener("htmx:afterSwap", update);
}
document.addEventListener("keydown", (event) => {
@@ -639,5 +665,6 @@ window.HookupRBChange = HookupRBChange;
window.FormLoad = FormLoad;
window.TextareaAutoResize = TextareaAutoResize;
InitGlobalHtmxNotice();
InitStickyActionBars();
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink, ItemsEditor, SingleSelectRemote, AlmanachEditPage, RelationsEditor, EditPage, FabMenu, LookupField };

View File

@@ -11,7 +11,7 @@ export class ScrollButton extends HTMLElement {
<button
class="
scroll-to-top
fixed bottom-5 right-5
fixed bottom-12 right-8 z-50
hidden
bg-gray-800 text-white
p-2