From a3c8b4083dae996cba144d736781c457f67d75cc Mon Sep 17 00:00:00 2001 From: Thomas Hooge Date: Mon, 17 Aug 2026 10:37:19 +0200 Subject: [PATCH] User management with native gui and further improvements --- INSTALL | 11 +- db/mariadb.sql | 100 ++++++--- db/minimal.sql | 3 +- webgui/config.inc-sample | 2 +- webgui/css/choices.min.css | 1 + webgui/documents.php | 6 +- webgui/equipment.php | 2 +- webgui/globals.inc | 26 ++- webgui/js/choices.min.js | 2 + webgui/js/shopping.js | 167 ++++++++++++++ webgui/locale/de_DE.po | 116 +++++----- webgui/locale/de_DE/LC_MESSAGES/yms.mo | Bin 23326 -> 23364 bytes webgui/manual.php | 2 + webgui/note.php | 2 +- webgui/provisions.php | 18 +- webgui/shapes/bavaria34.svg | 2 +- webgui/shapes/colinarcher40.svg | 138 ++++++++++++ webgui/shapes/elizabethan29.svg | 2 +- webgui/shapes/etap23.svg | 2 +- webgui/shapes/gulfstar41.svg | 289 +++++++++++++++++++++++++ webgui/task.php | 20 +- yms/ui/main.ui | 71 +++--- yms/user.py | 87 +++++++- ymsgui.py | 94 +++++++- 24 files changed, 1004 insertions(+), 159 deletions(-) create mode 100644 webgui/css/choices.min.css create mode 100644 webgui/js/choices.min.js create mode 100644 webgui/js/shopping.js create mode 100644 webgui/shapes/colinarcher40.svg create mode 100644 webgui/shapes/gulfstar41.svg diff --git a/INSTALL b/INSTALL index 930b82f..462719d 100644 --- a/INSTALL +++ b/INSTALL @@ -15,6 +15,7 @@ Yacht Management Software (YMS) Installation - Python GTK3 - python3-gi - gir1.2-gtk-3.0 + - gir-rsvg-2.0 1.3 Web GUI - Webserver @@ -69,7 +70,7 @@ Customize path and access rights to your needs To grant access for the local gui the directory has to be writeable by a special group (here as example 'yms'). The user which runs the local gui (here 'guiuser') has also to be a member of that group as well as the webserver user -''www-data'. +'www-data'. groupadd yms usermod -aG yms www-data @@ -107,14 +108,20 @@ Configfile should be readable by webserver but not writeable. Create a local database user with same name as your computer user and allow database acces with current unix credentials: mysql> CREATE USER 'ymsgui'@'localhost' IDENTIFIED VIA unix_socket; + mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON yms.* TO 'ymsgui'@'localhost'; + +Copy ymsgui.py to /usr/local/bin +Copy directory yms to /usr/local/share Start ymsgui.py, default configuration file is automatically created 7. Start using YMS Use webgui: -Start your browser and login to YMS with the default username/password: +Start your browser and login to YMS with the initial username/password: captain/captain Use local client: Start ymsgui.py +Change the password using the local GUI before normal use. + diff --git a/db/mariadb.sql b/db/mariadb.sql index 129af98..4bbcb22 100644 --- a/db/mariadb.sql +++ b/db/mariadb.sql @@ -3,6 +3,12 @@ /* MariaDB Scheme Version: 1 */ +/* TODO + - check all values if unsigned can be used or not + - implement foreign keys where possible + - rename provisions to provision(?) + */ + /* User userid is signed because nevative values are required for settings The user table must not be writeable by the webgui user! @@ -58,6 +64,28 @@ CREATE TABLE dropdown ( PRIMARY KEY (ddid, ddval) ) ENGINE=InnoDB; +CREATE TABLE company ( + compid smallint(6) NOT NULL AUTO_INCREMENT, + compname varchar(60) NOT NULL, + comptype tinyint(3) UNSIGNED NOT NULL DEFAULT 1, + comptype2 tinyint(3) UNSIGNED DEFAULT NULL, + shortname varchar(20) DEFAULT NULL, + street varchar(40), + zip varchar(10), + city varchar(30), + country varchar(30), + contact varchar(30), + phone varchar(30), + email varchar(50), + web varchar(40), + customerno varchar(20), + contractno varchar(20), + remarks varchar(150) DEFAULT NULL, + flags set('deleted','historic') DEFAULT NULL, + PRIMARY KEY (compid), + UNIQUE INDEX ix_compname (compname, comptype) +) ENGINE=InnoDB; + CREATE TABLE vessel ( vid smallint(6) NOT NULL AUTO_INCREMENT, vtype enum('mono','cat','tri') NOT NULL DEFAULT 'mono', @@ -76,14 +104,16 @@ CREATE TABLE vessel ( shapefile varchar(40) DEFAULT NULL, sid_default smallint(6) UNSIGNED DEFAULT NULL, -- default storage remarks varchar(150) DEFAULT NULL, - PRIMARY KEY (vid) + PRIMARY KEY (vid), + FOREIGN KEY fk_vessel_shipyard (shipyard) + REFERENCES company(compid) ) ENGINE=InnoDB; CREATE TABLE storage ( sid smallint(6) UNSIGNED NOT NULL AUTO_INCREMENT, vid smallint(6) NOT NULL DEFAULT 1, sname varchar(40) NOT NULL, - stype tinyint(3) NOT NULL DEFAULT 0, + stype tinyint(3) NOT NULL DEFAULT 0, -- ddid=3 capacity smallint(6) DEFAULT NULL, capaunit enum('kg','l') DEFAULT NULL, x smallint(6) UNSIGNED NOT NULL DEFAULT 0, @@ -148,6 +178,16 @@ CREATE TABLE equipment ( PRIMARY KEY(eid) ) ENGINE=InnoDB; +/* +convert to +sid SMALLINT UNSIGNED NULL, +boxid SMALLINT UNSIGNED NULL, +CHECK ( + (sid IS NOT NULL AND boxid IS NULL) + OR + (sid IS NULL AND boxid IS NOT NULL) +) +*/ CREATE TABLE inventory ( invid int(10) UNSIGNED NOT NULL AUTO_INCREMENT, invname varchar(80) NOT NULL, @@ -161,6 +201,7 @@ CREATE TABLE inventory ( purchdate date DEFAULT NULL, invcond enum('unknown','excellent','good','fair','bad','repairable','defect') NOT NULL default 'unknown', remarks varchar(150) DEFAULT NULL, + flags set('ordered','removed','deleted') DEFAULT NULL, PRIMARY KEY (invid), INDEX ix_invname (invname) ) ENGINE=InnoDB; @@ -199,6 +240,7 @@ CREATE table fuse ( UNIQUE INDEX ix_fusenumber (vid, fnumber) ) ENGINE=InnoDB; +-- change vals to decimal(12,4)? CREATE TABLE measurement ( mid smallint(6) NOT NULL AUTO_INCREMENT, mname varchar(80) NOT NULL, @@ -226,8 +268,10 @@ CREATE TABLE provisions ( weight float(5,2) UNSIGNED DEFAULT NULL, weight_net float(5,2) UNSIGNED DEFAULT NULL, unit tinyint(3) DEFAULT NULL, + calories smallint(6) UNSIGNED DEFAULT NULL, storedate date DEFAULT CURRENT_DATE, shelflife date DEFAULT NULL, + price decimal(12,2) DEFAULT NULL, remarks varchar(150) DEFAULT NULL, PRIMARY KEY (provid) ) ENGINE=InnoDB; @@ -259,38 +303,19 @@ CREATE TABLE docref ( drid int(10) NOT NULL AUTO_INCREMENT, reftype enum('vessel','box','equipment','project','task','user','company') NOT NULL DEFAULT 'vessel', docid smallint(6) NOT NULL, - refid smallint(6) NOT NULL, + refid int(10) NOT NULL, PRIMARY KEY (drid), - UNIQUE INDEX ix_docref (docid, refid), - FOREIGN KEY fx_docref_doc (docid) + UNIQUE INDEX ix_docref (docid, refid, reftype), + FOREIGN KEY fk_docref_doc (docid) REFERENCES document (docid) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB; -CREATE TABLE company ( - compid smallint(6) NOT NULL AUTO_INCREMENT, - compname varchar(60) NOT NULL, - comptype tinyint(3) UNSIGNED NOT NULL DEFAULT 1, - comptype2 tinyint(3) UNSIGNED DEFAULT NULL, - shortname varchar(20) DEFAULT NULL, - street varchar(40), - zip varchar(10), - city varchar(30), - country varchar(30), - contact varchar(30), - phone varchar(30), - email varchar(50), - web varchar(40), - customerno varchar(20), - contractno varchar(20), - remarks varchar(150) DEFAULT NULL, - flags set('deleted','historic') DEFAULT NULL, - PRIMARY KEY (compid), - UNIQUE INDEX ix_compname (compname, comptype) -) ENGINE=InnoDB; - -/* TODO optional field: plan date, target date, completion date */ +/* TODO optional fields: plan date, target date, completion date + interval_hours, last_hours + signalk: operating hours current vs maint every n operating hours in separate +*/ CREATE TABLE maintenance ( maintid int(10) NOT NULL AUTO_INCREMENT, vid smallint(6) NOT NULL DEFAULT 1, @@ -332,14 +357,16 @@ CREATE TABLE task ( finished datetime DEFAULT NULL, responsible smallint(6) NOT NULL, /* TODO executed_by : company or user? or both? */ - PRIMARY KEY (taskid) + PRIMARY KEY (taskid), + FOREIGN KEY fk_task_user (responsible) + REFERENCES user(userid) ) ENGINE=InnoDB; /* Notes can be assigned to tasks,maintenances and equipment */ CREATE TABLE note ( noteid int(10) NOT NULL AUTO_INCREMENT, notetype enum('task','maint','equip') NOT NULL DEFAULT 'task', - refid smallint(6) NOT NULL, + refid int(10) NOT NULL, annotation text NOT NULL, PRIMARY KEY (noteid) ) ENGINE=InnoDB; @@ -372,3 +399,16 @@ CREATE TABLE checkgroup ( title varchar(30) NOT NULL, PRIMARY KEY (groupid) ) ENGINE=InnoDB; + +/* Expansion for future use: signalk mapping (just an idea) */ +CREATE TABLE signalk ( + skid int(10) NOT NULL AUTO_INCREMENT, + vid smallint(6) NOT NULL, + objtype enum('equipment','storage') NOT NULL, + objid int(10) NOT NULL, + datatype enum('engine_hours','tank_level') NOT NULL, + skpath varchar(255) NOT NULL, + PRIMARY KEY (skid), + UNIQUE KEY ix_signalk + (vid, objtype, objid, datatype) +) ENGINE=InnoDB; diff --git a/db/minimal.sql b/db/minimal.sql index 0f020c5..80225f2 100644 --- a/db/minimal.sql +++ b/db/minimal.sql @@ -76,4 +76,5 @@ INSERT INTO dropdown (ddid, ddval, ddtext, flags) VALUES INSERT INTO dropdown (ddid, ddval, ddtext, flags) VALUES (3, 0, 'Stauraum allgemein', 'fixed'), -(3, 1, 'Tank', 'fixed'); +(3, 1, 'Tank', 'fixed'), +(3, 2, 'Extern', 'fixed'); -- not on board diff --git a/webgui/config.inc-sample b/webgui/config.inc-sample index 5a8f172..3d19717 100644 --- a/webgui/config.inc-sample +++ b/webgui/config.inc-sample @@ -17,7 +17,7 @@ define('APP_ILLUSTRATION', 'sailboat.svg'); // database connection $g_db_host = 'localhost'; -$g_db_username = 'yms'; +$g_db_username = 'ymsweb'; $g_db_password = 'changeme!'; $g_db_schema = 'yms'; diff --git a/webgui/css/choices.min.css b/webgui/css/choices.min.css new file mode 100644 index 0000000..cf79ea9 --- /dev/null +++ b/webgui/css/choices.min.css @@ -0,0 +1 @@ +.choices{position:relative;overflow:hidden;margin-bottom:24px;font-size:16px}.choices:focus{outline:0}.choices:last-child{margin-bottom:0}.choices.is-open{overflow:visible}.choices.is-disabled .choices__inner,.choices.is-disabled .choices__input{background-color:#eaeaea;cursor:not-allowed;-webkit-user-select:none;user-select:none}.choices.is-disabled .choices__item{cursor:not-allowed}.choices [hidden]{display:none!important}.choices[data-type*=select-one]{cursor:pointer}.choices[data-type*=select-one] .choices__inner{padding-bottom:7.5px}.choices[data-type*=select-one] .choices__input{display:block;width:100%;padding:10px;border-bottom:1px solid #ddd;background-color:#fff;margin:0}.choices[data-type*=select-one] .choices__button{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==);padding:0;background-size:8px;position:absolute;top:50%;right:0;margin-top:-10px;margin-right:25px;height:20px;width:20px;border-radius:10em;opacity:.25}.choices[data-type*=select-one] .choices__button:focus,.choices[data-type*=select-one] .choices__button:hover{opacity:1}.choices[data-type*=select-one] .choices__button:focus{box-shadow:0 0 0 2px #005f75}.choices[data-type*=select-one] .choices__item[data-placeholder] .choices__button{display:none}.choices[data-type*=select-one]::after{content:"";height:0;width:0;border-style:solid;border-color:#333 transparent transparent;border-width:5px;position:absolute;right:11.5px;top:50%;margin-top:-2.5px;pointer-events:none}.choices[data-type*=select-one].is-open::after{border-color:transparent transparent #333;margin-top:-7.5px}.choices[data-type*=select-one][dir=rtl]::after{left:11.5px;right:auto}.choices[data-type*=select-one][dir=rtl] .choices__button{right:auto;left:0;margin-left:25px;margin-right:0}.choices[data-type*=select-multiple] .choices__inner,.choices[data-type*=text] .choices__inner{cursor:text}.choices[data-type*=select-multiple] .choices__button,.choices[data-type*=text] .choices__button{position:relative;display:inline-block;margin:0-4px 0 8px;padding-left:16px;border-left:1px solid #003642;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==);background-size:8px;width:8px;line-height:1;opacity:.75;border-radius:0}.choices[data-type*=select-multiple] .choices__button:focus,.choices[data-type*=select-multiple] .choices__button:hover,.choices[data-type*=text] .choices__button:focus,.choices[data-type*=text] .choices__button:hover{opacity:1}.choices__inner{display:inline-block;vertical-align:top;width:100%;background-color:#f9f9f9;padding:7.5px 7.5px 3.75px;border:1px solid #ddd;border-radius:2.5px;font-size:14px;min-height:44px;overflow:hidden}.is-focused .choices__inner,.is-open .choices__inner{border-color:#b7b7b7}.is-open .choices__inner{border-radius:2.5px 2.5px 0 0}.is-flipped.is-open .choices__inner{border-radius:0 0 2.5px 2.5px}.choices__list{margin:0;padding-left:0;list-style:none}.choices__list--single{display:inline-block;padding:4px 16px 4px 4px;width:100%}[dir=rtl] .choices__list--single{padding-right:4px;padding-left:16px}.choices__list--single .choices__item{width:100%}.choices__list--multiple{display:inline}.choices__list--multiple .choices__item{display:inline-block;vertical-align:middle;border-radius:20px;padding:4px 10px;font-size:12px;font-weight:500;margin-right:3.75px;margin-bottom:3.75px;background-color:#005f75;border:1px solid #004a5c;color:#fff;word-break:break-all;box-sizing:border-box}.choices__list--multiple .choices__item[data-deletable]{padding-right:5px}[dir=rtl] .choices__list--multiple .choices__item{margin-right:0;margin-left:3.75px}.choices__list--multiple .choices__item.is-highlighted{background-color:#004a5c;border:1px solid #003642}.is-disabled .choices__list--multiple .choices__item{background-color:#aaa;border:1px solid #919191}.choices__list--dropdown,.choices__list[aria-expanded]{display:none;z-index:1;position:absolute;width:100%;background-color:#fff;border:1px solid #ddd;top:100%;margin-top:-1px;border-bottom-left-radius:2.5px;border-bottom-right-radius:2.5px;overflow:hidden;word-break:break-all}.is-active.choices__list--dropdown,.is-active.choices__list[aria-expanded]{display:block}.is-open .choices__list--dropdown,.is-open .choices__list[aria-expanded]{border-color:#b7b7b7}.is-flipped .choices__list--dropdown,.is-flipped .choices__list[aria-expanded]{top:auto;bottom:100%;margin-top:0;margin-bottom:-1px;border-radius:.25rem .25rem 0 0}.choices__list--dropdown .choices__list,.choices__list[aria-expanded] .choices__list{position:relative;max-height:300px;overflow:auto;-webkit-overflow-scrolling:touch;will-change:scroll-position}.choices__list--dropdown .choices__item,.choices__list[aria-expanded] .choices__item{position:relative;padding:10px;font-size:14px}[dir=rtl] .choices__list--dropdown .choices__item,[dir=rtl] .choices__list[aria-expanded] .choices__item{text-align:right}@media (min-width:640px){.choices__list--dropdown .choices__item--selectable[data-select-text],.choices__list[aria-expanded] .choices__item--selectable[data-select-text]{padding-right:100px}.choices__list--dropdown .choices__item--selectable[data-select-text]::after,.choices__list[aria-expanded] .choices__item--selectable[data-select-text]::after{content:attr(data-select-text);font-size:12px;opacity:0;position:absolute;right:10px;top:50%;transform:translateY(-50%)}[dir=rtl] .choices__list--dropdown .choices__item--selectable[data-select-text],[dir=rtl] .choices__list[aria-expanded] .choices__item--selectable[data-select-text]{text-align:right;padding-left:100px;padding-right:10px}[dir=rtl] .choices__list--dropdown .choices__item--selectable[data-select-text]::after,[dir=rtl] .choices__list[aria-expanded] .choices__item--selectable[data-select-text]::after{right:auto;left:10px}}.choices__list--dropdown .choices__item--selectable.is-highlighted,.choices__list[aria-expanded] .choices__item--selectable.is-highlighted{background-color:#f2f2f2}.choices__list--dropdown .choices__item--selectable.is-highlighted::after,.choices__list[aria-expanded] .choices__item--selectable.is-highlighted::after{opacity:.5}.choices__item{cursor:default}.choices__item--selectable{cursor:pointer}.choices__item--disabled{cursor:not-allowed;-webkit-user-select:none;user-select:none;opacity:.5}.choices__heading{font-weight:600;font-size:12px;padding:10px;border-bottom:1px solid #f7f7f7;color:gray}.choices__button{text-indent:-9999px;appearance:none;border:0;background-color:transparent;background-repeat:no-repeat;background-position:center;cursor:pointer}.choices__button:focus,.choices__input:focus{outline:0}.choices__input{display:inline-block;vertical-align:baseline;background-color:#f9f9f9;font-size:14px;margin-bottom:5px;border:0;border-radius:0;max-width:100%;padding:4px 0 4px 2px}.choices__input::-webkit-search-cancel-button,.choices__input::-webkit-search-decoration,.choices__input::-webkit-search-results-button,.choices__input::-webkit-search-results-decoration{display:none}.choices__input::-ms-clear,.choices__input::-ms-reveal{display:none;width:0;height:0}[dir=rtl] .choices__input{padding-right:2px;padding-left:0}.choices__placeholder{opacity:.5} \ No newline at end of file diff --git a/webgui/documents.php b/webgui/documents.php index 4eb8de2..da5583b 100644 --- a/webgui/documents.php +++ b/webgui/documents.php @@ -57,6 +57,7 @@ switch ($submit = form_get_action()) { // WIP New standalone document // debug $g_message->Add(print_r($_FILES, true)); // check for possible errors + // TODO use "noscale" option for images if (!isset($_FILES['files']['error']) || is_array($_FILES['files']['error'])) { $g_error->Add(_("Invalid file upload parameters.")); $action = ACT_ADD; @@ -434,7 +435,7 @@ echo '

