Moved alerts and dialogs from unsupported bootbox to Sweetalert2 library

This commit is contained in:
Jan Böhmer 2026-06-21 14:21:01 +02:00
parent a489380f49
commit 99e56c4b1d
12 changed files with 842 additions and 805 deletions

View file

@ -19,8 +19,7 @@
import {Controller} from "@hotwired/stimulus"; import {Controller} from "@hotwired/stimulus";
import {visit} from "@hotwired/turbo"; import {visit} from "@hotwired/turbo";
import * as bootbox from "bootbox"; import {ConfirmSwal} from "../../helpers/swal";
import "../../css/components/bootbox_extensions.css";
import "../../css/components/dirty_form.css"; import "../../css/components/dirty_form.css";
/** /**
@ -207,11 +206,10 @@ export default class extends Controller {
} }
_confirmNavigation(onConfirm) { _confirmNavigation(onConfirm) {
bootbox.confirm({ ConfirmSwal.fire({
title: this.confirmTitleValue, titleText: this.confirmTitleValue,
message: this.confirmMessageValue, text: this.confirmMessageValue,
callback: (result) => { if (result) onConfirm(); } }).then(({isConfirmed}) => { if (isConfirmed) onConfirm(); });
});
} }
_handleLinkClick(event) { _handleLinkClick(event) {

View file

@ -19,8 +19,7 @@
import {Controller} from "@hotwired/stimulus"; import {Controller} from "@hotwired/stimulus";
import * as bootbox from "bootbox"; import {AlertSwal, ConfirmSwal} from "../../helpers/swal";
import "../../css/components/bootbox_extensions.css";
import accept from "attr-accept"; import accept from "attr-accept";
export default class extends Controller { export default class extends Controller {
@ -62,7 +61,7 @@ export default class extends Controller {
if(!prototype) { if(!prototype) {
console.warn("Prototype is not set, we cannot create a new element. This is most likely due to missing permissions."); console.warn("Prototype is not set, we cannot create a new element. This is most likely due to missing permissions.");
bootbox.alert("You do not have the permissions to create a new element. (No protoype element is set)"); AlertSwal.fire({"text": "You do not have the permissions to create a new element. (No protoype element is set)"});
return; return;
} }
@ -226,8 +225,10 @@ export default class extends Controller {
} }
if(this.deleteMessageValue) { if(this.deleteMessageValue) {
bootbox.confirm(this.deleteMessageValue, (result) => { ConfirmSwal.fire({
if (result) { text: this.deleteMessageValue,
}).then(({isConfirmed}) => {
if (isConfirmed) {
del(); del();
} }
}); });

View file

@ -20,7 +20,7 @@
import DatatablesController from "./datatables_controller.js"; import DatatablesController from "./datatables_controller.js";
import TomSelect from "tom-select"; import TomSelect from "tom-select";
import * as bootbox from "bootbox"; import {ConfirmSwal} from "../../../helpers/swal";
/** /**
* This is the datatables controller for parts lists * This is the datatables controller for parts lists
@ -146,15 +146,17 @@ export default class extends DatatablesController {
bubbles: true, //This line is important, otherwise Turbo will not receive the event bubbles: true, //This line is important, otherwise Turbo will not receive the event
}); });
const confirm = bootbox.confirm({ ConfirmSwal.fire({
message: message, title: title, callback: function (result) { titleText: title,
//If the dialog was confirmed, then submit the form. text: message,
if (result) { icon: "warning"
that._confirmed = true; }).then(({isConfirmed}) => {
form.dispatchEvent(that._our_event); //If the dialog was confirmed, then submit the form.
} else { if (isConfirmed) {
that._confirmed = false; that._confirmed = true;
} form.dispatchEvent(that._our_event);
} else {
that._confirmed = false;
} }
}); });
} }

View file

@ -19,8 +19,7 @@
import {Controller} from "@hotwired/stimulus"; import {Controller} from "@hotwired/stimulus";
import * as bootbox from "bootbox"; import {ConfirmSwal} from "../../helpers/swal";
import "../../css/components/bootbox_extensions.css";
export default class extends Controller export default class extends Controller
{ {
@ -48,32 +47,33 @@ export default class extends Controller
const submitter = event.submitter; const submitter = event.submitter;
const that = this; const that = this;
const confirm = bootbox.confirm({ ConfirmSwal.fire({
message: message, title: title, callback: function (result) { titleText: title,
//If the dialog was confirmed, then submit the form. html: message, //Message contains a <br> tag and no user injectable HTML
if (result) { }).then(({isConfirmed}) => {
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form //If the dialog was confirmed, then submit the form.
that._confirmed = true; if (isConfirmed) {
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
that._confirmed = true;
//Create a submit button in the form and click it to submit the form //Create a submit button in the form and click it to submit the form
//Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273 //Before a submit event was dispatched, but this caused weird issues on Firefox causing the delete request being posted twice (and the second time was returning 404). See https://github.com/Part-DB/Part-DB-server/issues/273
const submit_btn = document.createElement('button'); const submit_btn = document.createElement('button');
submit_btn.type = 'submit'; submit_btn.type = 'submit';
submit_btn.style.display = 'none'; submit_btn.style.display = 'none';
//If the clicked button has a value, set it on the submit button //If the clicked button has a value, set it on the submit button
if (submitter.value) { if (submitter.value) {
submit_btn.value = submitter.value; submit_btn.value = submitter.value;
}
if (submitter.name) {
submit_btn.name = submitter.name;
}
form.appendChild(submit_btn);
submit_btn.click();
} else {
that._confirmed = false;
} }
if (submitter.name) {
submit_btn.name = submitter.name;
}
form.appendChild(submit_btn);
submit_btn.click();
} else {
that._confirmed = false;
} }
}); });
} }
} }

View file

@ -19,8 +19,7 @@
import {Controller} from "@hotwired/stimulus"; import {Controller} from "@hotwired/stimulus";
import * as bootbox from "bootbox"; import {ConfirmSwal} from "../../helpers/swal";
import "../../css/components/bootbox_extensions.css";
export default class extends Controller export default class extends Controller
{ {
@ -53,20 +52,19 @@ export default class extends Controller
const that = this; const that = this;
bootbox.confirm({ ConfirmSwal.fire({
title: this.titleValue, titleText: this.titleValue,
message: this.messageValue, text: this.messageValue,
callback: (result) => { }).then(({isConfirmed}) => {
if (result) { if (isConfirmed) {
//Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form //Set a flag to prevent the dialog from popping up again and allowing turbo to submit the form
that._confirmed = true; that._confirmed = true;
//Click the link //Click the link
that.element.click(); that.element.click();
} else { } else {
that._confirmed = false; that._confirmed = false;
}
} }
}); });
} }
} }

View file

@ -18,7 +18,7 @@
*/ */
import {Controller} from "@hotwired/stimulus"; import {Controller} from "@hotwired/stimulus";
import * as bootbox from "bootbox"; import {AlertSwal} from "../../helpers/swal";
export default class extends Controller { export default class extends Controller {
@ -35,12 +35,12 @@ export default class extends Controller {
const part_distance = document.getElementById('reel_part_distance').value; const part_distance = document.getElementById('reel_part_distance').value;
if (dia_inner == "" || dia_outer == "" || tape_thickness == "") { if (dia_inner == "" || dia_outer == "" || tape_thickness == "") {
bootbox.alert(this.errorMissingValuesValue); AlertSwal.fire({title: this.errorMissingValuesValue});
return; return;
} }
if (dia_outer**dia_outer < dia_inner**dia_inner) { if (dia_outer**dia_outer < dia_inner**dia_inner) {
bootbox.alert(this.errorOuterGreaterInnerValue); AlertSwal.fire({title: this.errorOuterGreaterInnerValue});
return; return;
} }
@ -61,12 +61,12 @@ export default class extends Controller {
return; return;
} }
var parts_per_meter = 1 / (part_distance / 1000); const parts_per_meter = 1 / (part_distance / 1000);
document.getElementById('result_parts_per_meter').textContent = parts_per_meter.toFixed(2) + ' 1/m'; document.getElementById('result_parts_per_meter').textContent = parts_per_meter.toFixed(2) + ' 1/m';
var parts_amount = (length/1000) * parts_per_meter; const parts_amount = (length / 1000) * parts_per_meter;
document.getElementById('result_amount').textContent = Math.floor(parts_amount); document.getElementById('result_amount').textContent = Math.floor(parts_amount).toString();
} }
} }

