1// @ts-check 2import { AtomButton } from "../atoms/atombutton.js"; 3import { AtomContainer } from "../atoms/atomcontainer.js"; 4import { AtomContainerHorizontal } from "../atoms/atomcontainerhorizontal.js"; 5import { AtomContainerVertical } from "../atoms/atomcontainervertical.js"; 6import { AtomDisplayHeader3 } from "../atoms/atomdisplayheader3.js"; 7import { AtomDisplayParagraph } from "../atoms/atomdisplayparagraph.js"; 8import { AtomInputSelection } from "../atoms/atominputselection.js"; 9import { AtomInputText } from "../atoms/atominputtext.js"; 10import { AtomInputTextArea } from "../atoms/atominputtextarea.js"; 11import { MoleculeFileDropZoneSelection } from "../molecules/moleculefiledropzoneselection.js"; 12import { MoleculeLabelCheckBox } from "../molecules/moleculelabelcheckbox.js"; 13import { LoadingScreen } from "../helper/loadingscreen.js"; 14import { UserMessage } from "./usermessage.js"; 15 16 17export class UserInput extends UserMessage { 18 19 /** 20 * @param {string} title 21 * @param {string} label 22 * @param {string | null} defaultValue 23 * @param {Function} callbackOk 24 * @param {Function | null} callbacCancel 25 */ 26 constructor(title, label, defaultValue, callbackOk, callbacCancel) { 27 super(title, label, "../images/msg_question.png") 28 this.callbackOk = callbackOk 29 this.callbackCancel = callbacCancel; 30 this.defaultValue = defaultValue; 31 32 /** 33 * @type {AtomInputText | AtomInputTextArea | AtomInputSelection | null} 34 */ 35 this.inputField = null; 36 37 /** 38 * @type {string | number | null | Object} 39 */ 40 this.callbackId = -1; 41 42 /** @type {string | null} */ 43 this._inputTestid = null; 44 45 /** @type {string | null} */ 46 this._saveTestid = null; 47 } 48 49 // can be overridden in derived class for custom buttons 50 addButtons() { 51 this.addSaveButton("OK", this.callbackOk, this._saveTestid ?? undefined) 52 this.addCancelButton("Abbrechen", this.callbackCancel); 53 } 54 55 ok() { 56 if (!this.inputField) { 57 return super.ok(); 58 } 59 60 if (this.saveDataCallback) { 61 62 let ret = null; 63 64 if (this.callbackId == -1) { 65 ret = this.saveDataCallback(this.inputField.getValue()); 66 } else { 67 ret = this.saveDataCallback(this.callbackId, this.inputField.getValue()); 68 } 69 70 if (ret != null || ret != undefined) { 71 if (ret == false) { 72 return; 73 } 74 } 75 } 76 77 this.close(); 78 } 79 80 cancel() { 81 LoadingScreen.hide(); 82 super.cancel(); 83 } 84} 85 86export class SecurityQuestion extends UserInput { 87 88 /** 89 * @param {string} title 90 * @param {string} label 91 * @param {Function} callbackYes 92 * @param {Function | null} callbackNo 93 */ 94 constructor(title, label, callbackYes, callbackNo) { 95 super(title, label, "", callbackYes, callbackNo); 96 this.imagePath = "../images/msg_warn.png"; 97 this.callbackYes = callbackYes 98 this.callbackNo = callbackNo; 99 100 } 101 102 // can be overridden in derived class for custom buttons 103 addButtons() { 104 if (this.buttonContainer != null) { 105 this.saveButton = new AtomButton(this.buttonContainer, "", "Ja", "./images/ok.svg", "confirm-yes"); 106 this.setSaveDataFunction(this.callbackYes); 107 } 108 this.addCancelButton("Nein", this.callbackNo); 109 } 110 111 ok() { 112 113 if (this.saveDataCallback) { 114 115 let ret = null; 116 117 if (this.callbackId == -1) { 118 ret = this.saveDataCallback(); 119 } else { 120 ret = this.saveDataCallback(this.callbackId); 121 } 122 123 if (ret != null || ret != undefined) { 124 if (ret == false) { 125 return; 126 } 127 } 128 } 129 130 this.close(); 131 } 132 133 134 /** 135 * @param {string} title 136 * @param {string} label 137 * @param {Function} callbackYes 138 * @param {Function | null} [callbackNo] 139 */ 140 static show(title, label, callbackYes, callbackNo = null) { 141 const dlg = new SecurityQuestion(title, label, callbackYes, callbackNo); 142 dlg.exec(); 143 } 144 145 /** 146 * @param {string} title 147 * @param {string} label 148 * @param {any} id 149 * @param {Function} callbackOk 150 * @param {Function | null} [callbackCancel] 151 */ 152 static showWithId(title, label, id, callbackOk, callbackCancel = null) { 153 const dlg = new SecurityQuestion(title, label, callbackOk, callbackCancel); 154 dlg.callbackId = id; 155 dlg.exec(); 156 } 157} 158 159export class LineInput extends UserInput { 160 161 /** 162 * @param {string} title 163 * @param {string} label 164 * @param {string | null} defaultValue 165 * @param {Function} callbackOk 166 * @param {Function | null} callbacCancel 167 */ 168 constructor(title, label, defaultValue, callbackOk, callbacCancel) { 169 super(title, label, defaultValue, callbackOk, callbacCancel); 170 } 171 172 /** 173 * @param {AtomContainerHorizontal} parent 174 */ 175 addInputField(parent) { 176 177 if (this.defaultValue != null) { 178 this.inputField = new AtomInputText(parent, "inputText", this.defaultValue, this._inputTestid ?? undefined) 179 } else { 180 this.inputField = new AtomInputText(parent, "inputText", "", this._inputTestid ?? undefined); 181 } 182 } 183 184 /** 185 * @param {string} title 186 * @param {string} label 187 * @param {string | null} defaultValue 188 * @param {Function} callbackOk 189 * @param {Function | null} [callbackCancel] 190 */ 191 static show(title, label, defaultValue, callbackOk, callbackCancel = null) { 192 const dlg = new LineInput(title, label, defaultValue, callbackOk, callbackCancel); 193 dlg.exec(); 194 } 195 196 /** 197 * @param {string} title 198 * @param {string} label 199 * @param {string | null} defaultValue 200 * @param {any} id 201 * @param {Function} callbackOk 202 * @param {Function | null} [callbackCancel] 203 */ 204 static showWithId(title, label, id, defaultValue, callbackOk, callbackCancel = null) { 205 const dlg = new LineInput(title, label, defaultValue, callbackOk, callbackCancel); 206 dlg.callbackId = id; 207 dlg.exec(); 208 } 209} 210 211export class TextInput extends UserInput { 212 213 /** 214 * @param {string} title 215 * @param {string} label 216 * @param {string | null} defaultValue 217 * @param {Function} callbackOk 218 * @param {Function | null} callbacCancel 219 */ 220 constructor(title, label, defaultValue, callbackOk, callbacCancel) { 221 super(title, label, defaultValue, callbackOk, callbacCancel) 222 } 223 224 renderUi() { 225 this.createOverlay(); 226 const dialog = document.createElement('div'); 227 dialog.id = this.id; 228 dialog.className = "dialog";
229 // i don't know why but if I set it here it works in css it will be ignored 230 dialog.style.zIndex = "60000"; 231 dialog.style.position = 'absolute'; 232 dialog.style.top = '50%'; 233 dialog.style.left = '50%'; 234 dialog.style.transform = 'translate(-50%, -50%)'; 235 dialog.style.backgroundColor = 'white'; 236 dialog.style.height = "400px"; 237 dialog.style.minHeight = "400px"; 238 dialog.style.maxHeight = "min(600px, calc(100vh - 24px))"; 239 dialog.style.marginTop = "5%"; 240 // Stay within the viewport on narrow screens â otherwise the 241 // fixed 400px min width is cut off on the right on mobile. 242 dialog.style.minWidth = "min(400px, calc(100vw - 24px))"; 243 dialog.style.maxWidth = "min(600px, calc(100vw - 24px))"; 244 dialog.style.width = "min(600px, calc(100vw - 24px))"; 245 dialog.style.boxSizing = "border-box"; 246 247 this.setRenderedElement(dialog); 248 document.body.appendChild(dialog); 249 this.translator.refreshTranslationsInBackend(); 250 } 251 252 /** 253 * @param {AtomContainerHorizontal} parent 254 */ 255 addInputField(parent) { 256 257 if (this.defaultValue != null) { 258 this.inputField = new AtomInputTextArea(parent, "inputText", this.defaultValue) 259 } else { 260 this.inputField = new AtomInputTextArea(parent, "inputText", ""); 261 } 262 } 263 264 /** 265 * @param {string} title 266 * @param {string} label 267 * @param {string | null} defaultValue 268 * @param {Function} callbackOk 269 * @param {Function | null} [callbackCancel] 270 */ 271 static show(title, label, defaultValue, callbackOk, callbackCancel = null) { 272 const dlg = new TextInput(title, label, defaultValue, callbackOk, callbackCancel); 273 dlg.exec(); 274 } 275 276 /** 277 * @param {string} title 278 * @param {string} label 279 * @param {string | null} defaultValue 280 * @param {string | number} id 281 * @param {Function} callbackOk 282 * @param {Function | null} [callbackCancel] 283 */ 284 static showWithId(title, label, id, defaultValue, callbackOk, callbackCancel = null) { 285 const dlg = new TextInput(title, label, defaultValue, callbackOk, callbackCancel); 286 dlg.callbackId = id; 287 dlg.exec(); 288 } 289} 290 291export class SelectValue extends UserInput { 292 293 /** 294 * @param {string} title 295 * @param {string} label 296 * @param {any} selections 297 * @param {string | null} defaultValue 298 * @param {Function} callbackOk 299 * @param {Function | null} callbacCancel 300 */ 301 constructor(title, label, selections, defaultValue, callbackOk, callbacCancel) { 302 super(title, label, defaultValue, callbackOk, callbacCancel) 303 this.selections = selections; 304 } 305 306 /** 307 * @param {AtomContainerHorizontal} parent 308 */ 309 addInputField(parent) { 310 311 if (this.defaultValue != null) { 312 this.inputField = new AtomInputSelection(parent, "selection", this.defaultValue) 313 } else { 314 this.inputField = new AtomInputSelection(parent, "selection", "") 315 } 316 } 317 318 fillData() { 319 this.selections.forEach((/** @type {{ id: string; name: string; }} */ entry) => { 320 if (this.inputField instanceof AtomInputSelection) { 321 this.inputField?.addSelection(entry.id, entry.name); 322 } 323 324 }); 325 } 326 327 /** 328 * @param {string} title 329 * @param {string} label 330 * @param {any} selections 331 * @param {string | null} defaultValue 332 * @param {string | number} id 333 * @param {Function} callbackOk 334 * @param {Function | null} [callbackCancel] 335 */ 336 static showWithId(title, label, selections, defaultValue, id, callbackOk, callbackCancel = null) { 337 const dlg = new SelectValue(title, label, selections, defaultValue, callbackOk, callbackCancel); 338 dlg.callbackId = id; 339 dlg.exec(); 340 } 341} 342 343export class FilleInput extends UserMessage { 344 345 /** 346 * Dieser Dialog wird ausschliesslich fuer Importe benutzt. Der Hinweis unter 347 * der Ueberschrift nennt deshalb standardmaessig JSON; Dialoge mit weiteren 348 * erlaubten Formaten (Agent-Backup, Swagger) geben ihren eigenen Text mit. 349 */ 350 static HINT_JSON = "JSON (.json)"; 351 352 /** 353 * @param {string} title 354 * @param {string} label 355 * @param {Function} callbackOk 356 * @param {Function | null} callbacCancel 357 * @param {string} [hintText] 358 */ 359 constructor(title, label, callbackOk, callbacCancel, hintText = FilleInput.HINT_JSON) { 360 // No side image â the drop zone takes the full dialog width. 361 super(title, label, ""); 362 this.callbackOk = callbackOk 363 this.callbackCancel = callbacCancel; 364 this._hintText = hintText; 365 366 /** @type {MoleculeFileDropZoneSelection | null} */ 367 this.dropzone = null; 368 369 /** 370 * @type {string | number | null | Object} 371 */ 372 this.callbackId = -1; 373 374 this._multi = false;
375 } 376 377 renderUi() { 378 this.createOverlay(); 379 const dialog = document.createElement('div'); 380 dialog.id = this.id; 381 dialog.className = "dialog"; 382 dialog.style.zIndex = "60000"; 383 dialog.style.position = 'absolute'; 384 dialog.style.top = '50%'; 385 dialog.style.left = '50%'; 386 dialog.style.transform = 'translate(-50%, -50%)'; 387 dialog.style.backgroundColor = 'white'; 388 dialog.style.display = 'flex'; 389 dialog.style.flexDirection = 'column'; 390 dialog.style.height = "auto"; 391 dialog.style.minHeight = "0"; 392 dialog.style.maxHeight = "min(560px, calc(100vh - 24px))"; 393 dialog.style.marginTop = "0"; 394 dialog.style.minWidth = "min(480px, calc(100vw - 24px))"; 395 dialog.style.maxWidth = "min(640px, calc(100vw - 24px))"; 396 dialog.style.width = "min(640px, calc(100vw - 24px))"; 397 dialog.style.boxSizing = "border-box"; 398 399 this.setRenderedElement(dialog); 400 document.body.appendChild(dialog); 401 this.translator.refreshTranslationsInBackend(); 402 } 403 404 // Override UserMessage.setupDialogUi: no image wrapper â full-width drop zone. 405 /** 406 * @param {AtomContainer} parent 407 */ 408 setupDialogUi(parent) { 409 if (this.label && this.label !== "") { 410 new AtomDisplayParagraph(parent, "message", this.label); 411 } 412 this.addInputField(parent); 413 this.addButtons(); 414 } 415 416 /** 417 * @param {AtomContainer} parent 418 */ 419 addInputField(parent) { 420 this.dropzone = new MoleculeFileDropZoneSelection( 421 parent, 422 "filleinput", 423 "Datei hier ablegen oder klicken", 424 "files", 425 this._hintText, 426 ); 427 } 428 429 addButtons() { 430 this.addSaveButton("OK", this.callbackOk) 431 this.addCancelButton("Abbrechen", this.callbackCancel); 432 } 433 434 async ok() { 435 if (this.saveDataCallback) { 436 let ret = null; 437 438 LoadingScreen.show(); 439 const files = this.dropzone?.getFileList(); 440 441 if (this._multi) { 442 const arr = files ? Array.from(files) : []; 443 ret = (this.callbackId == -1) 444 ? this.saveDataCallback(arr) 445 : this.saveDataCallback(this.callbackId, arr); 446 } else { 447 const fileObj = (files && files.length > 0) ? files[0] : null; 448 ret = (this.callbackId == -1) 449 ? this.saveDataCallback(fileObj) 450 : this.saveDataCallback(this.callbackId, fileObj); 451 } 452 453 LoadingScreen.hide(); 454 455 if (ret != null || ret != undefined) { 456 if (ret == false) { 457 return; 458 } 459 } 460 } 461 462 this.close(); 463 } 464 465 /** 466 * @param {string} title 467 * @param {string} label 468 * @param {Function} callbackOk 469 * @param {Function | null} [callbackCancel] 470 * @param {string} [hintText] â erlaubte Dateiformate; ohne Angabe JSON 471 */ 472 static show(title, label, callbackOk, callbackCancel = null, hintText = FilleInput.HINT_JSON) { 473 const dlg = new FilleInput(title, label, callbackOk, callbackCancel, hintText); 474 dlg.exec(); 475 } 476 477 /** 478 * @param {string} title 479 * @param {string} label 480 * @param {any} id 481 * @param {Function} callbackOk 482 * @param {Function | null} [callbackCancel] 483 * @param {string} [hintText] â erlaubte Dateiformate; ohne Angabe JSON 484 */ 485 static showWithId(title, label, id, callbackOk, callbackCancel = null, hintText = FilleInput.HINT_JSON) { 486 const dlg = new FilleInput(title, label, callbackOk, callbackCancel, hintText); 487 dlg.callbackId = id; 488 dlg.exec(); 489 } 490 491 /** 492 * Multi-file variant: the OK callback receives a File[] of every selected 493 * file (the drop zone already allows multiple in "files" mode). 494 * @param {string} title 495 * @param {string} label 496 * @param {Function} callbackOk receives File[] 497 * @param {Function | null} [callbackCancel] 498 * @param {string} [hintText] â erlaubte Dateiformate; ohne Angabe JSON 499 */ 500 static showMulti(title, label, callbackOk, callbackCancel = null, hintText = FilleInput.HINT_JSON) { 501 const dlg = new FilleInput(title, label, callbackOk, callbackCancel, hintText); 502 dlg._multi = true; 503 dlg.exec(); 504 } 505} 506 507/** 508 * Modal dialog showing the bilanz of a bulk operation: 509 * "X von Y erfolgreich, Z fehlgeschlagen" plus a collapsible per-failure 510 * list. Same visual frame as SecurityQuestion (built on UserMessage). 511 * 512 * Usage: 513 * BulkResultDialog.show("Löschen abgeschlossen", successCount, failures, () => { ... }); 514 * 515 * `failures` is an array of { documentId, name, error } objects matching 516 * domain.BulkDocumentFailure. The callback fires when the user clicks OK. 517 */ 518export class BulkResultDialog extends UserInput { 519 520 /** 521 * @param {string} title 522 * @param {number} successCount 523 * @param {Array<{documentId: string, name: string, error: string}>} failures 524 * @param {Function | null} callbackOk 525 */ 526 constructor(title, successCount, failures, callbackOk) { 527 const total = successCount + (failures?.length ?? 0); 528 const failedCount = failures?.length ?? 0; 529 const label = `${successCount} von ${total} erfolgreich, ${failedCount} fehlgeschlagen.`; 530 531 super(title, label, "", callbackOk ?? (() => {}), null); 532 533 this.imagePath = failedCount === 0 534 ? "../images/msg_info.png" 535 : "../images/msg_warn.png"; 536 this.failures = failures ?? []; 537 } 538 539 addButtons() { 540 this.addSaveButton("OK", this.callbackOk ?? (() => {})); 541 } 542 543 addInputField(parent) { 544 if (this.failures.length === 0) return; 545 546 // Build failure list through the atom layer so it lives inside the 547 // dialog body and gets cleaned up with the dialog. (Earlier co
547de 548 // used document.createElement and dangled <details> on document.body.) 549 const list = new AtomContainerVertical(parent, "bulk-failures-list"); 550 list.setCssClass("bulk-failures-list"); 551 552 new AtomDisplayHeader3(list, "bulk-failures-header", `Fehlerdetails (${this.failures.length})`); 553 554 for (const f of this.failures) { 555 const labelText = (f.name && f.name !== "") ? f.name : f.documentId; 556 const errText = f.error || "Unbekannter Fehler"; 557 new AtomDisplayParagraph(list, "", `${labelText}: ${errText}`); 558 } 559 } 560 561 ok() { 562 if (this.callbackOk) { 563 try { this.callbackOk(); } catch (err) { console.error(err); } 564 } 565 this.close(); 566 } 567 568 /** 569 * @param {string} title 570 * @param {number} successCount 571 * @param {Array<{documentId: string, name: string, error: string}>} failures 572 * @param {Function | null} [callbackOk] 573 */ 574 static show(title, successCount, failures, callbackOk = null) { 575 const dlg = new BulkResultDialog(title, successCount, failures, callbackOk); 576 dlg.exec(); 577 } 578} 579 580/** 581 * Confirms an export and lets the user choose whether transitively called 582 * functions/services are included as a self-contained dependency bundle. 583 * Same visual frame as LineInput, but the input field is a single checkbox 584 * (checked by default) instead of a text field. 585 * 586 * Usage: 587 * ExportOptionsDialog.show("Exportieren", "...", (includeDeps) => { ... }); 588 */ 589export class ExportOptionsDialog extends UserInput { 590 591 /** 592 * @param {string} title 593 * @param {string} label 594 * @param {(includeDeps: boolean) => void} onConfirm 595 */ 596 constructor(title, label, onConfirm) { 597 super(title, label, null, onConfirm, null); 598 599 /** @type {MoleculeLabelCheckBox | null} */ 600 this.includeDepsCheckbox = null; 601 } 602 603 /** 604 * @param {AtomContainerHorizontal} parent 605 */ 606 addInputField(parent) { 607 this.includeDepsCheckbox = new MoleculeLabelCheckBox( 608 parent, 609 "export-options-include-deps", 610 "Aufgerufene Funktionen/Services rekursiv mitexportieren", 611 true, 612 ); 613 } 614 615 ok() { 616 const includeDeps = this.includeDepsCheckbox ? this.includeDepsCheckbox.getValue() : true; 617 618 if (this.saveDataCallback) { 619 this.saveDataCallback(includeDeps); 620 } 621 622 this.close(); 623 } 624 625 /** 626 * @param {string} title 627 * @param {string} label 628 * @param {(includeDeps: boolean) => void} onConfirm 629 */ 630 static show(title, label, onConfirm) { 631 const dlg = new ExportOptionsDialog(title, label, onConfirm); 632 dlg.exec(); 633 } 634} 635 636/** 637 * Confirms a container-wide reorganize and lets the user choose between the 638 * selective run (only documents whose processing is outdated) and a full 639 * rebuild. Same frame as SecurityQuestion, plus one checkbox. 640 */ 641export class ReorganizeContainerQuestion extends SecurityQuestion { 642 643 /** 644 * @param {string} title 645 * @param {string} label 646 * @param {(onlyOutdated: boolean) => void} callbackYes 647 */ 648 constructor(title, label, callbackYes) { 649 super(title, label, callbackYes, null); 650 651 /** @type {MoleculeLabelCheckBox | null} */ 652 this.onlyOutdatedCheckbox = null; 653 } 654 655 /** 656 * @param {AtomContainerVertical} parent 657 */ 658 addInputField(parent) { 659 // Default checked: the selective run is the sensible default; a full 660 // rebuild stays one click away. 661 this.onlyOutdatedCheckbox = new MoleculeLabelCheckBox( 662 parent, 663 "reorganize-only-outdated", 664 "Nur veraltete Dokumente neu aufbereiten", 665 true, 666 ); 667 } 668 669 ok() { 670 const onlyOutdated = this.onlyOutdatedCheckbox ? this.onlyOutdatedCheckbox.getValue() : true; 671 672 if (this.saveDataCallback) { 673 this.saveDataCallback(onlyOutdated); 674 } 675 676 this.close(); 677 } 678 679 /** 680 * @param {string} title 681 * @param {string} label 682 * @param {(onlyOutdated: boolean) => void} callbackYes 683 */ 684 static show(title, label, callbackYes) { 685 const dlg = new ReorganizeContainerQuestion(title, label, callbackYes); 686 dlg.exec(); 687 } 688}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.