// ==UserScript==
// @name B1 Kiosk Mode
// @namespace http://tampermonkey.net/
// @homepage https://github.com/martynas2200/b1-labels
// @version 2.1.4
// @description Simplified kiosk interface for label printing
// @author Martynas Miliauskas
// @match https://www.b1.lt/*
// @match https://site.pro/My-Accounting/*
// @match https://site.pro/lt/My-Accounting/*
// @icon https://b1.lt/favicon.ico
// @connect b1.lt
// @connect raw.githubusercontent.com
// @downloadURL https://raw.githubusercontent.com/martynas2200/b1-labels/main/dist/kiosk.user.js
// @updateURL https://raw.githubusercontent.com/martynas2200/b1-labels/main/dist/kiosk.user.js
// @grant GM.setValue
// @grant GM.getValue
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @license GNU GPLv3
// ==/UserScript==
(function () {
'use strict';
var all = ".virtual-keyboard-body{padding:20px}.virtual-keyboard-input{font-size:18px;text-align:center;margin-bottom:15px}.virtual-keyboard-footer{border-top:1px solid #e5e5e5}.keyboard{display:grid;grid-template-columns:repeat(3, 1fr);gap:8px;max-width:300px;margin:0 auto}.keyboard .key{height:50px;font-size:18px;font-weight:bold;border:1px solid #ddd;background:#f8f9fa;border-radius:4px;cursor:pointer;transition:all .1s ease}.keyboard .key:hover{background:#e9ecef;border-color:#adb5bd}.keyboard .key:active{background:#dee2e6;transform:translateY(1px)}.keyboard .key.btn-grey{background:#6c757d;color:#fff;border-color:#6c757d}.keyboard .key.btn-grey:hover{background:#5a6268;border-color:#5a6268}.keyboard .key.btn-danger{background:#dc3545;color:#fff;border-color:#dc3545}.keyboard .key.btn-danger:hover{background:#c82333;border-color:#c82333}.keyboard .backspace{font-size:16px}@media(max-width: 480px){.keyboard{max-width:280px}.keyboard .key{height:45px;font-size:16px}}body.label-print-interface{background-color:#eee}body.label-print-interface tr.sub-grid-row,body.label-print-interface td.sub-grid,body.label-print-interface [ng-if=\"config.subGrid\"],body.label-print-interface [ng-if=\"!config.hideTopPager\"],body.label-print-interface .sidebar{display:none !important;pointer-events:none}body.label-print-interface .input-group{display:flex}body.label-print-interface .modal h5.header.blue.header-temp-files-list{margin-top:0}body.label-print-interface .modal .btn,body.label-print-interface .modal select,body.label-print-interface .modal input[type=text],body.label-print-interface .modal input[type=number],body.label-print-interface .modal input[type=date]{border-radius:4px !important}body.label-print-interface .navbar{background:#2e8b57}body.label-print-interface .navbar .navbar-brand{padding-left:1em}body.label-print-interface .navbar .navbar-shortcuts{margin-right:10px}body.label-print-interface .navbar a.navbar-shortcut{color:#eee}body.label-print-interface .navbar a.navbar-shortcut i{font-size:1.2em;margin:0 2px;text-align:center}body.label-print-interface .navbar li:has(.navbar-shortcut):hover{background:rgba(0,0,0,.3607843137)}body.label-print-interface .summary{background:#fff;font-size:1.75rem;border-top:#000 2px dashed;padding:10px;margin-top:10px}body.label-print-interface .summary .summary-amount{float:right;font-weight:700}body.label-print-interface .summary .summary-label{font-weight:700;color:#333}body.label-print-interface .summary .summary-buttons{display:flex;gap:10px;margin-top:10px}body.label-print-interface .summary .summary-buttons button{flex:1}.item-search-container *:not(.header):not(svg){border-radius:4px}.item-search-container .load-data{padding:10px}@media screen and (max-width: 992px){.item-search-container .load-data{padding:10px 0 !important}}.item-search-container .load-overlay{background-color:rgba(238,238,238,.7490196078)}.item-search-container .form-section{border:1px solid #ddd;margin-top:10px;background-color:#fcfcfc;padding:0 15px}.item-search-container .item-list{max-height:calc(100vh - 70px);overflow:auto}.item-search-container .item-list .item{transition:.5s;animation:highlight .5s ease-out;background-color:#fff;display:grid;grid-template-columns:minmax(0, 1fr) min-content min-content min-content;column-gap:5px;font-size:1.1em;border:1px solid #ddd;padding:8px;margin-bottom:4px;cursor:pointer;position:relative}.item-search-container .item-list .item .item-price,.item-search-container .item-list .item .item-stock{font-weight:bold;background-color:var(--theme-blue--dark-bg);color:#fff;padding:2px 8px;margin-right:4px;transition:.5s}.item-search-container .item-list .item.inactive span.item-price{filter:blur(1px);background-color:darkred;display:inline-block;animation:vibrate .3s linear infinite both}.item-search-container .item-list .item.inactive{color:darkred}.item-search-container .item-list .item .item-stock{background-color:#777}.item-search-container .item-list .item .item-main{grid-row:1;grid-column:1}.item-search-container .item-list .item .item-labels{grid-column:1;grid-row:2;align-self:center}.item-search-container .item-list .item .btn{grid-row:1/span 2;border:none}.item-search-container .item-list .item .btn-yellow{grid-column:-1}.item-search-container .item-list .item .last-row-btn{display:none}.item-search-container .item-list .item:not(.inactive):last-child .last-row-btn{display:block;grid-row:1/span 2;grid-row:2;display:flex;gap:5px;grid-column:2/span 2}.item-search-container .item-list .item:not(.inactive):last-child{margin-bottom:30px;animation:highlight .5s ease-out,emphasizeItem .5s ease-in-out 5s backwards}.item-search-container .item-list .item.inactive{background-color:#f8d7da}.item-search-container .item-list .item.mark{background-color:#fcf8e3}.item-search-container .item-list .item.green{background-color:#e4efc9}.item-search-container .item-list .item:hover{background-color:#f1f1f1}.item-search-container .item-list .item *:empty:not(i){display:none}.item-search-container .item-list .item:not(.inactive):last-child .item-price{animation:emphasizePrice .5s ease-in-out 5s backwards}.item-search-container .item-labels{font-size:.8em;color:#666}.item-search-container .item-labels span{margin-right:10px}.item-search-container .form-group{position:relative}.item-search-container .ready-for-scan{opacity:0;transition:.5s;position:absolute;right:0%;width:80px;text-align:right;color:var(--theme-orange--base-bg);padding-top:35px;text-transform:lowercase}.item-search-container #barcode:focus:placeholder-shown+.ready-for-scan{opacity:1}.item-search-container .recent-items .recent-tab-content{background:#fff;padding:10px;border-radius:0}.item-search-container .recent-items .item-list .item{padding:8px;column-gap:0px}.item-search-container .recent-items .item-list .item .btn{grid-row:unset}.item-search-container .recent-items .item-list .item .item-price,.item-search-container .recent-items .item-list .item .btn-xs{padding:0px 6px;margin-left:5px}.item-search-container .recent-items .item-list .item-main{text-overflow:clip;overflow:hidden;white-space:nowrap}.item-search-container .recent-items .item-list .item-price{margin-right:0px}.item-search-container .recent-items .item-list .item,.item-search-container .recent-items .item-list .item:last-child{animation:none;margin-bottom:4px;padding:6px}.item-search-container .recent-items .item-list .item:last-child .item-price{animation:none}.container.width-auto>.row{padding:5px 0px;border-bottom:1px solid #eee}.keyboard{display:grid;background-color:#f5f5f5;padding:10px;grid-template-columns:repeat(3, 1fr);gap:10px;width:200px;margin:0px auto;border-radius:2px}.keyboard>*{padding:10px;font-size:18px}.modal .list-group{display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-top:15px}.modal .list-group .label p:empty:nth-of-type(1)::before{content:\" \";display:block;background-image:repeating-linear-gradient(to right, black, black 4px, white 5px, white 10px);position:static;height:15px;width:120px}.modal .list-group .label.barcodeOnly p:empty:nth-of-type(1)::before{height:50px}.modal .list-group .label .barcode.dm{background-image:repeating-linear-gradient(to top, black, black 4px, white 4px, white 8px)}.modal .list-group .label.fridge::after{content:\"\";height:20px;width:100%;background-image:url(\"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKIHdpZHRoPSI3NjguMDAwMDAwcHQiIGhlaWdodD0iMTI4MC4wMDAwMDBwdCIgdmlld0JveD0iMCAwIDc2OC4wMDAwMDAgMTI4MC4wMDAwMDAiCiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsMTI4MC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiMwMDAwMDAiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik01NzgyIDEyNTIzIGMtMTYgLTQzIC00NTYgLTEyMTYgLTk3NyAtMjYwOCAtNTIxIC0xMzkxIC05NTAgLTI1MzMKLTk1NCAtMjUzNyAtMyAtNCAtMTI3IDMwMiAtMjc1IDY4MCAtMTQ4IDM3OCAtMzAyIDc3MCAtMzQyIDg3MiAtMzkgMTAyIC0xODcKNDc4IC0zMjcgODM1IC0xNDAgMzU4IC0yNzYgNzA0IC0zMDIgNzcwIC00NyAxMjAgLTI3NSA3MDEgLTYwMSAxNTMzIC05NiAyNDQKLTE3NCA0NDcgLTE3NCA0NTIgMCAzMCAtMzAgMTQgLTkyIC00NiAtMjMwIC0yMjYgLTM2NiAtNDkzIC00MzUgLTg1OCAtMjYKLTEzNCAtMjUgLTU4NSAwIC03NDQgNDAgLTI0NyA4NyAtNDA5IDIyNyAtNzc3IDU0IC0xNDMgMTQzIC0zODAgMTk4IC01MjUgNTUKLTE0NiAxMzMgLTM1MCAxNzIgLTQ1NSA0MCAtMTA0IDExMCAtMjkxIDE1NyAtNDE1IDQ3IC0xMjQgMTA5IC0yODggMTM4IC0zNjUKMjkgLTc3IDE0OCAtMzkyIDI2NSAtNzAwIDExNiAtMzA4IDI5NCAtNzc4IDM5NSAtMTA0NSAxMDEgLTI2NyAyMjYgLTU5OCAyNzgKLTczNyBsOTUgLTI1MSAtNDAgLTc3IGMtMTU3IC0yOTkgLTMwMSAtNjkwIC0zOTggLTEwNzQgLTY0IC0yNTQgLTk1IC00MTYKLTE4MCAtOTI2IC03NiAtNDUyIC0xMjQgLTU4MiAtMjI3IC02MTYgLTY4IC0yMiAtMTUzIDMgLTI5NyA4OCAtMTYyIDk1IC0yODIKMTIyIC01NTIgMTIzIC0xOTggMCAtMzAxIC0xMyAtNDIxIC01NCAtNDI4IC0xNDYgLTc2NiAtNTUyIC04NzUgLTEwNDkgLTIwCi04OSAtMjIgLTEzMSAtMjIgLTMyMiAwIC0xODggNCAtMjM1IDIyIC0zMjMgMzMgLTE1MSA2NyAtMjU0IDEyOCAtMzc3IDE4NAotMzc5IDQ4NSAtNjQwIDg0OSAtNzM2IDE1OCAtNDIgNDIxIC00OCA1OTIgLTE0IDI3OSA1NiA1MTUgMTg2IDcyOSAzOTkgMTMyCjEzMyAyMTYgMjQ5IDI4OSA0MDAgODQgMTc1IDExNSAzMDEgMTQxIDU2NiAxNyAxODUgMTYgMzYzIC03IDkyNSAtMzIgNzk0IDc0CjEyOTEgMzU2IDE2NjYgOTIgMTI0IDI2MiAyODMgMzk3IDM3MyA2MyA0MiAxMjAgNzYgMTI3IDc2IDYgMCA1MyAtMjUgMTAzIC01NgoyMzQgLTE0MiA0MTQgLTMxNCA1NTUgLTUzMiAxMzggLTIxMSAyMzMgLTQ4OCAyNzggLTgxMiAyMyAtMTYwIDMwIC01OTcgMTYKLTg4MyAtMTcgLTMyNyAzIC01OTQgNjQgLTg1MiAxNTggLTY2OSA1NTggLTEwOTggMTEzMSAtMTIxMSAxMzIgLTI3IDM3NSAtMjQKNTE0IDUgNDcyIDk5IDgyMiAzODIgOTY0IDc3OSAxNDUgNDA4IDE1MiA3OTMgMTkgMTE3NiAtMjM3IDY4NSAtODg4IDEwNDcKLTE1MzggODU0IC0zOCAtMTEgLTE0OSAtNTIgLTI0NSAtOTEgLTE1NSAtNjIgLTE4NCAtNzAgLTI1NSAtNzQgLTcwIC0zIC04NQotMSAtMTIwIDE5IC03NyA0NSAtOTcgOTQgLTE1MSAzNzMgLTk3IDUwMyAtMTMwIDY1NiAtMTc1IDgzNCAtMTA3IDQyMiAtMjM2Cjc5NiAtNDI1IDEyMzggbC02NiAxNTIgNTAgMTQzIGM1MCAxNDEgNTY2IDE2MTkgODQ5IDI0MjggNzkgMjI4IDE4MCA1MTYgMjIzCjY0MCA0MyAxMjQgMjA2IDU5MiAzNjMgMTA0MCAzNTggMTAyNiAzNzIgMTA4MSAzNzIgMTQ4NSAwIDI3NSAtMTkgNDAxIC05Mgo2MTUgLTgwIDIzNCAtMjYwIDUwNyAtNDM0IDY1OSBsLTI3IDIzIC0zMCAtNzl6IG02MjggLTk4NDggYzExMSAtMjggMjU3IC05MgozMzUgLTE0NSAxMjQgLTg1IDIzNiAtMjE5IDI4OCAtMzQ1IDczIC0xNzggMTAxIC0zMzIgOTQgLTUyNCAtMTEgLTI5NyAtMTEzCi01NDEgLTMwMCAtNzE3IC0xNjUgLTE1NSAtMzM0IC0yMTIgLTU5NyAtMjAxIC0xNTAgNiAtMjU4IDM1IC0zOTEgMTAzIC0yODUKMTQ4IC00OTYgNDEyIC01ODEgNzI5IC0xOCA2NSAtMjIgMTA3IC0yMiAyMzAgMSAxMzQgMyAxNTkgMjggMjM5IDM2IDExOSAxMjUKMjk5IDE4MCAzNjYgMTE4IDE0MiAzMDIgMjQ1IDUwMSAyNzkgMzMgNiA3MSAxMyA4NSAxNSA2OSAxMyAyNzggLTQgMzgwIC0yOXoKbS00NTE4IC0zNCBjMTgyIC00NyAyODAgLTEwMyA0MDggLTIzMSA4MSAtODEgMTAzIC0xMDkgMTQzIC0xOTAgMTE3IC0yMzYgMTQyCi00NTUgODEgLTcxMCAtOTAgLTM3MSAtMzg2IC03MDAgLTcxOSAtNzk5IC0yNDIgLTcyIC01NDQgLTI1IC03NTEgMTE3IC02NyA0NQotMTc1IDE1NCAtMjIzIDIyNCAtMTIxIDE3OSAtMTkwIDQ0MCAtMTc4IDY3MyA4IDE0MyAzNyAyNTYgOTcgMzgxIDE0MCAyOTAKNDExIDQ5MiA3MzggNTUwIDEwNyAxOSAzMDQgMTEgNDA0IC0xNXoiLz4KPC9nPgo8L3N2Zz4=\");background-size:81% 100%;background-position:90% 50%;background-repeat:no-repeat;bottom:.6mm;right:3em;opacity:.7;background-blend-mode:color-dodge;width:2em;height:4em;transform:rotate(90deg)}.modal .list-group .label{border:1px solid #ff9b79}.modal .list-group .label-preview:has(.half){position:relative;display:inline-flex}.modal .list-group .label-preview:has(.half)::after{content:\"\";height:100%;width:20px;background-image:url(\"data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKIHdpZHRoPSI3NjguMDAwMDAwcHQiIGhlaWdodD0iMTI4MC4wMDAwMDBwdCIgdmlld0JveD0iMCAwIDc2OC4wMDAwMDAgMTI4MC4wMDAwMDAiCiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsMTI4MC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiMwMDAwMDAiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik01NzgyIDEyNTIzIGMtMTYgLTQzIC00NTYgLTEyMTYgLTk3NyAtMjYwOCAtNTIxIC0xMzkxIC05NTAgLTI1MzMKLTk1NCAtMjUzNyAtMyAtNCAtMTI3IDMwMiAtMjc1IDY4MCAtMTQ4IDM3OCAtMzAyIDc3MCAtMzQyIDg3MiAtMzkgMTAyIC0xODcKNDc4IC0zMjcgODM1IC0xNDAgMzU4IC0yNzYgNzA0IC0zMDIgNzcwIC00NyAxMjAgLTI3NSA3MDEgLTYwMSAxNTMzIC05NiAyNDQKLTE3NCA0NDcgLTE3NCA0NTIgMCAzMCAtMzAgMTQgLTkyIC00NiAtMjMwIC0yMjYgLTM2NiAtNDkzIC00MzUgLTg1OCAtMjYKLTEzNCAtMjUgLTU4NSAwIC03NDQgNDAgLTI0NyA4NyAtNDA5IDIyNyAtNzc3IDU0IC0xNDMgMTQzIC0zODAgMTk4IC01MjUgNTUKLTE0NiAxMzMgLTM1MCAxNzIgLTQ1NSA0MCAtMTA0IDExMCAtMjkxIDE1NyAtNDE1IDQ3IC0xMjQgMTA5IC0yODggMTM4IC0zNjUKMjkgLTc3IDE0OCAtMzkyIDI2NSAtNzAwIDExNiAtMzA4IDI5NCAtNzc4IDM5NSAtMTA0NSAxMDEgLTI2NyAyMjYgLTU5OCAyNzgKLTczNyBsOTUgLTI1MSAtNDAgLTc3IGMtMTU3IC0yOTkgLTMwMSAtNjkwIC0zOTggLTEwNzQgLTY0IC0yNTQgLTk1IC00MTYKLTE4MCAtOTI2IC03NiAtNDUyIC0xMjQgLTU4MiAtMjI3IC02MTYgLTY4IC0yMiAtMTUzIDMgLTI5NyA4OCAtMTYyIDk1IC0yODIKMTIyIC01NTIgMTIzIC0xOTggMCAtMzAxIC0xMyAtNDIxIC01NCAtNDI4IC0xNDYgLTc2NiAtNTUyIC04NzUgLTEwNDkgLTIwCi04OSAtMjIgLTEzMSAtMjIgLTMyMiAwIC0xODggNCAtMjM1IDIyIC0zMjMgMzMgLTE1MSA2NyAtMjU0IDEyOCAtMzc3IDE4NAotMzc5IDQ4NSAtNjQwIDg0OSAtNzM2IDE1OCAtNDIgNDIxIC00OCA1OTIgLTE0IDI3OSA1NiA1MTUgMTg2IDcyOSAzOTkgMTMyCjEzMyAyMTYgMjQ5IDI4OSA0MDAgODQgMTc1IDExNSAzMDEgMTQxIDU2NiAxNyAxODUgMTYgMzYzIC03IDkyNSAtMzIgNzk0IDc0CjEyOTEgMzU2IDE2NjYgOTIgMTI0IDI2MiAyODMgMzk3IDM3MyA2MyA0MiAxMjAgNzYgMTI3IDc2IDYgMCA1MyAtMjUgMTAzIC01NgoyMzQgLTE0MiA0MTQgLTMxNCA1NTUgLTUzMiAxMzggLTIxMSAyMzMgLTQ4OCAyNzggLTgxMiAyMyAtMTYwIDMwIC01OTcgMTYKLTg4MyAtMTcgLTMyNyAzIC01OTQgNjQgLTg1MiAxNTggLTY2OSA1NTggLTEwOTggMTEzMSAtMTIxMSAxMzIgLTI3IDM3NSAtMjQKNTE0IDUgNDcyIDk5IDgyMiAzODIgOTY0IDc3OSAxNDUgNDA4IDE1MiA3OTMgMTkgMTE3NiAtMjM3IDY4NSAtODg4IDEwNDcKLTE1MzggODU0IC0zOCAtMTEgLTE0OSAtNTIgLTI0NSAtOTEgLTE1NSAtNjIgLTE4NCAtNzAgLTI1NSAtNzQgLTcwIC0zIC04NQotMSAtMTIwIDE5IC03NyA0NSAtOTcgOTQgLTE1MSAzNzMgLTk3IDUwMyAtMTMwIDY1NiAtMTc1IDgzNCAtMTA3IDQyMiAtMjM2Cjc5NiAtNDI1IDEyMzggbC02NiAxNTIgNTAgMTQzIGM1MCAxNDEgNTY2IDE2MTkgODQ5IDI0MjggNzkgMjI4IDE4MCA1MTYgMjIzCjY0MCA0MyAxMjQgMjA2IDU5MiAzNjMgMTA0MCAzNTggMTAyNiAzNzIgMTA4MSAzNzIgMTQ4NSAwIDI3NSAtMTkgNDAxIC05Mgo2MTUgLTgwIDIzNCAtMjYwIDUwNyAtNDM0IDY1OSBsLTI3IDIzIC0zMCAtNzl6IG02MjggLTk4NDggYzExMSAtMjggMjU3IC05MgozMzUgLTE0NSAxMjQgLTg1IDIzNiAtMjE5IDI4OCAtMzQ1IDczIC0xNzggMTAxIC0zMzIgOTQgLTUyNCAtMTEgLTI5NyAtMTEzCi01NDEgLTMwMCAtNzE3IC0xNjUgLTE1NSAtMzM0IC0yMTIgLTU5NyAtMjAxIC0xNTAgNiAtMjU4IDM1IC0zOTEgMTAzIC0yODUKMTQ4IC00OTYgNDEyIC01ODEgNzI5IC0xOCA2NSAtMjIgMTA3IC0yMiAyMzAgMSAxMzQgMyAxNTkgMjggMjM5IDM2IDExOSAxMjUKMjk5IDE4MCAzNjYgMTE4IDE0MiAzMDIgMjQ1IDUwMSAyNzkgMzMgNiA3MSAxMyA4NSAxNSA2OSAxMyAyNzggLTQgMzgwIC0yOXoKbS00NTE4IC0zNCBjMTgyIC00NyAyODAgLTEwMyA0MDggLTIzMSA4MSAtODEgMTAzIC0xMDkgMTQzIC0xOTAgMTE3IC0yMzYgMTQyCi00NTUgODEgLTcxMCAtOTAgLTM3MSAtMzg2IC03MDAgLTcxOSAtNzk5IC0yNDIgLTcyIC01NDQgLTI1IC03NTEgMTE3IC02NyA0NQotMTc1IDE1NCAtMjIzIDIyNCAtMTIxIDE3OSAtMTkwIDQ0MCAtMTc4IDY3MyA4IDE0MyAzNyAyNTYgOTcgMzgxIDE0MCAyOTAKNDExIDQ5MiA3MzggNTUwIDEwNyAxOSAzMDQgMTEgNDA0IC0xNXoiLz4KPC9nPgo8L3N2Zz4=\");background-size:100% 25%;background-repeat:no-repeat;background-position:50% 86%;position:absolute;left:calc(100% - 10px);margin-right:-10px;z-index:1;opacity:.7}@keyframes emphasizeItem{from{font-size:1.2em}}@keyframes emphasizePrice{from{margin-left:-10px;padding-left:10px;border-radius:0px 5px 5px 0px;background-color:#000}}@keyframes highlight{from{background-color:#1b6aaa;color:#fff;filter:opacity(0.5)}}@keyframes vibrate{0%{transform:translate(0)}20%{transform:translate(-2px, 2px)}40%{transform:translate(-2px, -2px)}60%{transform:translate(2px, 2px)}80%{transform:translate(2px, -2px)}100%{transform:translate(0)}}";
const MINIMAL_TRANSLATIONS = {
en: {
barcode: 'Barcode',
foundXItems: '{1} items found',
multipleItemsFound: 'Multiple items found',
itemCreated: 'Item created',
itemNotFound: 'Item not found!',
itemUpdated: 'Item updated',
notAllItemsActive: 'Not all items are active; Do you want to proceed?',
oddNumberOfItems: 'Odd number of items; Do you want to proceed?',
success: 'Success',
error: 'Error',
failed: 'Failed',
loading: 'Loading...',
nlabelsToBePrinted: 'labels to be printed',
noData: 'No data to print!',
invalidId: 'Invalid ID',
longTimeAgo: 'a long time ago',
twoMonthsAgo: 'two months ago',
oneMonthAgo: 'a month ago',
threeWeeksAgo: 'three weeks ago',
twoWeeksAgo: 'two weeks ago',
daysAgo: '{1} days ago',
yesterday: 'yesterday',
hoursAgo: '{1} hours ago',
minutesAgo: '{1} minutes ago',
},
lt: {
barcode: 'Barkodas',
foundXItems: 'Rasta {1} prekių',
multipleItemsFound: 'Rasta daugiau negu viena prekė!',
itemCreated: 'Prekė sukurta',
itemNotFound: 'Prekė nerasta!',
itemUpdated: 'Prekė atnaujinta',
notAllItemsActive: 'Ne visos prekės aktyvios, ar norite tęsti?',
oddNumberOfItems: 'Nelyginis prekių skaičius, ar norite tęsti?',
success: 'Sėkmingai atlikta',
error: 'Įvyko klaida',
failed: 'Nepavyko',
loading: 'Kraunama...',
nlabelsToBePrinted: 'etiketės spausdinimui',
noData: 'Nepakanka duomenų spausdinimui!',
invalidId: 'Neteisingas ID',
longTimeAgo: 'labai seniai',
twoMonthsAgo: 'prieš du mėnesius',
oneMonthAgo: 'prieš mėnesį',
threeWeeksAgo: 'prieš tris savaites',
twoWeeksAgo: 'prieš dvi savaites',
daysAgo: 'prieš {1} dienas',
yesterday: 'vakar',
hoursAgo: 'prieš {1} valandas',
minutesAgo: 'prieš {1} minutes',
},
};
const FULL_TRANSLATIONS = {
en: {
...MINIMAL_TRANSLATIONS.en,
normal: 'Normal',
half: 'Half',
add: 'Add',
addManufacturer: 'Add Manufacturer',
addPackageFee: 'Add Package Fee',
addToList: 'Add to List',
ageLimit: 'Age Limit',
ago: 'ago',
barcodeOnly: 'Barcode only',
fridge: 'Fridge',
alcohol: 'Alcohol',
asMentioned: 'As mentioned',
attributeName: 'Attribute Name',
autoLogin: 'Instant Login',
cancel: 'Cancel',
checked: 'Checked',
chooseLabelType: 'Choose label type',
chooseLabelTypeDescription: 'Choose the type of label you want to print',
cleanAll: 'Clean All',
close: 'Close',
code: 'Code',
confirm: 'Confirm',
confirmDeleteDraft: 'Are you sure you want to delete the draft?',
cost: 'Cost',
countryOfOriginName: 'Country of Origin Name',
date: 'Date',
defaultSaleService: 'Default Sale Service',
departmentNumber: 'Department',
description: 'Description',
discount: 'Discount',
discountPointsStatus: 'Discount Points Status',
discountRate: 'Discount Rate',
discountStatus: 'Discount Status',
done: 'Done',
draftBarcode: 'Draft Barcode',
enterBarcode: 'Enter the barcode',
enterName: 'Enter Name',
enterNewPrice: 'Enter new price',
enterQuantityFor: 'Enter quantity for',
expiryDate: 'Expiry Date',
files: 'Files',
fitRaso: 'Fit Raso',
freePrice: 'Free Price',
fullBarcode: 'Full Barcode',
fullName: 'Full Name',
grossWeight: 'Gross Weight',
groupName: 'Group Name',
help: 'If you have any problems, reach out via GitHub',
inactiveItem: 'The item is inactive. Do you want to continue?',
isActive: 'Is Active',
isCommentRequired: 'Is Comment Required',
isQuantitative: 'Is Quantitative',
isRefundable: 'Is Refundable',
itemAdded: 'Item added',
itemCatalog: 'Item Catalog',
itemDetails: 'Item Details',
itemNotActive: 'Item not active',
itemsFound: 'Items found',
kiloPrice: 'Price per kilo',
label: 'Label',
labels: 'Labels',
leftover: 'Leftover',
logout: 'Logout',
manufacturerName: 'Manufacturer Name',
markdowns: 'Markdowns',
maxDiscount: 'Max Discount',
measurementUnitCanBeWeighed: 'Measurement Unit Can Be Weighed',
measurementUnitName: 'Measurement Unit Name',
minPriceWithVat: 'Min Price With VAT',
minQuantity: 'Min Quantity',
missingBarcode: 'Missing barcode',
missingName: 'Missing name',
missingWeight: 'Missing weight',
modifiedAt: 'Last modified',
name: 'Name',
netWeight: 'Net Weight',
newItem: 'New Item',
nlabelsToBePrinted: ' labels to be printed',
no: 'No',
noItems: 'No items',
noItemsScanned: 'No items scanned yet',
noItemsSelected: 'No items selected',
notAllItemsActive: 'Not all selected items are active. Do you want to continue?',
number: 'Number',
oddNumberOfItems: 'Odd number of labels. Do you want to continue?',
packageCode: 'Package',
packageQuantity: 'Package Quantity',
pcs: 'pcs',
price: 'Price',
priceFrom: 'Price From',
priceMinQuantity: 'Price Min Quantity',
priceNotSet: 'Price not set',
pricePerKg: 'Price per kg',
priceUntil: 'Price Until',
priceWithoutVat: 'Price Without VAT',
priceWithVat: 'Price With VAT',
print: 'Print',
printJobIsSent: 'Print job is sent',
quantity: 'Quantity',
readyForScan: 'Ready for scan',
recentlyModified: 'Recently modified items',
recentlySearched: 'Recently Searched',
refresh: 'Refresh',
retrievedAt: 'Retrieved at',
save: 'Save',
sayOutLoud: 'Say out loud',
scannerIsNotConnected: 'Scanner is not connected',
show: 'Show',
showByDate: 'Show by Date',
showMore: 'Show More',
showStock: 'Show Stock',
simplifyForm: 'Simplify Form',
stock: 'Stock',
type: 'Label type',
thisIs: 'This is',
toggleKeyboard: 'Toggle Keyboard',
tooManyItems: 'Too many items',
total: 'Total',
totalPrice: 'Total Price',
validFrom: 'Valid From',
validUntil: 'Valid Until',
vatRate: 'VAT Rate',
weight: 'Weight',
weightedItem: 'Weighted item',
weightedItemAdded: 'Weighted item added',
weightLabel: 'Weight Label',
yes: 'Yes',
zero: 'zero',
draft: 'Draft',
saveDraft: 'Save Draft',
savedDrafts: 'Saved Drafts',
draftSaved: 'Draft saved',
printDraft: 'Print Draft',
},
lt: {
...MINIMAL_TRANSLATIONS.lt,
normal: 'Normali',
half: 'Pusė',
fridge: 'Trumpa (šaldytuvui)',
barcodeOnly: 'Tik brūkšninis kodas',
add: 'Pridėti',
ago: '',
addManufacturer: 'Pridėti gamintoją',
addPackageFee: 'Pridėti fasavimo maišelį',
addToList: 'Pridėti į sąrašą',
ageLimit: 'Amžiaus limitas',
alcohol: 'Alkoholis',
asMentioned: 'Kaip minėjau',
attributeName: 'Atributo pavadinimas',
autoLogin: 'Prisijungti automatiškai',
cancel: 'Atšaukti',
checked: 'Tikrinta prieš',
chooseLabelType: 'Pasirinkite etiketės tipą',
chooseLabelTypeDescription: 'Pasirinkite etiketės tipą, kurį norite spausdinti',
cleanAll: 'Išvalyti',
close: 'Uždaryti',
code: 'Kodas',
confirm: 'Patvirtinti',
confirmDeleteDraft: 'Ar tikrai norite ištrinti juodraštį?',
cost: 'Savikaina',
countryOfOriginName: 'Kilmės šalies pavadinimas',
date: 'Data',
defaultSaleService: 'Numatytoji pardavimo paslauga',
departmentNumber: 'S',
description: 'Aprašymas',
discount: 'Nuolaida',
discountPointsStatus: 'Nuolaidų taškų statusas',
discountRate: 'Nuolaidos dydis',
discountStatus: 'Nuolaidos statusas',
done: 'Atlikta',
draftBarcode: 'Kodas kasai',
enterBarcode: 'Įveskite brūkšninį kodą',
enterName: 'Įveskite prekės pavadinimą',
enterNewPrice: 'Įveskite naują kainą',
enterQuantityFor: 'Įveskite kiekį',
expiryDate: 'Galiojimo data',
files: 'Failai',
fitRaso: 'Tinkamas RASO importui',
freePrice: 'Laisva kaina',
fullBarcode: 'Pilnas brūkšninis kodas',
fullName: 'Pilnas pavadinimas',
grossWeight: 'Bendras svoris',
groupName: 'Grupės pavadinimas',
help: 'Jei kyla problemų, kreiptis pas Martyną',
inactiveItem: 'Prekė yra neaktyvi. Ar norite tęsti?',
isActive: 'Aktyvus',
isCommentRequired: 'Komentaro reikalavimas',
isQuantitative: 'Kiekinė',
isRefundable: 'Grąžinimo galimybė',
itemAdded: 'Prekė pridėta',
itemCatalog: 'Prekių žinynas',
itemDetails: 'Prekės informacija',
itemNotActive: 'Prekė neaktyvi',
kiloPrice: 'Kilogramo kaina',
label: 'Etiketė',
labels: 'Etiketės',
leftover: 'Liko',
logout: 'Atsijungti',
manufacturerName: 'Gamintojo pavadinimas',
markdowns: 'Nukainavimai',
maxDiscount: 'Max nuolaida',
measurementUnitCanBeWeighed: 'Prekė gali būti sveriama',
measurementUnitName: 'Matavimo vieneto pavadinimas',
minPriceWithVat: 'Min kaina su PVM',
minQuantity: 'Min kiekis',
missingBarcode: 'Trūksta brūkšninio kodo',
missingName: 'Trūksta pavadinimo',
missingWeight: 'Trūksta svorio',
modifiedAt: 'Paskutinis keitimas',
name: 'Pavadinimas',
netWeight: 'Neto svoris',
newItem: 'Nauja prekė',
nlabelsToBePrinted: ' etiketės bus spausdinamos',
no: 'Ne',
noItems: 'Nėra prekių',
noItemsScanned: 'Dar nėra nuskaitytų prekių',
noItemsSelected: 'Nėra pasirinktų prekių',
notAllItemsActive: 'Ne visos pasirinktos prekės yra aktyvios. Ar norite tęsti?',
number: 'Numeris',
oddNumberOfItems: 'Nelyginis etikečių skaičius. Ar norite tęsti?',
packageCode: 'Pakuotė',
packageQuantity: 'Pakuotės kiekis',
pcs: 'vnt.',
price: 'Kaina',
priceFrom: 'Kaina nuo',
priceMinQuantity: 'Kaina min kiekis',
priceNotSet: 'Kaina nėra nustatyta',
pricePerKg: 'Kaina už 1 kg',
priceUntil: 'Kaina iki',
priceWithoutVat: 'Kaina be PVM',
priceWithVat: 'Kaina su PVM',
print: 'Spausdinti',
printJobIsSent: 'Spausdinimo užduotis nusiųsta',
quantity: 'Kiekis',
readyForScan: 'Pasiruošęs skenavimui',
recentlyModified: 'Šiandien pakeista',
recentlySearched: 'Neseniai ieškota',
refresh: 'Atnaujinti',
retrievedAt: 'Gauta',
save: 'Išsaugoti',
sayOutLoud: 'Sakyti kainas balsu',
scannerIsNotConnected: 'Skenavimo įrenginys neprijungtas',
show: 'Rodyti',
showByDate: 'Rodyti pagal datą',
showMore: 'Rodyti daugiau',
showStock: 'Rodyti likutį',
simplifyForm: 'Supaprastinti formą',
stock: 'Likutis',
type: 'Etiketės tipas',
thisIs: 'Tai',
toggleKeyboard: 'Klaviatūra',
tooManyItems: 'Per daug prekių',
total: 'Bendra suma',
totalPrice: 'Apskaičiuota kaina',
validFrom: 'Galioja nuo',
validUntil: 'Galioja iki',
vatRate: 'PVM tarifas',
weight: 'Svoris',
weightedItem: 'Sveriama prekė',
weightedItemAdded: 'Sveriama prekė pridėta',
weightLabel: 'Svorio etiketė',
yes: 'Taip',
zero: 'nulis',
draft: 'Juodraštis',
saveDraft: 'Išsaugoti juodraštį',
savedDrafts: 'Išsaugoti juodraščiai',
draftSaved: 'Juodraštis išsaugotas',
printDraft: 'Spausdinti juodraštį',
},
};
const currentLanguage = navigator.language.split('-')[0] === 'lt' ? 'lt' : 'en';
const i18n = (key, values = []) => {
let translation = FULL_TRANSLATIONS[currentLanguage]?.[key] ?? FULL_TRANSLATIONS.en[key] ?? key;
values.forEach((value, index) => {
translation = translation.replace(`{${index + 1}}`, value);
});
return translation;
};
function mainHTML(i18n) { return ` {{ companyName }}
{{ 'total' | i18n }} {{ getTotalPrice() | number:2 }} € {{ 'saveDraft' | i18n }} {{ 'draftBarcode' | i18n }}
{{ draft.title }} {{ draft.date | date:'short' }} {{ getTotalPrice(draft.items) | number:2 }} € {{ (item.totalPrice || item.priceWithVat).toFixed(2) }} {{ item.stock }} {{ item.name }}
{{ label | i18n }}: {{ item[label] }} {{ 'kiloPrice' | i18n }}: {{ item.priceWithVat.toFixed(2) }} {{ 'weightedItem' | i18n }} {{ getPricePerUnit(item) }} {{ getFriendlyTime(item.modifiedAt) }}
{{ item.weight || 1 }} {{ 'printJobIsSent' | i18n }}. {{ 'noItemsScanned' | i18n }}.
{{ 'noItems' | i18n }}
{{ item.name }}
{{ item.priceWithVat.toFixed(2) }} {{ 'help' | i18n }}
`; }
function calculateTotalPrice(priceWithVat, quantity) {
if (priceWithVat == null || quantity == null) {
return 0;
}
const totalPrice = priceWithVat * quantity;
return Math.round((totalPrice + Number.EPSILON) * 100) / 100;
}
function getFriendlyTime(lastChanged) {
if (!lastChanged) {
return null;
}
const now = new Date();
const changedDate = new Date(new Date(lastChanged).getTime() - new Date().getTimezoneOffset() * 60000);
const diffMs = now.getTime() - changedDate.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffMinutes = Math.floor(diffMs / (1000 * 60));
if (diffDays > 60) {
return null;
}
else if (diffDays > 30) {
return i18n('oneMonthAgo');
}
else if (diffDays > 21) {
return i18n('threeWeeksAgo');
}
else if (diffDays > 14) {
return i18n('twoWeeksAgo');
}
else if (diffDays > 1) {
return i18n('daysAgo', [diffDays.toString()]);
}
else if (diffDays === 1) {
return i18n('yesterday');
}
else if (diffHours >= 1) {
return i18n('hoursAgo', [diffHours.toString()]);
}
else {
return i18n('minutesAgo', [diffMinutes.toString()]);
}
}
function isItRecent(lastChanged) {
const now = new Date();
const changedDate = new Date(lastChanged);
const diffMs = now.getTime() - changedDate.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
return diffDays < 1;
}
class ItemDetailsModal {
modalService;
notification;
req;
weightModal;
constructor(modalService, notification, req, weightModal) {
this.modalService = modalService;
this.notification = notification;
this.req = req;
this.weightModal = weightModal;
}
show(item) {
const filteredItem = Object.fromEntries(Object.entries(item).filter(([key, value]) => value !== null && !key.toString().toLowerCase().includes('id') && !key.toString().toLowerCase().includes('account')));
const resultString = Object.entries(filteredItem)
.map(([key, value]) => `
`)
.join('');
const modalTemplate = `
`;
void this.modalService.showModal({
template: modalTemplate,
scopeProperties: {
item,
changePrice: (item) => {
void this.req.quickPriceChange(item);
},
tagModal: (item) => {
void this.weightModal.show(item);
}
}
});
}
}
var printStyles = ".is_a_bug{display:none}@media print{body{margin:0;padding:0;max-width:58.3mm;display:inline-flex;flex-wrap:wrap}}.label{all:revert;position:relative;background:#fff;color:#000;height:31.75mm;width:57.15mm;border:.5px solid #ffdfd4;margin:0px;box-sizing:border-box;overflow:hidden}.item{height:19mm;overflow:hidden;padding:4px;font-family:Arial,sans-serif;font-size:initial;line-height:initial}.barcode{white-space:nowrap;position:absolute;bottom:0;z-index:3}.barcode div{font-size:10px;font-family:monospace;margin-left:6px;line-height:1em}.barcode p{margin:0}.dm{width:6mm;height:6mm;transform:rotate(270deg);padding:3px;fill:#000}.subtext{position:absolute;right:2px;bottom:3px;font-family:serif;font-size:18px;font-weight:500;z-index:10;line-height:1em}.price{position:absolute;bottom:21px;font-size:50px;right:0;overflow:hidden;object-position:center;margin-right:3px;line-height:1em;font-family:\"Book Antiqua\",serif;padding:0px 10px}.price-per-unit{color:#777}.barcodeOnly .item{font-size:.9em;margin-right:15px}.barcodeOnly .barcode{left:50%;transform:translateX(-50%) scale(1.4);width:max-content}.barcodeOnly .barcode>div{margin-left:0;font-size:8px;writing-mode:vertical-lr;position:absolute;right:-1.2em;bottom:1.2em}.barcodeOnly .price,.barcodeOnly .price-per-unit,.barcodeOnly .subtext{display:none}.label.fridge::before{content:\"\";display:block;position:absolute;border-top:.5px dashed #ff9b79;width:100%;bottom:7mm}.label.fridge .item{font-size:15px;height:12mm}.label.fridge .barcode p,.label.fridge .price-per-unit{display:none}.label.fridge .subtext{font-family:\"Book Antiqua\",serif;font-size:1em;bottom:11mm;left:0;right:unset;background:#888;color:#fff;padding:0px 3px;margin-left:6px;border-radius:3px;padding-top:2px}.label.fridge .price,.label.fridge .barcode{bottom:8mm;line-height:.9em}.label.half{width:28.575mm;display:block;border-right:.5px dashed #ff9b79}.label.half+.label.half:nth-of-type(even){border-right-style:solid;border-left-style:none}.label.half .item{font-size:.75em;font-size:12px;text-align:center}.label.half .subtext{padding:0px 3px;border-radius:3px;font-size:.95em}.label.half .price{right:0;left:0;margin:0px 3px;padding:0px;text-align:center;font-size:38px}.label.half .barcode.dm{transform:none;bottom:1}.label.half .barcode p{display:none}.label.weight{display:grid;grid-template-columns:min-content 1fr;align-items:center;justify-content:space-between;padding:5px;box-sizing:border-box;writing-mode:vertical-rl;grid-column-gap:2px;grid-row-gap:0px;font-family:\"Arial\"}.label.weight .barcode{grid-column:1;position:static;width:8mm;height:8mm;transform:unset}.label.weight .item{grid-column:span 2;text-align:left;font-size:11pt;font-size:10pt;max-width:60pt;padding:0px;height:unset}.label.weight .price{all:revert;grid-row:2;grid-column:span 2;text-align:center;justify-self:center;align-self:end;font-size:16pt;font-weight:bold;line-height:1em;font-family:\"Book Antiqua\",serif;margin-right:4px}.label.weight .price::after{content:\" €\"}.label.weight .kg-price{grid-column:2;justify-self:center;align-self:end}.label.weight .kg-text{grid-column:2}.label.weight .kg-text,.label.weight .weight-text{justify-self:center;align-self:baseline;color:#999;font-size:.8em;line-height:.6}.label.weight .weight-text{grid-column:1}.label.weight .weight{grid-column:1;justify-self:center;align-self:end}.label.weight .expiry{grid-column:2;text-align:center;justify-self:center;align-self:center;line-height:.9;font-size:.8em;color:#000}.label.weight .expiry::before{content:\"Geriausia iki \";color:#444;display:inline;font-family:Arial}.label.weight.half{display:grid;writing-mode:unset}.label.weight.half .price{font-size:18px}.label.weight.half *:not(.price){font-size:9pt}.label.weight.half .item{max-width:unset;max-height:11mm}.label.weight.half .dm{grid-row:3/span 3}.label.weight.half .weight{grid-column:2}.label.weight.half .expiry::before{content:\" \";display:inline-block;position:static;background-repeat:no-repeat;background-image:url(\"data:image/svg+xml,%3Csvg fill='%23000000' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='15px' height='15px' viewBox='0 0 612 612' xml:space='preserve'%3E%3Cg%3E%3Cg%3E%3Cpath d='M612,463.781c0-70.342-49.018-129.199-114.75-144.379c-10.763-2.482-21.951-3.84-33.469-3.84 c-3.218,0-6.397,0.139-9.562,0.34c-71.829,4.58-129.725,60.291-137.69,131.145c-0.617,5.494-0.966,11.073-0.966,16.734 c0,10.662,1.152,21.052,3.289,31.078C333.139,561.792,392.584,612,463.781,612C545.641,612,612,545.641,612,463.781z M463.781,561.797c-54.133,0-98.016-43.883-98.016-98.016s43.883-98.016,98.016-98.016s98.016,43.883,98.016,98.016 S517.914,561.797,463.781,561.797z'/%3E%3Cpolygon points='482.906,396.844 449.438,396.844 449.438,449.438 396.844,449.438 396.844,482.906 482.906,482.906 482.906,449.438 482.906,449.438 '/%3E%3Cpath d='M109.969,0c-9.228,0-16.734,7.507-16.734,16.734v38.25v40.641c0,9.228,7.506,16.734,16.734,16.734h14.344 c9.228,0,16.734-7.507,16.734-16.734V54.984v-38.25C141.047,7.507,133.541,0,124.312,0H109.969z'/%3E%3Cpath d='M372.938,0c-9.228,0-16.734,7.507-16.734,16.734v38.25v40.641c0,9.228,7.507,16.734,16.734,16.734h14.344 c9.228,0,16.734-7.507,16.734-16.734V54.984v-38.25C404.016,7.507,396.509,0,387.281,0H372.938z'/%3E%3Cpath d='M38.25,494.859h236.672c-2.333-11.6-3.572-23.586-3.572-35.859c0-4.021,0.177-7.999,0.435-11.953H71.719 c-15.845,0-28.688-12.843-28.688-28.688v-229.5h411.188v88.707c3.165-0.163,6.354-0.253,9.562-0.253 c11.437,0,22.61,1.109,33.469,3.141V93.234c0-21.124-17.126-38.25-38.25-38.25h-31.078v40.641c0,22.41-18.23,40.641-40.641,40.641 h-14.344c-22.41,0-40.641-18.231-40.641-40.641V54.984H164.953v40.641c0,22.41-18.231,40.641-40.641,40.641h-14.344 c-22.41,0-40.641-18.231-40.641-40.641V54.984H38.25C17.126,54.984,0,72.111,0,93.234v363.375 C0,477.733,17.126,494.859,38.25,494.859z'/%3E%3Ccircle cx='134.774' cy='260.578' r='37.954'/%3E%3Ccircle cx='248.625' cy='260.578' r='37.954'/%3E%3Ccircle cx='362.477' cy='260.578' r='37.954'/%3E%3Ccircle cx='248.625' cy='375.328' r='37.953'/%3E%3Ccircle cx='134.774' cy='375.328' r='37.953'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E\");height:15px;width:15px}.label.weight.half .expiry{display:flex;align-items:center}.label.weight.half .manufacturer,.label.weight.half .weight-text,.label.weight.half .kg-text,.label.weight.half .package{display:none}.label.weight .manufacturer,.label.weight .description{font-size:.7em;grid-column:span 2}.label.weight .package{font-size:.7em;grid-column:span 2}.label.weight .package{grid-row:3;grid-column:span 2;text-align:center;justify-self:center;align-self:center;margin-right:-5px;margin-left:5px;color:#444;font-size:10px;line-height:.8em}del{color:gray;position:relative;text-decoration:none;position:absolute;bottom:0;right:5px;font-size:1.4em;line-height:1em}del:after{content:\"\";display:block;position:absolute;width:110%;height:2px;border-radius:1px;background:darkred;top:9px;left:-5%;transform:skewY(-14deg)}";
const getDataMatrixMat = (text, rect = false) => {
var enc = [], cw = 0, ce = 0;
function push(val) {
cw = 40 * cw + val;
if (ce++ == 2) {
enc.push(++cw >> 8);
enc.push(cw & 255);
ce = cw = 0;
}
}
var cost = [
function (c) { return ((c - 48) & 255) < 10 ? 6 : c < 128 ? 12 : 24; },
function (c) { return ((c - 48) & 255) < 10 || ((c - 65) & 255) < 26 || c == 32 ? 8 : c < 128 ? 16 : 16 + cost[1](c & 127); },
function (c) { return ((c - 48) & 255) < 10 || ((c - 97) & 255) < 26 || c == 32 ? 8 : c < 128 ? 16 : 16 + cost[2](c & 127); },
function (c) { return ((c - 48) & 255) < 10 || ((c - 65) & 255) < 26 || c == 32 || c == 13 || c == 62 || c == 42 ? 8 : 1e9; },
function (c) { return c >= 32 && c < 95 ? 9 : 1e9; },
function (c) { return 12; }
];
var latch = [0, 24, 24, 24, 21, 25];
var count = [0, 12, 12, 12, 12, 25];
var c, i, p, cm = 0, nm = 0;
var bytes = [];
bytes[text.length] = count.slice();
for (p = text.length; p-- > 0;) {
for (c = 1e9, i = 0; i < count.length; i++) {
count[i] += cost[i](text.charCodeAt(p));
c = Math.min(c, Math.ceil(count[i] / 12) * 12);
}
if (cost[0](text.charCodeAt(p)) > 6)
count[0] = Math.ceil(count[0] / 12) * 12;
for (i = 0; i < count.length; i++)
if (c + latch[i] < count[i])
count[i] = c + latch[i];
bytes[p] = count.slice();
}
for (p = 0;; cm = nm) {
c = bytes[p][cm] - latch[cm];
if (p + [0, 2, 2, 2, 3, 0][cm] >= text.length)
nm = 0;
else
for (i = cost.length; i-- > 0;)
if (Math.ceil((bytes[p + 1][i] + cost[i](text.charCodeAt(p))) / 12) * 12 == c)
nm = i;
if (cm != nm && cm > 0)
if (cm < 4)
enc.push(254);
else if (cm == 4)
enc.push(31 | cw & 255);
else {
if (ce > 249)
enc.push((ce / 250 + 250 + (149 * (enc.length + 1)) % 255) & 255);
enc.push((ce % 250 + (149 * (enc.length + 1)) % 255 + 1) & 255);
for (; ce > 0; ce--)
enc.push((text.charCodeAt(p - ce) + (149 * (enc.length + 1)) % 255 + 1) & 255);
}
if (p >= text.length)
break;
if (cm != nm)
cw = ce = 0;
if (cm != nm && nm > 0)
enc.push([230, 239, 238, 240, 231][nm - 1]);
if (nm == 0) {
c = text.charCodeAt(p++);
i = (c - 48) & 255;
if (i < 10 && p < text.length && ((text.charCodeAt(p) - 48) & 255) < 10)
enc.push(i * 10 + text.charCodeAt(p++) - 48 + 130);
else {
if (c > 127)
enc.push(235);
enc.push((c & 127) + 1);
}
if (cm == 4 || ce < 0)
ce--;
}
else if (nm < 4) {
var set = [[31, 0, 32, 119, 47, 133, 57, 179, 64, 173, 90, 207, 95, 277, 127, 386, 255, 1],
[31, 0, 32, 119, 47, 133, 57, 179, 64, 173, 90, 258, 95, 277, 122, 335, 127, 386, 255, 1],
[13, 55, 32, 119, 42, 167, 57, 179, 62, 243, 90, 207, 255, 3]][nm - 1];
do {
c = text.charCodeAt(p++);
if (c > 127) {
push(1);
push(30);
c &= 127;
}
for (i = 0; c > set[i]; i += 2)
;
if ((set[i + 1] & 3) < 3)
push(set[i + 1] & 3);
push(c - (set[i + 1] >> 2));
} while (ce > 0);
}
else if (nm == 4) {
if (ce > 0)
enc.push(255 & cw + (text.charCodeAt(p++) & 63));
for (cw = ce = 0; ce < 3; ce++)
cw = 64 * (cw + (text.charCodeAt(p++) & 63));
enc.push(cw >> 16);
enc.push((cw >> 8) & 255);
}
else {
p++;
ce++;
}
}
var el = enc.length;
var h, w, nc = 1, nr = 1, fw, fh;
var j = -1, l, r, s, b = 1, k;
if (ce == -1 || (cm && cm < 5))
nm = 1;
if (rect && el - nm < 50) {
k = [16, 7, 28, 11, 24, 14, 32, 18, 32, 24, 44, 28];
do {
w = k[++j];
h = 6 + (j & 12);
l = w * h / 8;
} while (l - k[++j] < el - nm);
if (w > 25)
nc = 2;
}
else {
w = h = 6;
i = 2;
k = [5, 7, 10, 12, 14, 18, 20, 24, 28, 36, 42, 48, 56, 68, 84,
112, 144, 192, 224, 272, 336, 408, 496, 620];
do {
if (++j == k.length)
return [];
if (w > 11 * i)
i = 4 + i & 12;
w = h += i;
l = (w * h) >> 3;
} while (l - k[j] < el - nm);
if (w > 27)
nr = nc = 2 * Math.floor(w / 54) + 2;
if (l > 255)
b = 2 * (l >> 9) + 2;
}
s = k[j];
if (l - s + 1 == el && nm > 0) {
el--;
if (ce == -1)
enc[el - 1] ^= 31 ^ (enc[el] - 1) & 63;
}
fw = w / nc;
fh = h / nr;
if (el < l - s)
enc[el++] = 129;
while (el < l - s)
enc[el++] = (((149 * el) % 253) + 130) % 254;
s /= b;
var rs = new Array(70), rc = new Array(70);
var lg = new Array(256), ex = new Array(255);
for (j = 1, i = 0; i < 255; i++) {
ex[i] = j;
lg[j] = i;
j += j;
if (j > 255)
j ^= 301;
}
for (rs[s] = 0, i = 1; i <= s; i++)
for (j = s - i, rs[j] = 1; j < s; j++)
rs[j] = rs[j + 1] ^ ex[(lg[rs[j]] + i) % 255];
for (c = 0; c < b; c++) {
for (i = 0; i <= s; i++)
rc[i] = 0;
for (i = c; i < el; i += b)
for (j = 0, k = rc[0] ^ enc[i]; j < s; j++)
rc[j] = rc[j + 1] ^ (k ? ex[(lg[rs[j]] + lg[k]) % 255] : 0);
for (i = 0; i < s; i++)
enc[el + c + i * b] = rc[i];
}
var mat = Array(h + 2 * nr).fill(null).map(function () { return []; });
for (i = 0; i < w + 2 * nc; i += fw + 2)
for (j = 0; j < h; j++) {
mat[j + (j / fh | 0) * 2 + 1][i] = 1;
if ((j & 1) == 1)
mat[j + (j / fh | 0) * 2][i + fw + 1] = 1;
}
for (i = 0; i < h + 2 * nr; i += fh + 2)
for (j = 0; j < w + 2 * nc; j++) {
mat[i + fh + 1][j] = 1;
if ((j & 1) == 0)
mat[i][j] = 1;
}
s = 2;
c = 0;
r = 4;
for (i = 0; i < l; r -= s, c += s) {
if (r == h - 3 && c == -1)
k = [w, 6 - h, w, 5 - h, w, 4 - h, w, 3 - h, w - 1, 3 - h, 3, 2, 2, 2, 1, 2];
else if (r == h + 1 && c == 1 && (w & 7) == 0 && (h & 7) == 6)
k = [w - 2, -h, w - 3, -h, w - 4, -h, w - 2, -1 - h, w - 3, -1 - h, w - 4, -1 - h, w - 2, -2, -1, -2];
else {
if (r == 0 && c == w - 2 && (w & 3))
continue;
if (r < 0 || c >= w || r >= h || c < 0) {
s = -s;
r += 2 + s / 2;
c += 2 - s / 2;
while (r < 0 || c >= w || r >= h || c < 0) {
r -= s;
c += s;
}
}
if (r == h - 2 && c == 0 && (w & 3))
k = [w - 1, 3 - h, w - 1, 2 - h, w - 2, 2 - h, w - 3, 2 - h, w - 4, 2 - h, 0, 1, 0, 0, 0, -1];
else if (r == h - 2 && c == 0 && (w & 7) == 4)
k = [w - 1, 5 - h, w - 1, 4 - h, w - 1, 3 - h, w - 1, 2 - h, w - 2, 2 - h, 0, 1, 0, 0, 0, -1];
else if (r == 1 && c == w - 1 && (w & 7) == 0 && (h & 7) == 6)
continue;
else
k = [0, 0, -1, 0, -2, 0, 0, -1, -1, -1, -2, -1, -1, -2, -2, -2];
}
for (el = enc[i++], j = 0; el > 0; j += 2, el >>= 1) {
if (el & 1) {
var x = c + k[j], y = r + k[j + 1];
if (x < 0) {
x += w;
y += 4 - ((w + 4) & 7);
}
if (y < 0) {
y += h;
x += 4 - ((h + 4) & 7);
}
mat[y + 2 * (y / fh | 0) + 1][x + 2 * (x / fw | 0) + 1] = 1;
}
}
}
for (i = w; i & 3; i--)
mat[i][i] = 1;
return mat;
};
const toPath = (mat) => {
var path = "", x, y;
mat.forEach(function (y) { y.unshift(0); });
mat.push([]);
mat.unshift([]);
for (;;) {
for (y = 0; y + 2 < mat.length; y++)
if ((x = mat[y + 1].indexOf(1) - 1) >= 0 || (x = mat[y + 1].indexOf(5) - 1) >= 0)
break;
if (y + 2 == mat.length || path.length > 1e7)
return path;
var c = mat[y + 1][x + 1] >> 2, p = "";
for (var x0 = x, y0 = y, d = 1; p.length < 1e6;) {
do
x += 2 * d - 1;
while ((mat[y][x + d] ^ mat[y + 1][x + d]) & mat[y + d][x + d] & 1);
d ^= mat[y + d][x + d] & 1;
do
mat[d ? ++y : y--][x + 1] ^= 2;
while ((mat[y + d][x] ^ mat[y + d][x + 1]) & mat[y + d][x + 1 - d] & 1);
if (x == x0 && y == y0)
break;
d ^= 1 ^ mat[y + d][x + 1 - d] & 1;
if (c)
p = "V" + y + "H" + x + p;
else
p += "H" + x + "V" + y;
}
path += "M" + x + " " + y + p + (c ? "V" + y : "H" + x) + "Z";
for (d = 0, y = 1; y < mat.length - 1; y++)
for (x = 1; x < mat[y].length; x++) {
d ^= (mat[y][x] >> 1) & 1;
mat[y][x] = 5 * d ^ mat[y][x] & 5;
}
}
};
class Code128 {
text;
constructor(text) {
this.text = text;
}
encode() {
var t = 3, enc = [], i, j, c, mat = [];
for (i = 0; i < this.text.length; i++) {
c = this.text.charCodeAt(i);
if (t != 2) {
for (j = 0; j + i < this.text.length; j++)
if (this.text.charCodeAt(i + j) - 48 >>> 0 > 9)
break;
if ((j > 1 && i == 0) || (j > 3 && (i + j < this.text.length || (j & 1) == 0))) {
enc.push(i == 0 ? 105 : 99);
t = 2;
}
}
if (t == 2)
if (c - 48 >>> 0 < 10 && i + 1 < this.text.length && this.text.charCodeAt(i + 1) - 48 >>> 0 < 10)
enc.push(+this.text.substr(i++, 2));
else
t = 3;
if (t != 2) {
if (t > 2 || ((c & 127) < 32 && t) || ((c & 127) > 95 && !t)) {
for (j = t > 2 ? i : i + 1; j < this.text.length; j++)
if ((this.text.charCodeAt(j) - 32) & 64)
break;
j = j == this.text.length || (this.text.charCodeAt(j) & 96) ? 1 : 0;
enc.push(i == 0 ? 103 + j : j != t ? 101 - j : 98);
t = j;
}
if (c > 127)
enc.push(101 - t);
enc.push(((c & 127) + 64) % 96);
}
}
if (i == 0)
enc.push(103);
j = enc[0];
for (i = 1; i < enc.length; i++)
j += i * enc[i];
enc.push(j % 103);
enc.push(106);
c = [358, 310, 307, 76, 70, 38, 100, 98, 50, 292, 290, 274, 206, 110, 103, 230, 118, 115, 313, 302, 295, 370, 314, 439, 422, 406, 403,
434, 410, 409, 364, 355, 283, 140, 44, 35, 196, 52, 49, 324, 276, 273, 220, 199, 55, 236, 227, 59, 443, 327, 279, 372, 369, 375,
428, 419, 395, 436, 433, 397, 445, 289, 453, 152, 134, 88, 67, 22, 19, 200, 194, 104, 97, 26, 25, 265, 296, 477, 266, 61, 158, 94,
79, 242, 122, 121, 466, 458, 457, 367, 379, 475, 188, 143, 47, 244, 241, 468, 465, 239, 247, 431, 471, 322, 328, 334, 285];
for (t = i = 0; i < enc.length; i++, t++) {
mat[t++] = 1;
for (j = 256; j > 0; j >>= 1, t++)
if (c[enc[i]] & j)
mat[t] = 1;
}
mat[t++] = mat[t] = 1;
return [mat];
}
toHtml(mat, size = 3, blocks = 5) {
if (!Array.isArray(size))
size = [size || 3, size || 3];
let s = "barcode" + size[0] + size[1], b, ss;
let html = "";
for (i = 0; i < mat.length; i++)
for (j = 0; j < mat[i].length;) {
if (i && !j)
html += "
";
for (b = 0; j < mat[i].length; b++, j++)
if (!mat[i][j] || b + 1 == blocks)
break;
for (ss = 0; j < mat[i].length; ss++, j++)
if (mat[i][j] || ss + 1 == blocks)
break;
html += "
";
}
return html + "
";
}
}
class LabelGenerator {
items = [];
success = false;
type;
constructor(data = undefined, type = 'normal') {
this.type = type;
if (data == null) ;
else if (data instanceof Promise) {
void data.then((data) => {
this.items = data;
this.print();
});
}
else {
this.items = data;
this.print();
}
}
print() {
this.items = this.items.filter((item) => item.barcode != null);
if (!this.isAllItemsActive()) {
if (!confirm(i18n('notAllItemsActive'))) {
return;
}
}
if (this.type == 'half' && this.items.length % 2 != 0) {
if (!confirm(i18n('oddNumberOfItems'))) {
return;
}
}
if (this.items.length > 0) {
void this.printLabelsUsingBrowser(this.items);
}
else {
alert(i18n('noData'));
}
}
isAllItemsActive() {
return this.items.every((item) => item.isActive);
}
makeUpperCaseBold(text) {
const regex = /("[^"]+"|[A-ZŽĄČĘĖĮŠŲŪ]{3,})/g;
return text.replace(regex, '$1 ');
}
static getPricePerUnit(item) {
const regex = /(?:,?\s*)?(?:(\d+)\s*x\s*)?(\d+(\.\d+)?(?:,\d+)?)[\s]*(k?g|m?l|vnt|pak|rul)\b/i;
const match = item.name.match(regex);
if (match) {
const match2 = item.name.replace(match[0], '').match(regex);
if (match2) {
return null;
}
const multiplier = match[1] ? parseInt(match[1]) : 1;
const amount = parseFloat(match[2].replace(',', '.')) * multiplier;
const unit = match[4].replace('.', '').toLowerCase();
let pricePerUnit;
if (unit === 'g' || unit === 'ml') {
pricePerUnit =
(item.priceWithVat / (amount / 1000)).toFixed(2) +
(unit === 'ml' ? ' €/l' : ' €/kg');
}
else {
pricePerUnit = (item.priceWithVat / amount).toFixed(2) + ' €/' + unit;
}
if (parseFloat(pricePerUnit) === item.priceWithVat) {
return null;
}
return pricePerUnit;
}
return null;
}
generateLabel(data, type = this.type) {
const label = document.createElement('div');
label.className = 'label';
if (data.weight != null) {
return this.generateWeightLabel(data, type === 'half');
}
else if (type !== 'normal') {
label.classList.add(type);
}
label.appendChild(this.createDivWithClass('item', this.makeUpperCaseBold(data.name), true));
if (data.barcode != null && type !== 'half') {
label.appendChild(this.createCode123Div(data));
}
else if (data.barcode != null) {
label.appendChild(this.createDMDiv(data.barcode));
}
label.appendChild(this.createDivWithClass('price', this.getItemPrice(data)));
if (data.packageCode != null) {
const packagePrice = 0.1 * data.packageQuantity;
label.appendChild(this.createDivWithClass('subtext', 'Tara +' + packagePrice.toFixed(2)));
}
else if (data.measurementUnitCanBeWeighed == true) {
label.appendChild(this.createDivWithClass('subtext', '/ 1 ' + data.measurementUnitName));
}
return label;
}
getItemPrice(data) {
if (data.priceWithVat == null || data.priceWithVat === 0) {
return '';
}
if (typeof data.priceWithVat === 'number') {
return data.priceWithVat.toFixed(2);
}
else {
return parseFloat(data.priceWithVat).toFixed(2).toString();
}
}
createCode123Div(data) {
const barcode = document.createElement('div');
barcode.className = 'barcode';
barcode.appendChild(this.createDivWithClass('barcode-text', data.barcode));
const code128 = new Code128(data.barcode);
const p = document.createElement('p');
p.innerHTML = code128.toHtml(code128.encode(), this.type == 'barcodeOnly' ? [1, 50] : [1, 15]);
barcode.appendChild(p);
return barcode;
}
generateWeightLabel(data, half = false) {
const label = document.createElement('div');
if (data.weight == null ||
data.totalPrice == null ||
data.priceWithVat == null ||
data.barcode == null ||
data.barcode.length > 13) {
return label;
}
label.className = 'label weight' + (half ? ' half' : '');
const elements = [
{ className: 'item', text: data.name },
{
className: 'price',
text: half && data.addPackageFee
? (data.totalPrice + 0.01).toFixed(2)
: data.totalPrice.toFixed(2),
},
{
className: 'weight',
text: (data.measurementUnitCanBeWeighed
? Number(data.weight).toFixed(3)
: data.weight.toString()) +
(half ? ' ' + data.measurementUnitName : ''),
},
{
className: 'kg-price',
text: data.priceWithVat.toFixed(2) + (half ? ' €/kg' : ''),
},
{ className: 'weight-text', text: data.measurementUnitName },
{ className: 'kg-text', text: '€/' + data.measurementUnitName },
];
if (data.addPackageFee && !half) {
elements.push({ className: 'package', text: '+ 0,01 (fas. maišelis)' });
}
elements.forEach(({ className, text }) => {
label.appendChild(this.createDivWithClass(className, text));
});
const barcodeString = (data.addPackageFee ? '1102\t1\n' : '') +
this.createPackedItemBarcode(data) +
'\t1\n\r';
label.appendChild(this.createDMDiv(barcodeString));
if (data.expiryDate != null) {
const date = new Date(data.expiryDate).toLocaleDateString('lt-LT', {
month: '2-digit',
day: '2-digit',
});
label.appendChild(this.createDivWithClass('expiry', date));
}
if (data.addManufacturer == true && data.manufacturerName != null) {
label.appendChild(this.createDivWithClass('manufacturer', data.manufacturerName));
}
return label;
}
createDivWithClass(className, text, raw = false) {
const div = document.createElement('div');
div.className = className;
if (raw) {
div.innerHTML = text;
}
else {
div.textContent = text;
}
return div;
}
createPackageBarcode(items) {
if (items.length < 1) {
throw new Error('No items to create package barcode');
}
let barcodeString = '';
items.forEach((item) => {
if (item.barcode == null) {
throw new Error('Item has no barcode');
}
const quantity = item.weight != null ? item.weight : 1;
barcodeString += `${item.barcode}\t${quantity}\n`;
});
barcodeString += '\r';
return barcodeString;
}
createPackedItemBarcode(data) {
if (data.barcode == null) {
throw new Error('Item has no barcode');
}
return ('2200' +
data.barcode.padStart(13, '0') +
data.weight.toFixed(3).replace('.', '').padStart(4, '0'));
}
createDMDiv(barcodeString, big = false) {
if (typeof barcodeString !== 'string') {
throw new Error('Barcode string must be a string');
}
else if (barcodeString.length < 1) {
throw new Error('Barcode string cannot be empty');
}
const barcode = document.createElement('div');
barcode.className = 'barcode dm';
const svgNS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(svgNS, 'svg');
const path = document.createElementNS(svgNS, 'path');
const matrix = getDataMatrixMat(barcodeString);
const actualSize = matrix.length;
path.setAttribute('transform', 'scale(1)');
path.setAttribute('d', toPath(matrix));
svg.appendChild(path);
svg.setAttribute('class', 'datamatrix');
svg.setAttribute('viewBox', `0 0 ${actualSize} ${actualSize}`);
if (big === true) {
svg.style.width = '100%';
svg.style.height = '100%';
svg.style.maxWidth = '60px';
svg.style.maxHeight = '60px';
}
else {
svg.style.width = '100%';
svg.style.height = '100%';
svg.style.maxWidth = '30px';
svg.style.maxHeight = '30px';
}
svg.style.imageRendering = 'pixelated';
svg.style.shapeRendering = 'crispEdges';
barcode.appendChild(svg);
return barcode;
}
isItInAppMode() {
return (window.matchMedia('(display-mode: standalone)').matches ||
window.matchMedia('(display-mode: fullscreen)').matches);
}
async printLabelsUsingBrowser(data) {
const labels = data.map((item) => this.generateLabel(item));
const popup = window.open('', '_blank', this.isItInAppMode() ? 'width=250,height=300' : 'width=800,height=600');
if (popup == null) {
alert('Please allow popups for this site');
return;
}
popup.document.title = `${labels.length} ${i18n('nlabelsToBePrinted')}`;
popup.document.head.appendChild(this.createStyleElement());
labels.forEach((label) => {
popup.document.body.appendChild(label);
});
this.success = true;
popup.addEventListener('afterprint', () => {
popup.close();
});
popup.print();
}
createStyleElement() {
const style = document.createElement('style');
style.innerHTML = `${printStyles}`;
return style;
}
}
class ModalService {
$uibModal;
$rootScope;
modalInstance = null;
constructor() {
const injector = angular.element(document.body).injector();
this.$uibModal = injector.get('$uibModal');
this.$rootScope = injector.get('$rootScope');
}
async showModal(config) {
const modalScope = this.$rootScope.$new(true);
if (config.scopeProperties) {
Object.defineProperties(modalScope, Object.entries(config.scopeProperties).reduce((acc, [key, value]) => ({
...acc,
[key]: {
get: () => config.scopeProperties[key],
set: (v) => config.scopeProperties[key] = v,
enumerable: true,
configurable: true
}
}), {}));
}
this.modalInstance = this.$uibModal.open({
animation: true,
template: config.template,
scope: modalScope,
size: config.size || 'lg',
backdrop: config.backdrop || 'static',
windowClass: config.windowClass || '',
});
modalScope.closeModal = () => {
this.modalInstance.close();
modalScope.$destroy();
config.onClose?.();
};
return this.modalInstance.result;
}
}
class AngularServiceLocator {
static injector = null;
static getInjector() {
if (this.injector) {
return this.injector;
}
const appElement = document.querySelector('[ng-app]');
if (!appElement) {
throw new Error('Angular app not found');
}
this.injector = angular.element(appElement).injector();
return this.injector;
}
static getService(serviceName) {
return this.getInjector().get(serviceName);
}
}
class Request {
notifier;
items = {};
baseUrl = 'https://site.pro/lt/My-Accounting';
path = '/reference-book/items/search';
csrfToken;
headers;
turnstileService;
constructor(notifier) {
this.notifier = notifier;
this.turnstileService = AngularServiceLocator.getService('turnstileService');
const csrfTokenElement = document.querySelector('meta[name="csrf-token"]');
this.csrfToken = csrfTokenElement != null ? csrfTokenElement.content : '';
this.headers = {
accept: 'application/json, text/plain, */*',
'accept-language': 'en-GB,en;q=0.9,lt-LT;q=0.8,lt;q=0.7,en-US;q=0.6',
'content-type': 'application/json;charset=UTF-8',
origin: this.baseUrl,
referer: this.baseUrl,
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'x-requested-with': 'XMLHttpRequest',
'x-csrf-token': this.csrfToken,
cookie: '',
};
}
buildRequestBody(rules, pageSize = 20) {
return {
pageSize,
filters: {
groupOp: 'AND',
rules,
},
allSelected: false,
asString: '',
page: 1,
};
}
async fetchData(method, path, body) {
if (this.csrfToken === '') {
console.error('CSRF token is missing');
this.notifier.error('CSRF token is missing');
return;
}
const pathParts = path.split('/');
pathParts.pop();
this.headers.referer = `${this.baseUrl}${pathParts.join('/')}`;
this.getCookies();
try {
const response = await fetch(`${this.baseUrl}${path}`, {
method,
headers: this.headers,
body: JSON.stringify(body),
});
if (response.ok) {
return await response.json();
}
else if ('challenge' === response.headers.get('cf-mitigated') ||
response.status === 403) {
if (await this.handleChallenge()) {
console.info('Challenge handled, repeat the request');
return await this.fetchData(method, path, body);
}
}
else {
console.error('Request failed with status:', response.status);
this.notifier.error({
title: i18n('error'),
message: response.statusText,
});
}
}
catch (error) {
console.error('Error:', error);
this.notifier.error('Error: ' + error);
}
}
getCookies() {
const cookies = document.cookie.split(';').map((cookie) => cookie.trim());
cookies.forEach((cookie) => {
const [name, value] = cookie.split('=');
if ([
'YII_CSRF_TOKEN',
'b1-device_id',
'__cookie_law__',
'__SITE_PRO_SERVER__',
'site_language',
'site_currency',
'sp_user_fp',
'SSO_REFRESH_TOKEN',
'b1-session_id',
'isLoggedUser-v1',
'LoggedUserHash',
'cf_clearance',
'PHPSESSID',
'SPDOMAIN',
].includes(name.trim())) {
this.headers.cookie =
this.headers.cookie.length > 0
? `${this.headers.cookie}; ${name}=${value}`
: `${name}=${value}`;
}
});
}
isItDigits(barcode) {
return /^\d+$/.test(barcode);
}
async getItem(barcode) {
if (!this.isItDigits(barcode)) {
this.notifier.error('Invalid barcode');
return null;
}
if (Object.keys(this.items).includes(barcode)) {
const retrievedAt = this.items[barcode].retrievedAt;
if (retrievedAt != null &&
barcode.length > 10 &&
new Date().getTime() - retrievedAt.getTime() < 30000) {
return { ...this.items[barcode] };
}
else if (retrievedAt != null &&
barcode.length < 10 &&
new Date().getTime() - retrievedAt.getTime() < 60000) {
return { ...this.items[barcode] };
}
}
const rules = {
barcode: {
data: barcode,
field: 'barcode',
op: barcode[0] === '0' ? 'cn' : 'eq',
},
};
const body = this.buildRequestBody(rules);
const response = await this.fetchData('POST', this.path, body);
if (response == null || response.data[0] == null) {
return null;
}
response.data[0].retrievedAt = new Date();
this.items[barcode] = response.data[0];
if (response.data.length > 1) {
this.notifier.warning({
title: i18n('multipleItemsFound'),
delay: 20000,
message: i18n('foundXItems', [response.records]),
});
}
return response.data[0];
}
async getRecentlyModifiedItems(forced = false) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const body = this.buildRequestBody({
isActive: { data: true, field: 'isActive', op: 'eq' },
modifiedAt: {
data: today.toISOString().split('T')[0],
field: 'modifiedAt',
op: 'gt',
},
}, 200);
body.sort = { modifiedAt: 'desc' };
const response = await this.fetchData('POST', this.path, body);
const recentlySearched = JSON.parse(localStorage.getItem('items') ?? '[]');
const now = new Date();
if (response && response.data) {
response.data.forEach((item) => {
item.retrievedAt = now;
this.items[item.barcode] = item;
const found = recentlySearched.find((i) => i.barcode === item.barcode);
if (found && found.printedAt && item.modifiedAt && new Date(item.modifiedAt).getTime() <= new Date(found.printedAt).getTime()) {
item.noNeedToPrint = true;
}
});
}
if (forced) {
this.notifier.info(i18n('foundXItems', [response.records]));
}
return response.data || [];
}
async getItemsByIds(ids) {
const rules = {
id: { data: ids, field: 'id', op: 'in' },
};
const body = this.buildRequestBody(rules, 20);
const response = await this.fetchData('POST', this.path, body);
if (response && response.data) {
response.data.forEach((item) => {
item.retrievedAt = new Date();
this.items[item.barcode] = item;
});
}
if (response.code === 200) {
this.notifier.success({
title: i18n('success'),
message: i18n('foundXItems', [response.records]),
});
}
else {
this.notifier.error({
title: i18n('error'),
message: response.message,
});
}
return response.data || [];
}
async getAllItemsBatch(page = 1, pageSize = 100) {
const body = this.buildRequestBody({}, pageSize);
body.page = page;
const response = await this.fetchData('POST', this.path, body);
if (response && response.data) {
const hasMore = response.data.length === pageSize && page * pageSize < response.records;
return {
data: response.data,
hasMore
};
}
return { data: [], hasMore: false };
}
async getItemMovements(itemId, dateFrom, warehouseId) {
const rules = {
itemId: { data: itemId, field: 'itemId', op: 'eq' },
warehouseId: { data: warehouseId, field: 'warehouseId', op: 'eq' },
date: { data: dateFrom, field: 'date', op: 'eq' },
};
const body = this.buildRequestBody(rules, 20);
body.sort = {};
const response = await this.fetchData('POST', '/warehouse/item-movement/search', body);
if (response.code === 200) {
this.notifier.success({
title: i18n('success'),
message: i18n('foundXItems', [response.records]),
});
}
else {
this.notifier.error({
title: i18n('error'),
message: response.message,
});
}
return response.data || [];
}
async saveItem(id, data) {
if (!this.isItDigits(id)) {
this.notifier.error(i18n('invalidId'));
return false;
}
const response = await this.fetchData('POST', `/reference-book/items/update?id=${id}`, data);
if (response.code === 200) {
this.notifier.success({
title: i18n('itemUpdated'),
message: i18n('newPriceIs') + ' ' + data.priceWithVat,
delay: 15000,
});
}
else {
this.notifier.error({
title: i18n('failedToUpdateItem'),
message: response.message,
});
}
return response.code === 200;
}
async quickPriceChange(item) {
const price = prompt(i18n('enterNewPrice'), (item.priceWithVat ?? 0).toString());
if (price == null || item.id == null) {
this.notifier.info(i18n('error'));
return false;
}
const data = new Object();
data.isActive = true;
data.id = item.id;
data.priceWithVat = parseFloat(price.replace(',', '.'));
if (data.priceWithVat <= 0) {
this.notifier.error(i18n('missingPrice'));
return false;
}
data.priceWithoutVat = (data.priceWithVat / 1.21);
data.priceWithoutVat = Math.round((data.priceWithoutVat + Number.EPSILON) * 10000) / 10000;
item.priceWithVat = data.priceWithVat;
item.priceWithoutVat = data.priceWithoutVat;
return this.saveItem(data.id, data);
}
async createItem(data) {
const response = await this.fetchData('POST', '/reference-book/items/create', data);
if (response.code === 200) {
this.notifier.success(i18n('itemCreated'));
setTimeout(() => {
void this.saveItem(response.data.id, { isActive: true });
}, 400);
}
else {
this.notifier.error({
title: i18n('failedToCreateItem'),
message: response.message,
});
}
return response.code === 200;
}
async getSales(operationTypeName) {
const rules = {
operationTypeName: {
data: operationTypeName,
field: 'operationTypeName',
op: 'cn',
},
};
const body = this.buildRequestBody(rules, 20);
body.sort = { saleDate: 'desc' };
const path = '/warehouse/light-sales/search';
return this.fetchData('POST', path, body);
}
getSaleItems(lightSaleId) {
const rules = {
lightSaleId: { field: 'lightSaleId', op: 'eq', data: lightSaleId },
};
const body = this.buildRequestBody(rules, -1);
return this.fetchData('POST', '/warehouse/light-sale-items/search', body);
}
async handleChallenge() {
try {
await this.turnstileService.render();
console.info('Turnstile challenge passed!');
return true;
}
catch (error) {
console.error('Turnstile challenge failed.', error);
return false;
}
}
}
class TextToVoice {
language;
notifier;
apiKey = null;
numbers;
languages = {
'lt-LT': {
units: ['', 'vienas', 'du', 'trys', 'keturi', 'penki', 'šeši', 'septyni', 'aštuoni', 'devyni'],
teens: ['dešimt', 'vienuolika', 'dvylika', 'trylika', 'keturiolika', 'penkiolika', 'šešiolika', 'septyniolika', 'aštuoniolika', 'devyniolika'],
tens: ['', '', 'dvidešimt', 'trisdešimt', 'keturiasdešimt', 'penkiasdešimt', 'šešiasdešimt', 'septyniasdešimt', 'aštuoniasdešimt', 'devyniasdešimt'],
hundreds: ['', 'šimtas', 'du šimtai', 'trys šimtai', 'keturi šimtai', 'penki šimtai', 'šeši šimtai', 'septyni šimtai', 'aštuoni šimtai', 'devyni šimtai'],
},
'en-GB': {
units: ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'],
teens: ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'],
tens: ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'],
hundreds: ['', 'one hundred', 'two hundred', 'three hundred', 'four hundred', 'five hundred', 'six hundred', 'seven hundred', 'eight hundred', 'nine hundred'],
}
};
constructor(notifier) {
this.language = window.navigator.language.split('-')[0];
if (this.language == 'lt') {
this.language = 'lt-LT';
}
else {
this.language = 'en-GB';
}
this.numbers = this.languages[this.language];
this.notifier = notifier;
void this.checkApiKey();
}
async checkApiKey() {
this.apiKey = await GM.getValue('api-key', null);
if (this.apiKey != null && this.apiKey.length < 20) {
this.apiKey = null;
this.notifier.error(i18n('invalidApiKey'));
}
}
async speak(text) {
const audio = new Audio(await this.getAudioUrl(text));
void audio.play();
}
async getAudioUrl(text) {
if (this.apiKey == null) {
return;
}
const response = await fetch(`https://texttospeech.googleapis.com/v1/text:synthesize?key=${this.apiKey}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
input: { text },
voice: {
languageCode: this.language,
ssmlGender: 'MALE'
},
audioConfig: { audioEncoding: 'MP3' }
})
});
const data = await response.json();
if (data.audioContent == null) {
this.notifier.error({ title: i18n('error'), message: JSON.stringify(data) });
return;
}
const audioContent = data.audioContent;
const audioBlob = new Blob([Uint8Array.from(atob(audioContent), c => c.charCodeAt(0))], { type: 'audio/mp3' });
return URL.createObjectURL(audioBlob);
}
numberToWords(number) {
let words = [];
if (number === 0) {
words.push(i18n('zero'));
}
else {
const unitsPart = number % 10;
const tensPart = Math.floor(number / 10) % 10;
const hundredsPart = Math.floor(number / 100);
if (hundredsPart > 0) {
words.push(this.numbers.hundreds[hundredsPart]);
}
if (tensPart > 1) {
words.push(this.numbers.tens[tensPart]);
}
if (tensPart === 1) {
words.push(this.numbers.teens[unitsPart]);
}
else {
words.push(this.numbers.units[unitsPart]);
}
}
words = words.filter(word => word);
return words.join(' ');
}
digitsToPrice(number) {
const integer = Math.floor(number);
const decimal = Math.round((number - integer) * 100);
let words = [];
if (integer > 0) {
words.push(this.numberToWords(integer));
}
if (decimal > 0) {
if (integer !== 0 && this.language == 'lt-LT') {
if (integer === 1 || (integer % 10 === 1 && integer % 100 !== 11)) {
words.push('euras');
}
else if (integer % 10 === 0 || integer % 10 >= 10 || (integer % 100 >= 10 && integer % 100 <= 20)) {
words.push('eurų');
}
else {
words.push('eurai');
}
words.push('ir');
}
else if (integer > 1 && this.language == 'en-GB') {
words.push('euros');
}
else if (integer === 1 && this.language == 'en-GB') {
words.push('euro');
}
words.push(this.numberToWords(decimal));
if (this.language == 'en-GB' && decimal === 1) {
words.push('cent');
}
else if (this.language == 'en-GB') {
words.push('cents');
}
else if (decimal === 1 || (decimal % 10 === 1 && decimal % 100 !== 11)) {
words.push('centas');
}
else if (decimal % 10 === 0 || decimal % 10 >= 10 || (decimal % 100 >= 10 && decimal % 100 <= 20)) {
words.push('centų');
}
else {
words.push('centai');
}
}
words = words.filter(word => word);
return words.join(' ');
}
}
class UINotification {
notificationService;
constructor() {
try {
this.notificationService = AngularServiceLocator.getService('Notification');
}
catch (error) {
console.error('Failed to get Notification service', error);
this.notificationService = {
info: (options) => alert(options),
error: (options) => alert(options),
success: (options) => alert(options),
warning: (options) => alert(options),
primary: (options) => alert(options)
};
}
}
info(options) {
this.notificationService.info(options);
}
error(options) {
this.notificationService.error(options);
}
success(options) {
this.notificationService.success(options);
}
warning(options) {
this.notificationService.warning(options);
}
primary(options) {
this.notificationService.primary(options);
}
}
function modalHTML(i18n) { return `
`; }
class VirtualKeyboard {
options;
scope;
i18nFunction;
constructor(options = {}, i18nFunction) {
this.i18nFunction = i18nFunction;
this.options = {
initialValue: '',
placeholder: '',
title: 'Enter Value',
maxLength: 10,
allowDecimal: true,
...options,
};
this.scope = {
value: this.options.initialValue || '',
replaceInitialValue: this.options.initialValue !== undefined &&
this.options.initialValue !== null,
visible: false,
title: this.options.title || 'Enter Value',
placeholder: this.options.placeholder || '',
key: this.handleKey.bind(this),
confirm: this.handleConfirm.bind(this),
cancel: this.handleCancel.bind(this),
hide: this.hide.bind(this),
i18n: this.i18nFunction,
};
}
getScope() {
return this.scope;
}
show() {
this.scope.visible = true;
this.scope.value = '';
}
hide() {
this.scope.visible = false;
}
handleKey(input) {
if (input === 'd') {
this.scope.value = this.scope.value.slice(0, -1);
}
else if (input === 'c') {
this.scope.value = '';
}
else if (input === '.') {
if (this.options.allowDecimal && !this.scope.value.includes('.')) {
this.scope.value += input;
}
}
else if (/^\d$/.test(input)) {
if (this.scope.replaceInitialValue) {
this.scope.value = input;
this.scope.replaceInitialValue = false;
}
else if (!this.options.maxLength ||
this.scope.value.length < this.options.maxLength) {
this.scope.value += input;
}
}
if (this.options.onValueChange) {
this.options.onValueChange(this.scope.value);
}
}
handleConfirm() {
if (this.options.onConfirm) {
this.options.onConfirm(this.scope.value);
}
this.hide();
}
handleCancel() {
if (this.options.onCancel) {
this.options.onCancel();
}
this.hide();
}
setValue(value) {
this.scope.value = value;
if (this.options.onValueChange) {
this.options.onValueChange(this.scope.value);
}
}
getValue() {
return this.scope.value;
}
updateOptions(newOptions) {
this.options = { ...this.options, ...newOptions };
this.scope.title = this.options.title || 'Enter Value';
this.scope.placeholder = this.options.placeholder || '';
}
}
class WeightLabelModal {
modalScope;
modalService;
notifier;
controller;
virtualKeyboard;
constructor(modalService, notifier, controller) {
this.modalService = modalService;
this.notifier = notifier;
this.controller = controller;
this.virtualKeyboard = new VirtualKeyboard({
allowDecimal: true,
maxLength: 8,
onValueChange: (value) => {
if (this.modalScope.item) {
this.modalScope.item.weight = value;
this.handleWeightChange();
}
},
}, i18n);
this.modalScope = {
item: null,
weight: '',
virtualKeyboardVisible: this.controller !== undefined,
addButton: this.controller !== undefined,
hideVirtualKeyboard: this.hideVirtualKeyboard.bind(this),
handleWeightChange: this.handleWeightChange.bind(this),
key: this.key.bind(this),
add: this.add.bind(this),
print: this.print.bind(this),
picker: this.showPicker.bind(this),
i18n: i18n,
};
}
hideVirtualKeyboard() {
this.modalScope.virtualKeyboardVisible = false;
}
key(input) {
if (!this.modalScope.item) {
return;
}
if (input === 'd' && this.modalScope.item.weight) {
this.modalScope.item.weight = this.modalScope.item.weight
.toString()
.slice(0, -1);
}
else if (input === 'c') {
this.modalScope.item.weight = '';
}
else if (input !== 'd') {
this.modalScope.item.weight = (this.modalScope.item.weight || '') + input;
}
this.modalScope.handleWeightChange();
}
show(item) {
if (!item || !item.name || !item.priceWithVat) {
this.notifier.error(i18n('noData'));
return Promise.reject();
}
this.setupModalItem(item);
return this.modalService.showModal({
template: modalHTML(),
scopeProperties: this.modalScope,
size: 'md',
windowClass: 'weight-label-modal',
});
}
setupModalItem(item) {
this.modalScope.item = {
...item,
weight: '',
addManufacturer: false,
addPackageFee: true,
};
}
getWeightItem() {
const item = { ...this.modalScope.item };
if (!item || !item.weight) {
this.notifier.error(i18n('missingWeight'));
return null;
}
item.weight = item.measurementUnitCanBeWeighed
? this.gToKg(parseFloat(item.weight.toString()))
: parseFloat(item.weight.toString());
if (item.weight > 9.999) {
this.notifier.error(i18n('maxWeight'));
return null;
}
item.addManufacturer =
!!item.addManufacturer && !!item.manufacturerName?.length;
return item;
}
add() {
if (this.controller !== undefined) {
const item = this.getWeightItem();
if (item) {
void this.controller?.processItem(item, true);
}
}
}
print() {
const item = this.getWeightItem();
if (item) {
this.notifier.success({
title: i18n('printJobIsSent'),
message: `${item.weight}${item.measurementUnitName}`,
});
new LabelGenerator([item]);
}
}
handleWeightChange() {
if (!this.modalScope.item) {
return;
}
this.modalScope.item.totalPrice = calculateTotalPrice(this.modalScope.item.priceWithVat, this.modalScope.item.measurementUnitCanBeWeighed
? this.gToKg(Number(this.modalScope.item.weight || 0))
: Number(this.modalScope.item.weight || 0));
}
gToKg(weight) {
return weight / 1000;
}
showPicker(event) {
const target = event.target;
if (target?.showPicker) {
target.showPicker();
}
}
}
class LabelTypeModal {
type;
modal;
lg;
labelTypes = ['normal', 'fridge', 'half', 'barcodeOnly'];
fakeItems = [
{
name: 'Whiskey BLACK RAM, 40%, 0,7 l',
barcode: '3800032070302',
measurementUnitName: 'vnt.',
measurementUnitCanBeWeighed: false,
priceWithVat: 14.29,
isActive: true,
isRefundable: true,
},
{
name: 'Cookies BARNI Milk, 30 g',
barcode: '5906747318345',
measurementUnitName: 'vnt.',
measurementUnitCanBeWeighed: false,
priceWithVat: 0.5,
isActive: true,
isRefundable: true,
},
{
name: 'Coca-Cola, 0,5 l',
barcode: '4779036770016',
measurementUnitName: 'vnt.',
measurementUnitCanBeWeighed: false,
priceWithVat: 1.99,
isActive: true,
packageCode: '1100',
isRefundable: true,
},
];
constructor(type) {
this.type = type;
this.modal = new ModalService();
this.lg = new LabelGenerator();
}
open() {
return new Promise((resolve, reject) => {
void this.modal.showModal({
template: this.lg.createStyleElement().outerHTML +
`
{{ i18n('chooseLabelTypeDescription') }}
`,
scopeProperties: {
labelTypes: this.labelTypes,
current: this.type,
fakeItems: this.fakeItems,
selectLabelType: (x) => {
this.type = x;
resolve(x);
this.modal.modalInstance.close();
},
lg: this.lg,
i18n,
getLabelPreview: (labelType) => {
return this.lg.generateLabel(this.fakeItems[Math.floor(Math.random() * this.fakeItems.length)], labelType).outerHTML;
},
closeModal: () => {
reject(new Error('Modal closed without selection'));
this.modal.modalInstance.close();
},
},
});
});
}
}
class ReferenceBookItemsModal {
modalService;
notification;
weight;
controller;
constructor(modalService, notification, weight, controller) {
this.modalService = modalService;
this.notification = notification;
this.weight = weight;
this.controller = controller;
}
show() {
return this.modalService.showModal({
template: `
${i18n('print')}
${i18n('weightLabel')}
${i18n('add')}
${i18n('type')}
${i18n('close')}
`,
scopeProperties: {
weight: this.weight,
controller: this.controller,
weightLabel: (a) => {
if (!a || a.length !== 1 || !a[0].measurementUnitCanBeWeighed) {
this.notification.error(i18n('weightedItem') + '?');
return;
}
void this.weight?.show(a[0]);
},
print: async (e) => {
if (e.length < 1) {
this.notification.error(i18n('noItemsSelected'));
return;
}
let type = await this.controller?.getType();
if (type === undefined) {
await new LabelTypeModal().open().then((x) => {
type = x;
});
}
void new LabelGenerator(e, type);
},
selected: (a) => a.provider.getSelected().length,
isWeighted: (a) => {
const selected = a.provider.getSelected();
return selected.length === 1 && selected[0].measurementUnitCanBeWeighed;
},
proccessItem: (a) => {
if (!a || a.length < 1) {
this.notification.error(i18n('noItemsSelected'));
return;
}
else if (this.controller === undefined) {
this.notification.error(i18n('error'));
return;
}
a.forEach((i) => {
void this.controller?.processItem(i, true);
});
},
openTypeModal: () => {
void this.controller?.openTypeModal();
}
},
size: 'ext',
});
}
}
class TempFileListModal {
modalService;
constructor(modalService) {
this.modalService = modalService;
}
async show() {
await this.modalService.showModal({
template: `
`,
scopeProperties: {
typeId: null,
openForSelect: false,
},
});
}
}
class MarkdownModal {
request;
modalService;
tableData = [];
saleItems = [];
constructor(request) {
this.request = request;
this.modalService = new ModalService();
}
async show() {
if (this.tableData.length === 0) {
await this.loadData();
}
void this.modalService.showModal({
template: `
{{ noDataMessage }}
${i18n('number')}
${i18n('date')}
${i18n('discount')}
{{row.series}} {{row.number}}
{{row.saleDate}}
{{row.discount}}
${i18n('show')}
`,
scopeProperties: {
tableData: this.tableData,
noDataMessage: this.tableData.length ? '' : i18n('loading'),
closeModal: () => this.modalService.modalInstance.close(),
showSaleItems: this.showSaleItems.bind(this),
},
});
}
async loadData() {
const filter = await GM.getValue('markdowns.filter', 'nur');
const sales = await this.request.getSales(filter);
if (!sales) {
this.tableData = [];
}
else {
this.tableData = sales.data.map((sale) => ({
id: sale.id,
series: sale.series,
number: sale.number,
saleDate: sale.saleDate,
discount: sale.discount,
}));
}
}
async showSaleItems(id, date) {
const items = await this.request.getSaleItems(id);
this.saleItems =
items?.data
.map((item) => ({
itemId: item.itemId,
virtualName: item.virtualName,
quantity: item.quantity,
total: item.sumWithoutVat + item.vat,
discount: item.discount,
discountRate: item.discountRate,
virtualUnit: item.virtualUnit,
}))
.filter((item) => item.discount < 0) || [];
void this.modalService.showModal({
template: `
ID
${i18n('name')}
${i18n('quantity')}
${i18n('total')}
${i18n('discount')}
${i18n('discountRate')}
{{item.itemId}}
{{item.virtualName}}
{{item.quantity}} {{item.virtualUnit.measurementUnitName}}
{{item.total.toFixed(2)}}
{{item.discount}}
{{item.discountRate}}%
`,
scopeProperties: {
saleItems: this.saleItems,
closeModal: () => this.modalService.modalInstance.close(),
},
});
}
}
class EventEmitter {
events = {};
on(event, listener) {
(this.events[event] ||= []).push(listener);
}
emit(event, ...args) {
(this.events[event] || []).forEach((fn) => fn(...args));
}
}
class DeviceClient extends EventEmitter {
config;
notification;
wsConnections = {};
comPorts = {};
status = {
barcode: { connected: false },
scales: { connected: false },
printer: { connected: false },
};
constructor(config, notification) {
super();
this.config = config;
this.notification = notification;
}
async connect(device) {
const cfg = this.config[device];
if (cfg !== undefined && cfg.includes('ws')) {
return this.connectWebSocket(device, cfg);
}
else if (cfg !== undefined && cfg.includes('COM')) {
return this.connectComPort(device);
}
else {
throw new Error(`No configuration found for device: ${device}`);
}
}
async connectAndSend(device, initialMessage) {
await this.connect(device);
if (initialMessage) {
await this.sendToDevice(device, initialMessage);
}
}
connectWebSocket(device, url) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(url);
ws.onopen = () => {
this.status[device].connected = true;
this.status[device].error = undefined;
this.emit('connected', device);
resolve();
};
ws.onmessage = (event) => {
this.status[device].lastMessage = event.data;
switch (device) {
case 'barcode':
void this.readBarcodeMessage(event.data);
break;
case 'scales':
void this.readWeightMessage(event.data);
break;
case 'printer':
void this.readPrinterMessage(event.data);
break;
}
};
ws.onerror = (err) => {
this.status[device].error = 'WebSocket error';
this.status[device].connected = false;
this.notification.error(`Error connecting to ${device} WebSocket: ${err}`);
this.emit('error', device, err);
reject(new Error(`WebSocket connection failed for ${device}: ${err}`));
};
ws.onclose = () => {
this.status[device].connected = false;
this.emit('disconnected', device);
};
this.wsConnections[device] = ws;
});
}
async connectComPort(device, baudRate = 9600) {
if (!('serial' in navigator)) {
this.status[device].error = 'Web Serial API not supported';
throw new Error('Web Serial API not supported');
}
try {
const port = await navigator.serial.requestPort();
await port.open({ baudRate });
this.comPorts[device] = port;
this.status[device].connected = true;
this.status[device].error = undefined;
this.emit('connected', device);
void this.readFromComPort(device);
}
catch (err) {
this.status[device].error = 'COM port error: ' + err.message;
this.status[device].connected = false;
this.emit('error', device, err);
throw err;
}
}
async readFromComPort(device) {
const port = this.comPorts[device];
if (!port) {
return;
}
try {
const reader = port.readable.getReader();
while (this.status[device].connected) {
const { value, done } = await reader.read();
if (done) {
break;
}
if (value) {
const message = new TextDecoder().decode(value);
this.status[device].lastMessage = message;
switch (device) {
case 'barcode':
void this.readBarcodeMessage(message);
break;
case 'scales':
void this.readWeightMessage(message);
break;
case 'printer':
void this.readPrinterMessage(message);
break;
}
}
}
reader.releaseLock();
}
catch (err) {
this.status[device].error = 'Read error: ' + err.message;
}
}
async readBarcodeMessage(barcode) {
if (barcode.startsWith('BARCODE:')) {
const code = barcode.replace('BARCODE:', '').trim();
const cleanedCode = code.replace(/[\x00-\x1F\x7F]/g, '').replace(/[^0-9]/g, '');
if (cleanedCode.length === 0) {
this.notification.error(`Invalid barcode: ${code}`);
return;
}
this.emit('barcode', cleanedCode);
}
else if (barcode.startsWith('STATUS:')) {
const status = barcode.replace('STATUS:', '').trim();
if (status.includes('ERROR')) {
this.status.barcode.error = status;
this.notification.error(`Barcode device error: ${status}`);
this.status.barcode.connected = false;
}
else {
this.status.barcode.connected = status === 'CONNECTED';
this.status.barcode.error = undefined;
this.notification.success(`Barcode device status: ${status}`);
}
this.emit('status', { device: 'barcode', status });
}
}
async readWeightMessage(weight) {
this.emit('weight', weight);
}
async readPrinterMessage(printer) {
this.emit('printer', printer);
}
async sendToDevice(device, data) {
if (this.wsConnections[device]?.readyState === WebSocket.OPEN) {
this.wsConnections[device]?.send(data);
}
else if (this.comPorts[device]) {
const encoder = new TextEncoder();
const writer = this.comPorts[device]?.writable.getWriter();
if (writer) {
await writer.write(encoder.encode(data));
writer.releaseLock();
}
else {
throw new Error(`Failed to get writer for ${device}`);
}
}
else {
const error = `Device ${device} not connected`;
this.status[device].error = error;
throw new Error(error);
}
}
isConnected(device) {
return this.status[device].connected && (this.wsConnections[device]?.readyState === WebSocket.OPEN ||
!!this.comPorts[device]);
}
async disconnect(device) {
if (this.wsConnections[device]) {
this.wsConnections[device]?.close();
delete this.wsConnections[device];
}
if (this.comPorts[device]) {
await this.comPorts[device]?.close();
delete this.comPorts[device];
}
this.status[device].connected = false;
this.emit('disconnected', device);
}
}
class ConfigModal {
modalService;
constructor(modalService) {
this.modalService = modalService;
}
async show() {
await this.modalService.showModal({
template: `
${i18n('logout')}
Update Voice API Key
Update Markdown Operation Filter
Update Scanner settings
`,
scopeProperties: {
typeId: null,
openForSelect: false,
closeModal: () => this.modalService.modalInstance?.close(),
logout: () => {
window.location.href = "https://site.pro/lt/My-Accounting/logout";
},
updateVoiceApi: () => this.updateVoiceApi(),
updateDevices: () => this.updateDevices(),
updateMarkdownsFilter: () => this.updateMarkdownsFilter(),
},
});
}
async updateVoiceApi() {
const apiKey = prompt('Enter new Voice API Key:');
if (apiKey === null) {
return;
}
await GM.setValue('api-key', apiKey);
alert('API key saved');
}
async updateDevice(type) {
const config = prompt(`Should ${type} use WebSocket or Serial Port? Type ws link for WebSocket or 'COM' for Serial communication or 'none':`, 'none');
if (config?.includes('ws')) {
await this.saveConfig(`devices.${type}`, config);
}
else if (config === 'serial') {
await this.saveConfig(`devices.${type}`, 'COM');
window.alert('Serial Port configuration Browser API will follow soon.');
}
else if (config === 'none') {
await this.saveConfig(`devices.${type}`, '');
window.alert(`${type} device configuration cleared.`);
}
else {
window.alert('Cancelled device configuration update.');
}
}
async updateMarkdownsFilter() {
const filter = window.prompt('Enter the Markdown operation filter');
if (filter === null) {
return;
}
await GM.setValue('markdowns.filter', filter);
alert('Markdown operation filter saved');
}
async updateDevices() {
const devices = ['barcode', 'scales', 'printer'];
for (const device of devices) {
await this.updateDevice(device);
}
window.alert('Device configuration updated!');
}
closeModal() {
this.modalService.modalInstance?.close();
}
async saveConfig(key, value) {
if (!key || !value) {
alert('Invalid configuration data.');
return;
}
await GM.setValue(key, value);
}
}
class LabelerController {
$scope;
notification = new UINotification();
req = new Request(this.notification);
modalService = new ModalService();
textToVoice = new TextToVoice(this.notification);
modals;
config = {};
deviceClient = undefined;
constructor($scope) {
this.$scope = $scope;
const weightLabelModal = new WeightLabelModal(this.modalService, this.notification, this);
this.modals = {
weightLabel: weightLabelModal,
details: new ItemDetailsModal(this.modalService, this.notification, this.req, weightLabelModal),
};
}
async $onInit() {
const barcode = await GM.getValue('devices.barcode', "");
const scales = await GM.getValue('devices.scales', "");
const printer = await GM.getValue('devices.printer', "");
this.config = {
barcode,
scales,
printer
};
this.deviceClient = new DeviceClient(this.config, this.notification);
void this.initializeScope().then(() => {
this.$scope.$digest();
});
if (this.config.barcode) {
this.deviceClient.connectAndSend('barcode', 'ACTIVE').catch((err) => {
console.error('Error connecting to barcode device or sending ACTIVE:', err);
});
window.addEventListener('blur', () => {
this.deviceClient?.sendToDevice('barcode', 'INACTIVE').catch((err) => {
console.error('Error sending INACTIVE to barcode device:', err);
});
});
window.addEventListener('focus', () => {
this.deviceClient?.sendToDevice('barcode', 'ACTIVE').catch((err) => {
console.error('Error sending ACTIVE to barcode device:', err);
});
});
window.addEventListener('beforeunload', () => {
this.deviceClient?.sendToDevice('barcode', 'INACTIVE').catch((err) => {
console.error('Error sending INACTIVE to barcode device:', err);
});
});
this.deviceClient.on('barcode', (code) => {
const clean = code.toString().replace(/[\x00-\x1F\x7F]/g, '').replace(/[^0-9]/g, '');
if (document.hasFocus()) {
void this.searchBarcode(clean);
}
});
}
this.deviceClient.on('message', (deviceType, message) => {
console.log(`Device ${deviceType} message:`, message);
});
this.deviceClient.on('connected', (deviceType) => {
console.log(`Device ${deviceType} connected`);
this.notification.success(`${deviceType} device connected`);
});
this.deviceClient.on('disconnected', (deviceType) => {
console.log(`Device ${deviceType} disconnected`);
this.notification.info(`${deviceType} device disconnected`);
});
this.deviceClient.on('error', (deviceType, error) => {
console.error(`Device ${deviceType} error:`, error);
this.notification.error(`${deviceType} device error: ${error}`);
});
}
async initializeScope() {
Object.assign(this.$scope, {
items: {
grid: [],
recent: await this.req.getRecentlyModifiedItems(),
searched: this.getRecentlySearchedItems(),
},
settings: {
sayOutLoud: JSON.parse(localStorage.getItem('sayOutLoud') ?? 'true'),
showStock: JSON.parse(localStorage.getItem('showStock') ?? 'false'),
type: 'normal',
},
drafts: JSON.parse(localStorage.getItem('drafts') ?? '[]'),
tabs: true,
companyName: unsafeWindow?.currentCompanyUser?.company?.name || '',
loading: false,
barcode: null,
printed: false,
draft: false,
currentDraft: null,
modals: this.modals,
activeTab: 'recentlyModified',
scanner: this.deviceClient?.status.barcode.connected || this.config.barcode === '',
openMarkdowns: this.openMarkdowns.bind(this),
openCatalog: this.openCatalog.bind(this),
openFiles: this.openFiles.bind(this),
openConfig: this.openConfig.bind(this),
openTypeModal: this.openTypeModal.bind(this),
searchBarcode: this.searchBarcode.bind(this),
print: this.print.bind(this),
cleanAll: this.cleanAll.bind(this),
handleEnterPress: this.handleEnterPress.bind(this),
removeItem: this.removeItem.bind(this),
showDetails: this.showDetails.bind(this),
quickPriceChange: (item) => {
void this.req.quickPriceChange(item);
},
getPricePerUnit: LabelGenerator.getPricePerUnit,
getFriendlyTime: getFriendlyTime.bind(this),
isItRecent: isItRecent.bind(this),
showWeightModal: this.showWeightModal.bind(this),
clearBarcodeInput: this.clearBarcodeInput.bind(this),
refreshCurrentTab: this.refreshCurrentTab.bind(this),
changeActiveTab: this.changeActiveTab.bind(this),
toggleTabs: this.toggleTabs.bind(this),
getTotalPrice: this.getTotalPrice.bind(this),
saveDraft: this.saveDraft.bind(this),
deleteDraft: this.deleteDraft.bind(this),
showDraftBarcode: this.showDraftBarcode.bind(this),
loadDraft: this.loadDraft.bind(this),
changeQuantity: this.changeQuantity.bind(this),
});
}
openMarkdowns() {
const markdownsModal = new MarkdownModal(this.req);
void markdownsModal.show();
}
openCatalog() {
const referenceBookItemsModal = new ReferenceBookItemsModal(this.modalService, this.notification, this.modals.weightLabel, this);
void referenceBookItemsModal.show().then(() => {
this.$scope.$digest();
});
}
openFiles() {
const filesModal = new TempFileListModal(this.modalService);
void filesModal.show();
}
openConfig() {
const configModal = new ConfigModal(this.modalService);
void configModal.show();
}
openTypeModal() {
const typeModal = new LabelTypeModal(this.$scope.settings.type);
typeModal.open().then((type) => {
this.setType(type);
}).catch(() => {
this.notification.error(i18n('modalClosed'));
});
}
getType() {
const type = this.$scope.settings.type;
if (type === undefined) {
return new LabelTypeModal().open();
}
return type;
}
setType(type) {
this.$scope.settings.type = type;
this.$scope.$digest();
}
async searchBarcode(code) {
this.$scope.printed = false;
if (!this.$scope.barcode && !code) {
this.notification.error(i18n('missingBarcode'));
return;
}
const barcode = ((code != undefined) ? code : this.$scope.barcode).toString().trim();
this.clearBarcodeInput();
this.canItBePackaged(barcode)
? await this.searchforAPackagedItem(barcode)
: await this.searchItem(barcode);
}
canItBePackaged(barcode) {
const prefix = parseInt(barcode.slice(0, 2), 10);
return ((barcode.length === 13 || barcode.length === 21) &&
prefix > 20 &&
prefix < 30);
}
async searchItem(barcode) {
this.$scope.loading = true;
const item = (await this.req.getItem(barcode));
item ? await this.processItem(item) : this.notFound(barcode);
this.$scope.loading = false;
this.$scope.$digest();
}
async searchforAPackagedItem(barcode) {
const barcodePart = this.extractBarcodePart(barcode);
this.$scope.loading = true;
const item = (await this.req.getItem(barcodePart));
this.$scope.loading = false;
item
? await this.processPackagedItem(item, barcode)
: this.notFound(barcode);
this.$scope.$digest();
}
extractBarcodePart(barcode) {
if (barcode.length === 13 &&
['23', '24'].includes(barcode.slice(0, 2))) {
return barcode.slice(0, 8);
}
else if (barcode.length === 13 &&
['25', '29'].includes(barcode.slice(0, 2))) {
return barcode.slice(0, 7);
}
else if (barcode.length === 21) {
return barcode.slice(4, 17).replace(/^0+/, '');
}
return barcode;
}
async processPackagedItem(item, barcode) {
item.weight = this.calculateWeight(barcode);
item.totalPrice = calculateTotalPrice(item.priceWithVat, item.weight);
await this.processItem(item);
}
calculateWeight(barcode) {
const weightPart = barcode.length > 13 ? barcode.slice(17, 21) : barcode.slice(8, 12);
return parseInt(weightPart, 10) / 1000;
}
async processItem(item, silence = false) {
this.handleNotifications(item, silence);
this.$scope.items.grid.push(item);
this.scrollToLastItem();
if (!item.weight) {
void this.saveSearchedItemLocally(item);
}
}
handleNotifications(item, silence) {
if (silence) {
this.notification.success({
title: i18n('itemAdded'),
message: item.name,
delay: 2000,
});
}
else if (!this.$scope.settings.sayOutLoud) {
return;
}
else if (!item.isActive && item.priceWithVat > 0) {
void this.textToVoice.speak(i18n('itemNotActive'));
}
else if (this.$scope.settings.showStock && item.stock != null) {
void this.textToVoice.speak(item.stock.toString());
}
else if (this.isDuplicateItem(item)) {
void this.textToVoice.speak(this.getDuplicateItemMessage(item));
}
else if (item.priceWithVat > 0) {
void this.textToVoice.speak(i18n('price') +
' ' +
this.textToVoice.digitsToPrice(item.totalPrice ?? item.priceWithVat));
}
else {
void this.textToVoice.speak(i18n('priceNotSet'));
}
}
isDuplicateItem(item) {
const lastItem = this.$scope.items.grid[this.$scope.items.grid.length - 1];
return (lastItem &&
lastItem.barcode === item.barcode &&
'weight' in lastItem &&
'weight' in item &&
lastItem.weight === item.weight);
}
getDuplicateItemMessage(item) {
return (i18n('asMentioned') +
', ' +
i18n('price') +
' ' +
this.textToVoice.digitsToPrice(item.totalPrice ?? item.priceWithVat) +
(item.weight !== undefined
? '. ' +
i18n('weight') +
this.textToVoice.numberToWords(parseFloat(item.weight.toString())) +
item.measurementUnitName
: '') +
' ' +
i18n('thisIs') +
' ' +
item.name);
}
scrollToLastItem() {
setTimeout(() => {
const itemElements = document.querySelectorAll('.item-list .item');
const lastItem = itemElements[itemElements.length - 1];
lastItem?.scrollIntoView({ behavior: 'smooth', block: 'end' });
}, 0);
}
notFound(barcode) {
const item = {
name: `${i18n('itemNotFound')} (${i18n('barcode')}: ${barcode})`,
barcode,
isActive: false,
};
this.$scope.items.grid.push(item);
this.notification.error(`Item not found: ${barcode}`);
}
print(items = this.$scope.items.grid.filter((item) => item)) {
if (items.length === 0) {
this.notification.error(i18n('noData'));
return;
}
const labelGenerator = new LabelGenerator(items, this.$scope.settings.type);
if (labelGenerator.success) {
this.cleanAll();
this.$scope.printed = true;
}
}
cleanAll() {
this.$scope.items.grid = [];
}
async handleEnterPress(event) {
if (event.key === 'Enter') {
await this.searchBarcode();
}
}
removeItem(index) {
this.$scope.items.grid.splice(index, 1);
}
showDetails(item) {
void this.modals.details.show(item);
}
showWeightModal(i) {
void this.modals.weightLabel.show(i).then(() => {
this.$scope.$digest();
});
}
clearBarcodeInput() {
this.$scope.barcode = null;
}
getAgoText(retrievedAt) {
const now = new Date();
if (retrievedAt) {
const diff = Math.abs(now.getTime() - new Date(retrievedAt).getTime());
const seconds = Math.floor(diff / 1000);
if (seconds < 15) {
return null;
}
return i18n('ago') + ' ' + seconds + ' ' + i18n('seconds');
}
return null;
}
saveSearchedItemLocally(item) {
const items = this.getRecentlySearchedItems();
const existingItemIndex = items.findIndex((i) => i.barcode === item.barcode);
if (existingItemIndex !== -1) {
items.splice(existingItemIndex, 1);
}
item.printedAt = new Date().toISOString();
items.unshift(item);
if (items.length > 50) {
items.pop();
}
localStorage.setItem('items', JSON.stringify(items));
this.$scope.items.searched = items;
}
getRecentlySearchedItems() {
const items = JSON.parse(localStorage.getItem('items') ?? '[]');
return items;
}
getRecentlySearchedItemIds() {
const items = this.getRecentlySearchedItems();
const itemIds = items.map((item) => item.id);
return itemIds;
}
async updateSearchedItems() {
const itemIds = this.getRecentlySearchedItemIds();
const items = await this.req.getItemsByIds(itemIds);
localStorage.setItem('items', JSON.stringify(items));
this.$scope.items.searched = items;
this.$scope.$digest();
}
async updateRecentItems(forced = false) {
const items = await this.req.getRecentlyModifiedItems(forced);
this.$scope.items.recent = items;
this.$scope.$digest();
}
async refreshCurrentTab() {
if (this.$scope.activeTab === 'recentlyModified') {
void this.updateRecentItems(true);
}
else {
void this.updateSearchedItems();
}
}
changeActiveTab(tab) {
this.$scope.activeTab = tab;
}
toggleTabs() {
this.$scope.tabs = !this.$scope.tabs;
}
changeQuantity(index) {
const item = this.$scope.items.grid[index];
if (!item || !item.priceWithVat) {
this.notification.error(i18n('noData'));
return;
}
const newQuantity = prompt(i18n('changeQuantity'), item.weight?.toString() || '1');
if (newQuantity !== null) {
const quantity = parseFloat(newQuantity);
if (isNaN(quantity) || quantity <= 0) {
this.notification.error(i18n('invalidQuantity'));
return;
}
item.weight = quantity;
item.totalPrice = calculateTotalPrice(item.priceWithVat, quantity);
this.$scope.items.grid[index] = item;
this.notification.success({
title: i18n('quantityChanged'),
message: `${item.name} - ${quantity} ${item.measurementUnitName}`,
});
}
}
getTotalPrice(items) {
return (items ?? this.$scope.items.grid).reduce((acc, item) => acc + (item?.totalPrice ?? item.priceWithVat) +
(item.packageCode ? item.packageQuantity * 0.1 : 0), 0);
}
saveDraft() {
if (this.$scope.items.grid.length === 0) {
this.notification.error(i18n('noItems'));
return;
}
const draftItems = this.$scope.items.grid.map((item) => ({
...item,
}));
const newDraft = {
items: draftItems,
date: new Date().toISOString(),
title: (this.$scope.currentDraft == null) ? prompt(i18n('enterDraftTitle')) : this.$scope.drafts[this.$scope.currentDraft].title,
};
if (this.$scope.currentDraft != null && this.$scope.currentDraft >= 0) {
this.$scope.drafts[this.$scope.currentDraft] = newDraft;
}
else {
this.$scope.drafts.push(newDraft);
}
localStorage.setItem('drafts', JSON.stringify(this.$scope.drafts));
if (this.$scope.drafts.length > 50) {
this.$scope.drafts.splice(0, this.$scope.drafts.length - 50);
}
this.$scope.items.grid = [];
this.notification.success(i18n('draftSaved'));
}
deleteDraft() {
if (!window.confirm(i18n('confirmDeleteDraft'))) {
return;
}
const index = this.$scope.currentDraft;
if (index < 0 || index >= this.$scope.drafts.length) {
this.notification.error(i18n('invalidDraftIndex'));
return;
}
this.$scope.drafts.splice(index, 1);
localStorage.setItem('drafts', JSON.stringify(this.$scope.drafts));
this.notification.success(i18n('done'));
this.$scope.items.grid = [];
this.$scope.currentDraft = null;
}
showDraftBarcode() {
if (this.$scope.items.grid.length === 0) {
this.notification.error(i18n('noItems'));
return;
}
const labelGenerator = new LabelGenerator();
const barcodeString = labelGenerator.createPackageBarcode(this.$scope.items.grid);
const dm = labelGenerator.createDMDiv(barcodeString, true).outerHTML;
void this.modalService.showModal({
template: `
`,
scopeProperties: {
closeModal: () => {
this.modalService.modalInstance?.close();
},
},
size: 'md',
});
}
loadDraft(index) {
if (index < 0 || index >= this.$scope.drafts.length) {
this.notification.error(i18n('invalidDraftIndex'));
return;
}
const draft = this.$scope.drafts[index];
if (!draft || !draft.items || draft.items.length === 0) {
this.notification.error(i18n('draftIsEmpty'));
return;
}
this.$scope.items.grid = draft.items.map((item) => ({
...item,
printedAt: new Date().toISOString(),
retrievedAt: new Date().toISOString(),
}));
this.$scope.currentDraft = index;
}
}
class LabelerInterface {
active = false;
isActive() {
this.active = (document.querySelector('.item-search-container') !== null);
return this.active;
}
init() {
if (this.isActive()) {
return true;
}
const mainPage = document.querySelector('.main-container');
const navbar = document.querySelector('.navbar-dark');
const footer = document.querySelector('.footer');
if ((navbar == null) || (footer == null) || (mainPage == null)) {
return false;
}
this.removeElements(navbar);
this.injectHtml(mainPage);
this.removeElements(footer, mainPage);
this.changeDocumentTitle();
document.body.classList.add('label-print-interface');
this.bindEvents();
return true;
}
removeElements(...elements) {
elements.forEach((el => { el.remove(); }));
}
injectHtml(mainPage) {
const app = angular.module('b1', []);
app.controller('DynamicController', ['$scope', LabelerController]);
angular.module('b1').filter('i18n', () => {
return (key) => i18n(key);
});
const template = mainHTML();
const container = document.createElement('div');
container.setAttribute('ng-controller', 'DynamicController');
container.innerHTML = template;
mainPage.insertAdjacentElement('beforebegin', container);
angular.injector(['ng', 'b1']).invoke(['$compile', '$rootScope', function ($compile, $rootScope) {
const scope = $rootScope.$new();
$compile(container)(scope);
scope.$digest();
}]);
}
bindEvents() {
const mainInput = document.getElementById('barcodeInput');
if (mainInput != null) {
mainInput.focus();
document.addEventListener('click', (event) => {
let clickedInsideModal = false;
const modals = document.querySelectorAll('.modal.in');
modals.forEach(modal => {
if (modal.contains(event.target)) {
clickedInsideModal = true;
}
});
if (!clickedInsideModal && mainInput != null) {
mainInput.focus();
}
});
}
}
changeDocumentTitle() {
document.title = i18n('labels');
}
}
class LabelsUserscript {
interface = new LabelerInterface();
constructor() {
this.init();
console.debug('LabelsUserscript initialized');
}
init() {
this.addStyles();
if (unsafeWindow.currentUser != null &&
unsafeWindow.currentUser.name != null &&
unsafeWindow.currentUser.typeId <= 3) {
this.addActivateButton();
}
else if (unsafeWindow.currentUser != null &&
unsafeWindow.currentUser.name != null) {
this.interface.init();
}
}
addStyles() {
const style = document.createElement('style');
style.textContent = all;
document.head.appendChild(style);
}
addActivateButton() {
const navbarShortcuts = document.querySelector('.breadcrumbs');
if (navbarShortcuts != null) {
const button = document.createElement('button');
button.textContent = i18n('labels');
button.className = 'btn btn-sm';
button.addEventListener('click', () => {
this.interface.init();
});
navbarShortcuts.appendChild(button);
}
return navbarShortcuts != null;
}
}
window.addEventListener('load', () => {
void new LabelsUserscript();
});
})();