View file

@ -0,0 +1,50 @@
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Respect the dark mode of Bootstrap 5 set via data-bs-theme="dark" on the <html> element. This is done by overriding the CSS variables of the bootstrap-5 theme of SweetAlert2.
*/
html[data-bs-theme="dark"] [data-swal2-theme='bootstrap-5'] {
/* POPUP */
--swal2-background: #212529;
--swal2-color: #fff;
--swal2-border: 1px solid #495057;
/* INPUT */
--swal2-input-background: #2b3035;
--swal2-input-border: 1px solid #495057;
--swal2-input-focus-border: 1px solid #86b7fe;
--swal2-input-focus-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
/* VALIDATION MESSAGE */
--swal2-validation-message-background: #2c0b0e;
--swal2-validation-message-color: #ea868f;
/* FOOTER */
--swal2-footer-border-color: #495057;
--swal2-footer-background: #343a40;
--swal2-footer-color: #adb5bd;
/* CLOSE BUTTON */
--swal2-close-button-color: #fff;
/* TOASTS */
--swal2-toast-border: 1px solid #495057;
}

View file

@ -17,30 +17,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.modal-body > .bootbox-close-button { import Swal from 'sweetalert2';
position: absolute; import 'sweetalert2/themes/bootstrap-5.css';
top: 0; import '../css/components/swal.css'
right: 0;
padding: 0.5rem 0.75rem;
z-index: 1;
}
.modal .bootbox-close-button {
font-weight: 100;
}
button.bootbox-close-button {
padding: 0;
background-color: transparent;
border: 0;
-webkit-appearance: none;
}
.bootbox-close-button { const BaseSwal = Swal.mixin({
/* float: right; */ position: "top",
font-size: 1.40625rem; theme: "bootstrap-5",
font-weight: 600; });
line-height: 1;
color: #000; const ConfirmSwal = BaseSwal.mixin({
text-shadow: none; showCancelButton: true,
opacity: .5; showCloseButton: true,
} icon: "warning",
});
const AlertSwal = BaseSwal.mixin({
showCloseButton: true,
icon: "info",
});
export { ConfirmSwal, AlertSwal, BaseSwal, BaseSwal as default,};