', _('Add Document'), "

\n"; - + +
diff --git a/webgui/equipment.php b/webgui/equipment.php index f879bbd..b07955f 100644 --- a/webgui/equipment.php +++ b/webgui/equipment.php @@ -420,7 +420,7 @@ $flt = db_get_filter($user->id, 200);
'--- none ---'] + $opt_ecat, $flt->cat); +form_create_select('category', _('Category'), $g_opt_none + $opt_ecat, $flt->cat); form_create_select('manufacturer', _('Manufacturer'), $opt_manufacturer, $flt->manuf); ?>
diff --git a/webgui/globals.inc b/webgui/globals.inc index 5d98c32..4400403 100644 --- a/webgui/globals.inc +++ b/webgui/globals.inc @@ -904,7 +904,8 @@ function db_get_opt_user($userid, $default=NULL) { return $list; } -function db_get_opt_proj($vid, $default=NULL) { +function db_get_opt_proj($vid, $exclude=[], $default=NULL) { + // exclude is a list of project states global $pdo; global $g_error; if (isset($default)) { @@ -912,7 +913,15 @@ function db_get_opt_proj($vid, $default=NULL) { } else { $list = array(); } - $sql = "SELECT projid, projname FROM project WHERE vid=? ORDER BY projname"; + $sql = "SELECT projid, projname FROM project WHERE vid=?"; + $where = []; + foreach ($exclude as $excl) { + $where[] = "NOT FIND_IN_SET('$excl',projstate)"; + } + if (! empty($where)) { + $sql .= ' AND '. implode(' AND ', $where); + } + $sql .= " ORDER BY projname"; $sth = $pdo->prepare($sql); try { $sth->execute([$vid]); @@ -1047,6 +1056,18 @@ function form_create_text($fieldname, $label, $value) { echo "
\n"; } +function form_create_check($fieldname, $label, $checked) { + // create single checkbox with assigned label + echo '
', "\n"; + echo '\n"; + echo '\n"; + echo "
\n"; +} + function form_create_select($fieldname, $label, $optlist, $selval=NULL, $multiple=FALSE) { echo '
', "\n"; echo '\n"; @@ -1063,6 +1084,7 @@ function form_create_select($fieldname, $label, $optlist, $selval=NULL, $multipl } function form_create_checks($fieldname, $label, $optlist, $selected = []) { + // create multiple combined checkboxes with title echo '
'; echo '', $label, "\n"; echo '
'; diff --git a/webgui/js/choices.min.js b/webgui/js/choices.min.js new file mode 100644 index 0000000..cc5baa7 --- /dev/null +++ b/webgui/js/choices.min.js @@ -0,0 +1,2 @@ +/*! choices.js v11.1.0 | © 2025 Josh Johnson | https://github.com/jshjohnson/Choices#readme */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Choices=t()}(this,(function(){"use strict";var e=function(t,i){return e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i])},e(t,i)};function t(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return i=Object.assign||function(e){for(var t,i=1,n=arguments.length;i/g,">").replace(/=0&&!window.matchMedia("(min-height: ".concat(e+1,"px)")).matches:"top"===this.position&&(i=!0),i},e.prototype.setActiveDescendant=function(e){this.element.setAttribute("aria-activedescendant",e)},e.prototype.removeActiveDescendant=function(){this.element.removeAttribute("aria-activedescendant")},e.prototype.open=function(e,t){P(this.element,this.classNames.openState),this.element.setAttribute("aria-expanded","true"),this.isOpen=!0,this.shouldFlip(e,t)&&(P(this.element,this.classNames.flippedState),this.isFlipped=!0)},e.prototype.close=function(){j(this.element,this.classNames.openState),this.element.setAttribute("aria-expanded","false"),this.removeActiveDescendant(),this.isOpen=!1,this.isFlipped&&(j(this.element,this.classNames.flippedState),this.isFlipped=!1)},e.prototype.addFocusState=function(){P(this.element,this.classNames.focusState)},e.prototype.removeFocusState=function(){j(this.element,this.classNames.focusState)},e.prototype.enable=function(){j(this.element,this.classNames.disabledState),this.element.removeAttribute("aria-disabled"),this.type===_&&this.element.setAttribute("tabindex","0"),this.isDisabled=!1},e.prototype.disable=function(){P(this.element,this.classNames.disabledState),this.element.setAttribute("aria-disabled","true"),this.type===_&&this.element.setAttribute("tabindex","-1"),this.isDisabled=!0},e.prototype.wrap=function(e){var t=this.element,i=e.parentNode;i&&(e.nextSibling?i.insertBefore(t,e.nextSibling):i.appendChild(t)),t.appendChild(e)},e.prototype.unwrap=function(e){var t=this.element,i=t.parentNode;i&&(i.insertBefore(e,t),i.removeChild(t))},e.prototype.addLoadingState=function(){P(this.element,this.classNames.loadingState),this.element.setAttribute("aria-busy","true"),this.isLoading=!0},e.prototype.removeLoadingState=function(){j(this.element,this.classNames.loadingState),this.element.removeAttribute("aria-busy"),this.isLoading=!1},e}(),B=function(){function e(e){var t=e.element,i=e.type,n=e.classNames,s=e.preventPaste;this.element=t,this.type=i,this.classNames=n,this.preventPaste=s,this.isFocussed=this.element.isEqualNode(document.activeElement),this.isDisabled=t.disabled,this._onPaste=this._onPaste.bind(this),this._onInput=this._onInput.bind(this),this._onFocus=this._onFocus.bind(this),this._onBlur=this._onBlur.bind(this)}return Object.defineProperty(e.prototype,"placeholder",{set:function(e){this.element.placeholder=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"value",{get:function(){return this.element.value},set:function(e){this.element.value=e},enumerable:!1,configurable:!0}),e.prototype.addEventListeners=function(){var e=this.element;e.addEventListener("paste",this._onPaste),e.addEventListener("input",this._onInput,{passive:!0}),e.addEventListener("focus",this._onFocus,{passive:!0}),e.addEventListener("blur",this._onBlur,{passive:!0})},e.prototype.removeEventListeners=function(){var e=this.element;e.removeEventListener("input",this._onInput),e.removeEventListener("paste",this._onPaste),e.removeEventListener("focus",this._onFocus),e.removeEventListener("blur",this._onBlur)},e.prototype.enable=function(){this.element.removeAttribute("disabled"),this.isDisabled=!1},e.prototype.disable=function(){this.element.setAttribute("disabled",""),this.isDisabled=!0},e.prototype.focus=function(){this.isFocussed||this.element.focus()},e.prototype.blur=function(){this.isFocussed&&this.element.blur()},e.prototype.clear=function(e){return void 0===e&&(e=!0),this.element.value="",e&&this.setWidth(),this},e.prototype.setWidth=function(){var e=this.element;e.style.minWidth="".concat(e.placeholder.length+1,"ch"),e.style.width="".concat(e.value.length+1,"ch")},e.prototype.setActiveDescendant=function(e){this.element.setAttribute("aria-activedescendant",e)},e.prototype.removeActiveDescendant=function(){this.element.removeAttribute("aria-activedescendant")},e.prototype._onInput=function(){this.type!==_&&this.setWidth()},e.prototype._onPaste=function(e){this.preventPaste&&e.preventDefault()},e.prototype._onFocus=function(){this.isFocussed=!0},e.prototype._onBlur=function(){this.isFocussed=!1},e}(),H=function(){function e(e){this.element=e.element,this.scrollPos=this.element.scrollTop,this.height=this.element.offsetHeight}return e.prototype.prepend=function(e){var t=this.element.firstElementChild;t?this.element.insertBefore(e,t):this.element.append(e)},e.prototype.scrollToTop=function(){this.element.scrollTop=0},e.prototype.scrollToChildElement=function(e,t){var i=this;if(e){var n=t>0?this.element.scrollTop+(e.offsetTop+e.offsetHeight)-(this.element.scrollTop+this.element.offsetHeight):e.offsetTop;requestAnimationFrame((function(){i._animateScroll(n,t)}))}},e.prototype._scrollDown=function(e,t,i){var n=(i-e)/t;this.element.scrollTop=e+(n>1?n:1)},e.prototype._scrollUp=function(e,t,i){var n=(e-i)/t;this.element.scrollTop=e-(n>1?n:1)},e.prototype._animateScroll=function(e,t){var i=this,n=this.element.scrollTop,s=!1;t>0?(this._scrollDown(n,4,e),ne&&(s=!0)),s&&requestAnimationFrame((function(){i._animateScroll(e,t)}))},e}(),$=function(){function e(e){var t=e.classNames;this.element=e.element,this.classNames=t,this.isDisabled=!1}return Object.defineProperty(e.prototype,"isActive",{get:function(){return"active"===this.element.dataset.choice},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"dir",{get:function(){return this.element.dir},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"value",{get:function(){return this.element.value},set:function(e){this.element.setAttribute("value",e),this.element.value=e},enumerable:!1,configurable:!0}),e.prototype.conceal=function(){var e=this.element;P(e,this.classNames.input),e.hidden=!0,e.tabIndex=-1;var t=e.getAttribute("style");t&&e.setAttribute("data-choice-orig-style",t),e.setAttribute("data-choice","active")},e.prototype.reveal=function(){var e=this.element;j(e,this.classNames.input),e.hidden=!1,e.removeAttribute("tabindex");var t=e.getAttribute("data-choice-orig-style");t?(e.removeAttribute("data-choice-orig-style"),e.setAttribute("style",t)):e.removeAttribute("style"),e.removeAttribute("data-choice")},e.prototype.enable=function(){this.element.removeAttribute("disabled"),this.element.disabled=!1,this.isDisabled=!1},e.prototype.disable=function(){this.element.setAttribute("disabled",""),this.element.disabled=!0,this.isDisabled=!0},e.prototype.triggerEvent=function(e,t){var i;void 0===(i=t||{})&&(i=null),this.element.dispatchEvent(new CustomEvent(e,{detail:i,bubbles:!0,cancelable:!0}))},e}(),q=function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return t(i,e),i}($),W=function(e,t){return void 0===t&&(t=!0),void 0===e?t:!!e},U=function(e){if("string"==typeof e&&(e=e.split(" ").filter((function(e){return e.length}))),Array.isArray(e)&&e.length)return e},G=function(e,t,i){if(void 0===i&&(i=!0),"string"==typeof e){var n=I(e);return G({value:e,label:i||n===e?e:{escaped:n,raw:e},selected:!0},!1)}var s=e;if("choices"in s){if(!t)throw new TypeError("optGroup is not allowed");var o=s,r=o.choices.map((function(e){return G(e,!1)}));return{id:0,label:L(o.label)||o.value,active:!!r.length,disabled:!!o.disabled,choices:r}}var c=s;return{id:0,group:null,score:0,rank:0,value:c.value,label:c.label||c.value,active:W(c.active),selected:W(c.selected,!1),disabled:W(c.disabled,!1),placeholder:W(c.placeholder,!1),highlighted:!1,labelClass:U(c.labelClass),labelDescription:c.labelDescription,customProperties:c.customProperties}},z=function(e){return"SELECT"===e.tagName},J=function(e){function i(t){var i=t.template,n=t.extractPlaceholder,s=e.call(this,{element:t.element,classNames:t.classNames})||this;return s.template=i,s.extractPlaceholder=n,s}return t(i,e),Object.defineProperty(i.prototype,"placeholderOption",{get:function(){return this.element.querySelector('option[value=""]')||this.element.querySelector("option[placeholder]")},enumerable:!1,configurable:!0}),i.prototype.addOptions=function(e){var t=this,i=document.createDocumentFragment();e.forEach((function(e){var n=e;if(!n.element){var s=t.template(n);i.appendChild(s),n.element=s}})),this.element.appendChild(i)},i.prototype.optionsAsChoices=function(){var e=this,t=[];return this.element.querySelectorAll(":scope > option, :scope > optgroup").forEach((function(i){!function(e){return"OPTION"===e.tagName}(i)?function(e){return"OPTGROUP"===e.tagName}(i)&&t.push(e._optgroupToChoice(i)):t.push(e._optionToChoice(i))})),t},i.prototype._optionToChoice=function(e){return!e.hasAttribute("value")&&e.hasAttribute("placeholder")&&(e.setAttribute("value",""),e.value=""),{id:0,group:null,score:0,rank:0,value:e.value,label:e.label,element:e,active:!0,selected:this.extractPlaceholder?e.selected:e.hasAttribute("selected"),disabled:e.disabled,highlighted:!1,placeholder:this.extractPlaceholder&&(!e.value||e.hasAttribute("placeholder")),labelClass:void 0!==e.dataset.labelClass?U(e.dataset.labelClass):void 0,labelDescription:void 0!==e.dataset.labelDescription?e.dataset.labelDescription:void 0,customProperties:R(e.dataset.customProperties)}},i.prototype._optgroupToChoice=function(e){var t=this,i=e.querySelectorAll("option"),n=Array.from(i).map((function(e){return t._optionToChoice(e)}));return{id:0,label:e.label||"",element:e,active:!!n.length,disabled:e.disabled,choices:n}},i}($),X={items:[],choices:[],silent:!1,renderChoiceLimit:-1,maxItemCount:-1,closeDropdownOnSelect:"auto",singleModeForMultiSelect:!1,addChoices:!1,addItems:!0,addItemFilter:function(e){return!!e&&""!==e},removeItems:!0,removeItemButton:!1,removeItemButtonAlignLeft:!1,editItems:!1,allowHTML:!1,allowHtmlUserInput:!1,duplicateItemsAllowed:!0,delimiter:",",paste:!0,searchEnabled:!0,searchChoices:!0,searchFloor:1,searchResultLimit:4,searchFields:["label","value"],position:"auto",resetScrollPosition:!0,shouldSort:!0,shouldSortItems:!1,sorter:function(e,t){var i=e.label,n=t.label,s=void 0===n?t.value:n;return L(void 0===i?e.value:i).localeCompare(L(s),[],{sensitivity:"base",ignorePunctuation:!0,numeric:!0})},shadowRoot:null,placeholder:!0,placeholderValue:null,searchPlaceholderValue:null,prependValue:null,appendValue:null,renderSelectedChoices:"auto",loadingText:"Loading...",noResultsText:"No results found",noChoicesText:"No choices to choose from",itemSelectText:"Press to select",uniqueItemText:"Only unique values can be added",customAddItemText:"Only values matching specific conditions can be added",addItemText:function(e){return'Press Enter to add "'.concat(e,'"')},removeItemIconText:function(){return"Remove item"},removeItemLabelText:function(e){return"Remove item: ".concat(e)},maxItemText:function(e){return"Only ".concat(e," values can be added")},valueComparer:function(e,t){return e===t},fuseOptions:{includeScore:!0},labelId:"",callbackOnInit:null,callbackOnCreateTemplates:null,classNames:{containerOuter:["choices"],containerInner:["choices__inner"],input:["choices__input"],inputCloned:["choices__input--cloned"],list:["choices__list"],listItems:["choices__list--multiple"],listSingle:["choices__list--single"],listDropdown:["choices__list--dropdown"],item:["choices__item"],itemSelectable:["choices__item--selectable"],itemDisabled:["choices__item--disabled"],itemChoice:["choices__item--choice"],description:["choices__description"],placeholder:["choices__placeholder"],group:["choices__group"],groupHeading:["choices__heading"],button:["choices__button"],activeState:["is-active"],focusState:["is-focused"],openState:["is-open"],disabledState:["is-disabled"],highlightedState:["is-highlighted"],selectedState:["is-selected"],flippedState:["is-flipped"],loadingState:["is-loading"],notice:["choices__notice"],addChoice:["choices__item--selectable","add-choice"],noResults:["has-no-results"],noChoices:["has-no-choices"]},appendGroupInSearch:!1},Q=function(e){var t=e.itemEl;t&&(t.remove(),e.itemEl=void 0)},Y={groups:function(e,t){var i=e,n=!0;switch(t.type){case l:i.push(t.group);break;case h:i=[];break;default:n=!1}return{state:i,update:n}},items:function(e,t,i){var n=e,s=!0;switch(t.type){case u:t.item.selected=!0,(o=t.item.element)&&(o.selected=!0,o.setAttribute("selected","")),n.push(t.item);break;case d:var o;if(t.item.selected=!1,o=t.item.element){o.selected=!1,o.removeAttribute("selected");var c=o.parentElement;c&&z(c)&&c.type===_&&(c.value="")}Q(t.item),n=n.filter((function(e){return e.id!==t.item.id}));break;case r:Q(t.choice),n=n.filter((function(e){return e.id!==t.choice.id}));break;case p:var a=t.highlighted,h=n.find((function(e){return e.id===t.item.id}));h&&h.highlighted!==a&&(h.highlighted=a,i&&function(e,t,i){var n=e.itemEl;n&&(j(n,i),P(n,t))}(h,a?i.classNames.highlightedState:i.classNames.selectedState,a?i.classNames.selectedState:i.classNames.highlightedState));break;default:s=!1}return{state:n,update:s}},choices:function(e,t,i){var n=e,s=!0;switch(t.type){case o:n.push(t.choice);break;case r:t.choice.choiceEl=void 0,t.choice.group&&(t.choice.group.choices=t.choice.group.choices.filter((function(e){return e.id!==t.choice.id}))),n=n.filter((function(e){return e.id!==t.choice.id}));break;case u:case d:t.item.choiceEl=void 0;break;case c:var l=[];t.results.forEach((function(e){l[e.item.id]=e})),n.forEach((function(e){var t=l[e.id];void 0!==t?(e.score=t.score,e.rank=t.rank,e.active=!0):(e.score=0,e.rank=0,e.active=!1),i&&i.appendGroupInSearch&&(e.choiceEl=void 0)}));break;case a:n.forEach((function(e){e.active=t.active,i&&i.appendGroupInSearch&&(e.choiceEl=void 0)}));break;case h:n=[];break;default:s=!1}return{state:n,update:s}}},Z=function(){function e(e){this._state=this.defaultState,this._listeners=[],this._txn=0,this._context=e}return Object.defineProperty(e.prototype,"defaultState",{get:function(){return{groups:[],items:[],choices:[]}},enumerable:!1,configurable:!0}),e.prototype.changeSet=function(e){return{groups:e,items:e,choices:e}},e.prototype.reset=function(){this._state=this.defaultState;var e=this.changeSet(!0);this._txn?this._changeSet=e:this._listeners.forEach((function(t){return t(e)}))},e.prototype.subscribe=function(e){return this._listeners.push(e),this},e.prototype.dispatch=function(e){var t=this,i=this._state,n=!1,s=this._changeSet||this.changeSet(!1);Object.keys(Y).forEach((function(o){var r=Y[o](i[o],e,t._context);r.update&&(n=!0,s[o]=!0,i[o]=r.state)})),n&&(this._txn?this._changeSet=s:this._listeners.forEach((function(e){return e(s)})))},e.prototype.withTxn=function(e){this._txn++;try{e()}finally{if(this._txn=Math.max(0,this._txn-1),!this._txn){var t=this._changeSet;t&&(this._changeSet=void 0,this._listeners.forEach((function(e){return e(t)})))}}},Object.defineProperty(e.prototype,"state",{get:function(){return this._state},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"items",{get:function(){return this.state.items},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"highlightedActiveItems",{get:function(){return this.items.filter((function(e){return e.active&&e.highlighted}))},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"choices",{get:function(){return this.state.choices},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"activeChoices",{get:function(){return this.choices.filter((function(e){return e.active}))},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"searchableChoices",{get:function(){return this.choices.filter((function(e){return!e.disabled&&!e.placeholder}))},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"groups",{get:function(){return this.state.groups},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"activeGroups",{get:function(){var e=this;return this.state.groups.filter((function(t){var i=t.active&&!t.disabled,n=e.state.choices.some((function(e){return e.active&&!e.disabled}));return i&&n}),[])},enumerable:!1,configurable:!0}),e.prototype.inTxn=function(){return this._txn>0},e.prototype.getChoiceById=function(e){return this.activeChoices.find((function(t){return t.id===e}))},e.prototype.getGroupById=function(e){return this.groups.find((function(t){return t.id===e}))},e}(),ee="no-choices",te="no-results",ie="add-choice";function ne(e,t,i){return(t=function(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var i=t.call(e,"string");if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function se(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function oe(e){for(var t=1;t`Missing ${e} property in key`,fe=e=>`Property 'weight' in key '${e}' must be a positive integer`,me=Object.prototype.hasOwnProperty;class ge{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach((e=>{let i=ve(e);this._keys.push(i),this._keyMap[i.id]=i,t+=i.weight})),this._keys.forEach((e=>{e.weight/=t}))}get(e){return this._keyMap[e]}keys(){return this._keys}toJSON(){return JSON.stringify(this._keys)}}function ve(e){let t=null,i=null,n=null,s=1,o=null;if(ce(e)||re(e))n=e,t=_e(e),i=ye(e);else{if(!me.call(e,"name"))throw new Error(pe("name"));const r=e.name;if(n=r,me.call(e,"weight")&&(s=e.weight,s<=0))throw new Error(fe(r));t=_e(r),i=ye(r),o=e.getFn}return{path:t,id:i,weight:s,src:n,getFn:o}}function _e(e){return re(e)?e:e.split(".")}function ye(e){return re(e)?e.join("."):e}const be={useExtendedSearch:!1,getFn:function(e,t){let i=[],n=!1;const s=(e,t,o)=>{if(le(e))if(t[o]){const r=e[t[o]];if(!le(r))return;if(o===t.length-1&&(ce(r)||ae(r)||function(e){return!0===e||!1===e||function(e){return he(e)&&null!==e}(e)&&"[object Boolean]"==de(e)}(r)))i.push(function(e){return null==e?"":function(e){if("string"==typeof e)return e;let t=e+"";return"0"==t&&1/e==-1/0?"-0":t}(e)}(r));else if(re(r)){n=!0;for(let e=0,i=r.length;ee.score===t.score?e.idx{this._keysMap[e.id]=t}))}create(){!this.isCreated&&this.docs.length&&(this.isCreated=!0,ce(this.docs[0])?this.docs.forEach(((e,t)=>{this._addString(e,t)})):this.docs.forEach(((e,t)=>{this._addObject(e,t)})),this.norm.clear())}add(e){const t=this.size();ce(e)?this._addString(e,t):this._addObject(e,t)}removeAt(e){this.records.splice(e,1);for(let t=e,i=this.size();t{let s=t.getFn?t.getFn(e):this.getFn(e,t.path);if(le(s))if(re(s)){let e=[];const t=[{nestedArrIndex:-1,value:s}];for(;t.length;){const{nestedArrIndex:i,value:n}=t.pop();if(le(n))if(ce(n)&&!ue(n)){let t={v:n,i:i,n:this.norm.get(n)};e.push(t)}else re(n)&&n.forEach(((e,i)=>{t.push({nestedArrIndex:i,value:e})}))}i.$[n]=e}else if(ce(s)&&!ue(s)){let e={v:s,n:this.norm.get(s)};i.$[n]=e}})),this.records.push(i)}toJSON(){return{keys:this.keys,records:this.records}}}function we(e,t,{getFn:i=Ee.getFn,fieldNormWeight:n=Ee.fieldNormWeight}={}){const s=new Se({getFn:i,fieldNormWeight:n});return s.setKeys(e.map(ve)),s.setSources(t),s.create(),s}function Ie(e,{errors:t=0,currentLocation:i=0,expectedLocation:n=0,distance:s=Ee.distance,ignoreLocation:o=Ee.ignoreLocation}={}){const r=t/e.length;if(o)return r;const c=Math.abs(n-i);return s?r+c/s:c?1:r}const Ae=32;function xe(e){let t={};for(let i=0,n=e.length;i{this.chunks.push({pattern:e,alphabet:xe(e),startIndex:t})},l=this.pattern.length;if(l>Ae){let e=0;const t=l%Ae,i=l-t;for(;e{const{isMatch:f,score:m,indices:g}=function(e,t,i,{location:n=Ee.location,distance:s=Ee.distance,threshold:o=Ee.threshold,findAllMatches:r=Ee.findAllMatches,minMatchCharLength:c=Ee.minMatchCharLength,includeMatches:a=Ee.includeMatches,ignoreLocation:h=Ee.ignoreLocation}={}){if(t.length>Ae)throw new Error("Pattern length exceeds max of 32.");const l=t.length,u=e.length,d=Math.max(0,Math.min(n,u));let p=o,f=d;const m=c>1||a,g=m?Array(u):[];let v;for(;(v=e.indexOf(t,f))>-1;){let e=Ie(t,{currentLocation:v,expectedLocation:d,distance:s,ignoreLocation:h});if(p=Math.min(e,p),f=v+l,m){let e=0;for(;e=a;o-=1){let r=o-1,c=i[e.charAt(r)];if(m&&(g[r]=+!!c),C[o]=(C[o+1]<<1|1)&c,n&&(C[o]|=(_[o+1]|_[o])<<1|1|_[o+1]),C[o]&E&&(y=Ie(t,{errors:n,currentLocation:r,expectedLocation:d,distance:s,ignoreLocation:h}),y<=p)){if(p=y,f=r,f<=d)break;a=Math.max(1,2*d-f)}}if(Ie(t,{errors:n+1,currentLocation:d,expectedLocation:d,distance:s,ignoreLocation:h})>p)break;_=C}const C={isMatch:f>=0,score:Math.max(.001,y)};if(m){const e=function(e=[],t=Ee.minMatchCharLength){let i=[],n=-1,s=-1,o=0;for(let r=e.length;o=t&&i.push([n,s]),n=-1)}return e[o-1]&&o-n>=t&&i.push([n,o-1]),i}(g,c);e.length?a&&(C.indices=e):C.isMatch=!1}return C}(e,t,d,{location:n+p,distance:s,threshold:o,findAllMatches:r,minMatchCharLength:c,includeMatches:i,ignoreLocation:a});f&&(u=!0),l+=m,f&&g&&(h=[...h,...g])}));let d={isMatch:u,score:u?l/this.chunks.length:1};return u&&i&&(d.indices=h),d}}class Le{constructor(e){this.pattern=e}static isMultiMatch(e){return Me(e,this.multiRegex)}static isSingleMatch(e){return Me(e,this.singleRegex)}search(){}}function Me(e,t){const i=e.match(t);return i?i[1]:null}class Te extends Le{constructor(e,{location:t=Ee.location,threshold:i=Ee.threshold,distance:n=Ee.distance,includeMatches:s=Ee.includeMatches,findAllMatches:o=Ee.findAllMatches,minMatchCharLength:r=Ee.minMatchCharLength,isCaseSensitive:c=Ee.isCaseSensitive,ignoreLocation:a=Ee.ignoreLocation}={}){super(e),this._bitapSearch=new Oe(e,{location:t,threshold:i,distance:n,includeMatches:s,findAllMatches:o,minMatchCharLength:r,isCaseSensitive:c,ignoreLocation:a})}static get type(){return"fuzzy"}static get multiRegex(){return/^"(.*)"$/}static get singleRegex(){return/^(.*)$/}search(e){return this._bitapSearch.searchIn(e)}}class Ne extends Le{constructor(e){super(e)}static get type(){return"include"}static get multiRegex(){return/^'"(.*)"$/}static get singleRegex(){return/^'(.*)$/}search(e){let t,i=0;const n=[],s=this.pattern.length;for(;(t=e.indexOf(this.pattern,i))>-1;)i=t+s,n.push([t,i-1]);const o=!!n.length;return{isMatch:o,score:o?0:1,indices:n}}}const ke=[class extends Le{constructor(e){super(e)}static get type(){return"exact"}static get multiRegex(){return/^="(.*)"$/}static get singleRegex(){return/^=(.*)$/}search(e){const t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},Ne,class extends Le{constructor(e){super(e)}static get type(){return"prefix-exact"}static get multiRegex(){return/^\^"(.*)"$/}static get singleRegex(){return/^\^(.*)$/}search(e){const t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}},class extends Le{constructor(e){super(e)}static get type(){return"inverse-prefix-exact"}static get multiRegex(){return/^!\^"(.*)"$/}static get singleRegex(){return/^!\^(.*)$/}search(e){const t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends Le{constructor(e){super(e)}static get type(){return"inverse-suffix-exact"}static get multiRegex(){return/^!"(.*)"\$$/}static get singleRegex(){return/^!(.*)\$$/}search(e){const t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},class extends Le{constructor(e){super(e)}static get type(){return"suffix-exact"}static get multiRegex(){return/^"(.*)"\$$/}static get singleRegex(){return/^(.*)\$$/}search(e){const t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}},class extends Le{constructor(e){super(e)}static get type(){return"inverse-exact"}static get multiRegex(){return/^!"(.*)"$/}static get singleRegex(){return/^!(.*)$/}search(e){const t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}},Te],Fe=ke.length,De=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/,Pe=new Set([Te.type,Ne.type]);const je=[];function Re(e,t){for(let i=0,n=je.length;i!(!e[Ke]&&!e.$or),He=e=>({[Ke]:Object.keys(e).map((t=>({[t]:e[t]})))});function $e(e,t,{auto:i=!0}={}){const n=e=>{let s=Object.keys(e);const o=(e=>!!e[Ve])(e);if(!o&&s.length>1&&!Be(e))return n(He(e));if((e=>!re(e)&&he(e)&&!Be(e))(e)){const n=o?e[Ve]:s[0],r=o?e.$val:e[n];if(!ce(r))throw new Error((e=>`Invalid value for key ${e}`)(n));const c={keyId:ye(n),pattern:r};return i&&(c.searcher=Re(r,t)),c}let r={children:[],operator:s[0]};return s.forEach((t=>{const i=e[t];re(i)&&i.forEach((e=>{r.children.push(n(e))}))})),r};return Be(e)||(e=He(e)),n(e)}function qe(e,t){const i=e.matches;t.matches=[],le(i)&&i.forEach((e=>{if(!le(e.indices)||!e.indices.length)return;const{indices:i,value:n}=e;let s={indices:i,value:n};e.key&&(s.key=e.key.src),e.idx>-1&&(s.refIndex=e.idx),t.matches.push(s)}))}function We(e,t){t.score=e.score}class Ue{constructor(e,t={},i){this.options=oe(oe({},Ee),t),this._keyStore=new ge(this.options.keys),this.setCollection(e,i)}setCollection(e,t){if(this._docs=e,t&&!(t instanceof Se))throw new Error("Incorrect 'index' type");this._myIndex=t||we(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}add(e){le(e)&&(this._docs.push(e),this._myIndex.add(e))}remove(e=()=>!1){const t=[];for(let i=0,n=this._docs.length;i{let i=1;e.matches.forEach((({key:e,norm:n,score:s})=>{const o=e?e.weight:null;i*=Math.pow(0===s&&o?Number.EPSILON:s,(o||1)*(t?1:n))})),e.score=i}))}(c,{ignoreFieldNorm:r}),s&&c.sort(o),ae(t)&&t>-1&&(c=c.slice(0,t)),function(e,t,{includeMatches:i=Ee.includeMatches,includeScore:n=Ee.includeScore}={}){const s=[];return i&&s.push(qe),n&&s.push(We),e.map((e=>{const{idx:i}=e,n={item:t[i],refIndex:i};return s.length&&s.forEach((t=>{t(e,n)})),n}))}(c,this._docs,{includeMatches:i,includeScore:n})}_searchStringList(e){const t=Re(e,this.options),{records:i}=this._myIndex,n=[];return i.forEach((({v:e,i:i,n:s})=>{if(!le(e))return;const{isMatch:o,score:r,indices:c}=t.searchIn(e);o&&n.push({item:e,idx:i,matches:[{score:r,value:e,norm:s,indices:c}]})})),n}_searchLogical(e){const t=$e(e,this.options),i=(e,t,n)=>{if(!e.children){const{keyId:i,searcher:s}=e,o=this._findMatches({key:this._keyStore.get(i),value:this._myIndex.getValueForItemAtKeyId(t,i),searcher:s});return o&&o.length?[{idx:n,item:t,matches:o}]:[]}const s=[];for(let o=0,r=e.children.length;o{if(le(e)){let r=i(t,e,o);r.length&&(n[o]||(n[o]={idx:o,item:e,matches:[]},s.push(n[o])),r.forEach((({matches:e})=>{n[o].matches.push(...e)})))}})),s}_searchObjectList(e){const t=Re(e,this.options),{keys:i,records:n}=this._myIndex,s=[];return n.forEach((({$:e,i:n})=>{if(!le(e))return;let o=[];i.forEach(((i,n)=>{o.push(...this._findMatches({key:i,value:e[n],searcher:t}))})),o.length&&s.push({idx:n,item:e,matches:o})})),s}_findMatches({key:e,value:t,searcher:i}){if(!le(t))return[];let n=[];if(re(t))t.forEach((({v:t,i:s,n:o})=>{if(!le(t))return;const{isMatch:r,score:c,indices:a}=i.searchIn(t);r&&n.push({score:c,key:e,value:t,idx:s,norm:o,indices:a})}));else{const{v:s,n:o}=t,{isMatch:r,score:c,indices:a}=i.searchIn(s);r&&n.push({score:c,key:e,value:s,norm:o,indices:a})}return n}}Ue.version="7.0.0",Ue.createIndex=we,Ue.parseIndex=function(e,{getFn:t=Ee.getFn,fieldNormWeight:i=Ee.fieldNormWeight}={}){const{keys:n,records:s}=e,o=new Se({getFn:t,fieldNormWeight:i});return o.setKeys(n),o.setIndexRecords(s),o},Ue.config=Ee,Ue.parseQuery=$e,function(...e){je.push(...e)}(class{constructor(e,{isCaseSensitive:t=Ee.isCaseSensitive,includeMatches:i=Ee.includeMatches,minMatchCharLength:n=Ee.minMatchCharLength,ignoreLocation:s=Ee.ignoreLocation,findAllMatches:o=Ee.findAllMatches,location:r=Ee.location,threshold:c=Ee.threshold,distance:a=Ee.distance}={}){this.query=null,this.options={isCaseSensitive:t,includeMatches:i,minMatchCharLength:n,findAllMatches:o,ignoreLocation:s,location:r,threshold:c,distance:a},this.pattern=t?e:e.toLowerCase(),this.query=function(e,t={}){return e.split("|").map((e=>{let i=e.trim().split(De).filter((e=>e&&!!e.trim())),n=[];for(let e=0,s=i.length;e element"),this)},e.prototype.removeChoice=function(e){var t=this._store.choices.find((function(t){return t.value===e}));return t?(this._clearNotice(),this._store.dispatch(function(e){return{type:r,choice:e}}(t)),this._searcher.reset(),t.selected&&this.passedElement.triggerEvent(m,this._getChoiceForOutput(t)),this):this},e.prototype.clearChoices=function(e,t){var i=this;return void 0===e&&(e=!0),void 0===t&&(t=!1),e&&(t?this.passedElement.element.replaceChildren(""):this.passedElement.element.querySelectorAll(":not([selected])").forEach((function(e){e.remove()}))),this.itemList.element.replaceChildren(""),this.choiceList.element.replaceChildren(""),this._clearNotice(),this._store.withTxn((function(){var e=t?[]:i._store.items;i._store.reset(),e.forEach((function(e){i._store.dispatch(b(e)),i._store.dispatch(E(e))}))})),this._searcher.reset(),this},e.prototype.clearStore=function(e){return void 0===e&&(e=!0),this.clearChoices(e,!0),this._stopSearch(),this._lastAddedChoiceId=0,this._lastAddedGroupId=0,this},e.prototype.clearInput=function(){return this.input.clear(!this._isSelectOneElement),this._stopSearch(),this},e.prototype._validateConfig=function(){var e,t,i,n=this.config,s=(e=X,t=Object.keys(n).sort(),i=Object.keys(e).sort(),t.filter((function(e){return i.indexOf(e)<0})));s.length&&console.warn("Unknown config option(s) passed",s.join(", ")),n.allowHTML&&n.allowHtmlUserInput&&(n.addItems&&console.warn("Warning: allowHTML/allowHtmlUserInput/addItems all being true is strongly not recommended and may lead to XSS attacks"),n.addChoices&&console.warn("Warning: allowHTML/allowHtmlUserInput/addChoices all being true is strongly not recommended and may lead to XSS attacks"))},e.prototype._render=function(e){void 0===e&&(e={choices:!0,groups:!0,items:!0}),this._store.inTxn()||(this._isSelectElement&&(e.choices||e.groups)&&this._renderChoices(),e.items&&this._renderItems())},e.prototype._renderChoices=function(){var e=this;if(this._canAddItems()){var t=this.config,i=this._isSearching,n=this._store,s=n.activeGroups,o=n.activeChoices,r=0;if(i&&t.searchResultLimit>0?r=t.searchResultLimit:t.renderChoiceLimit>0&&(r=t.renderChoiceLimit),this._isSelectElement){var c=o.filter((function(e){return!e.element}));c.length&&this.passedElement.addOptions(c)}var a=document.createDocumentFragment(),h=function(e){return e.filter((function(e){return!e.placeholder&&(i?!!e.rank:t.renderSelectedChoices||!e.selected)}))},l=!1,u=function(n,s,o){i?n.sort(k):t.shouldSort&&n.sort(t.sorter);var c=n.length;c=!s&&r&&c>r?r:c,c--,n.every((function(n,s){var r=n.choiceEl||e._templates.choice(t,n,t.itemSelectText,o);return n.choiceEl=r,a.appendChild(r),!i&&n.selected||(l=!0),s1){var h=i.querySelector(D(n.classNames.placeholder));h&&h.remove()}else c||a||!this._placeholderValue||(c=!0,r(G({selected:!0,value:"",label:this._placeholderValue,placeholder:!0},!1)))}c&&(i.append(s),n.shouldSortItems&&!this._isSelectOneElement&&(t.sort(n.sorter),t.forEach((function(e){var t=o(e);t&&(t.remove(),s.append(t))})),i.append(s))),this._isTextElement&&(this.passedElement.value=t.map((function(e){return e.value})).join(n.delimiter))},e.prototype._displayNotice=function(e,t,i){void 0===i&&(i=!0);var n=this._notice;n&&(n.type===t&&n.text===e||n.type===ie&&(t===te||t===ee))?i&&this.showDropdown(!0):(this._clearNotice(),this._notice=e?{text:e,type:t}:void 0,this._renderNotice(),i&&e&&this.showDropdown(!0))},e.prototype._clearNotice=function(){if(this._notice){var e=this.choiceList.element.querySelector(D(this.config.classNames.notice));e&&e.remove(),this._notice=void 0}},e.prototype._renderNotice=function(e){var t=this._notice;if(t){var i=this._templates.notice(this.config,t.text,t.type);e?e.append(i):this.choiceList.prepend(i)}},e.prototype._getChoiceForOutput=function(e,t){return{id:e.id,highlighted:e.highlighted,labelClass:e.labelClass,labelDescription:e.labelDescription,customProperties:e.customProperties,disabled:e.disabled,active:e.active,label:e.label,placeholder:e.placeholder,value:e.value,groupValue:e.group?e.group.label:void 0,element:e.element,keyCode:t}},e.prototype._triggerChange=function(e){null!=e&&this.passedElement.triggerEvent("change",{value:e})},e.prototype._handleButtonAction=function(e){var t=this,i=this._store.items;if(i.length&&this.config.removeItems&&this.config.removeItemButton){var n=e&&Ze(e.parentElement),s=n&&i.find((function(e){return e.id===n}));s&&this._store.withTxn((function(){if(t._removeItem(s),t._triggerChange(s.value),t._isSelectOneElement&&!t._hasNonChoicePlaceholder){var e=(t.config.shouldSort?t._store.choices.reverse():t._store.choices).find((function(e){return e.placeholder}));e&&(t._addItem(e),t.unhighlightAll(),e.value&&t._triggerChange(e.value))}}))}},e.prototype._handleItemAction=function(e,t){var i=this;void 0===t&&(t=!1);var n=this._store.items;if(n.length&&this.config.removeItems&&!this._isSelectOneElement){var s=Ze(e);s&&(n.forEach((function(e){e.id!==s||e.highlighted?!t&&e.highlighted&&i.unhighlightItem(e):i.highlightItem(e)})),this.input.focus())}},e.prototype._handleChoiceAction=function(e){var t=this,i=Ze(e),n=i&&this._store.getChoiceById(i);if(!n||n.disabled)return!1;var s=this.dropdown.isActive;if(!n.selected){if(!this._canAddItems())return!0;this._store.withTxn((function(){t._addItem(n,!0,!0),t.clearInput(),t.unhighlightAll()})),this._triggerChange(n.value)}return s&&this.config.closeDropdownOnSelect&&(this.hideDropdown(!0),this.containerOuter.element.focus()),!0},e.prototype._handleBackspace=function(e){var t=this.config;if(t.removeItems&&e.length){var i=e[e.length-1],n=e.some((function(e){return e.highlighted}));t.editItems&&!n&&i?(this.input.value=i.value,this.input.setWidth(),this._removeItem(i),this._triggerChange(i.value)):(n||this.highlightItem(i,!1),this.removeHighlightedItems(!0))}},e.prototype._loadChoices=function(){var e,t=this,i=this.config;if(this._isTextElement){if(this._presetChoices=i.items.map((function(e){return G(e,!1)})),this.passedElement.value){var n=this.passedElement.value.split(i.delimiter).map((function(e){return G(e,!1,t.config.allowHtmlUserInput)}));this._presetChoices=this._presetChoices.concat(n)}this._presetChoices.forEach((function(e){e.selected=!0}))}else if(this._isSelectElement){this._presetChoices=i.choices.map((function(e){return G(e,!0)}));var s=this.passedElement.optionsAsChoices();s&&(e=this._presetChoices).push.apply(e,s)}},e.prototype._handleLoadingState=function(e){void 0===e&&(e=!0);var t=this.itemList.element;e?(this.disable(),this.containerOuter.addLoadingState(),this._isSelectOneElement?t.replaceChildren(this._templates.placeholder(this.config,this.config.loadingText)):this.input.placeholder=this.config.loadingText):(this.enable(),this.containerOuter.removeLoadingState(),this._isSelectOneElement?(t.replaceChildren(""),this._render()):this.input.placeholder=this._placeholderValue||"")},e.prototype._handleSearch=function(e){if(this.input.isFocussed)if(null!=e&&e.length>=this.config.searchFloor){var t=this.config.searchChoices?this._searchChoices(e):0;null!==t&&this.passedElement.triggerEvent(f,{value:e,resultCount:t})}else this._store.choices.some((function(e){return!e.active}))&&this._stopSearch()},e.prototype._canAddItems=function(){var e=this.config,t=e.maxItemCount,i=e.maxItemText;return!e.singleModeForMultiSelect&&t>0&&t<=this._store.items.length?(this.choiceList.element.replaceChildren(""),this._notice=void 0,this._displayNotice("function"==typeof i?i(t):i,ie),!1):(this._notice&&this._notice.type===ie&&this._clearNotice(),!0)},e.prototype._canCreateItem=function(e){var t=this.config,i=!0,n="";if(i&&"function"==typeof t.addItemFilter&&!t.addItemFilter(e)&&(i=!1,n=x(t.customAddItemText,e)),i&&this._store.choices.find((function(i){return t.valueComparer(i.value,e)}))){if(this._isSelectElement)return this._displayNotice("",ie),!1;t.duplicateItemsAllowed||(i=!1,n=x(t.uniqueItemText,e))}return i&&(n=x(t.addItemText,e)),n&&this._displayNotice(n,ie),i},e.prototype._searchChoices=function(e){var t=e.trim().replace(/\s{2,}/," ");if(!t.length||t===this._currentValue)return null;var i=this._searcher;i.isEmptyIndex()&&i.index(this._store.searchableChoices);var n=i.search(t);this._currentValue=t,this._highlightPosition=0,this._isSearching=!0;var s=this._notice;return(s&&s.type)!==ie&&(n.length?this._clearNotice():this._displayNotice(O(this.config.noResultsText),te)),this._store.dispatch(function(e){return{type:c,results:e}}(n)),n.length},e.prototype._stopSearch=function(){this._isSearching&&(this._currentValue="",this._isSearching=!1,this._clearNotice(),this._store.dispatch({type:a,active:!0}),this.passedElement.triggerEvent(f,{value:"",resultCount:0}))},e.prototype._addEventListeners=function(){var e=this._docRoot,t=this.containerOuter.element,i=this.input.element;e.addEventListener("touchend",this._onTouchEnd,!0),t.addEventListener("keydown",this._onKeyDown,!0),t.addEventListener("mousedown",this._onMouseDown,!0),e.addEventListener("click",this._onClick,{passive:!0}),e.addEventListener("touchmove",this._onTouchMove,{passive:!0}),this.dropdown.element.addEventListener("mouseover",this._onMouseOver,{passive:!0}),this._isSelectOneElement&&(t.addEventListener("focus",this._onFocus,{passive:!0}),t.addEventListener("blur",this._onBlur,{passive:!0})),i.addEventListener("keyup",this._onKeyUp,{passive:!0}),i.addEventListener("input",this._onInput,{passive:!0}),i.addEventListener("focus",this._onFocus,{passive:!0}),i.addEventListener("blur",this._onBlur,{passive:!0}),i.form&&i.form.addEventListener("reset",this._onFormReset,{passive:!0}),this.input.addEventListeners()},e.prototype._removeEventListeners=function(){var e=this._docRoot,t=this.containerOuter.element,i=this.input.element;e.removeEventListener("touchend",this._onTouchEnd,!0),t.removeEventListener("keydown",this._onKeyDown,!0),t.removeEventListener("mousedown",this._onMouseDown,!0),e.removeEventListener("click",this._onClick),e.removeEventListener("touchmove",this._onTouchMove),this.dropdown.element.removeEventListener("mouseover",this._onMouseOver),this._isSelectOneElement&&(t.removeEventListener("focus",this._onFocus),t.removeEventListener("blur",this._onBlur)),i.removeEventListener("keyup",this._onKeyUp),i.removeEventListener("input",this._onInput),i.removeEventListener("focus",this._onFocus),i.removeEventListener("blur",this._onBlur),i.form&&i.form.removeEventListener("reset",this._onFormReset),this.input.removeEventListeners()},e.prototype._onKeyDown=function(e){var t=e.keyCode,i=this.dropdown.isActive,n=1===e.key.length||2===e.key.length&&e.key.charCodeAt(0)>=55296||"Unidentified"===e.key;switch(this._isTextElement||i||27===t||9===t||16===t||(this.showDropdown(),!this.input.isFocussed&&n&&(this.input.value+=e.key," "===e.key&&e.preventDefault())),t){case 65:return this._onSelectKey(e,this.itemList.element.hasChildNodes());case 13:return this._onEnterKey(e,i);case 27:return this._onEscapeKey(e,i);case 38:case 33:case 40:case 34:return this._onDirectionKey(e,i);case 8:case 46:return this._onDeleteKey(e,this._store.items,this.input.isFocussed)}},e.prototype._onKeyUp=function(){this._canSearch=this.config.searchEnabled},e.prototype._onInput=function(){var e=this.input.value;e?this._canAddItems()&&(this._canSearch&&this._handleSearch(e),this._canAddUserChoices&&(this._canCreateItem(e),this._isSelectElement&&(this._highlightPosition=0,this._highlightChoice()))):this._isTextElement?this.hideDropdown(!0):this._stopSearch()},e.prototype._onSelectKey=function(e,t){(e.ctrlKey||e.metaKey)&&t&&(this._canSearch=!1,this.config.removeItems&&!this.input.value&&this.input.element===document.activeElement&&this.highlightAll())},e.prototype._onEnterKey=function(e,t){var i=this,n=this.input.value,s=e.target;if(e.preventDefault(),s&&s.hasAttribute("data-button"))this._handleButtonAction(s);else if(t){var o=this.dropdown.element.querySelector(D(this.config.classNames.highlightedState));if(!o||!this._handleChoiceAction(o))if(s&&n){if(this._canAddItems()){var r=!1;this._store.withTxn((function(){if(!(r=i._findAndSelectChoiceByValue(n,!0))){if(!i._canAddUserChoices)return;if(!i._canCreateItem(n))return;i._addChoice(G(n,!1,i.config.allowHtmlUserInput),!0,!0),r=!0}i.clearInput(),i.unhighlightAll()})),r&&(this._triggerChange(n),this.config.closeDropdownOnSelect&&this.hideDropdown(!0))}}else this.hideDropdown(!0)}else(this._isSelectElement||this._notice)&&this.showDropdown()},e.prototype._onEscapeKey=function(e,t){t&&(e.stopPropagation(),this.hideDropdown(!0),this._stopSearch(),this.containerOuter.element.focus())},e.prototype._onDirectionKey=function(e,t){var i,n,s,o=e.keyCode;if(t||this._isSelectOneElement){this.showDropdown(),this._canSearch=!1;var r=40===o||34===o?1:-1,c=void 0;if(e.metaKey||34===o||33===o)c=this.dropdown.element.querySelector(r>0?"".concat(et,":last-of-type"):et);else{var a=this.dropdown.element.querySelector(D(this.config.classNames.highlightedState));c=a?function(e,t,i){void 0===i&&(i=1);for(var n="".concat(i>0?"next":"previous","ElementSibling"),s=e[n];s;){if(s.matches(t))return s;s=s[n]}return null}(a,et,r):this.dropdown.element.querySelector(et)}c&&(i=c,n=this.choiceList.element,void 0===(s=r)&&(s=1),(s>0?n.scrollTop+n.offsetHeight>=i.offsetTop+i.offsetHeight:i.offsetTop>=n.scrollTop)||this.choiceList.scrollToChildElement(c,r),this._highlightChoice(c)),e.preventDefault()}},e.prototype._onDeleteKey=function(e,t,i){this._isSelectOneElement||e.target.value||!i||(this._handleBackspace(t),e.preventDefault())},e.prototype._onTouchMove=function(){this._wasTap&&(this._wasTap=!1)},e.prototype._onTouchEnd=function(e){var t=(e||e.touches[0]).target;this._wasTap&&this.containerOuter.element.contains(t)&&((t===this.containerOuter.element||t===this.containerInner.element)&&(this._isTextElement?this.input.focus():this._isSelectMultipleElement&&this.showDropdown()),e.stopPropagation()),this._wasTap=!0},e.prototype._onMouseDown=function(e){var t=e.target;if(t instanceof HTMLElement){if(Qe&&this.choiceList.element.contains(t)){var i=this.choiceList.element.firstElementChild;this._isScrollingOnIe="ltr"===this._direction?e.offsetX>=i.offsetWidth:e.offsetXthis._highlightPosition?t[this._highlightPosition]:t[t.length-1])||(i=t[0]),P(i,n),i.setAttribute("aria-selected","true"),this.passedElement.triggerEvent("highlightChoice",{el:i}),this.dropdown.isActive&&(this.input.setActiveDescendant(i.id),this.containerOuter.setActiveDescendant(i.id))}},e.prototype._addItem=function(e,t,i){if(void 0===t&&(t=!0),void 0===i&&(i=!1),!e.id)throw new TypeError("item.id must be set before _addItem is called for a choice/item");(this.config.singleModeForMultiSelect||this._isSelectOneElement)&&this.removeActiveItems(e.id),this._store.dispatch(E(e)),t&&(this.passedElement.triggerEvent("addItem",this._getChoiceForOutput(e)),i&&this.passedElement.triggerEvent("choice",this._getChoiceForOutput(e)))},e.prototype._removeItem=function(e){if(e.id){this._store.dispatch(C(e));var t=this._notice;t&&t.type===ee&&this._clearNotice(),this.passedElement.triggerEvent(m,this._getChoiceForOutput(e))}},e.prototype._addChoice=function(e,t,i){if(void 0===t&&(t=!0),void 0===i&&(i=!1),e.id)throw new TypeError("Can not re-add a choice which has already been added");var n=this.config;if(n.duplicateItemsAllowed||!this._store.choices.find((function(t){return n.valueComparer(t.value,e.value)}))){this._lastAddedChoiceId++,e.id=this._lastAddedChoiceId,e.elementId="".concat(this._baseId,"-").concat(this._idNames.itemChoice,"-").concat(e.id);var s=n.prependValue,o=n.appendValue;s&&(e.value=s+e.value),o&&(e.value+=o.toString()),(s||o)&&e.element&&(e.element.value=e.value),this._clearNotice(),this._store.dispatch(b(e)),e.selected&&this._addItem(e,t,i)}},e.prototype._addGroup=function(e,t){var i=this;if(void 0===t&&(t=!0),e.id)throw new TypeError("Can not re-add a group which has already been added");this._store.dispatch(function(e){return{type:l,group:e}}(e)),e.choices&&(this._lastAddedGroupId++,e.id=this._lastAddedGroupId,e.choices.forEach((function(n){n.group=e,e.disabled&&(n.disabled=!0),i._addChoice(n,t)})))},e.prototype._createTemplates=function(){var e=this,t=this.config.callbackOnCreateTemplates,i={};"function"==typeof t&&(i=t.call(this,A,T,F));var n={};Object.keys(this._templates).forEach((function(t){n[t]=t in i?i[t].bind(e):e._templates[t].bind(e)})),this._templates=n},e.prototype._createElements=function(){var e=this._templates,t=this.config,i=this._isSelectOneElement,n=t.position,s=t.classNames,o=this._elementType;this.containerOuter=new V({element:e.containerOuter(t,this._direction,this._isSelectElement,i,t.searchEnabled,o,t.labelId),classNames:s,type:o,position:n}),this.containerInner=new V({element:e.containerInner(t),classNames:s,type:o,position:n}),this.input=new B({element:e.input(t,this._placeholderValue),classNames:s,type:o,preventPaste:!t.paste}),this.choiceList=new H({element:e.choiceList(t,i)}),this.itemList=new H({element:e.itemList(t,i)}),this.dropdown=new K({element:e.dropdown(t),classNames:s,type:o})},e.prototype._createStructure=function(){var e=this,t=e.containerInner,i=e.containerOuter,n=e.passedElement,s=this.dropdown.element;n.conceal(),t.wrap(n.element),i.wrap(t.element),this._isSelectOneElement?this.input.placeholder=this.config.searchPlaceholderValue||"":(this._placeholderValue&&(this.input.placeholder=this._placeholderValue),this.input.setWidth()),i.element.appendChild(t.element),i.element.appendChild(s),t.element.appendChild(this.itemList.element),s.appendChild(this.choiceList.element),this._isSelectOneElement?this.config.searchEnabled&&s.insertBefore(this.input.element,s.firstChild):t.element.appendChild(this.input.element),this._highlightPosition=0,this._isSearching=!1},e.prototype._initStore=function(){var e=this;this._store.subscribe(this._render).withTxn((function(){e._addPredefinedChoices(e._presetChoices,e._isSelectOneElement&&!e._hasNonChoicePlaceholder,!1)})),(!this._store.choices.length||this._isSelectOneElement&&this._hasNonChoicePlaceholder)&&this._render()},e.prototype._addPredefinedChoices=function(e,t,i){var n=this;void 0===t&&(t=!1),void 0===i&&(i=!0),t&&-1===e.findIndex((function(e){return e.selected}))&&e.some((function(e){return!e.disabled&&!("choices"in e)&&(e.selected=!0,!0)})),e.forEach((function(e){"choices"in e?n._isSelectElement&&n._addGroup(e,i):n._addChoice(e,i)}))},e.prototype._findAndSelectChoiceByValue=function(e,t){var i=this;void 0===t&&(t=!1);var n=this._store.choices.find((function(t){return i.config.valueComparer(t.value,e)}));return!(!n||n.disabled||n.selected||(this._addItem(n,!0,t),0))},e.prototype._generatePlaceholderValue=function(){var e=this.config;if(!e.placeholder)return null;if(this._hasNonChoicePlaceholder)return e.placeholderValue;if(this._isSelectElement){var t=this.passedElement.placeholderOption;return t?t.text:null}return null},e.prototype._warnChoicesInitFailed=function(e){if(!this.config.silent){if(!this.initialised)throw new TypeError("".concat(e," called on a non-initialised instance of Choices"));if(!this.initialisedOK)throw new TypeError("".concat(e," called for an element which has multiple instances of Choices initialised on it"))}},e.version="11.1.0",e}()})); diff --git a/webgui/js/shopping.js b/webgui/js/shopping.js new file mode 100644 index 0000000..f0ca150 --- /dev/null +++ b/webgui/js/shopping.js @@ -0,0 +1,167 @@ +let db; + +const request = indexedDB.open("shopping", 1); + +request.onupgradeneeded = function(event) { + db = event.target.result; + const store = db.createObjectStore( + "items", + { keyPath: "provid" } + ); +}; + +request.onsuccess = function(event) { + db = event.target.result; + checkDatabase(); +}; + +function checkDatabase() +{ + let tx = db.transaction( + "items", + "readonly" + ); + let store = tx.objectStore("items"); + let request = store.count(); + request.onsuccess = function(){ + if (request.result > 0) { + renderLists(); + } else { + importList(initialList); + } + }; +} + +function importList(list) +{ + let tx = db.transaction( + "items", + "readwrite" + ); + let store = tx.objectStore("items"); + list.forEach(item => { + item.done = false; + store.put(item); + }); + tx.oncomplete = function(){ + renderLists(); + }; +} + +function renderLists() +{ + let tx = db.transaction( + "items", + "readonly" + ); + let store = tx.objectStore("items"); + let request = store.getAll(); + request.onsuccess = function(){ + let pending = ""; + let completed = ""; + request.result.forEach(item => { + let html = ` +
+ + ${item.provname} + + + + ${item.buy} ${item.unit} + + + +
+ `; + if (item.completed) { + completed += html; + } else { + pending += html; + } + }); + document.getElementById("shoppingList").innerHTML = pending || "No items"; + document.getElementById("completedList").innerHTML = completed || "No completed items"; + }; +} + +function plus(id) +{ + let tx = db.transaction( + "items", + "readwrite" + ); + let store = tx.objectStore("items"); + let req = store.get(id); + req.onsuccess = function(){ + let item = req.result; + item.buy++; + store.put(item); + renderLists(); + }; +} + +function minus(id) +{ + let tx = db.transaction( + "items", + "readwrite" + ); + let store = tx.objectStore("items"); + let req = store.get(id); + req.onsuccess = function(){ + let item = req.result; + item.buy--; + store.put(item); + renderLists(); + }; +} + +function completeItem(id) +{ + let tx = db.transaction( + "items", + "readwrite" + ); + let store = tx.objectStore("items"); + let request = store.get(id); + request.onsuccess = function(){ + let item = request.result; + item.completed = true; + store.put(item); + renderLists(); + }; +} + +function undo(id) +{ + let tx = db.transaction( + "items", + "readwrite" + ); + let store = tx.objectStore("items"); + let req = store.get(id); + req.onsuccess = function(){ + let item = req.result; + item.done = false; + store.put(item); + showList(); + }; +} + +function done(id) +{ + let tx = db.transaction( + "items", + "readwrite" + ); + let store = tx.objectStore("items"); + let req = store.get(id); + req.onsuccess = function(){ + let item = req.result; + item.done = true; + store.put(item); + showList(); + }; +} diff --git a/webgui/locale/de_DE.po b/webgui/locale/de_DE.po index e5084bb..d3e4b40 100644 --- a/webgui/locale/de_DE.po +++ b/webgui/locale/de_DE.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: YMS 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-27 08:02+0200\n" +"POT-Creation-Date: 2026-08-14 08:33+0200\n" "PO-Revision-Date: 2024-03-15 13:50+0100\n" "Last-Translator: Thomas Hooge \n" "Language-Team: LANGUAGE \n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: box.php:19 index.php:353 globals.inc:1536 +#: box.php:19 index.php:357 globals.inc:1536 msgid "Boxes" msgstr "Kisten" @@ -48,7 +48,7 @@ msgid "Content" msgstr "Inhalt" #: box.php:98 box.php:183 box.php:248 cable.php:211 equipment.php:464 -#: index.php:361 index.php:377 inventory.php:381 provisions.php:284 +#: index.php:365 index.php:381 inventory.php:384 provisions.php:284 msgid "Weight" msgstr "Gewicht" @@ -64,7 +64,7 @@ msgid "View" msgstr "Ansehen" #: box.php:115 cable.php:151 company.php:231 documents.php:407 dropdown.php:162 -#: equipment.php:399 equipment_js.php:247 fuse.php:176 index.php:318 +#: equipment.php:399 equipment_js.php:247 fuse.php:176 index.php:322 #: inventory.php:284 maintenance.php:197 maintenance.php:318 #: measurement.php:235 projects.php:219 projects.php:264 projects.php:351 #: provisions.php:219 storage.php:143 storage.php:182 tag.php:136 task.php:256 @@ -77,7 +77,7 @@ msgid "Add Box" msgstr "Kiste hinzufügen" #: box.php:138 box.php:184 box.php:311 inventory.php:208 inventory.php:313 -#: inventory.php:320 inventory.php:374 inventory.php:424 provisions.php:260 +#: inventory.php:320 inventory.php:374 inventory.php:427 provisions.php:260 #: provisions.php:290 provisions.php:344 storage.php:12 storage.php:225 #: globals.inc:564 globals.inc:1532 msgid "Storage" @@ -87,7 +87,7 @@ msgstr "Stauraum" #: company.php:206 company.php:295 company.php:420 documents.php:430 #: documents.php:538 documents.php:778 equipment.php:360 equipment.php:431 #: equipment.php:469 equipment.php:684 equipment_js.php:69 fuse.php:237 -#: index.php:445 index.php:555 inventory.php:385 inventory.php:452 +#: index.php:449 index.php:559 inventory.php:388 inventory.php:455 #: maintenance.php:180 maintenance.php:280 maintenance.php:350 projects.php:176 #: projects.php:200 projects.php:290 projects.php:317 projects.php:403 #: provisions.php:291 provisions.php:347 storage.php:122 storage.php:234 @@ -97,7 +97,7 @@ msgstr "Bemerkungen" #: box.php:156 company.php:255 company.php:427 documents.php:455 #: documents.php:781 equipment.php:435 equipment.php:691 equipment_js.php:116 -#: equipment_js.php:283 index.php:449 inventory.php:324 maintenance.php:238 +#: equipment_js.php:283 index.php:453 inventory.php:324 maintenance.php:238 #: measurement.php:284 note.php:158 projects.php:293 projects.php:406 #: provisions.php:262 settings.php:146 storage.php:208 storage.php:363 #: task.php:286 globals.inc:1112 globals.inc:1119 @@ -107,7 +107,7 @@ msgstr "Speichern" #: box.php:157 company.php:256 company.php:428 company.php:454 #: documents.php:456 documents.php:782 dropdown.php:214 dropdown.php:281 #: equipment.php:436 equipment.php:692 equipment_js.php:117 -#: equipment_js.php:251 equipment_js.php:284 index.php:450 inventory.php:325 +#: equipment_js.php:251 equipment_js.php:284 index.php:454 inventory.php:325 #: maintenance.php:239 measurement.php:285 note.php:159 projects.php:294 #: projects.php:407 provisions.php:263 provisions.php:385 search.php:36 #: settings.php:147 storage.php:209 storage.php:364 task.php:287 @@ -139,12 +139,12 @@ msgstr "Keine verknüpften Bilder" msgid "Inventory in box" msgstr "Inventar in der Kiste" -#: box.php:246 index.php:375 inventory.php:12 search.php:80 globals.inc:1500 +#: box.php:246 index.php:379 inventory.php:12 search.php:80 globals.inc:1500 msgid "Inventory" msgstr "Inventar" #: box.php:247 dropdown.php:150 inventory.php:261 inventory.php:307 -#: inventory.php:380 inventory.php:414 provisions.php:183 provisions.php:248 +#: inventory.php:383 inventory.php:417 provisions.php:183 provisions.php:248 #: provisions.php:282 provisions.php:316 msgid "Number" msgstr "Anzahl" @@ -162,13 +162,13 @@ msgstr "― keine ―" msgid "Edit Box" msgstr "Kiste bearbeiten" -#: box.php:308 equipment.php:668 inventory.php:418 provisions.php:324 +#: box.php:308 equipment.php:668 inventory.php:421 provisions.php:324 msgid "Weight, kg" msgstr "Gewicht, kg" #: box.php:327 cable.php:298 checklist.php:170 company.php:465 #: documents.php:815 dropdown.php:287 equipment.php:718 equipment_js.php:293 -#: fuse.php:290 index.php:566 inventory.php:480 maintenance.php:371 +#: fuse.php:290 index.php:570 inventory.php:483 maintenance.php:371 #: measurement.php:369 note.php:226 projects.php:419 provisions.php:391 #: settings.php:153 storage.php:390 tag.php:294 task.php:415 msgid "Unknown function call: Please report to system development!" @@ -222,8 +222,8 @@ msgstr "Kabel" #: cable.php:127 cable.php:169 cable.php:235 checklist.php:153 company.php:204 #: company.php:251 company.php:274 company.php:355 dropdown.php:185 #: dropdown.php:233 equipment.php:419 equipment.php:651 equipment_js.php:101 -#: equipment_js.php:268 index.php:437 inventory.php:259 inventory.php:303 -#: inventory.php:410 measurement.php:256 measurement.php:303 +#: equipment_js.php:268 index.php:441 inventory.php:259 inventory.php:303 +#: inventory.php:413 measurement.php:256 measurement.php:303 #: measurement.php:333 projects.php:169 projects.php:196 projects.php:245 #: projects.php:283 projects.php:310 projects.php:377 provisions.php:244 #: provisions.php:312 storage.php:120 storage.php:202 storage.php:316 @@ -244,7 +244,7 @@ msgid "Cross section" msgstr "Querschnitt" #: cable.php:132 cable.php:213 cable.php:266 inventory.php:222 -#: inventory.php:262 inventory.php:384 inventory.php:436 +#: inventory.php:262 inventory.php:387 inventory.php:439 msgid "Condition" msgstr "Zustand" @@ -429,7 +429,7 @@ msgstr "Stadt" msgid "Delete Company" msgstr "Firma löschen" -#: company.php:441 documents.php:802 equipment.php:707 inventory.php:470 +#: company.php:441 documents.php:802 equipment.php:707 inventory.php:473 #: note.php:217 storage.php:379 #, php-format msgid "Record no. %d" @@ -455,7 +455,7 @@ msgstr "Neuer GD Imagestream kann nicht initialisiert werden" msgid "Resource not found!" msgstr "Resource nicht gefunden!" -#: documents.php:21 index.php:381 task.php:343 globals.inc:1516 +#: documents.php:21 index.php:385 task.php:343 globals.inc:1516 msgid "Documents" msgstr "Dokumente" @@ -515,7 +515,7 @@ msgstr "SQL-Fehler: %s" msgid "Removed document reference" msgstr "Dokumentreferenz wurde entfernt" -#: documents.php:345 note.php:103 +#: documents.php:345 note.php:103 note.php:178 msgid "Reference" msgstr "Referenz" @@ -537,7 +537,7 @@ msgstr "Dokument" #: documents.php:437 msgid "Clear selection" -msgstr "Auswah leeren" +msgstr "Auswahl leeren" #: documents.php:473 msgid "View Document" @@ -597,14 +597,14 @@ msgid "Add new reference" msgstr "Neue Referenz hinzufügen" #: documents.php:654 equipment.php:13 equipment_js.php:14 fuse.php:236 -#: index.php:359 inventory.php:450 maintenance.php:95 maintenance.php:179 +#: index.php:363 inventory.php:453 maintenance.php:95 maintenance.php:179 #: maintenance.php:225 maintenance.php:274 maintenance.php:348 #: measurement.php:281 measurement.php:312 measurement.php:358 search.php:52 #: globals.inc:532 globals.inc:1496 msgid "Equipment" msgstr "Ausrüstung" -#: documents.php:700 inventory.php:314 inventory.php:321 inventory.php:425 +#: documents.php:700 inventory.php:314 inventory.php:321 inventory.php:428 #: storage.php:155 globals.inc:522 msgid "Box" msgstr "Kiste" @@ -750,7 +750,7 @@ msgstr "" #: equipment.php:357 equipment.php:427 equipment.php:462 equipment.php:660 #: equipment_js.php:67 equipment_js.php:109 equipment_js.php:138 -#: equipment_js.php:276 index.php:441 index.php:505 +#: equipment_js.php:276 index.php:445 index.php:509 msgid "Model" msgstr "Modell" @@ -785,11 +785,11 @@ msgstr "Entfernt" msgid "Add Equipment" msgstr "Ausrüstung hinzufügen" -#: equipment.php:465 inventory.php:382 +#: equipment.php:465 inventory.php:385 msgid "Price" msgstr "Preis" -#: equipment.php:466 equipment.php:676 inventory.php:383 inventory.php:432 +#: equipment.php:466 equipment.php:676 inventory.php:386 inventory.php:435 msgid "Purchase date" msgstr "Kaufdatum" @@ -841,7 +841,7 @@ msgstr "Keine Ersatzteile gefunden." msgid "Edit Equipment" msgstr "Ausrüstung bearbeiten" -#: equipment.php:672 inventory.php:428 +#: equipment.php:672 inventory.php:431 #, php-format msgid "Price, %s" msgstr "Preis, %s" @@ -968,99 +968,99 @@ msgstr "Eigenbau" msgid "Built by %s" msgstr "Gebaut von %s" -#: index.php:303 +#: index.php:307 msgid "Boat data" msgstr "Bootsdaten" -#: index.php:305 +#: index.php:309 msgid "Loa" msgstr "LüA" -#: index.php:306 +#: index.php:310 msgid "Lwl" msgstr "LWL" -#: index.php:307 index.php:517 +#: index.php:311 index.php:521 msgid "Beam" msgstr "Breite" -#: index.php:308 index.php:521 +#: index.php:312 index.php:525 msgid "Draught" msgstr "Tiefgang" -#: index.php:309 index.php:525 +#: index.php:313 index.php:529 msgid "Draught, min." msgstr "Tiefgang, min." -#: index.php:310 index.php:529 +#: index.php:314 index.php:533 msgid "Displacement" msgstr "Verdrängung" -#: index.php:311 index.php:533 +#: index.php:315 index.php:537 msgid "Ballast" msgstr "Ballast" -#: index.php:312 index.php:537 +#: index.php:316 index.php:541 msgid "Belt position aft" msgstr "Gurtposition achtern" -#: index.php:313 index.php:541 +#: index.php:317 index.php:545 msgid "Belt position bow" msgstr "Gurtposition vorne" -#: index.php:329 +#: index.php:333 msgid "Statistics" msgstr "Statistik" -#: index.php:345 +#: index.php:349 msgid "Storage type" msgstr "Stauraumart" -#: index.php:360 index.php:376 index.php:382 +#: index.php:364 index.php:380 index.php:386 msgid "Count" msgstr "Anzahl" -#: index.php:397 +#: index.php:401 msgid "Base data" msgstr "Stammdaten" -#: index.php:409 +#: index.php:413 msgid "Additional modules" msgstr "Zusatzmodule" -#: index.php:432 +#: index.php:436 msgid "Add additional yacht" msgstr "Zusätzliche Yacht hinzufügen" -#: index.php:481 +#: index.php:485 msgid "― Custom-built ―" msgstr "― Eigenbau ―" -#: index.php:497 +#: index.php:501 msgid "Edit Yacht" msgstr "Yacht bearbeiten" -#: index.php:501 +#: index.php:505 msgid "Vesselname" msgstr "Yachtname" -#: index.php:509 +#: index.php:513 msgid "LoA" msgstr "LüA" -#: index.php:513 +#: index.php:517 msgid "LwL" msgstr "LWL" -#: index.php:549 +#: index.php:553 msgid "Shape file" msgstr "Rumpfform-Datei" -#: index.php:550 +#: index.php:554 msgid "Shipyard" msgstr "Werft" -#: index.php:551 +#: index.php:555 msgid "Default Storage" msgstr "Standard-Stauraum" @@ -1085,7 +1085,7 @@ msgstr "Keine Tags zugeordnet" msgid "Add Inventory" msgstr "Inventar hinzufügen" -#: inventory.php:311 inventory.php:423 +#: inventory.php:311 inventory.php:426 msgid "Container type" msgstr "Behälterart" @@ -1094,19 +1094,23 @@ msgstr "Behälterart" msgid "%s in %s" msgstr "" -#: inventory.php:379 +#: inventory.php:378 +msgid "View Box" +msgstr "Box ansehen" + +#: inventory.php:382 msgid "Location ID" msgstr "Position (x, y, z)" -#: inventory.php:406 +#: inventory.php:409 msgid "Edit Inventory" msgstr "Inventar bearbeiten" -#: inventory.php:469 +#: inventory.php:472 msgid "Delete Inventory" msgstr "Inventar löschen" -#: inventory.php:473 +#: inventory.php:476 msgid "" "Deleting an inventory item is final. There is no way back. Only delete if " "you are absolute sure." @@ -1323,10 +1327,6 @@ msgstr "Notiz" msgid "View Note" msgstr "Notiz ansehen" -#: note.php:178 -msgid "Referenz" -msgstr "" - #: note.php:196 msgid "Edit Note" msgstr "Notiz bearbeiten" diff --git a/webgui/locale/de_DE/LC_MESSAGES/yms.mo b/webgui/locale/de_DE/LC_MESSAGES/yms.mo index a2fd9cbe987c75d77f224abfc24d6d9bb40fcce8..8aeffeb22fa7b6ee9129d2f7e183c530f81182ee 100644 GIT binary patch delta 7038 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 delta 6992 zcmXxn36zy(8Nl)H%CHPDGpsW(1I(}vOCX|%A|RW{rUHU7Oe&xraZ3f1aJyI_f*WvI zQUN8yqm?EQR2U_MEh36y5sI3Sh**evMo|;{|L1$+oH@Vm-Jkb)-|ybb&Mld>TQb>q zn&quX__r>TByF%$L6Y=5B}u+hYHgDIvqO?J!1u5n9>s>3-!Uz>z?PIx$6_3Xt#K+g z$J?+OK8R(w4l8jRGH*6{pND!>9789r!8(|9N|O4JhxynTi7hF{MmA$(>>c$3(SBp1 zJPC^^Ux9957CP>Z@LsIX`pNR>uo_#?VSSXhq7(cv+>1>qABy%GETnuAUAUlgl61im zbjO3xjf}@BI0@_Gb7&x2up#RwJECGY)}_23o%mhM!(-?~pP?D~0$uRiXwPR=11k(G z&;|OS{RW}qN1_=Ui)N$>vvqlx&O;NNfexID)-S+%cptJ>vJ4&g8}ul)qYJzm_50By zJctJVA9S8i(M+Dedf0#qV0}>~`FDbrRJgNp9Eg=@q*tNsGs8R31@6K6xHRe?Ml<$A z)Ne$`Z$&rsXEcDl==eiu#*S5zeq8{WqdJ-Vy#44RlYmznkU3)PIa#nxtDgL36ZYdDtuJ2ciLtLdRVaUX5%j`2qI8 z$I(DuM&IOrV0Zis-FZ6-W+L00hx$C6gTBAR(49}gR`~sJA^QG4j+60scp0{2HOsTn zTfQ5oV%P7aF2ZWcN6~;r^i1#21Y{%Gq?(6@G|WgVl3CGV9vZ+RG@zeg0dB#3+=-_A z6*NXvCfu{aeG?0bp zxTWFRsQ)>-vCZKNSV(ynI_^zu;`{%vXsAIW%{w){lm%$V=231N<&J1(dPaE!nu&2y zo`l||%hCB}Vg=qF<<;m>tjF5-e*+J`1;0io+J=6H??5{qi1s6BijScg`W*SHNb-88 zXaH}b3milP z{|F8I6LjLw(ed9zd;QZQqv$7L^RVPJ^6x}tRG6|(=#KlL6AeQb7#;QF!tX`<40OU9 z!&%`Rbe`L>2p6JvWEC3dPtp0G&PKy?=nl7`xBYcAu*2v?AEOg|jSg(kC;dn+LhnLf zG|&st1ty|_RiT-&*arQ*a2nE{P0r`Rh$o{vyA-_xS7LLVAMHOzFWnln z-_Oy7HllapC3K$Gqr4B@`MYTU52OA#nyC}DWzPR34|c5AH=U>nn)+5~X4*%6|8Quu zk47iD7#&{~^;e()T_5Gy=;y$l*b?tWH?me)Klud@rgSSh(QD{gzlBb81Wo1fC}+-0 zC(1`(r*d?G{%FAGp_#i3J))Utpm(7Q&qp)046~+Y6%WO@77KARTE7$h&9@7S@o1Ro zm)`1D$j`x~E82fdI04OI6&l!dbYnL}{TwuqdHpzlBYTjFrnnp(@FW`H26P8o(0)6j z{V(W*d!zhzv>%D`F*Kkr(F`VMrQeDT(9E8JwhuXr{5#ResF;8bs6uxz4b8~4*b3*O z{g-s752zR2d_aXGX|2*om&HJZ2>Vr-&ARHc!MRz(etj5|i zMHjjO&CpErOz#dKLNDzaG-J=A8Tn0=wyZW* zY>JjE(A4)p$Df5QaZI#NL+8034df9zY5z)ep8nx*bm0rp@l&!q_*zUwC%z^+ z%!v*^LIYS7+36yJ??F^2~5FgC>X=utcuZbv^SUPnK04oCSLG}TSVqziUI$DJMJ z(df8KqkQui@^6Rv(QyTu+9%P8eusW?y@n3_5RLpBG|;BQHjIANqQ|gZ4j+rv5m(fyNi4fwV>2yP*M} zjs|)@*1-!f567c{UXta(g{I*tI19`1Ry2^6XeQQRH{6Jw@Bq5A%!TPr8lwFw(E7gU z0)x=NM}_0jd8^Psvr~B}4OF^5KZm4XulF&;07$hThJXYz@B(N zw!y7YzYpEuJ6Ol}{}2y_RD4id!A~Qsp!`)>HZJ{q7=YgP>DUkFqVN3%yaab)XY4RO zy|g2-3FWES5pPD1c15&r#xmAVcJp9_AEFc1n~+|v3iOr^Kr=D{-PvSpi9uCI>>Mujj{(5YTOVIh&MEet29-MFkHpT7HVQ+NUkM8&bG!w_s{uyqZ0W?K- z)&`xpYt;7&&qfy>j^2s!=*A{TIXj&P7q|xbft$=kCww;Citgl3=$XEOPILgB=uou( z2Mz3NbbO=lrU90q<2s-l?2h&ufb`2IV|g%u$=Cy@VI@9*?r1am!SQ_f2Xw+eq5{SK{1c*F9p$UBmGA#eJanbQBJ{P|hxR%|Pp_bmx^} zudpu~=zwr28rVoQ&s zMJ~UrJhL*_t?Y`-L%9daF3SHAwU02*!rc31{W42(ZQHzB_de>@EBVIEz23H0{vsO3G9Z`BZ?`bBG`F99%@yCx+@Jev#pRi0o4U0x&t#V8Ms+C9Jea$t fLpHN+(}x|_mTX$m|3v*wi-vwwylGz5&dmP+nX>$w diff --git a/webgui/manual.php b/webgui/manual.php index 80e7f3f..3df8921 100644 --- a/webgui/manual.php +++ b/webgui/manual.php @@ -75,6 +75,8 @@ Es wird das Einlagerungsdatum erfaßt und das Haltbarkeitsdatum

oder aber frei. Freier Proviant kann später zugeordnet werden. Er ist erkennbar am Einkaufswagen-Symbol .

+

Proviant wird mit Brutto- und Nettogewicht geführt, hat einen Energiegehalt in kCal +und ggf. einen Preis

TODO/Noch nichtimplementiert: Proviant kann auch in eine Kiste gelegt werden

diff --git a/webgui/note.php b/webgui/note.php index 02bd081..8120a0a 100644 --- a/webgui/note.php +++ b/webgui/note.php @@ -175,7 +175,7 @@ $note = $sth->fetch(PDO::FETCH_OBJ); // edit button // list where annotaion used echo '', "\n"; -echo '\n"; +echo '\n"; echo '\n"; echo '\n"; echo "
', _('Referenz'),"", $note->refid, "
', _('Reference'),"", $note->refid, "
', _('Type'),"", $note->notetype, "
', _('Annotation'),"", nl2br($note->annotation), "
\n"; diff --git a/webgui/provisions.php b/webgui/provisions.php index c866aa9..3d96043 100644 --- a/webgui/provisions.php +++ b/webgui/provisions.php @@ -61,8 +61,10 @@ switch ($submit = form_get_action()) { $p[':weight'] = min(gpc_get_float($_POST, 'weight'), 999.99); // limit max value $p[':weight_net'] = min(gpc_get_float($_POST, 'weight_net'), 999.99); $p[':unit'] = gpc_get_int($_POST, 'unit'); + $p[':calories'] = gpc_get_uint($_POST, 'calories'); $p[':storedate'] = gpc_get_string($_POST, 'storedate'); $p[':shelflife'] = gpc_get_string($_POST, 'shelflife'); + $p[':price'] = gpc_get_currency($_POST, 'price'); $p[':sid'] = gpc_get_int($_POST, 'sid'); if ($p[':sid'] <= 0) $p[':sid'] = NULL; $p[':remarks'] = gpc_get_string($_POST, 'remarks', 150); @@ -269,7 +271,7 @@ elseif ($action == ACT_VIEW): // ========== VARIANT: view single record ===================================== $sql = "SELECT provname, provcat, number, target, unit, weight, weight_net," - . " storedate, shelflife, sid, remarks " + . " calories, storedate, shelflife, price, sid, remarks " . "FROM provisions " . "WHERE provid=?"; $sth = $pdo->prepare($sql); @@ -284,9 +286,11 @@ echo "", _('Target'), "", $prov->target, "\n"; echo "", _('Weight'), "", format_float($prov->weight, 2, 'kg'), "\n"; echo "", _('Weight net'), "", format_float($prov->weight_net, 2, 'kg'), "\n"; echo '', _('Unit'),"", $opt_unit[$prov->unit], "\n"; +echo "", _('Calories'), "", $prov->calories, " kcal\n"; echo '', _('Category'),"", $opt_category[$prov->provcat] ?? 'n/a', "\n"; echo '', _('Storedate'),"", $prov->storedate, "\n"; echo '', _('Shelflife'),"", $prov->shelflife, "\n"; +echo '', _('Price'), "", format_currency($prov->price), "\n"; echo '', _('Storage'),"", ($opt_storage[$prov->sid] ?? ''), "\n"; echo '', _('Remarks'),"", nl2br($prov->remarks), "\n"; echo "\n"; @@ -297,7 +301,7 @@ elseif ($action == ACT_EDIT): // ========== VARIANT: edit single record ===================================== $sql = "SELECT provname, provcat, number, target, unit, weight, weight_net," - . " storedate, shelflife, sid, remarks " + . " calories, storedate, shelflife, price, sid, remarks " . "FROM provisions " . "WHERE provid=?"; $sth = $pdo->prepare($sql); @@ -330,6 +334,12 @@ echo '

', _('Edit provision'), "

\n";
unit); +?> +
+ + +
+provcat); ?>
@@ -340,6 +350,10 @@ form_create_select('provcat', _('Category'), $g_opt_none + $opt_category, $prov-
+
+ + +
sid); ?> diff --git a/webgui/shapes/bavaria34.svg b/webgui/shapes/bavaria34.svg index 5644535..3a7db33 100644 --- a/webgui/shapes/bavaria34.svg +++ b/webgui/shapes/bavaria34.svg @@ -16,7 +16,7 @@ style="display:inline">Colin Archer 40Colin Archer 40Thomas Hooge diff --git a/webgui/shapes/elizabethan29.svg b/webgui/shapes/elizabethan29.svg index 6dd6552..0f926af 100644 --- a/webgui/shapes/elizabethan29.svg +++ b/webgui/shapes/elizabethan29.svg @@ -30,7 +30,7 @@ + id="centerline" /> + + +Gulfstar 41Shape templateThomas Hooge diff --git a/webgui/task.php b/webgui/task.php index 439da43..b1e9151 100644 --- a/webgui/task.php +++ b/webgui/task.php @@ -13,7 +13,7 @@ $pagetitle = _('Task'); $id = gpc_get_int($_REQUEST, 'id', 0); -$opt_projects = db_get_opt_proj($user->vid, array(-1 => _('unassigned'))); +$opt_projects = db_get_opt_proj($user->vid, [], array(-1 => _('unassigned'))); $opt_priority = db_load_enum('task', 'priority', true, true); $opt_state = db_load_enum('task', 'taskstate', true, true); @@ -30,6 +30,7 @@ switch ($submit = form_get_action()) { case 'filter': $flt = array(); + $flt['proj'] = gpc_get_int($_POST, 'flt_proj'); $flt['prio'] = gpc_get_enum($_POST, 'flt_prio', array_merge(array_keys($opt_priority), ['-2', '-1'])); $flt['txt'] = gpc_get_string($_POST, 'flt_txt'); $allowed = db_load_enum('task', 'taskstate'); @@ -125,7 +126,7 @@ if ($action == ACT_DEFAULT): page_caption_search($pagetitle); // load filter from db -$flt = db_get_filter($user->id, 206, ['txt','prio', 'stat']); +$flt = db_get_filter($user->id, 206, ['txt', 'prio', 'stat', 'proj']); $w = array('(t.vid=:vid OR t.vid IS NULL)'); $p = array(':vid' => $user->vid); @@ -133,16 +134,18 @@ if (strlen($flt->txt) > 1) { $w[] = '(taskname LIKE :txt)'; $p[':txt'] = '%'.$flt->txt.'%'; } +if ($flt->proj > 0) { + $w[] = 'projid=:projid'; + $p[':projid'] = $flt->proj; +} elseif ($flt->proj == -1) { + $w[] = 'projid IS NULL'; +} if (strlen($flt->prio) > 2 ) { $w[] = 'priority=:priority'; $p[':priority'] = $flt->prio; } elseif ($flt->prio == -1) { $w[] = 'priority IS NULL'; } -/*if (strlen($flt->stat) > 2 ) { - $w[] = 'taskstate=:taskstate'; - $p[':taskstate'] = $flt->stat; -} */ $parts = ["taskstate=''", 'taskstate IS NULL']; foreach ($flt->stat as $f) { $parts[] = "FIND_IN_SET('$f', taskstate)"; @@ -201,9 +204,10 @@ $res = $sth->fetchAll();
Filter
-vid, ['finished']); +filter_create_select('flt_proj', _('Project'), $g_opt_all + $g_opt_none + $opt_proj_filter, $flt->proj); filter_create_select('flt_prio', _('Priority'), $g_opt_all + $g_opt_none + $opt_priority, $flt->prio); -//filter_create_select('flt_stat', _('State'), $g_opt_all + $opt_state, $flt->stat); ?>
diff --git a/yms/ui/main.ui b/yms/ui/main.ui index 6e95486..3531252 100644 --- a/yms/ui/main.ui +++ b/yms/ui/main.ui @@ -163,26 +163,25 @@ False vertical - + True - False - - - False - True - 0 - - - - - True - False - vertical + True + in + + + True + True + + + + + + True True - 1 + 0 @@ -191,11 +190,12 @@ False end - + New True True True + True @@ -204,11 +204,12 @@ - + Delete True True True + True @@ -220,7 +221,7 @@ False True - 3 + 2 @@ -262,7 +263,7 @@ - + True True @@ -272,7 +273,7 @@ - + True True @@ -304,7 +305,7 @@ - + True False @@ -314,7 +315,7 @@ - + True False @@ -335,7 +336,7 @@ - + True True @@ -345,11 +346,12 @@ - + Create True True True + 2 @@ -357,7 +359,7 @@ - + Locked True True @@ -370,7 +372,7 @@ - + Deleted True True @@ -446,11 +448,12 @@ False end - + Save True True True + True @@ -480,7 +483,7 @@ True False - Lists + User 2 @@ -560,7 +563,7 @@ True False - User + Lists 2 @@ -846,7 +849,7 @@ True in - + True True @@ -1831,7 +1834,7 @@ True in - + True True @@ -2040,7 +2043,7 @@ True in - + True True @@ -2270,7 +2273,7 @@ True in - + True True @@ -2500,7 +2503,7 @@ True in - + True True diff --git a/yms/user.py b/yms/user.py index 6433995..e6a9778 100644 --- a/yms/user.py +++ b/yms/user.py @@ -1,13 +1,20 @@ # -*- coding: utf-8 -*- import MySQLdb.cursors +import bcrypt +import random +import string class User(object): + @classmethod + def genpass(cls, length=12): + return ''.join(random.choices(string.ascii_letters + string.digits, k=length)) + def __init__(self, dbconn, user_id=None): self.conn = dbconn self.cur = dbconn.cursor(MySQLdb.cursors.DictCursor) - if user_id: + if user_id is not None: self.user_id = int(user_id) self.load() else: @@ -15,13 +22,13 @@ class User(object): self.displayname = None self.language = 'en' self.role = 'undef' - self.flags = '' + self.flags = [] - def load(self): + def load(self, user_id=0): sql = ( "SELECT login, displayname, language, role, flags " "FROM user " - "WHERE user_id=%s") + "WHERE userid=%s") self.cur.execute(sql, (self.user_id, )) row = self.cur.fetchone() if row: @@ -30,15 +37,32 @@ class User(object): self.displayname = row['displayname'] self.language = row['language'] self.role = row['role'] - self.flags = row['flags'] + self.flags = row['flags'].split(',') if row['flags'] else [] else: self.data = None def save(self): - """ - Speichern eines Benutzers ist momentan nicht vorgesehen - """ - pass + fields = ('login', 'displayname', 'language', 'role') + sqlfield = [] + values = [] + for field in fields: + newdata = getattr(self, field) + if self.data[field] != newdata: + sqlfield.append(f"{field}=%s") + values.append(newdata) + self.data[field] = newdata + # flags set handled separately + newflags = ','.join(self.flags) if self.flags else None + if self.data['flags'] != newflags: + sqlfield.append(f"flags=%s") + values.append(newflags) + self.data['flags'] = newflags + if values: + # only save if changed values exist + sql = "UPDATE user SET " + ",".join(sqlfield) + " WHERE userid=%s" + values.append(self.user_id) + self.cur.execute(sql, values) + self.conn.commit() """ unused at the moment def mark_login(self): @@ -52,6 +76,51 @@ class User(object): self.conn.commit() """ + def create(self): + """ + Creates a new user with the first free id > 0 + The role is the lowest possible one + """ + sql = ( + "SELECT MIN(u1.userid+1) AS newid " + "FROM user AS u1 LEFT JOIN user AS u2 ON u2.userid=u1.userid+1 " + "WHERE u1.userid>=0 AND u2.userid IS NULL") + self.cur.execute(sql) + newid = self.cur.fetchone()['newid'] + self.user_id = newid + # TODO check if login is unused else create other name + # add function for that feature + self.login = f"user{newid}" + self.displayname = f"User #{newid}" + self.language = 'en' + self.role = 'sailor' + self.flags = None + sql = ( + "INSERT INTO user (userid, login, pass, displayname, role, flags, language) " + "VALUES (%s,%s,%s,%s,%s,%s,%s)" + ) + cryptpass = bcrypt.hashpw(self.genpass().encode(), bcrypt.gensalt()) + self.cur.execute(sql, (newid, self.login, cryptpass, self.displayname, self.role, self.flags, self.language)) + # create mandatory settings + sql = "INSERT INTO settings (userid, sno, valint) VALUES (%s, 1, 0)" + self.cur.execute(sql, (newid,)) + self.conn.commit() + return newid + + def delete(self): + sql = "DELETE FROM settings WHERE userid=%s" + self.cur.execute(sql, (self.user_id,)) + sql = "DELETE FROM user WHERE userid=%s" + self.cur.execute(sql, (self.user_id,)) + self.conn.commit() + + def setpass(self, newpass): + sql = "UPDATE user SET pass=%s WHERE userid=%s" + cryptpass = bcrypt.hashpw(newpass.encode(), bcrypt.gensalt()) + print(cryptpass, self.user_id) + self.cur.execute(sql, (cryptpass, self.user_id)) + self.conn.commit() + def __repr__(self): # Unique description out = "User(%d)" % self.user_id diff --git a/ymsgui.py b/ymsgui.py index d54dd54..34bc679 100755 --- a/ymsgui.py +++ b/ymsgui.py @@ -30,7 +30,8 @@ gi.require_version('Rsvg', '2.0') from gi.repository import GLib, Gtk, Rsvg from yms import database -from yms import gtkutils +from yms import gtkutils, combobox, treeview +from yms import user cfg = { 'cfgfile': '~/.config/ymsgui.conf', @@ -69,10 +70,11 @@ class Frontend(): self.vname = "Ghost(0)" # get list of vessels - db.cur.execute("SELECT vid, vesselname, model FROM vessel ORDER BY vid") - for row in db.cur.fetchall(): - print(row) - print(type(self.combo['vessel'])) + vessel = {} + db.dcur.execute("SELECT vid, vesselname, model FROM vessel ORDER BY vid") + for row in db.dcur.fetchall(): + vessel[row['vid']] = row['vesselname'] + " - " + row['model'] + combobox.load_from_dict(self.combo['vessel'], vessel, self.vid) # load shape of vessel if available handle = Rsvg.Handle() @@ -84,17 +86,97 @@ class Frontend(): #viewport.height = 240 #self._svg.render_document(ctx, viewport) + # combos + roles = {'captain': 'Captain', 'helmsman': 'Helmsman', 'sailor': 'Sailor'} + combobox.load_from_dict(self.combo['user_role'], roles) + langs = {'de': 'Deutsch', 'en': 'English'} + combobox.load_from_dict(self.combo['user_lang'], langs) + # prepare lists self.lst_user = Gtk.ListStore(int, str) - db.cur.execute("SELECT userid, login FROM user ORDER BY login") + db.cur.execute("SELECT userid, login FROM user WHERE userid > 0 ORDER BY login") for row in db.cur.fetchall(): self.lst_user.append(row) + treeview.add_column(self.tree['user'], 'ID', 0) + treeview.add_column(self.tree['user'], 'Login', 1) + self.tree['user'].set_model(self.lst_user) + + # current selected objects + self._user = user.User(db.conn, 0) + print(self._user) def run(self): self.label['vessel'].set_text(self.vname) self.window.show_all() Gtk.main() + def user_display(self) + self.entry['user_login'].set_text(self._user.login) + self.entry['user_displayname'].set_text(self._user.displayname) + combobox.activate_row(self.combo['user_role'], self._user.role) + combobox.activate_row(self.combo['user_lang'], self._user.language) + self.check['user_locked'].set_active('locked' in self._user.flags) + self.check['user_deleted'].set_active('deleted' in self._user.flags) + self.entry['user_pass'].set_text('') + + def on_tvw_user_row_activated(self, widget, path, column): + # load selected user + self._user.user_id = treeview.get_value(widget, 0) + self._user.load() + self.user_display() + + def on_but_user_new_clicked(self, widget): + # get first free user id and create a new record with name "user" + self._user.create() + self.lst_user.append((self._user.user_id, self._user.login)) + + def on_but_user_del_clicked(self, widget): + print("Delete user") + self._user.delete() + selection = self.tree['user'].get_selection() + model, tree_iter = selection.get_selected() + model.remove(tree_iter) + # TODO Select some other user from new list selection + self._user.user_id = treeview.get_value(self.tree['user']) + self._user.load() + self.user_display() + + def on_but_user_newpass_clicked(self, widget): + self.entry['user_pass'].set_text(user.User.genpass()) + # self.lst_user. + + def on_but_user_save_clicked(self, widget): + newlogin = self.entry['user_login'].get_text() + # check if changed login exists and throw error in that case + if newlogin != self._user.login: + sql = "SELECT COUNT(login) FROM user WHERE login=%s" + db.cur.execute(sql, (newlogin, )) + if db.cur.fetchone()[0] > 0: + # TODO set error indicator + print("Error: Login already exists!") + return + sql = "UPDATE user SET login=%s WHERE userid=%s" + db.cur.execute(sql, (newlogin, self._user.user_id)) + treeview.set_value(self.tree['user'], 1, newlogin) + self._user.displayname = self.entry['user_displayname'].get_text() + self._user.role = combobox.get_list_value(self.combo['user_role']) + self._user.language = combobox.get_list_value(self.combo['user_lang']) + self._user.flags = [] + if self.check['user_locked'].get_active(): self._user.flags.append('locked') + if self.check['user_deleted'].get_active(): self._user.flags.append('deleted') + self._user.save() + # password field is different. if filled set new password + newpass = self.entry['user_pass'].get_text() + if len(newpass) >= 8: + try: + self._user.setpass(newpass) + finally: + newpass = None + self.entry['user_pass'].set_text('') + else: + # Password to short + pass + def on_but_info_clicked(self, widget): # Userlist sql = ("SELECT userid, login, displayname FROM user ORDER BY login")