View file

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import * as bootbox from "bootbox"; import Swal from "../helpers/swal";
/** /**
* If this class is imported the user is shown an error dialog if he calls an page via Turbo and an error is responded. * If this class is imported the user is shown an error dialog if he calls an page via Turbo and an error is responded.
@ -99,33 +99,26 @@ class ErrorHandlerHelper {
const short_location = trimString(location, 50); const short_location = trimString(location, 50);
const alert = bootbox.alert( let url = location;
{ let msg = `Error calling <a href="${url}">${short_location}</a>.<br>`;
size: 'large', msg += '<b>Try to reload the page or contact the administrator if this error persists.</b>';
message: function() { msg += '<br><br><a class="btn btn-outline-secondary mb-2" data-bs-toggle="collapse" href="#iframe_div">View details</a>';
let url = location; msg += "<div class=\"collapse\" id='iframe_div'><iframe height='512' width='100%' id='error-iframe'></iframe></div>";
let msg = `Error calling <a href="${url}">${short_location}</a>.<br>`;
msg += '<b>Try to reload the page or contact the administrator if this error persists.</b>';
msg += '<br><br><a class=\"btn btn-outline-secondary mb-2\" data-bs-toggle=\"collapse\" href=\"#iframe_div\" >' + 'View details' + "</a>"; Swal.fire({
msg += "<div class=\" collapse\" id='iframe_div'><iframe height='512' width='100%' id='error-iframe'></iframe></div>"; title: title,
html: msg,
return msg; width: '800px',
}, didOpen: () => {
title: title, var dstFrame = document.getElementById('error-iframe');
callback: function () { //@ts-ignore
//Remove blur var dstDoc = dstFrame.contentDocument || dstFrame.contentWindow.document;
$('#content').removeClass('loading-content'); dstDoc.write(responseHTML);
} dstDoc.close();
},
}); }).then(() => {
//Remove blur
alert.init(function (){ $('#content').removeClass('loading-content');
var dstFrame = document.getElementById('error-iframe');
//@ts-ignore
var dstDoc = dstFrame.contentDocument || dstFrame.contentWindow.document;
dstDoc.write(responseHTML)
dstDoc.close();
}); });
} }
@ -171,4 +164,4 @@ class ErrorHandlerHelper {
} }
} }
export default new ErrorHandlerHelper(); export default new ErrorHandlerHelper();

View file

@ -43,12 +43,11 @@
"@zxcvbn-ts/language-de": "^4.1.1", "@zxcvbn-ts/language-de": "^4.1.1",
"@zxcvbn-ts/language-en": "^4.1.1", "@zxcvbn-ts/language-en": "^4.1.1",
"@zxcvbn-ts/language-fr": "^4.1.1", "@zxcvbn-ts/language-fr": "^4.1.1",
"@zxcvbn-ts/language-ja": "^4.1.1",
"@zxcvbn-ts/language-it": "^4.1.1", "@zxcvbn-ts/language-it": "^4.1.1",
"@zxcvbn-ts/language-ja": "^4.1.1",
"@zxcvbn-ts/language-pl": "^4.1.1", "@zxcvbn-ts/language-pl": "^4.1.1",
"attr-accept": "^2.2.5", "attr-accept": "^2.2.5",
"barcode-detector": "^3.0.5", "barcode-detector": "^3.0.5",
"bootbox": "^6.0.0",
"bootswatch": "^5.1.3", "bootswatch": "^5.1.3",
"bs-custom-file-input": "^1.3.4", "bs-custom-file-input": "^1.3.4",
"ckeditor5": "^48.0.0", "ckeditor5": "^48.0.0",
@ -71,6 +70,7 @@
"marked-mangle": "^1.0.1", "marked-mangle": "^1.0.1",
"pdfmake": "^0.3.7", "pdfmake": "^0.3.7",
"stimulus-use": "^0.52.0", "stimulus-use": "^0.52.0",
"sweetalert2": "^11.26.25",
"tom-select": "^2.1.0", "tom-select": "^2.1.0",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"typescript": "^6.0.2" "typescript": "^6.0.2"

View file

@ -490,7 +490,7 @@ The user will have to set up all two-factor authentication methods again and pri
<unit id="BtSvnI0" name="entity.delete.confirm_title"> <unit id="BtSvnI0" name="entity.delete.confirm_title">
<segment state="translated"> <segment state="translated">
<source>entity.delete.confirm_title</source> <source>entity.delete.confirm_title</source>
<target>You really want to delete %name%?</target> <target>Do you really want to delete %name%?</target>
</segment> </segment>
</unit> </unit>
<unit id="WR9DNyZ" name="entity.delete.message"> <unit id="WR9DNyZ" name="entity.delete.message">

1360
yarn.lock

File diff suppressed because it is too large Load diff