diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 000000000..8552fed9a --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,26 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: npm test CI + +on: + push: + branches: [ "master", "develop" ] + pull_request: + branches: [ "master", "develop" ] + workflow_dispatch: + +jobs: + build-and-test: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: 'npm' + - run: npm ci + - run: npm run build --if-present + - run: npm test -- --browsers=ChromeHeadless --watch=false diff --git a/config.xml b/config.xml index ce93948c4..0fa6ab0b3 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - + Beanconqueror Lars Saalbach diff --git a/package-lock.json b/package-lock.json index 280f85555..7ba5d102f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,8 @@ "@ionic/cordova-builders": "^8.0.0", "@ionic/storage": "^4.0.0", "@ionic/storage-angular": "^4.0.0", + "@meticulous-home/espresso-api": "^0.3.1", + "@meticulous-home/espresso-profile": "^0.4.0", "@ngx-translate/core": "^15.0.0", "@ngx-translate/http-loader": "^4.0.0", "@zip.js/zip.js": "^2.7.14", @@ -61,7 +63,6 @@ "lodash": "^4.17.21", "long": "^5.2.0", "luxon": "^2.3.0", - "meticulous-api": "github:FFFuego/meticulous-typescript-api#dist", "moment": "^2.29.4", "ngx-stars": "^1.6.4", "postcss": "^8.3.5", @@ -91,7 +92,7 @@ "@wisdomgarden/cordova-plugin-filepath": "git+https://github.com/wisdom-garden/cordova-plugin-filepath.git", "cordova-android": "^13.0.0", "cordova-clipboard": "^1.3.0", - "cordova-ios": "^7.1.0", + "cordova-ios": "^7.1.1", "cordova-plugin-add-swift-support": "^2.0.2", "cordova-plugin-advanced-http": "^3.3.1", "cordova-plugin-android-permissions": "^1.1.5", @@ -6294,6 +6295,23 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@meticulous-home/espresso-api": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@meticulous-home/espresso-api/-/espresso-api-0.3.1.tgz", + "integrity": "sha512-KeZuK0RDDc9HhMNxyYj0BRfEgGvNOUKu5zyn8RYI4ezUCnn61PP4FbykiB7YxzBvsr8PT+5BOPyXSvqkDaC1FA==", + "license": "GPLv3", + "dependencies": { + "axios": "^1.6.8", + "meticulous-typescript-profile": "github:MeticulousHome/meticulous-typescript-profile#dist", + "socket.io-client": "^4.7.5" + } + }, + "node_modules/@meticulous-home/espresso-profile": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@meticulous-home/espresso-profile/-/espresso-profile-0.4.0.tgz", + "integrity": "sha512-jHG2y6F6mi5YTFE0nmyc5NKUGmyDxorLiix1h8CB5ch/Plae+fvD7RsxetA6nh3lAn8Zfz9U9am2kolokY0Zyg==", + "license": "GPL-3.0" + }, "node_modules/@netflix/nerror": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@netflix/nerror/-/nerror-1.1.3.tgz", @@ -9262,10 +9280,11 @@ } }, "node_modules/cordova-ios": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-7.1.0.tgz", - "integrity": "sha512-9/vPU+GWRdfxNIkAc9Gq6yejMIgpy59ycP8WyVJJ7HfuPSTBUQT8AS2h5ZJOeN4Y/URyEwxQCrAqSADDBVCESA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-7.1.1.tgz", + "integrity": "sha512-JwTyPxWcAZlbIOR5QO6TaJzkoSzfrp7jrlX01bWZ7Sxp0PYXejAJbA6J0W4u11M+atrQRNimNltZDyAlSBW2tw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "cordova-common": "^5.0.0", "elementtree": "^0.1.7", @@ -9286,6 +9305,7 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=16" } @@ -9295,6 +9315,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -12750,6 +12771,7 @@ "resolved": "https://registry.npmjs.org/ios-sim/-/ios-sim-8.0.2.tgz", "integrity": "sha512-P7nEG771bfd+JoMRjnis1gpZOkjTUUxu+4Ek1Z+eoaEEoT9byllU9pxfQ8Df7hL3gSkIQxNwTSLhos2I8tWUQA==", "dev": true, + "license": "MIT", "dependencies": { "bplist-parser": "^0.0.6", "nopt": "1.0.9", @@ -12767,13 +12789,15 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.0.6.tgz", "integrity": "sha512-fGeghPEH4Eytvf+Mi446aKcDqvkA/+eh6r7QGiZWMQG6TzqrnsToLP379XFfqRSZ41+676hhGIm++maNST1Apw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ios-sim/node_modules/nopt": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.9.tgz", "integrity": "sha512-CmUZ3rzN0/4kRHum5pGRiGkhmBMzgtEDxrZVHqRJDSv8qK6s+wzaig/xeyB22Due5aZQeTiEZg/nrmMH2tapDQ==", "dev": true, + "license": "MIT", "dependencies": { "abbrev": "1" }, @@ -15075,16 +15099,6 @@ "node": ">= 0.6" } }, - "node_modules/meticulous-api": { - "version": "0.0.2", - "resolved": "git+ssh://git@github.com/FFFuego/meticulous-typescript-api.git#c1ae8038e22db7ff9547f88038b9566b4f1f9718", - "license": "GPLv3", - "dependencies": { - "axios": "^1.6.8", - "meticulous-typescript-profile": "github:MeticulousHome/meticulous-typescript-profile#dist", - "socket.io-client": "^4.7.5" - } - }, "node_modules/meticulous-typescript-profile": { "version": "0.1.0", "resolved": "git+ssh://git@github.com/MeticulousHome/meticulous-typescript-profile.git#c4520f2238770d8432272e2fbc5d29f90a78720d", @@ -18915,6 +18929,7 @@ "resolved": "https://registry.npmjs.org/simctl/-/simctl-2.0.3.tgz", "integrity": "sha512-kKCak0yszxHae5eVWcmrjV3ouUGac3sjlhjdLWpyPu4eiQcWoHsCrqS34kkgzHN8Ystqkh/LFjzrldk/g3BYJg==", "dev": true, + "license": "MIT", "dependencies": { "shelljs": "^0.8.5", "tail": "^0.4.0" @@ -21824,6 +21839,7 @@ "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" @@ -21837,6 +21853,7 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", "dev": true, + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -21865,7 +21882,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.1.0.tgz", "integrity": "sha512-B/T4sDK8Z6aUh/qNr7mjKAwwncIljFuUP+DO/D5hloYFj+90O88z8Wf7oSucZTHxBAsC1/CTP4rtx/x1Uf72Mg==", - "dev": true + "dev": true, + "license": "MIT License" }, "node_modules/xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 316b14aff..457cd34b9 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,8 @@ "@ionic/cordova-builders": "^8.0.0", "@ionic/storage": "^4.0.0", "@ionic/storage-angular": "^4.0.0", + "@meticulous-home/espresso-api": "^0.3.1", + "@meticulous-home/espresso-profile": "^0.4.0", "@ngx-translate/core": "^15.0.0", "@ngx-translate/http-loader": "^4.0.0", "@zip.js/zip.js": "^2.7.14", @@ -68,7 +70,6 @@ "lodash": "^4.17.21", "long": "^5.2.0", "luxon": "^2.3.0", - "meticulous-api": "github:FFFuego/meticulous-typescript-api#dist", "moment": "^2.29.4", "ngx-stars": "^1.6.4", "postcss": "^8.3.5", @@ -98,7 +99,7 @@ "@wisdomgarden/cordova-plugin-filepath": "git+https://github.com/wisdom-garden/cordova-plugin-filepath.git", "cordova-android": "^13.0.0", "cordova-clipboard": "^1.3.0", - "cordova-ios": "^7.1.0", + "cordova-ios": "^7.1.1", "cordova-plugin-add-swift-support": "^2.0.2", "cordova-plugin-advanced-http": "^3.3.1", "cordova-plugin-android-permissions": "^1.1.5", @@ -210,22 +211,22 @@ "cordova-plugin-nativestorage": {}, "cordova-plugin-androidx": {}, "cordova-plugin-androidx-adapter": {}, - "cordova-plugin-ble-central": { - "BLUETOOTH_USAGE_DESCRIPTION": "Bluetooth access needed to connect smartscales", - "IOS_INIT_ON_LOAD": "false", - "ACCESS_BACKGROUND_LOCATION": "false", - "BLUETOOTH_RESTORE_STATE": "false" - }, "cordova-plugin-inappbrowser": {}, "skwas-cordova-plugin-datetimepicker": {}, "cordova-plugin-statusbar": {}, "cordova-plugin-file": { "ANDROIDX_WEBKIT_VERSION": "1.4.0" + }, + "cordova-plugin-ble-central": { + "BLUETOOTH_USAGE_DESCRIPTION": "Bluetooth access needed to connect smartscales", + "IOS_INIT_ON_LOAD": "false", + "ACCESS_BACKGROUND_LOCATION": "false", + "BLUETOOTH_RESTORE_STATE": "false" } }, "platforms": [ - "ios", - "android" + "android", + "ios" ] }, "platforms": [ @@ -240,4 +241,4 @@ "*.css": "stylelint --fix", "*.{ts,js,css,md}": "prettier --write" } -} +} \ No newline at end of file diff --git a/resources/excel-templates/Green_Bean_Import_Template.xlsx b/resources/excel-templates/Green_Bean_Import_Template.xlsx new file mode 100644 index 000000000..cacd3bdd2 Binary files /dev/null and b/resources/excel-templates/Green_Bean_Import_Template.xlsx differ diff --git a/resources/excel-templates/Roasted_Bean_Import_Template.xlsx b/resources/excel-templates/Roasted_Bean_Import_Template.xlsx new file mode 100644 index 000000000..05c8fe825 Binary files /dev/null and b/resources/excel-templates/Roasted_Bean_Import_Template.xlsx differ diff --git a/src/app/app.component.html b/src/app/app.component.html index 6b5e19ef7..5926893c5 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -54,27 +54,9 @@ {{pages.settings.title | translate}} - - -

{{"SUPPORT_ME" | translate}}
-
- - - - - - - - - - - - - - - + @@ -99,10 +81,6 @@ - - - - { let component: BeanArchivePopoverComponent; @@ -18,8 +18,13 @@ describe('BeanArchivePopoverComponent', () => { const settingsMock = {} as Settings; TestBed.configureTestingModule({ - declarations: [BeanArchivePopoverComponent, TranslatePipe], - imports: [IonicModule.forRoot()], + declarations: [BeanArchivePopoverComponent], + imports: [ + IonicModule.forRoot(), + TranslateModule.forRoot(), + TranslateModule.forChild(), + FormsModule, + ], providers: [ { provide: UIBeanStorage, @@ -43,10 +48,6 @@ describe('BeanArchivePopoverComponent', () => { toFixedIfNecessary: (_value, _dp): number => 0, } as UIHelper, }, - { - provide: TranslateService, - useValue: TranslateServiceMock, - }, ], }).compileComponents(); diff --git a/src/app/beans/bean-filter/bean-filter.component.spec.ts b/src/app/beans/bean-filter/bean-filter.component.spec.ts index 1bcc8a175..786f380e8 100644 --- a/src/app/beans/bean-filter/bean-filter.component.spec.ts +++ b/src/app/beans/bean-filter/bean-filter.component.spec.ts @@ -9,10 +9,10 @@ import { UIPreparationStorage } from '../../../services/uiPreparationStorage'; import { UIBeanStorage } from '../../../services/uiBeanStorage'; import { UIMillStorage } from '../../../services/uiMillStorage'; import { IBeanPageFilter } from '../../../interfaces/bean/iBeanPageFilter'; -import { TranslatePipe, TranslateService } from '@ngx-translate/core'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateServiceMock } from '../../../classes/mock'; import { FormsModule } from '@angular/forms'; -import { KeysPipe } from '../../../pipes/keys'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BeanFilterComponent', () => { let component: BeanFilterComponent; @@ -39,8 +39,13 @@ describe('BeanFilterComponent', () => { }, } as Settings; TestBed.configureTestingModule({ - declarations: [BeanFilterComponent, TranslatePipe, KeysPipe], - imports: [IonicModule.forRoot(), FormsModule], + declarations: [BeanFilterComponent], + imports: [ + IonicModule.forRoot(), + FormsModule, + PipesModule, + TranslateModule.forChild(), + ], providers: [ { provide: UIHelper, diff --git a/src/app/beans/bean-modal-select/bean-modal-select.component.html b/src/app/beans/bean-modal-select/bean-modal-select.component.html index 28499557f..45fa1b392 100644 --- a/src/app/beans/bean-modal-select/bean-modal-select.component.html +++ b/src/app/beans/bean-modal-select/bean-modal-select.component.html @@ -101,6 +101,11 @@
+
+ +
+
@@ -178,6 +183,11 @@
+
+ +
+ @@ -260,6 +270,11 @@
( +
+ +
+
@@ -348,7 +363,11 @@
( +
+ +
+
@@ -430,6 +449,11 @@
+
+ +
+
@@ -511,7 +535,11 @@
( +
+ +
+
diff --git a/src/app/beans/bean-modal-select/bean-modal-select.component.ts b/src/app/beans/bean-modal-select/bean-modal-select.component.ts index a862ad998..f04058aee 100644 --- a/src/app/beans/bean-modal-select/bean-modal-select.component.ts +++ b/src/app/beans/bean-modal-select/bean-modal-select.component.ts @@ -409,7 +409,8 @@ export class BeanModalSelectComponent implements OnInit { e.name?.toLowerCase().includes(searchText) || e.roaster?.toLowerCase().includes(searchText) || e.aromatics?.toLowerCase().includes(searchText) || - e.frozenId?.toLowerCase().includes(searchText) + e.frozenId?.toLowerCase().includes(searchText) || + e.ean_article_number?.toLowerCase().includes(searchText) ); } if (_type === 'open') { diff --git a/src/app/beans/bean-popover-actions/bean-popover-actions.component.spec.ts b/src/app/beans/bean-popover-actions/bean-popover-actions.component.spec.ts index 6cddb6c55..4dab5c1e5 100644 --- a/src/app/beans/bean-popover-actions/bean-popover-actions.component.spec.ts +++ b/src/app/beans/bean-popover-actions/bean-popover-actions.component.spec.ts @@ -5,8 +5,8 @@ import { BeanPopoverActionsComponent } from './bean-popover-actions.component'; import { UIHelper } from '../../../services/uiHelper'; import { UIBeanHelper } from '../../../services/uiBeanHelper'; import { IBean } from '../../../interfaces/bean/iBean'; -import { TranslatePipe, TranslateService } from '@ngx-translate/core'; -import { TranslateServiceMock } from '../../../classes/mock'; +import { IonicStorageModule } from '@ionic/storage-angular'; +import { TranslateModule } from '@ngx-translate/core'; describe('BeanPopoverActionsComponent', () => { let component: BeanPopoverActionsComponent; @@ -14,8 +14,13 @@ describe('BeanPopoverActionsComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [BeanPopoverActionsComponent, TranslatePipe], - imports: [IonicModule.forRoot()], + declarations: [BeanPopoverActionsComponent], + imports: [ + IonicModule.forRoot(), + IonicStorageModule.forRoot(), + TranslateModule.forRoot(), + TranslateModule.forChild(), + ], providers: [ { provide: NavParams, @@ -39,17 +44,15 @@ describe('BeanPopoverActionsComponent', () => { provide: UIBeanHelper, useValue: {}, }, - { - provide: TranslateService, - useValue: TranslateServiceMock, - }, ], }).compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(BeanPopoverActionsComponent); component = fixture.componentInstance; fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.html b/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.html index 3d977ecbc..773cd6f8a 100644 --- a/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.html +++ b/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.html @@ -43,11 +43,11 @@ prevent-characters remove-empty-number spellcheck="false" type="text" tabIndex="2"> - - diff --git a/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.spec.ts b/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.spec.ts index 4a7219ed7..b9887205e 100644 --- a/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.spec.ts +++ b/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.spec.ts @@ -2,6 +2,12 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { IonicModule } from '@ionic/angular'; import { BeanPopoverFreezeComponent } from './bean-popover-freeze.component'; +import { IonicStorageModule } from '@ionic/storage-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { UIHelper } from 'src/services/uiHelper'; +import { UIHelperMock } from 'src/classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; +import { Bean } from 'src/classes/bean/bean'; describe('BeanPopoverFreezeComponent', () => { let component: BeanPopoverFreezeComponent; @@ -10,13 +16,23 @@ describe('BeanPopoverFreezeComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [BeanPopoverFreezeComponent], - imports: [IonicModule.forRoot()], + imports: [ + IonicModule.forRoot(), + IonicStorageModule.forRoot(), + TranslateModule.forChild(), + TranslateModule.forRoot(), + PipesModule, + ], + providers: [{ provide: UIHelper, useClass: UIHelperMock }], }).compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(BeanPopoverFreezeComponent); component = fixture.componentInstance; + component.bean = new Bean(); fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.ts b/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.ts index d7a006e28..8bb2d0fd3 100644 --- a/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.ts +++ b/src/app/beans/bean-popover-freeze/bean-popover-freeze.component.ts @@ -59,7 +59,10 @@ export class BeanPopoverFreezeComponent implements OnInit { public ngOnInit() { // cant be done in constructor, else the bean object is not known - this.leftOverBeanBagWeight = this.bean.weight - this.getUsedWeightCount(); + this.leftOverBeanBagWeight = this.uiHelper.toFixedIfNecessary( + this.bean.weight - this.getUsedWeightCount(), + 1 + ); } public getUsedWeightCount(): number { @@ -86,8 +89,10 @@ export class BeanPopoverFreezeComponent implements OnInit { } public async save() { - const spillOver = - this.leftOverBeanBagWeight - this.getActualFreezingQuantity(); + const spillOver = this.uiHelper.toFixedIfNecessary( + this.leftOverBeanBagWeight - this.getActualFreezingQuantity(), + 1 + ); let index = 1; @@ -109,10 +114,16 @@ export class BeanPopoverFreezeComponent implements OnInit { this.bean.config.uuid ); if (brews.length > 0) { - const oldWeight = this.bean.weight; - this.bean.weight = this.bean.weight - this.getActualFreezingQuantity(); + const oldWeight = this.uiHelper.toFixedIfNecessary(this.bean.weight, 1); + this.bean.weight = this.uiHelper.toFixedIfNecessary( + this.bean.weight - this.getActualFreezingQuantity(), + 1 + ); try { - const newCost = (this.bean.cost * this.bean.weight) / oldWeight; + const newCost = this.uiHelper.toFixedIfNecessary( + (this.bean.cost * this.bean.weight) / oldWeight, + 2 + ); this.bean.cost = newCost; } catch (ex) { this.bean.cost = 0; @@ -148,10 +159,16 @@ export class BeanPopoverFreezeComponent implements OnInit { /** * Because we had already maybe some brews, we take the bean weight, and subtract it with the spill over, because just using the spill over would not take previus brews into account */ - const oldWeight = this.bean.weight; - this.bean.weight = this.bean.weight - this.getActualFreezingQuantity(); + const oldWeight = this.uiHelper.toFixedIfNecessary(this.bean.weight, 1); + this.bean.weight = this.uiHelper.toFixedIfNecessary( + this.bean.weight - this.getActualFreezingQuantity(), + 1 + ); try { - const newCost = (this.bean.cost * this.bean.weight) / oldWeight; + const newCost = this.uiHelper.toFixedIfNecessary( + (this.bean.cost * this.bean.weight) / oldWeight, + 2 + ); this.bean.cost = newCost; } catch (ex) { this.bean.cost = 0; @@ -189,13 +206,16 @@ export class BeanPopoverFreezeComponent implements OnInit { if (this.bean.cost !== 0) { try { - const newCost = (this.bean.cost * _freezingWeight) / this.bean.weight; + const newCost = this.uiHelper.toFixedIfNecessary( + (this.bean.cost * _freezingWeight) / this.bean.weight, + 2 + ); clonedBean.cost = newCost; } catch (ex) { clonedBean.cost = 0; } } - clonedBean.weight = _freezingWeight; + clonedBean.weight = this.uiHelper.toFixedIfNecessary(_freezingWeight, 1); clonedBean.config = new Config(); const newClonedBean = await this.uiBeanStorage.add(clonedBean); const newBean: Bean = new Bean(); @@ -244,18 +264,35 @@ export class BeanPopoverFreezeComponent implements OnInit { return quantity; } + public isAddingBagDisabled() { + if (this.freezePartialBagGrams <= 0) { + return true; + } + if ( + this.uiHelper.toFixedIfNecessary( + Number(this.freezePartialBagGrams) + this.getActualFreezingQuantity(), + 1 + ) > this.leftOverBeanBagWeight + ) { + return true; + } + return false; + } + public addOnePartialBag() { this.addedBags.push({ - weight: this.freezePartialBagGrams, + weight: Number(this.freezePartialBagGrams), type: this.frozenStorage, }); - const leftFreezingCount = - this.leftOverBeanBagWeight - this.getActualFreezingQuantity(); + const leftFreezingCount = this.uiHelper.toFixedIfNecessary( + this.leftOverBeanBagWeight - this.getActualFreezingQuantity(), + 1 + ); if (leftFreezingCount < this.freezePartialBagGrams) { this.freezePartialBagGrams = this.uiHelper.toFixedIfNecessary( leftFreezingCount, - 2 + 1 ); } } @@ -263,16 +300,18 @@ export class BeanPopoverFreezeComponent implements OnInit { public addMaxPartialBags() { while (true) { this.addedBags.push({ - weight: this.freezePartialBagGrams, + weight: Number(this.freezePartialBagGrams), type: this.frozenStorage, }); - const leftFreezingCount = - this.leftOverBeanBagWeight - this.getActualFreezingQuantity(); + const leftFreezingCount = this.uiHelper.toFixedIfNecessary( + this.leftOverBeanBagWeight - this.getActualFreezingQuantity(), + 1 + ); if (leftFreezingCount < this.freezePartialBagGrams) { this.freezePartialBagGrams = this.uiHelper.toFixedIfNecessary( leftFreezingCount, - 2 + 1 ); break; } @@ -281,8 +320,10 @@ export class BeanPopoverFreezeComponent implements OnInit { public deleteBag(_index) { this.addedBags.splice(_index, 1); - const leftFreezingCount = - this.leftOverBeanBagWeight - this.getActualFreezingQuantity(); + const leftFreezingCount = this.uiHelper.toFixedIfNecessary( + this.leftOverBeanBagWeight - this.getActualFreezingQuantity(), + 1 + ); if (leftFreezingCount < this.freezePartialBagGrams) { this.freezePartialBagGrams = leftFreezingCount; } diff --git a/src/app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component.spec.ts b/src/app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component.spec.ts index 4526042a0..604d9322e 100644 --- a/src/app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component.spec.ts +++ b/src/app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component.spec.ts @@ -1,7 +1,11 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { IonicModule } from '@ionic/angular'; import { BeanPopoverFrozenListComponent } from './bean-popover-frozen-list.component'; +import { IonicStorageModule } from '@ionic/storage-angular'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { UIHelperMock } from 'src/classes/mock'; +import { UIHelper } from 'src/services/uiHelper'; +import { IonicModule } from '@ionic/angular'; describe('BeanPopoverFrozenListComponent', () => { let component: BeanPopoverFrozenListComponent; @@ -10,13 +14,21 @@ describe('BeanPopoverFrozenListComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [BeanPopoverFrozenListComponent], - imports: [IonicModule.forRoot()], + imports: [ + IonicModule.forRoot(), + IonicStorageModule.forRoot(), + TranslateModule.forChild(), + TranslateModule.forRoot(), + ], + providers: [{ provide: UIHelper, useClass: UIHelperMock }], }).compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(BeanPopoverFrozenListComponent); component = fixture.componentInstance; fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component.ts b/src/app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component.ts index 29c022e95..ebd9e99e3 100644 --- a/src/app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component.ts +++ b/src/app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component.ts @@ -7,11 +7,9 @@ import { ViewChild, } from '@angular/core'; import { Bean } from '../../../classes/bean/bean'; -import { Brew } from '../../../classes/brew/brew'; import { AgVirtualSrollComponent } from 'ag-virtual-scroll'; import { ModalController } from '@ionic/angular'; import { UIBeanHelper } from '../../../services/uiBeanHelper'; -import { UIBrewHelper } from '../../../services/uiBrewHelper'; @Component({ selector: 'app-bean-popover-frozen-list', diff --git a/src/app/beans/bean-popover-list/bean-popover-list.component.html b/src/app/beans/bean-popover-list/bean-popover-list.component.html new file mode 100644 index 000000000..08d289c2e --- /dev/null +++ b/src/app/beans/bean-popover-list/bean-popover-list.component.html @@ -0,0 +1,19 @@ + + + {{"BEAN_LIST" | translate}} + + + + + + + + + + + + + + + diff --git a/src/app/beans/bean-popover-list/bean-popover-list.component.scss b/src/app/beans/bean-popover-list/bean-popover-list.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/beans/bean-popover-list/bean-popover-list.component.spec.ts b/src/app/beans/bean-popover-list/bean-popover-list.component.spec.ts new file mode 100644 index 000000000..5af36a70d --- /dev/null +++ b/src/app/beans/bean-popover-list/bean-popover-list.component.spec.ts @@ -0,0 +1,32 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { BeanPopoverListComponent } from './bean-popover-list.component'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { TranslateServiceMock } from '../../../classes/mock'; + +describe('BeanPopoverListComponent', () => { + let component: BeanPopoverListComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [BeanPopoverListComponent], + imports: [IonicModule.forRoot(), TranslateModule.forRoot()], + providers: [ + { + provide: TranslateService, + useValue: TranslateServiceMock, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(BeanPopoverListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/beans/bean-popover-list/bean-popover-list.component.ts b/src/app/beans/bean-popover-list/bean-popover-list.component.ts new file mode 100644 index 000000000..7d565d153 --- /dev/null +++ b/src/app/beans/bean-popover-list/bean-popover-list.component.ts @@ -0,0 +1,68 @@ +import { + Component, + ElementRef, + HostListener, + Input, + OnInit, + ViewChild, +} from '@angular/core'; +import { Bean } from '../../../classes/bean/bean'; +import { AgVirtualSrollComponent } from 'ag-virtual-scroll'; +import { ModalController } from '@ionic/angular'; + +@Component({ + selector: 'app-bean-popover-list', + templateUrl: './bean-popover-list.component.html', + styleUrls: ['./bean-popover-list.component.scss'], +}) +export class BeanPopoverListComponent { + public static readonly COMPONENT_ID = 'bean-popover-list'; + + @Input() public beansList: Array = undefined; + + @ViewChild('openScroll', { read: AgVirtualSrollComponent, static: false }) + public openScroll: AgVirtualSrollComponent; + + @ViewChild('beanContent', { read: ElementRef }) + public beanContent: ElementRef; + + constructor(private readonly modalController: ModalController) {} + + public async ionViewWillEnter() { + this.loadBrews(); + } + + @HostListener('window:resize') + @HostListener('window:orientationchange', ['$event']) + public onOrientationChange(_event: any) { + this.retriggerScroll(); + } + + private retriggerScroll() { + setTimeout(async () => { + const el = this.beanContent.nativeElement; + let scrollComponent: AgVirtualSrollComponent; + scrollComponent = this.openScroll; + scrollComponent.el.style.height = + el.offsetHeight - scrollComponent.el.offsetTop + 'px'; + }, 150); + } + + public loadBrews() { + this.retriggerScroll(); + } + + public async brewAction(): Promise { + this.loadBrews(); + } + + public dismiss(): void { + this.modalController.dismiss( + { + dismissed: true, + }, + undefined, + BeanPopoverListComponent.COMPONENT_ID + ); + } +} diff --git a/src/app/beans/bean-sort/bean-sort.component.html b/src/app/beans/bean-sort/bean-sort.component.html index a5db09b4f..d7c7ed4d5 100644 --- a/src/app/beans/bean-sort/bean-sort.component.html +++ b/src/app/beans/bean-sort/bean-sort.component.html @@ -43,6 +43,10 @@ {{"BREW_DATA_RATING" | translate}} + + + {{"BEAN_SORT_BEAN_AGE" | translate}} + diff --git a/src/app/beans/beans-add/beans-add.component.spec.ts b/src/app/beans/beans-add/beans-add.component.spec.ts index d5028fa37..c9edc96b8 100644 --- a/src/app/beans/beans-add/beans-add.component.spec.ts +++ b/src/app/beans/beans-add/beans-add.component.spec.ts @@ -4,7 +4,6 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { BeansAddComponent } from './beans-add.component'; import { TranslateModule } from '@ngx-translate/core'; -import { KeysPipe } from '../../../pipes/keys'; import { FormsModule } from '@angular/forms'; import { IonicModule, ModalController } from '@ionic/angular'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; @@ -21,6 +20,7 @@ import { UIAnalytics } from '../../../services/uiAnalytics'; import { UIBeanHelper } from '../../../services/uiBeanHelper'; import { UISettingsStorage } from '../../../services/uiSettingsStorage'; import { Settings } from '../../../classes/settings/settings'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BeansAddComponent', () => { let component: BeansAddComponent; @@ -38,8 +38,9 @@ describe('BeansAddComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [BeansAddComponent, KeysPipe], + declarations: [BeansAddComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/beans/beans-add/beans-add.component.ts b/src/app/beans/beans-add/beans-add.component.ts index 830946364..81f5afaf6 100644 --- a/src/app/beans/beans-add/beans-add.component.ts +++ b/src/app/beans/beans-add/beans-add.component.ts @@ -64,6 +64,69 @@ export class BeansAddComponent implements OnInit { } } + private async checkIfInformationAreSetButNotDisplayed() { + try { + const params = this.settings.bean_manage_parameters; + if (this.data.bean_information.length > 0) { + const info: IBeanInformation = this.data.bean_information[0]; + let hasDataSet: boolean = false; + if (info.country && info.country !== '') { + hasDataSet = true; + } + if (info.region && info.region !== '') { + hasDataSet = true; + } + if (info.farm && info.farm !== '') { + hasDataSet = true; + } + if (info.farmer && info.farmer !== '') { + hasDataSet = true; + } + if (info.elevation && info.elevation !== '') { + hasDataSet = true; + } + if (info.harvest_time && info.harvest_time !== '') { + hasDataSet = true; + } + if (info.variety && info.variety !== '') { + hasDataSet = true; + } + if (info.processing && info.processing !== '') { + hasDataSet = true; + } + if (info.certification && info.certification !== '') { + hasDataSet = true; + } + if (info.percentage && info.percentage > 0) { + hasDataSet = true; + } + if (info.purchasing_price && info.purchasing_price > 0) { + hasDataSet = true; + } + if (info.fob_price && info.fob_price > 0) { + hasDataSet = true; + } + + if (params.bean_information === false && hasDataSet === true) { + //Woopsi doopsi, user hasn't enabled the bean_information, lets display him a popover + //#623 + try { + const yes = await this.uiAlert.showConfirm( + 'BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION', + 'INFORMATION', + true + ); + this.settings.bean_manage_parameters.bean_information = true; + await this.uiSettingsStorage.update(this.settings); + //Activate + } catch (ex) { + // Don't activate + } + } + } + } catch (ex) {} + } + public async ionViewWillEnter() { this.uiAnalytics.trackEvent(BEAN_TRACKING.TITLE, BEAN_TRACKING.ACTIONS.ADD); @@ -71,6 +134,7 @@ export class BeansAddComponent implements OnInit { // TODO how to handle roasting beans which wil be repeated? if (this.bean_template) { await this.__loadBean(this.bean_template); + await this.checkIfInformationAreSetButNotDisplayed(); } // Download images after loading the bean, else they would be copied :O diff --git a/src/app/beans/beans-edit/beans-edit.component.spec.ts b/src/app/beans/beans-edit/beans-edit.component.spec.ts index 6a3bcfa04..76ff6d186 100644 --- a/src/app/beans/beans-edit/beans-edit.component.spec.ts +++ b/src/app/beans/beans-edit/beans-edit.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { File } from '@awesome-cordova-plugins/file/ngx'; import { Camera } from '@awesome-cordova-plugins/camera/ngx'; @@ -19,6 +18,7 @@ import { UIAnalytics } from '../../../services/uiAnalytics'; import { UIBeanHelper } from '../../../services/uiBeanHelper'; import { UISettingsStorage } from '../../../services/uiSettingsStorage'; import { Settings } from '../../../classes/settings/settings'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BeansEditComponent', () => { let component: BeansEditComponent; @@ -32,8 +32,9 @@ describe('BeansEditComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [BeansEditComponent, KeysPipe, AsyncImageComponent], + declarations: [BeansEditComponent, AsyncImageComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/beans/beans.page.html b/src/app/beans/beans.page.html index 73bb7d49a..97fb274ce 100644 --- a/src/app/beans/beans.page.html +++ b/src/app/beans/beans.page.html @@ -74,6 +74,10 @@ + + + +
@@ -85,8 +89,8 @@
- - + @@ -109,6 +113,10 @@ + + + +
@@ -118,8 +126,8 @@ {{"PAGE_BEANS_LIST_YOU_GOT_NO_FROZEN_BEANS" | translate}}
- - + @@ -142,6 +150,10 @@ + + + +
@@ -151,8 +163,8 @@ {{"PAGE_BEANS_LIST_YOU_GOT_NO_FINISHED_BEANS" | translate}}
- - + diff --git a/src/app/beans/beans.page.spec.ts b/src/app/beans/beans.page.spec.ts index 0029fe0e7..6ee5db822 100644 --- a/src/app/beans/beans.page.spec.ts +++ b/src/app/beans/beans.page.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController } from '@ionic/angular'; -import { KeysPipe } from '../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { File } from '@awesome-cordova-plugins/file/ngx'; import { Camera } from '@awesome-cordova-plugins/camera/ngx'; @@ -14,12 +13,12 @@ import { ImagePicker } from '@awesome-cordova-plugins/image-picker/ngx'; import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions/ngx'; import { Router } from '@angular/router'; import { AsyncImageComponent } from '../../components/async-image/async-image.component'; -import { FormatDatePipe } from '../../pipes/formatDate'; import { UIBeanStorage } from '../../services/uiBeanStorage'; import { UISettingsStorage } from '../../services/uiSettingsStorage'; import { UIAnalytics } from '../../services/uiAnalytics'; import { IntentHandlerService } from '../../services/intentHandler/intent-handler.service'; import { UIBeanHelper } from '../../services/uiBeanHelper'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BeansPage', () => { let component: BeansPage; @@ -32,8 +31,9 @@ describe('BeansPage', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [BeansPage, KeysPipe, AsyncImageComponent, FormatDatePipe], + declarations: [BeansPage, AsyncImageComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/beans/beans.page.ts b/src/app/beans/beans.page.ts index c47a41e5d..63bff1870 100644 --- a/src/app/beans/beans.page.ts +++ b/src/app/beans/beans.page.ts @@ -74,6 +74,10 @@ export class BeansPage implements OnDestroy { sort_order: BEAN_SORT_ORDER.UNKOWN, }; + public openBeansCollapsed: boolean = false; + public archivedBeansCollapsed: boolean = false; + public frozenBeansCollapsed: boolean = false; + public archivedBeansFilterText: string = ''; public openBeansFilterText: string = ''; public frozenBeansFilterText: string = ''; @@ -100,10 +104,15 @@ export class BeansPage implements OnDestroy { this.settings = this.uiSettingsStorage.getSettings(); this.archivedBeansSort = this.settings.bean_sort.ARCHIVED; this.openBeansSort = this.settings.bean_sort.OPEN; + this.frozenBeansSort = this.settings.bean_sort.FROZEN; this.archivedBeansFilter = this.settings.bean_filter.ARCHIVED; this.frozenBeansFilter = this.settings.bean_filter.FROZEN; this.openBeansFilter = this.settings.bean_filter.OPEN; + + this.openBeansCollapsed = this.settings.bean_collapsed.OPEN; + this.archivedBeansCollapsed = this.settings.bean_collapsed.ARCHIVED; + this.frozenBeansCollapsed = this.settings.bean_collapsed.FROZEN; this.loadBeans(); this.beanStorageChangeSubscription = this.uiBeanStorage @@ -119,6 +128,39 @@ export class BeansPage implements OnDestroy { this.beanStorageChangeSubscription = undefined; } } + public isCollapseActive() { + let collapsed: boolean = false; + if (this.bean_segment === 'open') { + collapsed = this.openBeansCollapsed; + } else if (this.bean_segment === 'archive') { + collapsed = this.archivedBeansCollapsed; + } else if (this.bean_segment === 'frozen') { + collapsed = this.frozenBeansCollapsed; + } + return collapsed; + } + + public toggleCollapseBeans() { + if (this.bean_segment === 'open') { + this.openBeansCollapsed = !this.openBeansCollapsed; + } else if (this.bean_segment === 'archive') { + this.archivedBeansCollapsed = !this.archivedBeansCollapsed; + } else if (this.bean_segment === 'frozen') { + this.frozenBeansCollapsed = !this.frozenBeansCollapsed; + } + this.__saveCollapseFilter(); + + this.__initializeBeans(); + this.changeDetectorRef.detectChanges(); + this.retriggerScroll(); + } + private async __saveCollapseFilter() { + this.settings.bean_collapsed.OPEN = this.openBeansCollapsed; + this.settings.bean_collapsed.ARCHIVED = this.archivedBeansCollapsed; + this.settings.bean_collapsed.FROZEN = this.frozenBeansCollapsed; + await this.uiSettingsStorage.saveSettings(this.settings); + } + public loadBeans(): void { this.__initializeBeans(); this.changeDetectorRef.detectChanges(); @@ -495,10 +537,10 @@ export class BeansPage implements OnDestroy { break; case BEAN_SORT_AFTER.ROASTING_DATE: filterBeans = filterBeans.sort((a, b) => { - if (a.roastingDate > b.roastingDate) { + if (a.roastingDate < b.roastingDate) { return -1; } - if (a.roastingDate < b.roastingDate) { + if (a.roastingDate > b.roastingDate) { return 1; } return 0; @@ -506,10 +548,21 @@ export class BeansPage implements OnDestroy { break; case BEAN_SORT_AFTER.RATING: filterBeans = filterBeans.sort((a, b) => { + if (a.rating < b.rating) { + return -1; + } if (a.rating > b.rating) { + return 1; + } + return 0; + }); + break; + case BEAN_SORT_AFTER.BEAN_AGE: + filterBeans = filterBeans.sort((a, b) => { + if (a.beanAgeInDays() < b.beanAgeInDays()) { return -1; } - if (a.rating < b.rating) { + if (a.beanAgeInDays() > b.beanAgeInDays()) { return 1; } return 0; diff --git a/src/app/brew/brew-add/brew-add.component.html b/src/app/brew/brew-add/brew-add.component.html index 879e5da1a..1ebe900a8 100644 --- a/src/app/brew/brew-add/brew-add.component.html +++ b/src/app/brew/brew-add/brew-add.component.html @@ -18,6 +18,16 @@ + + + + + + + + + + diff --git a/src/app/brew/brew-add/brew-add.component.spec.ts b/src/app/brew/brew-add/brew-add.component.spec.ts index 8353394c1..130225fa6 100644 --- a/src/app/brew/brew-add/brew-add.component.spec.ts +++ b/src/app/brew/brew-add/brew-add.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock, UIHelperMock } from '../../../classes/mock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -14,7 +13,6 @@ import { Camera } from '@awesome-cordova-plugins/camera/ngx'; import { ImagePicker } from '@awesome-cordova-plugins/image-picker/ngx'; import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions/ngx'; import { Router } from '@angular/router'; -import { FormatDatePipe } from '../../../pipes/formatDate'; import { BrewTimerComponent } from '../../../components/brew-timer/brew-timer.component'; import { AsyncImageComponent } from '../../../components/async-image/async-image.component'; import { UIBeanStorage } from '../../../services/uiBeanStorage'; @@ -34,6 +32,7 @@ import { Preparation } from '../../../classes/preparation/preparation'; import { Mill } from '../../../classes/mill/mill'; import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; import { UIHelper } from '../../../services/uiHelper'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BrewAddComponent', () => { let component: BrewAddComponent; @@ -47,14 +46,9 @@ describe('BrewAddComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [ - BrewAddComponent, - KeysPipe, - FormatDatePipe, - BrewTimerComponent, - AsyncImageComponent, - ], + declarations: [BrewAddComponent, BrewTimerComponent, AsyncImageComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/brew/brew-choose-preparation-to-brew/brew-choose-preparation-to-brew.component.html b/src/app/brew/brew-choose-preparation-to-brew/brew-choose-preparation-to-brew.component.html index f77150771..b2f769298 100644 --- a/src/app/brew/brew-choose-preparation-to-brew/brew-choose-preparation-to-brew.component.html +++ b/src/app/brew/brew-choose-preparation-to-brew/brew-choose-preparation-to-brew.component.html @@ -8,7 +8,8 @@ - {{prep.name}} ({{"LAST_USE" | translate}}: + {{prep.name}} ({{"LAST_USE" | translate}}: {{lastUsed(prep) | formatDate:[settings?.date_format]}} -) diff --git a/src/app/brew/brew-detail/brew-detail.component.spec.ts b/src/app/brew/brew-detail/brew-detail.component.spec.ts index f923ee53a..ebb68450e 100644 --- a/src/app/brew/brew-detail/brew-detail.component.spec.ts +++ b/src/app/brew/brew-detail/brew-detail.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -18,6 +17,7 @@ import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ScreenOrientation } from '@awesome-cordova-plugins/screen-orientation/ngx'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BrewDetailComponent', () => { let component: BrewDetailComponent; @@ -31,8 +31,9 @@ describe('BrewDetailComponent', () => { CommonModule, IonicModule, HttpClientTestingModule, + PipesModule, ], - declarations: [BrewDetailComponent, KeysPipe], + declarations: [BrewDetailComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/brew/brew-edit/brew-edit.component.html b/src/app/brew/brew-edit/brew-edit.component.html index 72bb081fe..3531024c4 100644 --- a/src/app/brew/brew-edit/brew-edit.component.html +++ b/src/app/brew/brew-edit/brew-edit.component.html @@ -11,10 +11,20 @@ - + + + + + + + + + + + diff --git a/src/app/brew/brew-edit/brew-edit.component.spec.ts b/src/app/brew/brew-edit/brew-edit.component.spec.ts index 620d0f00b..981c0bb16 100644 --- a/src/app/brew/brew-edit/brew-edit.component.spec.ts +++ b/src/app/brew/brew-edit/brew-edit.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -15,11 +14,11 @@ import { ImagePicker } from '@awesome-cordova-plugins/image-picker/ngx'; import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions/ngx'; import { Router } from '@angular/router'; import { AsyncImageComponent } from '../../../components/async-image/async-image.component'; -import { FormatDatePipe } from '../../../pipes/formatDate'; import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { Insomnia } from '@awesome-cordova-plugins/insomnia/ngx'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BrewEditComponent', () => { let component: BrewEditComponent; @@ -34,13 +33,9 @@ describe('BrewEditComponent', () => { CommonModule, IonicModule, HttpClientTestingModule, + PipesModule, ], - declarations: [ - BrewEditComponent, - KeysPipe, - AsyncImageComponent, - FormatDatePipe, - ], + declarations: [BrewEditComponent, AsyncImageComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.html b/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.html new file mode 100644 index 000000000..9025f3160 --- /dev/null +++ b/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.html @@ -0,0 +1,42 @@ + + + {{"IMPORT_SHOT_FROM_METICULOUS" | translate}} + + + + + + + + + + + + + + + +

{{entry.profile.name}}

+
{{this.uiHelper.formatTimeNumber(entry.time * 1000)}}
+
+ +
+
+
+
+ + +
+
+ + + + {{"CANCEL" | translate}} + + + {{"CHOOSE" | translate}} + + + diff --git a/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.scss b/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.scss new file mode 100644 index 000000000..cbdca4145 --- /dev/null +++ b/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.scss @@ -0,0 +1,11 @@ +:host { + ion-item { + margin-left: 16px; + margin-right: 0px; + line-height: 12px; + font-size: 12px; + } + ion-card-content { + background:#FFFFFF; + } +} diff --git a/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.spec.ts b/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.spec.ts new file mode 100644 index 000000000..629913fa2 --- /dev/null +++ b/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.spec.ts @@ -0,0 +1,39 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { BrewModalImportShotMeticulousComponent } from './brew-modal-import-shot-meticulous.component'; +import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; +import { File } from '@awesome-cordova-plugins/file/ngx'; +import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; +import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { TranslateServiceMock } from '../../../classes/mock'; +describe('BrewModalImportShotMeticulousComponent', () => { + let component: BrewModalImportShotMeticulousComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [BrewModalImportShotMeticulousComponent], + imports: [IonicModule.forRoot(), TranslateModule.forRoot()], + providers: [ + { provide: InAppBrowser }, + { provide: File }, + { provide: SocialSharing }, + { provide: FileTransfer }, + { + provide: TranslateService, + useValue: TranslateServiceMock, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(BrewModalImportShotMeticulousComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.ts b/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.ts new file mode 100644 index 000000000..3ffbddffa --- /dev/null +++ b/src/app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component.ts @@ -0,0 +1,107 @@ +import { + Component, + ElementRef, + HostListener, + Input, + OnInit, + ViewChild, +} from '@angular/core'; + +import { MeticulousDevice } from '../../../classes/preparationDevice/meticulous/meticulousDevice'; +import { ModalController } from '@ionic/angular'; +import { HistoryListingEntry } from '@meticulous-home/espresso-api/dist/types'; +import { UIHelper } from '../../../services/uiHelper'; +import { AgVirtualSrollComponent } from 'ag-virtual-scroll'; + +@Component({ + selector: 'app-brew-modal-import-shot-meticulous', + templateUrl: './brew-modal-import-shot-meticulous.component.html', + styleUrls: ['./brew-modal-import-shot-meticulous.component.scss'], +}) +export class BrewModalImportShotMeticulousComponent implements OnInit { + public static COMPONENT_ID: string = 'brew-modal-import-shot-meticulous'; + + @Input() public meticulousDevice: MeticulousDevice; + public radioSelection: string; + public history: Array = []; + + @ViewChild('ionItemEl', { read: ElementRef, static: false }) + public ionItemEl: ElementRef; + + @ViewChild('historyShotContent', { read: ElementRef }) + public historyShotContent: ElementRef; + + @ViewChild('shotDataScroll', { + read: AgVirtualSrollComponent, + static: false, + }) + public shotDataScroll: AgVirtualSrollComponent; + + @ViewChild('footerContent', { read: ElementRef }) + public footerContent: ElementRef; + + constructor( + private readonly modalController: ModalController, + public readonly uiHelper: UIHelper + ) {} + + public ngOnInit() { + this.readHistory(); + } + + private async readHistory() { + this.history = await this.meticulousDevice?.getHistory(); + this.retriggerScroll(); + } + @HostListener('window:resize') + @HostListener('window:orientationchange', ['$event']) + public onOrientationChange(event) { + this.retriggerScroll(); + } + + private retriggerScroll() { + setTimeout(async () => { + const el = this.historyShotContent.nativeElement; + const scrollComponent: AgVirtualSrollComponent = this.shotDataScroll; + + if (scrollComponent) { + scrollComponent.el.style.height = el.offsetHeight - 20 + 'px'; + } + }, 150); + } + + public getElementOffsetWidth() { + if (this.ionItemEl?.nativeElement?.offsetWidth) { + return this.ionItemEl?.nativeElement?.offsetWidth - 50; + } + return 0; + } + + public dismiss(): void { + this.modalController.dismiss( + { + dismissed: true, + }, + undefined, + BrewModalImportShotMeticulousComponent.COMPONENT_ID + ); + } + public choose(): void { + let returningData; + + for (const entry of this.history) { + if (entry.id === this.radioSelection) { + returningData = entry; + break; + } + } + this.modalController.dismiss( + { + choosenHistory: returningData, + dismissed: true, + }, + undefined, + BrewModalImportShotMeticulousComponent.COMPONENT_ID + ); + } +} diff --git a/src/app/brew/brew-popover-actions/brew-popover-actions.component.html b/src/app/brew/brew-popover-actions/brew-popover-actions.component.html index 02819e3e7..79e2f04b4 100644 --- a/src/app/brew/brew-popover-actions/brew-popover-actions.component.html +++ b/src/app/brew/brew-popover-actions/brew-popover-actions.component.html @@ -55,6 +55,10 @@ {{"SHOW_VISUALIZER" | translate}} + + + + {{"SHOW_GRAPH" | translate}} diff --git a/src/app/brew/brew-popover-actions/brew-popover-actions.component.spec.ts b/src/app/brew/brew-popover-actions/brew-popover-actions.component.spec.ts index e47f74e5b..b0065aa54 100644 --- a/src/app/brew/brew-popover-actions/brew-popover-actions.component.spec.ts +++ b/src/app/brew/brew-popover-actions/brew-popover-actions.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -18,6 +17,7 @@ import { UIHelper } from '../../../services/uiHelper'; import { UIPreparationStorage } from '../../../services/uiPreparationStorage'; import { Brew } from '../../../classes/brew/brew'; import { Preparation } from '../../../classes/preparation/preparation'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BrewPopoverActionsComponent', () => { let component: BrewPopoverActionsComponent; @@ -30,8 +30,9 @@ describe('BrewPopoverActionsComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [BrewPopoverActionsComponent, KeysPipe], + declarations: [BrewPopoverActionsComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/brew/brew.page.html b/src/app/brew/brew.page.html index 6bdb6ffd4..776ca7fdb 100644 --- a/src/app/brew/brew.page.html +++ b/src/app/brew/brew.page.html @@ -55,6 +55,12 @@ + + + + + +
@@ -65,8 +71,8 @@
- - + @@ -84,6 +90,10 @@ + + + +
@@ -94,8 +104,8 @@
- - + diff --git a/src/app/brew/brew.page.spec.ts b/src/app/brew/brew.page.spec.ts index 15afed976..72e94b0a9 100644 --- a/src/app/brew/brew.page.spec.ts +++ b/src/app/brew/brew.page.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -18,6 +17,7 @@ import { BrewInformationComponent } from '../../components/brew-information/brew import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; import { UIHelper } from '../../services/uiHelper'; import { UIHelperMock } from '../../classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BrewPage', () => { let component: BrewPage; @@ -30,8 +30,9 @@ describe('BrewPage', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [BrewPage, KeysPipe, BrewInformationComponent], + declarations: [BrewPage, BrewInformationComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/brew/brew.page.ts b/src/app/brew/brew.page.ts index dfb6e6188..73b55b437 100644 --- a/src/app/brew/brew.page.ts +++ b/src/app/brew/brew.page.ts @@ -6,8 +6,7 @@ import { OnInit, ViewChild, } from '@angular/core'; -import { ModalController, Platform } from '@ionic/angular'; -import { UIAlert } from '../../services/uiAlert'; +import { ModalController } from '@ionic/angular'; import { UIHelper } from '../../services/uiHelper'; import { UIBrewStorage } from '../../services/uiBrewStorage'; import { UISettingsStorage } from '../../services/uiSettingsStorage'; @@ -19,7 +18,6 @@ import { Bean } from '../../classes/bean/bean'; import { BrewFilterComponent } from './brew-filter/brew-filter.component'; import { Settings } from '../../classes/settings/settings'; import { AgVirtualSrollComponent } from 'ag-virtual-scroll'; -import { UIAnalytics } from '../../services/uiAnalytics'; @Component({ selector: 'brew', @@ -49,18 +47,18 @@ export class BrewPage implements OnInit { public archivedBrewsFilter: IBrewPageFilter; public openBrewsFilter: IBrewPageFilter; + public openBrewsCollapsed: boolean = false; + public archivedBrewsCollapsed: boolean = false; + public settings: Settings; constructor( private readonly modalCtrl: ModalController, - private readonly platform: Platform, private readonly uiBrewStorage: UIBrewStorage, private readonly changeDetectorRef: ChangeDetectorRef, - private readonly uiAlert: UIAlert, public uiHelper: UIHelper, public uiBrewHelper: UIBrewHelper, - private readonly uiSettingsStorage: UISettingsStorage, - private readonly uiAnalytics: UIAnalytics + private readonly uiSettingsStorage: UISettingsStorage ) { this.settings = this.uiSettingsStorage.getSettings(); this.archivedBrewsFilter = this.settings.GET_BREW_FILTER(); @@ -70,6 +68,8 @@ export class BrewPage implements OnInit { public ionViewWillEnter(): void { this.archivedBrewsFilter = this.settings.brew_filter.ARCHIVED; this.openBrewsFilter = this.settings.brew_filter.OPEN; + this.openBrewsCollapsed = this.settings.brew_collapsed.OPEN; + this.archivedBrewsCollapsed = this.settings.brew_collapsed.ARCHIVED; this.loadBrews(); this.retriggerScroll(); @@ -123,6 +123,26 @@ export class BrewPage implements OnInit { this.__initializeBrewView('archiv'); } + public isCollapseActive() { + let collapsed: boolean = false; + if (this.brew_segment === 'open') { + collapsed = this.openBrewsCollapsed; + } else { + collapsed = this.archivedBrewsCollapsed; + } + return collapsed; + } + + public toggleCollapseBrews() { + if (this.brew_segment === 'open') { + this.openBrewsCollapsed = !this.openBrewsCollapsed; + } else { + this.archivedBrewsCollapsed = !this.archivedBrewsCollapsed; + } + this.__saveCollapseFilter(); + this.research(); + } + public isFilterActive(): boolean { let checkingFilter: IBrewPageFilter; let checkingFilterText: string = ''; @@ -237,6 +257,11 @@ export class BrewPage implements OnInit { this.settings.brew_filter.ARCHIVED = this.archivedBrewsFilter; await this.uiSettingsStorage.saveSettings(this.settings); } + private async __saveCollapseFilter() { + this.settings.brew_collapsed.OPEN = this.openBrewsCollapsed; + this.settings.brew_collapsed.ARCHIVED = this.archivedBrewsCollapsed; + await this.uiSettingsStorage.saveSettings(this.settings); + } public research() { this.__initializeBrewView(this.brew_segment); diff --git a/src/app/graph-section/graph/graph-detail/graph-detail.component.ts b/src/app/graph-section/graph/graph-detail/graph-detail.component.ts index 4bc1b9dd5..8999fb49f 100644 --- a/src/app/graph-section/graph/graph-detail/graph-detail.component.ts +++ b/src/app/graph-section/graph/graph-detail/graph-detail.component.ts @@ -11,7 +11,6 @@ import BeanconquerorFlowTestDataDummy from '../../../../assets/BeanconquerorFlow import { BrewFlow } from '../../../../classes/brew/brewFlow'; import { Settings } from '../../../../classes/settings/settings'; import { TranslateService } from '@ngx-translate/core'; -import { Graph } from '../../../../classes/graph/graph'; import { IGraph } from '../../../../interfaces/graph/iGraph'; import GRAPH_TRACKING from '../../../../data/tracking/graphTracking'; import { UIAnalytics } from '../../../../services/uiAnalytics'; @@ -19,6 +18,7 @@ import { UIHelper } from '../../../../services/uiHelper'; import { ModalController, Platform } from '@ionic/angular'; import { UIFileHelper } from '../../../../services/uiFileHelper'; import { UISettingsStorage } from '../../../../services/uiSettingsStorage'; +import { IBrew } from '../../../../interfaces/brew/iBrew'; declare var Plotly; @Component({ selector: 'app-graph-detail', @@ -44,9 +44,9 @@ export class GraphDetailComponent implements OnInit { @ViewChild('profileDiv', { read: ElementRef, static: true }) public profileDiv: ElementRef; - public data: Graph = new Graph(); - @Input() private graph: IGraph; + @Input() private brew: IBrew; + @Input() private flowProfileData: any; constructor( @@ -68,8 +68,9 @@ export class GraphDetailComponent implements OnInit { GRAPH_TRACKING.ACTIONS.DETAIL ); if (this.graph) { - this.data = this.uiHelper.copyData(this.graph); - await this.readFlowProfile(); + await this.readFlowProfile(this.graph.flow_profile); + } else if (this.brew) { + await this.readFlowProfile(this.brew.flow_profile); } else { this.flow_profile_raw = this.uiHelper.cloneData(this.flowProfileData); } @@ -174,6 +175,16 @@ export class GraphDetailComponent implements OnInit { }, }; + const graph_pressure_settings = this.settings.graph_pressure; + const suggestedMinPressure: number = graph_pressure_settings.lower; + let suggestedMaxPressure = graph_pressure_settings.upper; + try { + if (this.pressureTrace?.y.length > 0) { + suggestedMaxPressure = Math.max(...this.pressureTrace.y); + suggestedMaxPressure = Math.ceil(suggestedMaxPressure + 1); + } + } catch (ex) {} + layout['yaxis4'] = { title: '', titlefont: { color: '#05C793' }, @@ -183,7 +194,7 @@ export class GraphDetailComponent implements OnInit { side: 'right', showgrid: false, position: 0.93, - range: [0, 10], + range: [suggestedMinPressure, suggestedMaxPressure], visible: true, }; @@ -349,6 +360,7 @@ export class GraphDetailComponent implements OnInit { } } } + if ( this.flow_profile_raw?.pressureFlow && this.flow_profile_raw.pressureFlow.length > 0 @@ -397,13 +409,11 @@ export class GraphDetailComponent implements OnInit { }, 100); } - private async readFlowProfile() { + private async readFlowProfile(_path) { if (this.platform.is('cordova')) { - if (this.graph.flow_profile !== '') { + if (_path !== '') { try { - const jsonParsed = await this.uiFileHelper.getJSONFile( - this.graph.flow_profile - ); + const jsonParsed = await this.uiFileHelper.getJSONFile(_path); this.flow_profile_raw = jsonParsed; } catch (ex) {} } diff --git a/src/app/home/home.page.spec.ts b/src/app/home/home.page.spec.ts index 6549281c5..b0c7e16bc 100644 --- a/src/app/home/home.page.spec.ts +++ b/src/app/home/home.page.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { File } from '@awesome-cordova-plugins/file/ngx'; import { Camera } from '@awesome-cordova-plugins/camera/ngx'; @@ -15,6 +14,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { UIHelper } from '../../services/uiHelper'; import { NavParamsMock, UIHelperMock } from '../../classes/mock'; import { RouterTestingModule } from '@angular/router/testing'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('HomePage', () => { let component: HomePage; @@ -28,8 +28,9 @@ describe('HomePage', () => { CommonModule, IonicModule, RouterTestingModule, + PipesModule, ], - declarations: [HomePage, KeysPipe], + declarations: [HomePage], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/info/about/about.component.spec.ts b/src/app/info/about/about.component.spec.ts index 79d81019e..31ac21c8b 100644 --- a/src/app/info/about/about.component.spec.ts +++ b/src/app/info/about/about.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -15,6 +14,7 @@ import { ImagePicker } from '@awesome-cordova-plugins/image-picker/ngx'; import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions/ngx'; import { Router } from '@angular/router'; import { AppVersion } from '@awesome-cordova-plugins/app-version/ngx'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('AboutComponent', () => { let component: AboutComponent; @@ -27,8 +27,9 @@ describe('AboutComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [AboutComponent, KeysPipe], + declarations: [AboutComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/info/contact/contact.component.spec.ts b/src/app/info/contact/contact.component.spec.ts index eb61aae77..725db0b8b 100644 --- a/src/app/info/contact/contact.component.spec.ts +++ b/src/app/info/contact/contact.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { UIHelper } from '../../../services/uiHelper'; import { UIHelperMock } from '../../../classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('ContactComponent', () => { let component: ContactComponent; @@ -28,8 +28,9 @@ describe('ContactComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [ContactComponent, KeysPipe], + declarations: [ContactComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/info/credits/credits.component.spec.ts b/src/app/info/credits/credits.component.spec.ts index 486acbe6f..c59279033 100644 --- a/src/app/info/credits/credits.component.spec.ts +++ b/src/app/info/credits/credits.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('CreditsComponent', () => { let component: CreditsComponent; @@ -28,8 +28,9 @@ describe('CreditsComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [CreditsComponent, KeysPipe], + declarations: [CreditsComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/info/licences/licences.component.spec.ts b/src/app/info/licences/licences.component.spec.ts index ae68b5603..139cf5ef8 100644 --- a/src/app/info/licences/licences.component.spec.ts +++ b/src/app/info/licences/licences.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { UIHelper } from '../../../services/uiHelper'; import { UIHelperMock } from '../../../classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('LicencesComponent', () => { let component: LicencesComponent; @@ -28,8 +28,9 @@ describe('LicencesComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [LicencesComponent, KeysPipe], + declarations: [LicencesComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/info/log/log.component.spec.ts b/src/app/info/log/log.component.spec.ts index a9a28a66e..d3cd39863 100644 --- a/src/app/info/log/log.component.spec.ts +++ b/src/app/info/log/log.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock, UIHelperMock } from '../../../classes/mock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; import { UIHelper } from '../../../services/uiHelper'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('LogComponent', () => { let component: LogComponent; @@ -28,8 +28,9 @@ describe('LogComponent', () => { TranslateModule.forRoot(), CommonModule, IonicModule, + PipesModule, ], - declarations: [LogComponent, KeysPipe], + declarations: [LogComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/info/privacy/privacy.component.spec.ts b/src/app/info/privacy/privacy.component.spec.ts index 133632046..19563a691 100644 --- a/src/app/info/privacy/privacy.component.spec.ts +++ b/src/app/info/privacy/privacy.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { UIHelper } from '../../../services/uiHelper'; import { UIHelperMock } from '../../../classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('PrivacyComponent', () => { let component: PrivacyComponent; @@ -28,8 +28,9 @@ describe('PrivacyComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [PrivacyComponent, KeysPipe], + declarations: [PrivacyComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/info/terms/terms.component.spec.ts b/src/app/info/terms/terms.component.spec.ts index efa391292..0101c4a08 100644 --- a/src/app/info/terms/terms.component.spec.ts +++ b/src/app/info/terms/terms.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { RouterTestingModule } from '@angular/router/testing'; import { UIHelper } from '../../../services/uiHelper'; import { UIHelperMock } from '../../../classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('TermsComponent', () => { let component: TermsComponent; @@ -29,8 +29,9 @@ describe('TermsComponent', () => { CommonModule, IonicModule, RouterTestingModule, + PipesModule, ], - declarations: [TermsComponent, KeysPipe], + declarations: [TermsComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/info/thanks/thanks.component.spec.ts b/src/app/info/thanks/thanks.component.spec.ts index 67b33a376..4c5744f32 100644 --- a/src/app/info/thanks/thanks.component.spec.ts +++ b/src/app/info/thanks/thanks.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -14,6 +13,7 @@ import { Camera } from '@awesome-cordova-plugins/camera/ngx'; import { ImagePicker } from '@awesome-cordova-plugins/image-picker/ngx'; import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions/ngx'; import { Router } from '@angular/router'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('ThanksComponent', () => { let component: ThanksComponent; @@ -26,8 +26,9 @@ describe('ThanksComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [ThanksComponent, KeysPipe], + declarations: [ThanksComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/mill/mill-add/mill-add.component.spec.ts b/src/app/mill/mill-add/mill-add.component.spec.ts index 35ffd8742..be7c7bb86 100644 --- a/src/app/mill/mill-add/mill-add.component.spec.ts +++ b/src/app/mill/mill-add/mill-add.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { UIHelper } from '../../../services/uiHelper'; import { UIHelperMock } from '../../../classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('MillAddComponent', () => { let component: MillAddComponent; @@ -28,8 +28,9 @@ describe('MillAddComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [MillAddComponent, KeysPipe], + declarations: [MillAddComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/mill/mill-edit/mill-edit.component.spec.ts b/src/app/mill/mill-edit/mill-edit.component.spec.ts index bf0df4c7a..9752510f1 100644 --- a/src/app/mill/mill-edit/mill-edit.component.spec.ts +++ b/src/app/mill/mill-edit/mill-edit.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock, UIHelperMock } from '../../../classes/mock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -15,6 +14,7 @@ import { ImagePicker } from '@awesome-cordova-plugins/image-picker/ngx'; import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions/ngx'; import { Router } from '@angular/router'; import { UIHelper } from '../../../services/uiHelper'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('MillEditComponent', () => { let component: MillEditComponent; @@ -27,8 +27,9 @@ describe('MillEditComponent', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [MillEditComponent, KeysPipe], + declarations: [MillEditComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/mill/mill.page.spec.ts b/src/app/mill/mill.page.spec.ts index a5d7fdd43..47c3b9e74 100644 --- a/src/app/mill/mill.page.spec.ts +++ b/src/app/mill/mill.page.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { UIHelper } from '../../services/uiHelper'; import { UIHelperMock } from '../../classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('MillPage', () => { let component: MillPage; @@ -28,8 +28,9 @@ describe('MillPage', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [MillPage, KeysPipe], + declarations: [MillPage], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/preparation/preparation-add-type/preparation-add-type.component.spec.ts b/src/app/preparation/preparation-add-type/preparation-add-type.component.spec.ts index e1e070771..d96d9c12c 100644 --- a/src/app/preparation/preparation-add-type/preparation-add-type.component.spec.ts +++ b/src/app/preparation/preparation-add-type/preparation-add-type.component.spec.ts @@ -7,6 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { UIHelper } from '../../../services/uiHelper'; import { NavParamsMock, UIHelperMock } from '../../../classes/mock'; import { FormsModule } from '@angular/forms'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('PreparationAddTypeComponent', () => { let component: PreparationAddTypeComponent; @@ -15,7 +16,12 @@ describe('PreparationAddTypeComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [PreparationAddTypeComponent], - imports: [IonicModule.forRoot(), TranslateModule.forRoot(), FormsModule], + imports: [ + IonicModule.forRoot(), + TranslateModule.forRoot(), + FormsModule, + HttpClientTestingModule, + ], providers: [ { provide: Storage }, { diff --git a/src/app/preparation/preparation-add-type/preparation-add-type.component.ts b/src/app/preparation/preparation-add-type/preparation-add-type.component.ts index 4d2a4a228..09385ab7b 100644 --- a/src/app/preparation/preparation-add-type/preparation-add-type.component.ts +++ b/src/app/preparation/preparation-add-type/preparation-add-type.component.ts @@ -11,6 +11,7 @@ import { PREPARATION_STYLE_TYPE } from '../../../enums/preparations/preparationS import { PreparationTool } from '../../../classes/preparation/preparationTool'; import PREPARATION_TRACKING from '../../../data/tracking/preparationTracking'; import { UIAnalytics } from '../../../services/uiAnalytics'; +import { UIPreparationHelper } from '../../../services/uiPreparationHelper'; @Component({ selector: 'preparation-add-type', @@ -34,9 +35,11 @@ export class PreparationAddTypeComponent implements OnInit { private readonly navParams: NavParams, private readonly uiToast: UIToast, private readonly translate: TranslateService, - private readonly uiAnalytics: UIAnalytics + private readonly uiAnalytics: UIAnalytics, + private readonly uiPreparationHelper: UIPreparationHelper ) { this.data.type = this.navParams.get('type'); + if (this.data.type !== PREPARATION_TYPES.CUSTOM_PREPARATION) { this.data.name = this.translate.instant( 'PREPARATION_TYPE_' + this.data.type @@ -69,11 +72,19 @@ export class PreparationAddTypeComponent implements OnInit { this.data.manage_parameters.coffee_first_drip_time = false; this.data.default_last_coffee_parameters.coffee_first_drip_time = false; } - await this.uiPreparationStorage.add(this.data); + const newPreparation = await this.uiPreparationStorage.add(this.data); this.dismiss(true); if (!this.hide_toast_message) { this.uiToast.showInfoToast('TOAST_PREPARATION_ADDED_SUCCESSFULLY'); } + + if ( + this.data.type === PREPARATION_TYPES.METICULOUS || + this.data.type === PREPARATION_TYPES.XENIA || + this.data.type === PREPARATION_TYPES.SANREMO_YOU + ) { + await this.uiPreparationHelper.connectDevice(newPreparation); + } } public async dismiss(_added: boolean) { diff --git a/src/app/preparation/preparation-add/preparation-add.component.html b/src/app/preparation/preparation-add/preparation-add.component.html index 590de67ee..7a6d4394b 100644 --- a/src/app/preparation/preparation-add/preparation-add.component.html +++ b/src/app/preparation/preparation-add/preparation-add.component.html @@ -12,28 +12,31 @@ - - - + -
- -
+ + + + +
+ +
-
- {{'PREPARATION_TYPE_' + key | translate}} -
-
-
- + {{'PREPARATION_TYPE_' + key | translate}} +
+
+ +
+ +
+
+ {{'PREPARATION_TYPE_ADD_CUSTOM' | translate}}
-
- {{'PREPARATION_TYPE_ADD_CUSTOM' | translate}} -
-
-
-
+
+
+
+
diff --git a/src/app/preparation/preparation-add/preparation-add.component.spec.ts b/src/app/preparation/preparation-add/preparation-add.component.spec.ts index 209b893d9..10fe5ca35 100644 --- a/src/app/preparation/preparation-add/preparation-add.component.spec.ts +++ b/src/app/preparation/preparation-add/preparation-add.component.spec.ts @@ -3,8 +3,8 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { PreparationAddComponent } from './preparation-add.component'; import { TranslateModule } from '@ngx-translate/core'; import { ModalController } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { UIAnalytics } from '../../../services/uiAnalytics'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('PreparationAddComponent', () => { let component: PreparationAddComponent; @@ -12,8 +12,8 @@ describe('PreparationAddComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], - declarations: [PreparationAddComponent, KeysPipe], + imports: [TranslateModule.forRoot(), PipesModule], + declarations: [PreparationAddComponent], providers: [ { provide: UIAnalytics, diff --git a/src/app/preparation/preparation-add/preparation-add.component.ts b/src/app/preparation/preparation-add/preparation-add.component.ts index 47f688498..cd800c9ef 100644 --- a/src/app/preparation/preparation-add/preparation-add.component.ts +++ b/src/app/preparation/preparation-add/preparation-add.component.ts @@ -7,6 +7,7 @@ import { NgForm } from '@angular/forms'; import { PreparationAddTypeComponent } from '../preparation-add-type/preparation-add-type.component'; import PREPARATION_TRACKING from '../../../data/tracking/preparationTracking'; import { UIAnalytics } from '../../../services/uiAnalytics'; +import { environment } from '../../../environments/environment'; @Component({ selector: 'preparation-add', templateUrl: './preparation-add.component.html', @@ -18,6 +19,8 @@ export class PreparationAddComponent implements OnInit { public preparation_types_enum = PREPARATION_TYPES; + public ENVIRONMENT = environment; + @ViewChild('addPreparationForm', { static: false }) public preparationForm: NgForm; @@ -35,6 +38,16 @@ export class PreparationAddComponent implements OnInit { ); } + public individualPreparationVisible(_key) { + if (_key === 'SANREMO_YOU') { + if (this.ENVIRONMENT.FEATURES_ACTIVE.SANREMO_YOU === true) { + return true; + } + return false; + } + return true; + } + public async choosePreparation(_prepType: PREPARATION_TYPES) { //Animated false, else backdrop would sometimes not disappear and stay until user touches again. const modal = await this.modalController.create({ diff --git a/src/app/preparation/preparation-connected-device/preparation-connected-device.component.html b/src/app/preparation/preparation-connected-device/preparation-connected-device.component.html index 42bf70628..85fca6bce 100644 --- a/src/app/preparation/preparation-connected-device/preparation-connected-device.component.html +++ b/src/app/preparation/preparation-connected-device/preparation-connected-device.component.html @@ -12,18 +12,19 @@ - {{"PREPARATION_DEVICE.TYPE.NONE" | translate}} {{"PREPARATION_DEVICE.TYPE.XENIA" | translate}} {{"PREPARATION_DEVICE.TYPE.METICULOUS" | translate}} + {{"PREPARATION_DEVICE.TYPE.SANREMO_YOU" | translate}} - + {{"PREPARATION_DEVICE.URL" | translate}} - @@ -31,7 +32,7 @@ - V1 V2 @@ -40,7 +41,7 @@

{{"PREPARATION_DEVICE.RESIDUAL_LAG_TIME" | translate }} {{uiHelper.toFixedIfNecessary(preparation.connectedPreparationDevice.customParams.residualLagTime,2)}}

+ style="vertical-align: top;">{{uiHelper.toFixedIfNecessary(data.connectedPreparationDevice.customParams.residualLagTime,2)}}
@@ -49,33 +50,59 @@

{{"PREPARATION_DEVICE.RESIDUAL_LAG_TIME" | translate }}  - - +
{{"SAVE_LOGFILES_TO_NOTES_FROM_MACHINE" | translate}}
- - + + + {{"PREPARATION_DEVICE.URL" | translate}} + + + + + + + + {{"PREPARATION_DEVICE.URL" | translate}} - + + + + + +

{{"PREPARATION_DEVICE.RESIDUAL_LAG_TIME" | translate }} {{uiHelper.toFixedIfNecessary(data.connectedPreparationDevice.customParams.residualLagTime,2)}}

+
+
+ + +

{{"PREPARATION_DEVICE.RESIDUAL_LAG_TIME_DESCRIPTION" | translate }}

+
+
+ + + + +
- - - - + diff --git a/src/app/preparation/preparation-connected-device/preparation-connected-device.component.spec.ts b/src/app/preparation/preparation-connected-device/preparation-connected-device.component.spec.ts index 1fa68204f..b85168cd9 100644 --- a/src/app/preparation/preparation-connected-device/preparation-connected-device.component.spec.ts +++ b/src/app/preparation/preparation-connected-device/preparation-connected-device.component.spec.ts @@ -32,7 +32,9 @@ describe('PreparationConnectedDeviceComponent', () => { }, ], }).compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(PreparationConnectedDeviceComponent); component = fixture.componentInstance; component.preparation = { @@ -41,7 +43,7 @@ describe('PreparationConnectedDeviceComponent', () => { }, } as Preparation; fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/app/preparation/preparation-connected-device/preparation-connected-device.component.ts b/src/app/preparation/preparation-connected-device/preparation-connected-device.component.ts index e4425e92c..32c86679d 100644 --- a/src/app/preparation/preparation-connected-device/preparation-connected-device.component.ts +++ b/src/app/preparation/preparation-connected-device/preparation-connected-device.component.ts @@ -12,6 +12,11 @@ import { UIAlert } from '../../../services/uiAlert'; import { UIHelper } from '../../../services/uiHelper'; import { UISettingsStorage } from '../../../services/uiSettingsStorage'; import { Settings } from '../../../classes/settings/settings'; +import { environment } from '../../../environments/environment'; +import { PREPARATION_TYPES } from '../../../enums/preparations/preparationTypes'; +import { SanremoYOUParams } from '../../../classes/preparationDevice/sanremo/sanremoYOUDevice'; +import { MeticulousParams } from '../../../classes/preparationDevice/meticulous/meticulousDevice'; +import { XeniaParams } from '../../../classes/preparationDevice/xenia/xeniaDevice'; @Component({ selector: 'app-preparation-connected-device', @@ -26,6 +31,8 @@ export class PreparationConnectedDeviceComponent { public PREPARATION_DEVICE_TYPE = PreparationDeviceType; @Input() public preparation: IPreparation; + public ENVIRONMENT_PARAMS = environment; + public pinFormatter(value: any) { const parsedFloat = parseFloat(value); if (isNaN(parsedFloat)) { @@ -48,6 +55,26 @@ export class PreparationConnectedDeviceComponent { if (this.preparation !== undefined) { this.data.initializeByObject(this.preparation); } + if ( + this.data.connectedPreparationDevice.type === PreparationDeviceType.NONE + ) { + if (this.data.type === PREPARATION_TYPES.METICULOUS) { + this.data.connectedPreparationDevice.type = + PreparationDeviceType.METICULOUS; + this.data.connectedPreparationDevice.customParams = + new MeticulousParams(); + } + if (this.data.type === PREPARATION_TYPES.XENIA) { + this.data.connectedPreparationDevice.type = PreparationDeviceType.XENIA; + this.data.connectedPreparationDevice.customParams = new XeniaParams(); + } + if (this.data.type === PREPARATION_TYPES.SANREMO_YOU) { + this.data.connectedPreparationDevice.type = + PreparationDeviceType.SANREMO_YOU; + this.data.connectedPreparationDevice.customParams = + new SanremoYOUParams(); + } + } } public dismiss(): void { @@ -98,7 +125,36 @@ export class PreparationConnectedDeviceComponent { this.data.connectedPreparationDevice.customParams.residualLagTime = 1.35; } } - + if ( + this.data.connectedPreparationDevice.type === + PreparationDeviceType.METICULOUS + ) { + if (this.data.connectedPreparationDevice.url.endsWith('/') === true) { + this.data.connectedPreparationDevice.url = + this.data.connectedPreparationDevice.url.slice(0, -1); + } + if ( + this.data.connectedPreparationDevice.url.startsWith('http') === false + ) { + this.data.connectedPreparationDevice.url = + 'http://' + this.data.connectedPreparationDevice.url; + } + } + if ( + this.data.connectedPreparationDevice.type === + PreparationDeviceType.SANREMO_YOU + ) { + if (this.data.connectedPreparationDevice.url.endsWith('/') === true) { + this.data.connectedPreparationDevice.url = + this.data.connectedPreparationDevice.url.slice(0, -1); + } + if ( + this.data.connectedPreparationDevice.url.startsWith('http') === false + ) { + this.data.connectedPreparationDevice.url = + 'http://' + this.data.connectedPreparationDevice.url; + } + } if ( this.data.connectedPreparationDevice.type !== PreparationDeviceType.NONE ) { @@ -107,6 +163,8 @@ export class PreparationConnectedDeviceComponent { */ const settings: Settings = this.uiSettingsStorage.getSettings(); settings.bluetooth_scale_espresso_stop_on_no_weight_change = true; + settings.bluetooth_scale_stay_connected = true; + settings.wake_lock = true; await this.uiSettingsStorage.update(settings); } await this.uiPreparationStorage.update(this.data); diff --git a/src/app/preparation/preparation-edit/preparation-edit.component.spec.ts b/src/app/preparation/preparation-edit/preparation-edit.component.spec.ts index 79e656927..e384490b1 100644 --- a/src/app/preparation/preparation-edit/preparation-edit.component.spec.ts +++ b/src/app/preparation/preparation-edit/preparation-edit.component.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock, UIHelperMock } from '../../../classes/mock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { UIHelper } from '../../../services/uiHelper'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('PreparationEditComponent', () => { let component: PreparationEditComponent; @@ -29,8 +29,9 @@ describe('PreparationEditComponent', () => { CommonModule, IonicModule, HttpClientTestingModule, + PipesModule, ], - declarations: [PreparationEditComponent, KeysPipe], + declarations: [PreparationEditComponent], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/preparation/preparation-modal-select/preparation-modal-select.component.html b/src/app/preparation/preparation-modal-select/preparation-modal-select.component.html index 5d775696c..3151de2b7 100644 --- a/src/app/preparation/preparation-modal-select/preparation-modal-select.component.html +++ b/src/app/preparation/preparation-modal-select/preparation-modal-select.component.html @@ -28,7 +28,8 @@ -
{{prep.name}}
+
{{prep.name}}

{{"LAST_USE" | translate}}: {{lastUsed(prep) | formatDate:[settings?.date_format]}} - @@ -47,7 +48,8 @@ -

{{prep.name}}
+
{{prep.name}}

{{"LAST_USE" | translate}}: {{lastUsed(prep) | formatDate:[settings?.date_format]}} - @@ -72,7 +74,8 @@

{{prep.name}}
-
{{prep.name}}
+
{{prep.name}}

{{"LAST_USE" | translate}}: {{lastUsed(prep) | formatDate:[settings?.date_format]}} - @@ -91,7 +94,8 @@

{{prep.name}}
-
{{prep.name}}
+
{{prep.name}}

{{"LAST_USE" | translate}}: {{lastUsed(prep) | formatDate:[settings?.date_format]}} - diff --git a/src/app/preparation/preparation-sort-tools/preparation-sort-tools.component.spec.ts b/src/app/preparation/preparation-sort-tools/preparation-sort-tools.component.spec.ts index d24f5651f..c4aafce8e 100644 --- a/src/app/preparation/preparation-sort-tools/preparation-sort-tools.component.spec.ts +++ b/src/app/preparation/preparation-sort-tools/preparation-sort-tools.component.spec.ts @@ -2,6 +2,12 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { IonicModule } from '@ionic/angular'; import { PreparationSortToolsComponent } from './preparation-sort-tools.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { Preparation } from 'src/classes/preparation/preparation'; +import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; +import { File } from '@awesome-cordova-plugins/file/ngx'; +import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; +import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; describe('PreparationSortToolsComponent', () => { let component: PreparationSortToolsComponent; @@ -10,13 +16,26 @@ describe('PreparationSortToolsComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [PreparationSortToolsComponent], - imports: [IonicModule.forRoot()], + imports: [ + IonicModule.forRoot(), + TranslateModule.forChild(), + TranslateModule.forRoot(), + ], + providers: [ + { provide: InAppBrowser }, + { provide: File }, + { provide: SocialSharing }, + { provide: FileTransfer }, + ], }).compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(PreparationSortToolsComponent); component = fixture.componentInstance; + component.preparation = new Preparation(); fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/app/preparation/preparation-sort-tools/preparation-sort-tools.component.ts b/src/app/preparation/preparation-sort-tools/preparation-sort-tools.component.ts index ad1c717fe..d0eac0ffb 100644 --- a/src/app/preparation/preparation-sort-tools/preparation-sort-tools.component.ts +++ b/src/app/preparation/preparation-sort-tools/preparation-sort-tools.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { Preparation } from '../../../classes/preparation/preparation'; import { ModalController } from '@ionic/angular'; +import { UIHelper } from '../../../services/uiHelper'; @Component({ selector: 'app-preparation-sort-tools', @@ -20,7 +21,8 @@ export class PreparationSortToolsComponent implements OnInit { @Input() public preparation: Preparation; constructor( private readonly modalController: ModalController, - private readonly changeDetectorRef: ChangeDetectorRef + private readonly changeDetectorRef: ChangeDetectorRef, + private readonly uiHelper: UIHelper ) {} public ngOnInit() { @@ -40,13 +42,12 @@ export class PreparationSortToolsComponent implements OnInit { reorderVar.splice(ev.detail.to, 0, reorderVar.splice(ev.detail.from, 1)[0]); - const swapElements = (myArray, index1, index2) => { - [myArray[index1], myArray[index2]] = [myArray[index2], myArray[index1]]; - }; - - swapElements(this.preparation.tools, ev.detail.from, ev.detail.to); + this.preparation.tools.splice( + ev.detail.to, + 0, + this.preparation.tools.splice(ev.detail.from, 1)[0] + ); - // console.log(this.settings.brew_order); // Finish the reorder and position the item in the DOM based on // where the gesture ended. This method can also be called directly // by the reorder group diff --git a/src/app/preparation/preparation.page.spec.ts b/src/app/preparation/preparation.page.spec.ts index aaf44a400..6a3b223e8 100644 --- a/src/app/preparation/preparation.page.spec.ts +++ b/src/app/preparation/preparation.page.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -17,6 +16,7 @@ import { Router } from '@angular/router'; import { UIHelper } from '../../services/uiHelper'; import { UIHelperMock } from '../../classes/mock'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('PreparationPage', () => { let component: PreparationPage; @@ -30,8 +30,9 @@ describe('PreparationPage', () => { CommonModule, IonicModule, HttpClientTestingModule, + PipesModule, ], - declarations: [PreparationPage, KeysPipe], + declarations: [PreparationPage], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/settings/settings.page.html b/src/app/settings/settings.page.html index 33306782b..7a10c8944 100644 --- a/src/app/settings/settings.page.html +++ b/src/app/settings/settings.page.html @@ -51,16 +51,28 @@

{{"EXPORT" | translate}}

- + Temp: Export Brew By Weight - Xenia - + + Temp: Export Brew By Weight - Sanremo + + + {{"EXCEL_EXPORT" | translate}} - - {{"IMPORT_BEANS_EXCEL" | translate}} + + {{"IMPORT_ROASTED_BEANS_EXCEL" | translate}} + + + + {{"IMPORT_GREEN_BEANS_EXCEL" | translate}} + + + + {{"DOWNLOAD_IMPORT_EXCEL_TEMPLATES" | translate}} @@ -84,6 +96,7 @@

{{"EXPORT" | translate}}

{{"PAGE_SETTINGS_LANGUAGE_FRANCE" | translate}} {{"PAGE_SETTINGS_LANGUAGE_INDONESIA" | translate}} {{"PAGE_SETTINGS_LANGUAGE_POLISH" | translate}} + {{"PAGE_SETTINGS_LANGUAGE_DUTCH" | translate}}
@@ -361,7 +374,7 @@

{{"PAGE_SETTINGS_BREW_TIMER_START_DELAY_ACTIVE" | translate}}

{{"PAGE_SETTINGS_MANAGE_FEATURES" | translate}} -
{{"ACTIVE_BEAN_FREEZING_FEATURE" | translate}}
@@ -674,8 +687,8 @@

{{"SMART_SCALE_ESPRESSO_STOP_ON_NO_WEIGHT_CHANGE" | translate}}

{{"SMART_SCALE_ESPRESSO_STOP_ON_NO_WEIGHT_CHANGE_MIN_FLOW_DESCRIPTION" | translate}}

- - +
{{settings.bluetooth_scale_espresso_stop_on_no_weight_change_min_flow | number : '.0-2'}} g/s
@@ -982,6 +995,25 @@

{{"PRESSURE_LOG" | translate}}

+ + + + {{"PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS" | translate }} +

{{"PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION" | translate}}

+
+ + + {{"PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE" | translate}} {{this.uiHelper.toFixedIfNecessary(settings.graph_pressure.lower,2)}} - {{this.uiHelper.toFixedIfNecessary(settings.graph_pressure.upper,2)}} + + + + + + + +
+
diff --git a/src/app/settings/settings.page.spec.ts b/src/app/settings/settings.page.spec.ts index efcc0ea43..85debc003 100644 --- a/src/app/settings/settings.page.spec.ts +++ b/src/app/settings/settings.page.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -21,6 +20,7 @@ import { UIHelperMock } from '../../classes/mock'; import { UIHelper } from '../../services/uiHelper'; import { AppVersion } from '@awesome-cordova-plugins/app-version/ngx'; import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('SettingsPage', () => { let component: SettingsPage; @@ -33,8 +33,9 @@ describe('SettingsPage', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [SettingsPage, KeysPipe], + declarations: [SettingsPage], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/settings/settings.page.ts b/src/app/settings/settings.page.ts index 46b8bd9b0..63d1ef7ac 100644 --- a/src/app/settings/settings.page.ts +++ b/src/app/settings/settings.page.ts @@ -14,7 +14,6 @@ import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { DirectoryEntry, FileEntry } from '@awesome-cordova-plugins/file'; import { FileChooser } from '@awesome-cordova-plugins/file-chooser/ngx'; import { File } from '@awesome-cordova-plugins/file/ngx'; -import { FilePath } from '@awesome-cordova-plugins/file-path/ngx'; import { IBean } from '../../interfaces/bean/iBean'; import { IBrew } from '../../interfaces/brew/iBrew'; import { ISettings } from '../../interfaces/settings/iSettings'; @@ -918,6 +917,9 @@ export class SettingsPage { public async resetFilter() { this.settings.resetFilter(); } + public async resetBeanSort() { + this.settings.resetBeanSort(); + } public async fixWeightChangeMinFlowNumber() { //We need to trigger this, because the slider sometimes procudes values like 0.60000001, and we need to fix this before saving @@ -1208,44 +1210,88 @@ export class SettingsPage { this.uiExcel.export(); } - public doWeHaveBrewByWeights(): boolean { + public pinFormatter(value: any) { + const parsedFloat = parseFloat(value); + if (isNaN(parsedFloat)) { + return `${0}`; + } + const newValue = +parsedFloat.toFixed(2); + return `${newValue}`; + } + + public doWeHaveBrewByWeights(_type: string): boolean { const allPreparations = this.uiPreparationStorage.getAllEntries(); for (const prep of allPreparations) { if ( + _type === 'xenia' && prep.connectedPreparationDevice.type === PreparationDeviceType.XENIA ) { return true; + } else if ( + _type === 'sanremo' && + prep.connectedPreparationDevice.type === + PreparationDeviceType.SANREMO_YOU + ) { + return true; } } } - public async exportBrewByWeight() { + public async exportBrewByWeight(_type: string) { await this.uiAlert.showLoadingSpinner(); try { - const allXeniaPreps = []; - const allPreparations = this.uiPreparationStorage.getAllEntries(); + const allPreps = []; + let allPreparations = this.uiPreparationStorage.getAllEntries(); + // Just take 60, else the excel will be exploding. + allPreparations = allPreparations.reverse().slice(0, 60); for (const prep of allPreparations) { if ( + _type === 'xenia' && prep.connectedPreparationDevice.type === PreparationDeviceType.XENIA ) { - allXeniaPreps.push(prep); + allPreps.push(prep); + } else if ( + _type === 'sanremo' && + prep.connectedPreparationDevice.type === + PreparationDeviceType.SANREMO_YOU + ) { + allPreps.push(prep); } } - const allBrewsWithProfiles = this.uiBrewStorage - .getAllEntries() - .filter( - (e) => - e.flow_profile !== null && - e.flow_profile !== undefined && - e.flow_profile !== '' && - allXeniaPreps.find( - (pr) => pr.config.uuid === e.method_of_preparation - ) && - e.preparationDeviceBrew && - e.preparationDeviceBrew.params && - e.preparationDeviceBrew.params.brew_by_weight_active === true - ); + let allBrewsWithProfiles = []; + + if (_type === 'xenia') { + allBrewsWithProfiles = this.uiBrewStorage + .getAllEntries() + .filter( + (e) => + e.flow_profile !== null && + e.flow_profile !== undefined && + e.flow_profile !== '' && + allPreps.find( + (pr) => pr.config.uuid === e.method_of_preparation + ) && + e.preparationDeviceBrew && + e.preparationDeviceBrew.params && + e.preparationDeviceBrew.params.brew_by_weight_active === true + ); + } else if (_type === 'sanremo') { + allBrewsWithProfiles = this.uiBrewStorage + .getAllEntries() + .filter( + (e) => + e.flow_profile !== null && + e.flow_profile !== undefined && + e.flow_profile !== '' && + allPreps.find( + (pr) => pr.config.uuid === e.method_of_preparation + ) && + e.preparationDeviceBrew && + e.preparationDeviceBrew.params && + e.preparationDeviceBrew.params.stopAtWeight > 0 + ); + } const allBrewFlows: Array<{ BREW: Brew; FLOW: BrewFlow }> = []; for await (const brew of allBrewsWithProfiles) { @@ -1274,7 +1320,13 @@ export class SettingsPage { } } - public importBeansExcel(): void { + public downloadImportExcelTemplates() { + this.uiHelper.openExternalWebpage( + 'https://beanconqueror.gitbook.io/beanconqueror/resources/files' + ); + } + + public importBeansExcel(_type: string = 'roasted'): void { if (this.platform.is('cordova')) { this.uiAnalytics.trackEvent( SETTINGS_TRACKING.TITLE, @@ -1291,7 +1343,11 @@ export class SettingsPage { this.uiFileHelper.readFileEntryAsArrayBuffer(fileEntry).then( async (_arrayBuffer) => { - this.uiExcel.importBeansByExcel(_arrayBuffer); + if (_type === 'roasted') { + this.uiExcel.importBeansByExcel(_arrayBuffer); + } else { + this.uiExcel.importGreenBeansByExcel(_arrayBuffer); + } }, () => { // Backup, maybe it was a .JSON? @@ -1317,7 +1373,11 @@ export class SettingsPage { } this.uiFileHelper.readFileAsArrayBuffer(path, file).then( async (_arrayBuffer) => { - this.uiExcel.importBeansByExcel(_arrayBuffer); + if (_type === 'roasted') { + this.uiExcel.importBeansByExcel(_arrayBuffer); + } else { + this.uiExcel.importGreenBeansByExcel(_arrayBuffer); + } }, () => { // Backup, maybe it was a .JSON? @@ -1333,7 +1393,6 @@ export class SettingsPage { ); } } else { - this.__importDummyData(); } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 79005eabb..731d7b0f2 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -1,7 +1,5 @@ import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; -import { FormatDatePipe } from '../../pipes/formatDate'; -import { KeysPipe } from '../../pipes/keys'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { AsyncImageComponent } from '../../components/async-image/async-image.component'; @@ -52,7 +50,6 @@ import { LogTextComponent } from '../info/log/log-text/log-text.component'; import { TranslateModule } from '@ngx-translate/core'; import { Globalization } from '@awesome-cordova-plugins/globalization/ngx'; import { AppVersion } from '@awesome-cordova-plugins/app-version/ngx'; -import { EnumToArrayPipe } from '../../pipes/enumToArray'; import { HelperPage } from '../helper/helper.page'; import { BrewInformationComponent } from '../../components/brew-information/brew-information.component'; import { CuppingRadarComponent } from '../../components/cupping-radar/cupping-radar.component'; @@ -191,6 +188,10 @@ import { MeticulousHelpPopoverComponent } from '../../popover/meticulous-help-po import { BeanPopoverFreezeComponent } from '../beans/bean-popover-freeze/bean-popover-freeze.component'; import { BeanFreezeInformationComponent } from '../../components/beans/bean-freeze-information/bean-freeze-information.component'; import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-list/bean-popover-frozen-list.component'; +import { PipesModule } from 'src/pipes/pipes.module'; +import { BrewModalImportShotMeticulousComponent } from '../brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component'; +import { DataCorruptionFoundComponent } from '../../popover/data-corruption-found/data-corruption-found.component'; +import { BeanPopoverListComponent } from '../beans/bean-popover-list/bean-popover-list.component'; @NgModule({ declarations: [ @@ -216,6 +217,7 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis BeansAddComponent, BrewFlowComponent, BrewChooseGraphReferenceComponent, + BrewModalImportShotMeticulousComponent, BrewMaximizeControlsComponent, BeansEditComponent, BeansDetailComponent, @@ -232,6 +234,7 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis WelcomePopoverComponent, AnalyticsPopoverComponent, MeticulousHelpPopoverComponent, + DataCorruptionFoundComponent, QrCodeScannerPopoverComponent, UpdatePopoverComponent, DatetimePopoverComponent, @@ -273,9 +276,6 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis BrewChoosePreparationToBrewComponent, BrewFlavorPickerComponent, BrewBeverageQuantityCalculatorComponent, - FormatDatePipe, - KeysPipe, - EnumToArrayPipe, AsyncImageComponent, BrewInformationComponent, BrewGraphReferenceCardComponent, @@ -320,6 +320,7 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis BeanPopoverAddComponent, BeanPopoverFreezeComponent, BeanPopoverFrozenListComponent, + BeanPopoverListComponent, BeanArchivePopoverComponent, MillPopoverActionsComponent, BeanModalSelectComponent, @@ -368,6 +369,7 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis RouterModule, NgxStarsModule, AgVirtualScrollModule, + PipesModule, ], providers: [ AppVersion, @@ -387,9 +389,6 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis TooltipDirective, TransformDateDirective, DisableDoubleClickDirective, - FormatDatePipe, - KeysPipe, - EnumToArrayPipe, InAppBrowser, File, Device, @@ -427,6 +426,7 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis BeansAddComponent, BrewFlowComponent, BrewChooseGraphReferenceComponent, + BrewModalImportShotMeticulousComponent, BrewMaximizeControlsComponent, BeansEditComponent, BrewRatingComponent, @@ -448,6 +448,7 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis WelcomePopoverComponent, AnalyticsPopoverComponent, MeticulousHelpPopoverComponent, + DataCorruptionFoundComponent, QrCodeScannerPopoverComponent, UpdatePopoverComponent, DatetimePopoverComponent, @@ -487,9 +488,6 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis BrewChoosePreparationToBrewComponent, BrewFlavorPickerComponent, BrewBeverageQuantityCalculatorComponent, - FormatDatePipe, - KeysPipe, - EnumToArrayPipe, AsyncImageComponent, BrewInformationComponent, BrewGraphReferenceCardComponent, @@ -533,6 +531,7 @@ import { BeanPopoverFrozenListComponent } from '../beans/bean-popover-frozen-lis BeanPopoverAddComponent, BeanPopoverFreezeComponent, BeanPopoverFrozenListComponent, + BeanPopoverListComponent, BeanArchivePopoverComponent, BeanModalSelectComponent, AssociatedBrewsComponent, diff --git a/src/app/statistic/statistic.page.spec.ts b/src/app/statistic/statistic.page.spec.ts index 4cf3429bd..e8dda4ebc 100644 --- a/src/app/statistic/statistic.page.spec.ts +++ b/src/app/statistic/statistic.page.spec.ts @@ -6,7 +6,6 @@ import { FormsModule } from '@angular/forms'; import { Storage } from '@ionic/storage'; import { CommonModule } from '@angular/common'; import { IonicModule, ModalController, NavParams } from '@ionic/angular'; -import { KeysPipe } from '../../pipes/keys'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { NavParamsMock } from '../../classes/mock/NavParamsMock'; import { File } from '@awesome-cordova-plugins/file/ngx'; @@ -16,6 +15,7 @@ import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions import { Router } from '@angular/router'; import { UIHelper } from '../../services/uiHelper'; import { UIHelperMock } from '../../classes/mock'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('StatisticPage', () => { let component: StatisticPage; @@ -28,8 +28,9 @@ describe('StatisticPage', () => { FormsModule, CommonModule, IonicModule, + PipesModule, ], - declarations: [StatisticPage, KeysPipe], + declarations: [StatisticPage], providers: [ { provide: InAppBrowser }, { provide: ModalController }, diff --git a/src/app/water-section/water/water-add/water-add.component.spec.ts b/src/app/water-section/water/water-add/water-add.component.spec.ts index a1c9053c7..ec6af628e 100644 --- a/src/app/water-section/water/water-add/water-add.component.spec.ts +++ b/src/app/water-section/water/water-add/water-add.component.spec.ts @@ -7,7 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { UIHelper } from '../../../../services/uiHelper'; import { UIHelperMock } from '../../../../classes/mock'; import { FormsModule } from '@angular/forms'; -import { KeysPipe } from 'src/pipes/keys'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('WaterAddComponent', () => { let component: WaterAddComponent; @@ -15,8 +15,13 @@ describe('WaterAddComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [WaterAddComponent, KeysPipe], - imports: [IonicModule.forRoot(), TranslateModule.forRoot(), FormsModule], + declarations: [WaterAddComponent], + imports: [ + IonicModule.forRoot(), + TranslateModule.forRoot(), + FormsModule, + PipesModule, + ], providers: [ { provide: Storage }, { provide: UIHelper, useClass: UIHelperMock }, diff --git a/src/app/water-section/water/water-edit/water-edit.component.spec.ts b/src/app/water-section/water/water-edit/water-edit.component.spec.ts index f6c73e39f..2bbbdafbc 100644 --- a/src/app/water-section/water/water-edit/water-edit.component.spec.ts +++ b/src/app/water-section/water/water-edit/water-edit.component.spec.ts @@ -7,7 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { UIHelper } from '../../../../services/uiHelper'; import { UIHelperMock } from '../../../../classes/mock'; import { FormsModule } from '@angular/forms'; -import { KeysPipe } from '../../../../pipes/keys'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('WaterEditComponent', () => { let component: WaterEditComponent; @@ -15,8 +15,13 @@ describe('WaterEditComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [WaterEditComponent, KeysPipe], - imports: [IonicModule.forRoot(), TranslateModule.forRoot(), FormsModule], + declarations: [WaterEditComponent], + imports: [ + IonicModule.forRoot(), + TranslateModule.forRoot(), + FormsModule, + PipesModule, + ], providers: [ { provide: Storage }, { provide: UIHelper, useClass: UIHelperMock }, diff --git a/src/assets/custom-ion-icons/beanconqueror-expand-active.svg b/src/assets/custom-ion-icons/beanconqueror-expand-active.svg new file mode 100644 index 000000000..c7db982c6 --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-expand-active.svg @@ -0,0 +1 @@ + diff --git a/src/assets/custom-ion-icons/beanconqueror-expand-inactive.svg b/src/assets/custom-ion-icons/beanconqueror-expand-inactive.svg new file mode 100644 index 000000000..a4b978e82 --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-expand-inactive.svg @@ -0,0 +1 @@ + diff --git a/src/assets/custom-ion-icons/beanconqueror-preparation-sanremo-you.svg b/src/assets/custom-ion-icons/beanconqueror-preparation-sanremo-you.svg new file mode 100644 index 000000000..2bee80085 --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-preparation-sanremo-you.svg @@ -0,0 +1,35 @@ + + + + diff --git a/src/assets/custom-ion-icons/beanconqueror-preparation-xenia.svg b/src/assets/custom-ion-icons/beanconqueror-preparation-xenia.svg new file mode 100644 index 000000000..352ab078a --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-preparation-xenia.svg @@ -0,0 +1,22 @@ + + + + diff --git a/src/assets/custom-ion-icons/beanconqueror-sanremo-you-logo.svg b/src/assets/custom-ion-icons/beanconqueror-sanremo-you-logo.svg new file mode 100644 index 000000000..65a29f37f --- /dev/null +++ b/src/assets/custom-ion-icons/beanconqueror-sanremo-you-logo.svg @@ -0,0 +1,56 @@ + + + + + + + + + + diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 90777414d..5ec30d249 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "Keins", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "Sanremo YOU" }, "URL": "Url", "CHOOSE_DEVICE": "Gerät auswählen", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "Profil wählen", "SHOT_STARTED": "Shot gestartet", "SHOT_ENDED": "Shot beendet" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "Sanremo YOU Maschine", + "STOP_AT_WEIGHT": "Stopp bei Gewicht", + "SELECT_MODE": "Modus auswählen", + "MODE_LISTENING": "Hören", + "MODE_CONTROL": "Steuern", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" } }, "DEVICE_CONNECTION": "Geräteverbindung", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "Minuten anzeigen", "BEANS_UNARCHIVE": "Archivierung aufheben", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Bean wurde dearchiviert", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "Version 7.4.0: Das ist neu", - "DESCRIPTION": [ - "Bohnen<\/b>", - "Es ist jetzt möglich Kaffeebohnen einzufrieren. Aktiviere diese Funktion in den Einstellungen", - "Bestes und offenes Datum für Bohnen hinzugefügt. Diese Parameter müssen aktiviert werden.", - "Bohnen können nun direkt aus der Auswahlübersicht hinzugefügt werden", - "", - " Thermometer <\/b>", - "Unterstützung des Combustion Inc. Thermometers – Danke für das Gerät!", - "Unterstützung des Meater-Thermometers (nicht Meater 2 oder Meater +) – Danke an Yannick!", - "", - "Wasserbereich<\/b>", - "Pure Coffee Water hinzugefügt", - "Empirical Water hinzugefügt", - "", - "Brühmethoden Tools<\/b>", - "Sortiere deine Zubereitungsmethoden", - "", - "Mühlen<\/b>", - "Mühlenbilder werden in der Übersicht angezeigt", - "", - "Einstellungen<\/b>", - "Sicherheitsmeldung, wenn du die Bewertung für Brühen oder Bohnen geändert hast und das Maximum niedriger ist als ein bereits bewerteter Eintrag", - "", - "Sonstiges<\/b>", - "Der direkte Sekundenfokus beim Öffnen des Timers wurde ausgebaut", - "Einige technische Änderungen im Code", - "Kleine Optimierungen" - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Pure Coffee Water", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "empirical water GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "empirical water SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "Notizen zum Einfrieren", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "Wenn diese Option aktiviert ist, wird ein Diagramm erstellt, das eine Sekunde zurückliegt", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "Wenn diese Option aktiviert ist, wird ein Diagramm erstellt, das eine Sekunde zurückliegt", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Maschinenprotokolle speichern, wenn die Brühung gespeichert wird" -} \ No newline at end of file + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Maschinenprotokolle speichern, wenn die Brühung gespeichert wird", + "IMPORT_SHOT_FROM_METICULOUS": "Bezug importieren", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Alle gefundenen Einträge in der Excel-Liste wurden in neue Bohnen umgewandelt, genieße eine gute Brühung!", + "IMPORT_UNSUCCESSFULLY": "Import nicht erfolgreich", + "BEAN_LIST": "Bohnenliste", + "IMPORT_ROASTED_BEANS_EXCEL": "Geröstete Bohnen über Excel importieren", + "IMPORT_GREEN_BEANS_EXCEL": "Grüne Bohnen über Excel importieren", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "Es sieht so aus, als ob die ausgewählte Excel-Datei beschädigt ist, falsche Daten enthält, falsch strukturiert ist oder falsch ausgewählt wurde. Aus diesem Grund konnten keine Bohnen importiert werden.", + "OK": "Ok", + "BEAN_SORT_BEAN_AGE": "Bohnenalter", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Alle in der Excel-Liste gefundenen Einträge wurden in neue grüne Bohnen umgewandelt, happy Roasting!", + "PREPARATION_TYPE_SANREMO_YOU": "Sanremo YOU", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "Du fügst eine Bohne hinzu, die Sorteninformationen enthält, aber diese Parameter sind nicht aktiviert. Möchtest du sie jetzt aktivieren?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Definieren der Achsen für den Druckgraph", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Festlegen der Anfangs- und Endgröße der Achsen für den Druck", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Druckachsen", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Holländisch", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "Importvorlagen herunterladen", + "SHOW_GRAPH": "Graph anzeigen", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Version 7.5.0: Das ist neu", + "DESCRIPTION": [ + "Neue Sprache<\/b>", + "Unterstützung von Niederländisch - Danke an Ygg", + "", + "Brühungen<\/b>", + "Das Einklappen der Brühliste ist nun möglich", + "Anzeigen, ob eine gefrorene Bohne verwendet wurde", + "Bohnenbild jetzt in der Auswahl sichtbar", + "Lesen und importieren von alten Brühungen der Meticulous", + "Brühlisten-Einträge können jetzt gewischt werden, wenn sie ein Diagramm haben", + "", + "Bohnen<\/b>", + "Das Einklappen der Bohnenliste ist jetzt möglich", + "Bohnen können jetzt nach Bohnenalter sortiert werden", + "Sortierungen werden beim Archivieren einer Bean nicht mehr zurückgesetzt", + "", + "Rohkaffee<\/b>", + "Rohkaffee sind jetzt importierbar - siehe Einstellungen", + "", + "Einstellungen<\/b>", + "Einstellen der Startachse für den Druck", + "Importieren von Rohbohnen und geröstetem Kaffee", + "", + " Erkennung von Datenbeschädigungen <\/b>", + "Beanconqueror prüft nun, ob eine Datenbeschädigung aufgetreten ist und zeigt Ihnen in diesem Fall ein Popup an, um das Einspielen einer Sicherungskopie zu ermöglichen.", + "", + "Sonstiges<\/b>", + "Buxfix zur Sortierung von Zubereitungsmethoden", + "Korrektur der Berechnung von gefrorenen Bohnen - Behebung von Rundungsproblemen", + "Einige technische Änderungen im Code", + "Kleine Optimierungen" + ] + } + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index b73e29944..7a183f5bf 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "None", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "Sanremo YOU" }, "URL": "Url", "CHOOSE_DEVICE": "Choose device", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "Choose profile", "SHOT_STARTED": "Shot started", "SHOT_ENDED": "Shot ended" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "Sanremo YOU Machine", + "STOP_AT_WEIGHT": "Stop at weight", + "SELECT_MODE": "Select mode", + "MODE_LISTENING": "Listening", + "MODE_CONTROL": "Control", + "MANUAL_CONTROLLING": "Manual Control", + "PROFILE_P1_CONTROLLING": "Profile P1 Control", + "PROFILE_P2_CONTROLLING": "Profile P2 Control", + "PROFILE_P3_CONTROLLING": "Profile P3 Control", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "You did not define a target weight, the chosen profile will be fully executed", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "You did not define a target weight, please stop extraction manually" } }, "DEVICE_CONNECTION": "Device connection", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "Show minutes", "BEANS_UNARCHIVE": "Unarchive", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Bean has been unarchived", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "Version 7.4.0: What's new", - "DESCRIPTION": [ - "Beans<\/b>", - "Its now possible to freeze your coffee beans, activate this feature in the settings", - "Added best and open date for beans, you need to activate this parameter", - "Beans can now be directly added from the select overview", - "", - "Thermometer<\/b>", - "Support of the Combustion Inc. Thermometer - Thanks for the Device!", - "Support of the Meater Thermometer (not Meater 2 or Meater +) - Thanks to Yannick!", - "", - "Water Section<\/b>", - "Added Pure Coffee Water", - "Added Empirical Water", - "", - "Preparation Tools<\/b>", - "Sort now your preparation tools", - "", - "Grinder<\/b>", - "Grinder images are now shown in the select overview", - "", - "Settings<\/b>", - "Security message if you changed the rating for brews or beans, and the maximum is lower then an already rated entry", - "", - "Other<\/b>", - "Reverted the direct seconds focus when opening the timer", - "Some technical changes in the code", - "Small tweaks" - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Pure Coffee Water", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "empirical water GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "empirical water SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "Frozen notes", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "Activating this, results into a graph which will be one second behind", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "Activating this, results into a graph which will be one second behind", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Save the machine logs when saving a brew" + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Save the machine logs when saving a brew", + "IMPORT_SHOT_FROM_METICULOUS": "Import a shot", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "All found entries in the excel list, have been transformed into new beans, enjoy a tasty brew!", + "IMPORT_UNSUCCESSFULLY": "Import not successful", + "BEAN_LIST": "Bean list", + "IMPORT_ROASTED_BEANS_EXCEL": "Import roasted beans via excel", + "IMPORT_GREEN_BEANS_EXCEL": "Import green beans via excel", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "It looks like that the chosen excel file is corrupted, has wrong data, is wrong structured, or was wrongly chosen. Because of this no beans could be imported.", + "OK": "Ok", + "BEAN_SORT_BEAN_AGE": "Bean age", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "All found entries in the excel list, have been transformed into new green beans, have great roasts!", + "PREPARATION_TYPE_SANREMO_YOU": "Sanremo YOU", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "You're adding a bean which has variety information in it, but those parameters are not activated. Do you want to activate them now?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Define the axes for the pressure graph", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Set the starting and end size of the axes for pressure", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Pressure axes", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Dutch", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "Download import templates", + "SHOW_GRAPH": "Show graph", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Version 7.5.0: What's new", + "DESCRIPTION": [ + "New language<\/b>", + "Dutch support - Thanks to Ygg", + "", + "Brews<\/b>", + "Collapsing a brew-list is now possible", + "Displaying if a frozen bean was used", + "Bean image now visible on selection", + "Read and import a history shot from the Meticulous", + "Brew list entries are now swipe able when they have graph", + "", + "Beans<\/b>", + "Collapsing the bean list is now possible", + "Beans are now sortable by bean age", + "Sorting are not reset anymore when archiving a bean", + "", + "Green beans<\/b>", + "Green beans are now importable - See Settings", + "", + "Settings<\/b>", + "Set the starting axis for the pressure", + "Import green beans or roasted beans", + "", + "Data corruption detection<\/b>", + "Beanconqueror now checks if somehow a data corruption occured, and if yes, it shows you a popup to make it possible to import a backup", + "", + "Other<\/b>", + "Fixing sorting of preparation tools", + "Fixing frozen bean calculation to fix rounding issues", + "Some technical changes in the code", + "Small tweaks" + ] + } + } } \ No newline at end of file diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json index 9736cc490..f6d2501ef 100644 --- a/src/assets/i18n/es.json +++ b/src/assets/i18n/es.json @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "Ninguno", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "" }, "URL": "URL", "CHOOSE_DEVICE": "Seleccionar dispositivo", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "Elegir perfil", "SHOT_STARTED": "Preparación iniciada", "SHOT_ENDED": "Preparación finalizada" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "", + "STOP_AT_WEIGHT": "", + "SELECT_MODE": "", + "MODE_LISTENING": "", + "MODE_CONTROL": "", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" } }, "DEVICE_CONNECTION": "Conexión del dispositivo", @@ -1228,7 +1242,7 @@ "PAGE_SETTINGS_BREW_TIMER_START_DELAY_ACTIVE_DESCRIPTION": "Elige el tiempo de retraso que quieres que se muestre al iniciar una preparación", "STARTING_IN": "Iniciando en... {{time}}", "IOS_DATABASE_ISSUE_TITLE": "¡ATENCIÓN! SE HA PERDIDO LA CONEXIÓN CON LA BASE DE DATOS", - "IOS_DATABASE_ISSUE_DESCRIPTION": "Lamentamos comunicarte que la conexión con la base de datos se ha perdido. Este problema es debido a un fallo no resuelto en el sistema operativo por parte de Apple, que va más allá del control de Beanconqueror. Para evitar que ser pierdan datos, cierra completamente la aplicación y ábrela de nuevo.", + "IOS_DATABASE_ISSUE_DESCRIPTION": "Hemos perdido la conexión a la base de datos. Esto es debido a un fallo no resuelto en iOS por parte de Apple, que escapa al control de Beanconqueror. Para evitar que se pierdan datos, cierra completamente la aplicación y ábrela de nuevo.", "RELOAD_APP": "Recargar aplicación ahora", "WATER_TYPE_ADD_CUSTOM": "Agua personalizada", "WATER_TYPE_THIRD_WAVE_WATER_CLASSIC_LIGHT_ROAST_PROFILE": "Third Wave Water - Classic Light Roast Profile", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "Mostrar minutos", "BEANS_UNARCHIVE": "Desarchivar", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Café desarchivado", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "Versión 7.4.0: Novedades", - "DESCRIPTION": [ - "Cafés<\/b>", - "Ahora es posible congelar tus cafés, puedes activar esta opción en ajustes.", - "Añadidas fechas de apertura y de consumo óptimo para tus cafés (desactivadas por defecto).", - "Ahora puedes añadir un nuevo café desde el menú de selección de cafés de una preparación.", - "", - "Termómetro<\/b>", - "Añadido soporte para el termómetro Combustion Inc. - Gracias por el aporte!", - "Añadido soporte para el termómetro Meater (no Meater 2 o Meater+) - Gracias a Yannick!", - "", - "Sección de agua<\/b>", - "Añadida agua Pure Coffee Water", - "Añadida agua Empirical Water", - "", - "Métodos de preparación<\/b>", - "Ahora puedes ordenar tus métodos de preparación", - "", - "Molinos<\/b>", - "Ahora se muestran las imágenes de los molinos en el menú de selección.", - "", - "Ajustes<\/b>", - "Se muestra una alerta si cambias la puntuación de una preparación o un café, y el máximo es menor que otra entrada ya puntuada.", - "", - "Otros<\/b>", - "El campo \"segundos\" ya no se selecciona automáticamente al abrir el selector de tiempo.", - "Cambios técnicos en el código.", - "Pequeños ajustes y mejoras." - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Pure Coffee Water", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "empirical water GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "empirical water SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "Información de congelado", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "Activar esto añadirá un segundo de retardo en los gráficos", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "Activar esto añadirá un segundo de retardo en los gráficos", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Guardar los registros de la máquina con la preparación" + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Guardar los registros de la máquina con la preparación", + "IMPORT_SHOT_FROM_METICULOUS": "Importar tiro de espresso", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Se han importado con éxito los cafés. ¡A disfrutarlos!", + "IMPORT_UNSUCCESSFULLY": "Importación fallida", + "BEAN_LIST": "Lista de cafés", + "IMPORT_ROASTED_BEANS_EXCEL": "Importar cafés tostados desde Excel", + "IMPORT_GREEN_BEANS_EXCEL": "Importar cafés verdes desde Excel", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "El archivo Excel seleccionado tiene datos erróneos o no tiene el formato correcto. No se han podido importar los datos.", + "OK": "OK", + "BEAN_SORT_BEAN_AGE": "Antigüedad", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Se han importado con éxito los cafés verdes. ¡A tostar!", + "PREPARATION_TYPE_SANREMO_YOU": "", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "Vas a añadir un café con datos que están actualmente ocultos. ¿Quieres que cambiemos los ajustes para que se muestren?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Definir ejes para la gráfica de presión", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Rango de valores para el eje de presión", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Ejes de presión", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Neerlandés", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "Descargar plantilla Excel para importar datos", + "SHOW_GRAPH": "Mostrar gráfica", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Versión 7.5.0: Novedades", + "DESCRIPTION": [ + "Nuevo idioma<\/b>", + "Añadida traducción al Neerlandés - Gracias a Ygg", + "", + "Preparaciones<\/b>", + "Ahora se puede contraer la lista de preparaciones.", + "Ahora se muestra si se ha usado un café congelado.", + "El selector de cafés muestra ahora la foto de cada uno.", + "Añadido soporte para importar espressos preparados en Meticulous.", + "Si las preparaciones tienen gráficas asociadas, ahora puedes deslizar para moverte entre ellas.", + "", + "Cafés<\/b>", + "Ahora se puede contraer la lista de cafés.", + "Añadida opción para ordenar por antigüedad del café.", + "El ordenado ya no se pierde al archivar un café.", + "", + "Cafés verdes<\/b>", + "Añadido soporte para cafés verdes (sin tostar).", + "", + "Configuración<\/b>", + "Ahora se puede ajustar el rango del eje de presión en las gráficas.", + "Ahora puedes añadir cafés verdes (sin tostar) o tostados.", + "", + "Detección de datos corruptos<\/b>", + "Beanconqueror ahora verifica si los datos guardados se han corrompido. En ese caso, se intentará restaurar una copia de seguridad.", + "", + "Otros<\/b>", + "Solucionados errores al ordenar herramientas de preparación.", + "Solucionados errores numéricos de redondeo al congelar cafés.", + "Cambios técnicos en el código.", + "Pequeñas mejoras." + ] + } + } } \ No newline at end of file diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index 8f71307e2..1d7c7d782 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -153,7 +153,7 @@ "BREW_DATA_ATTACHMENTS": "Fichiers jointes \/ Photos", "BREW_DATA_RATING": "Evaluation", "BREW_DATA_CALCULATED_COFFEE_BREW_TIME": "Temps d'infusion du café", - "BREW_DATA_TDS": "Total des solides dissous (TDS)", + "BREW_DATA_TDS": "Solides dissous totaux (SDT)", "BREW_DATA_CALCULATED_EXTRACTION_YIELD": "Rendement d’extraction %", "BREW_INFORMATION_BREW_RATIO": "Ratio d’infusion", "BREW_INFORMATION_BEAN_AGE": "Âge des grains", @@ -479,7 +479,7 @@ "CUSTOM_MANAGE_PARAMETERS": "Gérer", "CUSTOM_SORT_PARAMETERS": "Trier", "BREW_PARAMETER_CUSTOMIZE_TITLE": "Paramètres personnalisés pour chaque méthode de préparation", - "BREW_PARAMETER_CUSTOMIZE_DESCRIPTION": "Tu veux choisir des paramètres personnalisés pour chaque méthode de préparation ? Navigues jusqu'à \"Méthodes\", ouvre le menu de la méthode de préparation spécifique et choisissez \"Personnaliser les paramètres\". Là, tu peux choisir quels paramètres seront utilisés pour cette préparation !", + "BREW_PARAMETER_CUSTOMIZE_DESCRIPTION": "Tu veux choisir des paramètres personnalisés pour chaque méthode de préparation ? Navigue jusqu'à \"Méthodes\", ouvre le menu de la méthode de préparation spécifique et choisissez \"Personnaliser les paramètres\". Là, tu peux choisir quels paramètres seront utilisés pour cette préparation !", "BREW_DATA_BREW_QUANTITY_TOOLTIP": "Quantité d'eau (non utilisable pour l'espresso)", "BREW_DATA_COFFEE_FIRST_DRIP_TIME_TOOLTIP": "Premier temps d'égouttage du café (espresso uniquement)", "BREW_DATA_PREPARATION_METHOD_TOOLTIP": "Préparation (uniquement personnalisable lorsqu'elle est active)", @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "Aucun", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "Sanremo YOU" }, "URL": "Url", "CHOOSE_DEVICE": "Choisir l'appareil", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "Choisir un profil", "SHOT_STARTED": "L'infusion a commencé", "SHOT_ENDED": "Infusion terminée" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "Machine San Remo YOU", + "STOP_AT_WEIGHT": "Arrêt au poids", + "SELECT_MODE": "Sélectionner le mode", + "MODE_LISTENING": "Écouter", + "MODE_CONTROL": "Contrôle", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" } }, "DEVICE_CONNECTION": "Connexion de l'appareil", @@ -1148,7 +1162,7 @@ }, "SHOT": { "UPLOAD_SUCCESSFULLY": "Infusion téléchargé sur Visualizer.", - "UPLOAD_UNSUCCESSFULLY": "L'infusion n'a pas pu être téléchargé sur Visualizer." + "UPLOAD_UNSUCCESSFULLY": "La préparation n'a pas pu être téléchargée sur Visualizer." }, "URL": "URL du serveur", "USERNAME": "Nom d'utilisateur", @@ -1223,7 +1237,7 @@ "PAGE_SETTINGS_LANGUAGE_POLISH": "Polonais", "BREW_CANT_START_BECAUSE_TIMER_NOT_RESETTED_GENERAL_DESCRIPTION": "Tu dois d'abord réinitialiser ton minuteur avant de pouvoir commencer", "SMART_SCALE_FIRST_DRIP_THRESHOLD": "Seuil de la première goutte", - "SMART_SCALE_FIRST_DRIP_THRESHOLD_TOOLTIP": "À quel poids de balance la première goutte doit-elle être comptée ? Valeur par défaut : >= 0,1 g", + "SMART_SCALE_FIRST_DRIP_THRESHOLD_TOOLTIP": "À quel poids au niveau de la balance la première goutte doit-elle être comptée ? Valeur par défaut : >= 0,1 g", "PAGE_SETTINGS_BREW_TIMER_START_DELAY_ACTIVE": "Délai de démarrage de l’infusion", "PAGE_SETTINGS_BREW_TIMER_START_DELAY_ACTIVE_DESCRIPTION": "Définir un délai pour l'affichage de l'essoreuse de chargement jusqu'à ce que l'infusion commence", "STARTING_IN": "Partir à {{time}}", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "Afficher les minutes", "BEANS_UNARCHIVE": "Désarchiver", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Le grain a été désarchivé", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "Version 7.4.0: c'est nouveau", - "DESCRIPTION": [ - "Grains<\/b>", - "Il est possible de congeler des grains de café. Active cette fonction dans les réglages", - "Ajout de la meilleure date et de la date d'ouverture pour les grains. Ces paramètres doivent être activés.", - "Les grains peuvent désormais être ajoutés directement à partir de l’aperçu de la sélection", - "", - " Thermomètre <\/b>", - "Support pour les thermomètres de Combustion Inc. – Merci pour l'appareil !", - "Support du thermomètre Meater (pas Meater 2 ou Meater +) – Merci à Yannick !", - "", - " Zone d'eau <\/b>", - "Ajouté Pure Coffee Water", - "Ajouté Empirical Water", - "", - " Outils de méthode d'infusion <\/b>", - "Trier tes méthodes de préparation", - "", - "Moulins<\/b>", - "Les images des moulins sont affichées dans l'aperçu", - "", - "Paramètres<\/b>", - "Message de sécurité si tu as modifié l'évaluation des infusions ou des grains et que le maximum est inférieur à une entrée déjà évaluée.", - "", - "Autres<\/b>", - "La mise au point directe en secondes lors de l'ouverture de la minuterie a été rétablie", - "Quelques changements techniques dans le code", - "Petites optimisations" - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Pure Coffee Water", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "empirical water GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "empirical water SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "Notes sur geler", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "En activant cette fonction, le graphique sera décalé d'une seconde.", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "En activant cette fonction, le graphique sera décalé d'une seconde.", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Enregistrer les journaux de la machine lors de l’enregistrement d’une infusion" + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Enregistrer les journaux de la machine lors de l’enregistrement d’une infusion", + "IMPORT_SHOT_FROM_METICULOUS": "Importer une infusion", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Toutes les entrées trouvées dans la liste Excel ont été transformées en nouveaux grains, savoure une infusion délicieuse!", + "IMPORT_UNSUCCESSFULLY": "L'importation n'a pas abouti", + "BEAN_LIST": "Liste des grains", + "IMPORT_ROASTED_BEANS_EXCEL": "Importer des grains torréfiés via Excel", + "IMPORT_GREEN_BEANS_EXCEL": "Importer des grains verts via Excel", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "Il semble que le fichier Excel choisi soit corrompu, qu’il contienne des données incorrectes, qu’il soit mal structuré ou qu’il ait été mal choisi. Pour cette raison, aucun grain ne pouvait être importé.", + "OK": "Ok", + "BEAN_SORT_BEAN_AGE": "Âge des grains", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Toutes les entrées trouvées dans la liste excel ont été transformées en nouveaux grains verts, happy roasting !", + "PREPARATION_TYPE_SANREMO_YOU": "Sanremo YOU", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "Tu ajoutes un grain qui contient des informations de variété, mais ces paramètres ne sont pas activés. Veux-tu les activer maintenant ?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Définir les axes du graphique de pression", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Définir la taille initiale et finale des axes de pression", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Axes de pression", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Néerlandais", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "Télécharger les modèles d'importation", + "SHOW_GRAPH": "Afficher le graphique", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Version 7.5.0: What's new?", + "DESCRIPTION": [ + " Nouvelle langue <\/b>", + "Support néerlandais - Merci à Ygg", + "", + "Infusions<\/b>", + "Il est possible de réduire une liste des infusions", + "Affichage si un grain congelé a été utilisé", + "L'image du grain est désormais visible lors de la sélection", + "Lisez et importez une infusion historique du Meticulous", + "Les entrées de la liste des infusions peuvent être balayées lorsqu’elles ont un graphique.", + "", + "Grains<\/b>", + "Il est possible de réduire la liste des grains", + "Les grains peuvent maintenant être triés par âge", + "Les tris ne sont plus réinitialisés lors de l'archivage d'un grain", + "", + "Grains verts<\/b>", + "Les haricots verts sont désormais importables - Voir les Paramètres", + "", + "Paramètres<\/b>", + "Définir l'axe de départ pour la pression", + "Importer des grains verts ou des grains torréfiés", + "", + "Détection de la corruption des données<\/b>", + "Beanconqueror vérifie maintenant si une corruption de données s'est produite, et si c'est le cas, il affiche une fenêtre contextuelle pour permettre l'importation d'une sauvegarde", + "", + " Autre <\/b>", + "Correction du tri des outils de préparation", + "Correction du calcul des grains congelés pour résoudre les problèmes d'arrondi", + "Quelques changements techniques dans le code", + "Petits ajustements" + ] + } + } } \ No newline at end of file diff --git a/src/assets/i18n/id.json b/src/assets/i18n/id.json index fc9969cdf..f9538a840 100644 --- a/src/assets/i18n/id.json +++ b/src/assets/i18n/id.json @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "None", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "" }, "URL": "Url", "CHOOSE_DEVICE": "Choose device", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "Pilih profil", "SHOT_STARTED": "Shot dimulai", "SHOT_ENDED": "Shot selesai" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "", + "STOP_AT_WEIGHT": "", + "SELECT_MODE": "", + "MODE_LISTENING": "", + "MODE_CONTROL": "", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" } }, "DEVICE_CONNECTION": "Device connection", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "Tampilkan menit", "BEANS_UNARCHIVE": "Tidak diarsipkan", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Bean tidak diarsipkan", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "Versi 7.4.0: Apa yang baru", - "DESCRIPTION": [ - "Biji<\/b>", - "Sekarang dimungkinkan untuk mencatat pembekuan biji kopi Anda, aktifkan fitur ini di pengaturan", - "Menambahkan tanggal terbaik dan tanggal membuka kopi, Anda perlu mengaktifkan parameter ini", - "Biji sekarang dapat langsung ditambahkan dari pilihan", - "", - "Thermometer<\/b>", - "Dukungan Termometer Combustion Inc. - Terima kasih untuk Perangkatnya!", - "Dukungan Termometer Meater (bukan Meater 2 atau Meater +) - Terima kasih kepada Yannick!", - "", - "Water Section<\/b>", - "Penambahan Pure Coffee Water", - "Penambahan Empirical Water", - "", - "Preparation Tools<\/b>", - "Jenis alat-alat seduhan dapat diurutkan", - "", - "Grinder<\/b>", - "Gambar grinder sekarang ditampilkan dalam pilihan", - "", - "Pengaturan<\/b>", - "Pesan keamanan jika Anda mengubah peringkat untuk seduhan atau biji kopi, dan nilai maksimumnya lebih rendah dari entri yang sudah diberi peringkat", - "", - "Lain-lain<\/b>", - "Mengembalikan fokus detik langsung saat membuka pengatur waktu", - "Beberapa perubahan teknis pada kode", - "Perubahan kecil" - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Pure Coffee Water", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "empirical water GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "empirical water SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "Notes beku", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "Mengaktifkannya akan menghasilkan grafik yang tertinggal satu detik", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "Mengaktifkannya akan menghasilkan grafik yang tertinggal satu detik", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Simpan log mesin saat menyimpan seduhan" + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Simpan log mesin saat menyimpan seduhan", + "IMPORT_SHOT_FROM_METICULOUS": "Mengimpor seduhan", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Semua entri yang ditemukan dalam daftar Excel, telah diubah menjadi entri biji baru, selamat menikmati!", + "IMPORT_UNSUCCESSFULLY": "Impor tidak berhasil", + "BEAN_LIST": "Daftar kopi", + "IMPORT_ROASTED_BEANS_EXCEL": "Impor kopi menggunakan Excel", + "IMPORT_GREEN_BEANS_EXCEL": "Impor green beans menggunakan Excel", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "Tampaknya file excel yang dipilih rusak, memiliki data yang salah, terstruktur salah, atau dipilih secara salah. Oleh karena itu, tidak ada data biji kopi yang dapat diimpor.", + "OK": "Ok", + "BEAN_SORT_BEAN_AGE": "Umur biji", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Semua entri yang ditemukan dalam daftar excel, telah diubah menjadi entrti green beans baru, selamat meroasting!", + "PREPARATION_TYPE_SANREMO_YOU": "", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "Anda menambahkan kopi yang berisi informasi varietas, tetapi parameter tersebut tidak diaktifkan. Apakah Anda ingin mengaktifkannya sekarang?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Tentukan sumbu untuk grafik tekanan", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Atur ukuran awal dan akhir sumbu untuk tekanan", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Sumbu untuk tekanan", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Belanda", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "Unduh templat impor", + "SHOW_GRAPH": "Tampilkan grafik", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Versi 7.5.0: Apa yang baru?", + "DESCRIPTION": [ + "Bahasa baru<\/b>", + "Dukungan bahasa Belanda - Terima kasih kepada Ygg", + "", + "Seduhan<\/b>", + "Sekarang dimungkinkan untuk menutup daftar seduhan", + "Menampilkan jika biji kopi beku digunakan", + "Foto biji kopi sekarang terlihat pada pilihan", + "Membaca dan mengimpor riwayat penyeduhan dari Meticulous", + "Entri daftar brew sekarang dapat digeser ketika memiliki grafik", + "", + "Beans<\/b>", + "Sekarang dimungkinkan untuk menutup daftar kopi", + "Kopi sekarang dapat diurutkan berdasarkan umur", + "Penyortiran tidak ter-reset lagi saat mengarsipkan kacang", + "", + "Green beans<\/b>", + "Green bean sekarang dapat diimpor - Lihat Pengaturan", + "", + "Pengaturan<\/b>", + "Atur sumbu awal untuk tekanan", + "Impor green beans atau kopi matang", + "", + "Kerusakan data terdeteksi<\/b>", + "Beanconqueror sekarang memeriksa apakah ada kerusakan data yang terjadi, dan jika ya, ini akan menampilkan popup untuk memungkinkan impor cadangan", + "", + "Lainnya<\/b>", + "Memperbaiki penyortiran alat seduh", + "Memperbaiki perhitungan (masalah pembulatan) biji kopi beku", + "Beberapa perubahan kecil dalam kode", + "Perubahan kecil" + ] + } + } } \ No newline at end of file diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index 0face123a..2fba94e44 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -44,13 +44,13 @@ "PAGE_HOME_DIFFERENT_PREPARATION_METHODS": "metodi di preparazione", "PAGE_HOME_DIFFERENT_MILLS": "macinacaffè", "PAGE_HOME_SUPPORTER": "App Supporter", - "PAGE_HOME_START_BREW": "", + "PAGE_HOME_START_BREW": "Inizia la preparazione", "PAGE_BEANS_LIST_OBTAINABLE": "Disponibili", "PAGE_BEANS_LIST_YOU_GOT_NO_FRESH_BEANS": "Non c'è più caffè!", "PAGE_BEANS_LIST_YOU_GOT_NO_FINISHED_BEANS": "Nessun caffè archiviato", "PAGE_MILL_LIST_NO_MILL_EXISTING": "Nessun macinacaffè", "PAGE_PREPARATION_LIST_NO_PREPARATION_EXISTING": "Nessun metodo di preparazione", - "PAGE_CONTACT_SUGGESTIONS_QUESTIONS_WISHES": "Suggerimenti, domande, bug o richieste?", + "PAGE_CONTACT_SUGGESTIONS_QUESTIONS_WISHES": "Suggerimenti, domande, bugs o richieste?", "PAGE_THANKS_THANKS_FOR_YOUR_SUPPORT": "Grazie per il sostegno!", "PAGE_SETTINGS_LANGUAGE": "Impostazioni della lingua", "PAGE_SETTINGS_LANGUAGE_GERMAN": "Deutsch", @@ -61,8 +61,8 @@ "PAGE_SETTINGS_GENERAL_SETTINGS": "Impostazioni generali", "PAGE_SETTINGS_TRANSFER": "Trasferisci dati", "PAGE_SETTINGS_PRESET_LAST_BREW": "Attiva parametri preimpostati", - "PAGE_SETTINGS_DISPLAY": "", - "PAGE_SETTINGS_DISPLAY_SINGLE_PAGE": "", + "PAGE_SETTINGS_DISPLAY": "Visualizza", + "PAGE_SETTINGS_DISPLAY_SINGLE_PAGE": "Pagina singola", "PAGE_SETTINGS_DISPLAY_PAGING": "Paginazione", "PAGE_SETTINGS_STARTUP_VIEW": "Pagina iniziale", "PAGE_SETTINGS_ANALYTICS_INFORMATION": "Analytics", @@ -76,10 +76,10 @@ "PAGE_STATISTICS_TOTAL_GROUND_BEANS": "Totale caffè macinato", "PAGE_STATISTICS_MONEY_SPENT_FOR_COFFEE": "Soldi spesi in caffè", "PAGE_STATISTICS_DRUNKEN_BREWS": "Numero totale preparazioni", - "PAGE_STATISTICS_BREW_PROCESSES": "Preparazioni", + "PAGE_STATISTICS_BREW_PROCESSES": "Preparazioni totali", "PAGE_STATISTICS_DRUNKEN_QUANTITY": "Quantità consumata", "PAGE_STATISTICS_BEAN_WEIGHT_USED": "Totale caffè macinato", - "PAGE_BREW_TEXT_INFORMATION_FROM_ROASTER": "", + "PAGE_BREW_TEXT_INFORMATION_FROM_ROASTER": "Info sulla torrefazione", "PAGE_ABOUT_NO_VERSION_AVAILABLE": "Nessuna versione disponibile", "PAGE_ABOUT_APP_VERSION": "Versione App", "PAGE_LICENCES_WEBSITE": "Sito web", @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "Nessuno", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "" }, "URL": "URL", "CHOOSE_DEVICE": "Scegli dispositivo", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "Scegli profilo", "SHOT_STARTED": "Estrazione iniziata", "SHOT_ENDED": "Estrazione terminata" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "", + "STOP_AT_WEIGHT": "", + "SELECT_MODE": "", + "MODE_LISTENING": "", + "MODE_CONTROL": "", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" } }, "DEVICE_CONNECTION": "Connessione dispositivi", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "Mostra minuti", "BEANS_UNARCHIVE": "Annulla l'archiviazione", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Il caffè è stato disarchiviato", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "Versione 7.4.0: Cosa c'è di nuovo", - "DESCRIPTION": [ - "Caffè<\/b>", - "Ora è possibile tenere traccia del caffè congelato, puoi attivare questa funzione nelle impostazioni", - "Aggiunta della data suggerita e di apertura per i caffè, è necessario attivare questo parametro nelle impostazioni", - "Ora è possibile aggiungere del caffè direttamente dalla selezione", - "", - " Termometro <\/b>", - "Supporto del termometro Combustion Inc. - Grazie per il dispositivo!", - "Supporto del termometro Meater (non Meater 2 o Meater +) - Grazie a Yannick!", - "", - "Sezione Acqua<\/b>", - "Aggiunta l'acqua Pure Coffee Water", - "Aggiunta l'acqua Empirical Water", - "", - "Accessori preparazione<\/b>", - "Ordina ora i tuoi strumenti di preparazione", - "", - "Macinacaffè<\/b>", - "Le immagini del macinacaffè sono mostrate nella selezione", - "", - "Impostazioni<\/b>", - "Messaggio di sicurezza se hai modificato la valutazione e il massimo è inferiore a cioè che è già valutato", - "", - "Altro<\/b>", - "Rimossa la recente aggiunta di auto-focus sui secondi del timer della preparazione", - "Piccole migliorie nel codice", - "Piccole migliorie" - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Pure Coffee Water", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "empirical water GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "empirical water SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "Note sul congelato", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "Attivando questa opzione, il grafico avrà un secondo di ritardo", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "Attivando questa opzione, il grafico avrà un secondo di ritardo", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Salva i log della macchina al salvataggio della preparazione" + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Salva i log della macchina al salvataggio della preparazione", + "IMPORT_SHOT_FROM_METICULOUS": "Importa un'estrazione", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Tutte le voci dell'excel, sono state importate, buona preparazione!", + "IMPORT_UNSUCCESSFULLY": "Importazione fallita", + "BEAN_LIST": "Lista caffè", + "IMPORT_ROASTED_BEANS_EXCEL": "Importa caffè tostato tramite Excel ", + "IMPORT_GREEN_BEANS_EXCEL": "Importa verde tramite Excel", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "Sembra che il file excel scelto sia corrotto, abbia dati sbagliati, sia strutturato male o non sia quello giusto. Non è stato possibile importare alcun caffè.", + "OK": "Ok", + "BEAN_SORT_BEAN_AGE": "Giorni dalla tostatura", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Tutte le voci dell'excel, sono state importate, buona tostatura!", + "PREPARATION_TYPE_SANREMO_YOU": "", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "Si sta aggiungendo un caffè che contiene informazioni sulla varietà, ma questi parametri non sono attivi. Vuoi attivarla?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Imposta gli assi per il grafico della pressione", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Imposta la scala degli assi per l'asse della pressione", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Asse pressione", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Nederlands", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "Scarica i template di importazione", + "SHOW_GRAPH": "Mostra grafico", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Versione 7.5.0: Novità", + "DESCRIPTION": [ + "Nuova lingua<\/b>", + "Supporto dell'olandese - Grazie a Ygg", + "", + "Preparazioni<\/b>", + "Ora è possibile comprimere la lista delle preparazioni", + "È possibile mostrare se è stato usato un caffè congelato", + "L'immagine del caffè è visibile alla selezione", + "È possibile leggere e importare la cronologia delle preparazioni di una Meticulous", + "È possibile fare swipe sugli elementi della lista preparazioni per mostrare il grafico, se presente", + "", + "Caffè<\/b>", + "Ora è possibile comprimere la lista dei caffè", + "È possibile ordinare i caffè per età", + "L'ordinamento in lista non viene più ripristinato quando si archivia un caffè", + "", + "Verde<\/b>", + "È ora possibile importare il verde in Impostazioni", + "", + "Impostazioni<\/b>", + "Imposta l'asse di partenza per la pressione", + "Importa verde o caffè tostati", + "", + "Rilevamento della corruzione dei dati<\/b>", + "Beanconqueror ora controlla se in qualche modo si è verificato una corruzione dei dati mostrando un popup per importare di un backup", + "", + "Altro<\/b>", + "Correzione dell'ordinamento degli strumenti di preparazione", + "Correzione del calcolo sui caffè congelati (problemi di arrotondamento)", + "Piccole migliorie nel codice", + "Migliorie minori" + ] + } + } } \ No newline at end of file diff --git a/src/assets/i18n/nl.json b/src/assets/i18n/nl.json new file mode 100644 index 000000000..8b67f4fa2 --- /dev/null +++ b/src/assets/i18n/nl.json @@ -0,0 +1,1393 @@ +{ + "NAV_MENU": "Menu", + "NAV_HOME": "Thuis", + "NAV_SETTINGS": "Instellingen", + "NAV_BREWS": "Brouwsels", + "NAV_BEANS": "Bonen", + "NAV_PREPARATION": "Methoden", + "NAV_MILL": "Malers", + "NAV_ABOUT_US": "Over ons", + "NAV_CONTACT": "Contact", + "NAV_PRIVACY": "Privacy", + "NAV_CREDITS": "Credits", + "NAV_TERMS": "Algemene voorwaarden", + "NAV_THANKS": "Bedankt!", + "NAV_LICENCES": "OSS-licenties", + "NAV_STATISTICS": "Statistieken", + "NAV_IMPRESSUM": "Impressum", + "NAV_COOKIE": "Cookies", + "NAV_LOGS": "Logboeken", + "NAV_BREW_PARAMS": "Brouwparameters", + "NAV_INFORMATION_TO_APP": "Over Beanconqueror", + "NAV_WATER_SECTION": "Water", + "NAV_HELPER": "Calculaties", + "POPOVER_BREWS_OPTION_REPEAT": "Herhalen", + "POPOVER_BREWS_OPTION_DETAIL": "Details", + "DETAIL": "Details", + "POPOVER_BREWS_OPTION_EDIT": "Bewerking", + "POPOVER_BREWS_OPTION_DELETE": "Verwijder", + "POPOVER_BREWS_OPTION_PHOTO_GALLERY": "Fotogalerij", + "POPOVER_BREWS_OPTION_CUPPING": "Cupping", + "POPOVER_BREWS_OPTION_MAP_COORDINATES": "Toon op kaart", + "POPOVER_BREWS_OPTION_FAST_REPEAT": "Snel brouwen herhalen", + "PAGE_BREWS_NO_ENTRIES": "Nog geen brouwsels toegevoegd", + "PAGE_BREWS_NO_ARCHIVED_ENTRIES": "Je hebt nog geen brouwsels gearchiveerd", + "CANT_START_NEW_BREW_TITLE": "Er ontbreekt hier iets...", + "CANT_START_NEW_BREW_DESCRIPTION": "Om te beginnen, maak een beschrijving voor één type bonen, één zetmethode en één grinder. Gebruik het menu om naar de verschillende categorieën te navigeren om deze informatie toe te voegen.", + "PAGE_HOME_WELCOME_GREETINGS": "Leuk dat je er bent!", + "PAGE_HOME_TOTAL_BREW": "Brouwen", + "PAGE_HOME_TOTAL_BREWS": "Brouwsels", + "PAGE_HOME_BEAN_EXPLORED": "Bonen onderzocht", + "PAGE_HOME_BEANS_EXPLORED": "Bonen onderzocht", + "PAGE_HOME_LAST_BREWS": "Laatste brouwsels", + "PAGE_HOME_LAST_BREW": "Laatste brouwsel", + "PAGE_HOME_DIFFERENT_PREPARATION_METHODS": "Verschillende bereidingsmethoden", + "PAGE_HOME_DIFFERENT_MILLS": "Verschillende malers", + "PAGE_HOME_SUPPORTER": "App Supporter", + "PAGE_HOME_START_BREW": "Begin met brouwen", + "PAGE_BEANS_LIST_OBTAINABLE": "Beschikbaar", + "PAGE_BEANS_LIST_YOU_GOT_NO_FRESH_BEANS": "Je hebt geen verse bonen meer!", + "PAGE_BEANS_LIST_YOU_GOT_NO_FINISHED_BEANS": "Je hebt geen gearchiveerde bonen.", + "PAGE_MILL_LIST_NO_MILL_EXISTING": "Je hebt nog geen grinders toegevoegd.", + "PAGE_PREPARATION_LIST_NO_PREPARATION_EXISTING": "Je hebt nog geen zetmethodes toegevoegd.", + "PAGE_CONTACT_SUGGESTIONS_QUESTIONS_WISHES": "Suggesties, vragen, bugs of verzoeken?", + "PAGE_THANKS_THANKS_FOR_YOUR_SUPPORT": "Bedankt voor je steun!", + "PAGE_SETTINGS_LANGUAGE": "Taalinstellingen", + "PAGE_SETTINGS_LANGUAGE_GERMAN": "Duits", + "PAGE_SETTINGS_LANGUAGE_ENGLISH": "Engels", + "PAGE_SETTINGS_LANGUAGE_SPANISH": "Spaans", + "PAGE_SETTINGS_LANGUAGE_TURKISH": "Turks", + "PAGE_SETTINGS_LANGUAGE_CHINESE": "Chinees", + "PAGE_SETTINGS_GENERAL_SETTINGS": "Algemene instellingen", + "PAGE_SETTINGS_TRANSFER": "Gegevens overdragen", + "PAGE_SETTINGS_PRESET_LAST_BREW": "Vooraf ingestelde waarden?", + "PAGE_SETTINGS_DISPLAY": "Weergave", + "PAGE_SETTINGS_DISPLAY_SINGLE_PAGE": "Eén pagina", + "PAGE_SETTINGS_DISPLAY_PAGING": "Paging", + "PAGE_SETTINGS_STARTUP_VIEW": "Startpagina", + "PAGE_SETTINGS_ANALYTICS_INFORMATION": "Analytics", + "PAGE_SETTINGS_ANALYTICS_INFORMATION_TOOLTIP": "Druk op i voor meer informatie", + "PAGE_SETTINGS_TRACK_BREW_COORDINATES": "Geolocatie van brouwsel opslaan", + "PAGE_SETTINGS_FAST_REPEAT": "Snel herhalen", + "PAGE_SETTINGS_TRACK_CAFFEINE_CONSUMPTION": "bewaar cafeïneconsumptie", + "PAGE_SETTINGS_WAKE_LOCK": "Houd het display actief tijdens het brouwen", + "PAGE_SETTINGS_CURRENCY": "Valuta", + "PAGE_STATISTICS_DIFFERENT_PREPARATION_METHOD": "Bereidingsmethoden", + "PAGE_STATISTICS_TOTAL_GROUND_BEANS": "Totaal gemalen bonen", + "PAGE_STATISTICS_MONEY_SPENT_FOR_COFFEE": "Geld uitgegeven aan bonen", + "PAGE_STATISTICS_DRUNKEN_BREWS": "Totaal aantal brouwsels", + "PAGE_STATISTICS_BREW_PROCESSES": "Totaal brouwsels", + "PAGE_STATISTICS_DRUNKEN_QUANTITY": "Verbruikte hoeveelheid", + "PAGE_STATISTICS_BEAN_WEIGHT_USED": "Totaal gemalen bonen", + "PAGE_BREW_TEXT_INFORMATION_FROM_ROASTER": "Informatie over de brander", + "PAGE_ABOUT_NO_VERSION_AVAILABLE": "Geen Versie beschikbaar", + "PAGE_ABOUT_APP_VERSION": "App Versie", + "PAGE_LICENCES_WEBSITE": "Website", + "BEAN_DATA_ROAST_NAME": "Mate van branding", + "BEAN_DATA_CUSTOM_ROAST_NAME": "Aangepaste mate van branding", + "BEAN_DATA_ROASTING_DATE": "Brand datum", + "BEAN_DATA_ROASTER": "Brander", + "BEAN_DATA_VARIETY": "Variëteit", + "BEAN_DATA_PROCESSING": "Verwerking", + "BEAN_DATA_COUNTRY": "Land", + "BEAN_DATA_MIX": "Melange", + "BEAN_DATA_AROMATICS": "Smaakprofiel", + "BEAN_DATA_WEIGHT": "Gewicht", + "BEAN_DATA_COST": "kosten", + "BEAN_DATA_NAME": "Naam", + "BEAN_DATA_REGION": "Regio", + "BEAN_DATA_FARM": "Boerderij", + "BEAN_DATA_FARMER": "Boer", + "BEAN_DATA_ELEVATION": "Elevatie", + "BEAN_DATA_HARVEST_TIME": "Geoogst", + "BEAN_DATA_PERCENTAGE": "Percentage", + "BEAN_DATA_CERTIFICATION": "Bonencertificering", + "BEAN_DATA_ROASTING_TYPE": "type branding", + "BEAN_DATA_DECAFFEINATED": "Cafeïnevrij", + "BEAN_DATA_URL": "Website", + "BEAN_DATA_EAN": "EAN \/ Artikelnummer", + "BEAN_DATA_PURCHASING_PRICE": "Aankoopprijs", + "BEAN_DATA_FOB_PRICE": "FOB-prijs", + "BEAN_DATA_CUPPING_POINTS": "Cupping-punten", + "BREW_DATA_CUSTOM_BREW_TIME": "Aangepaste brouwtijd", + "BREW_CREATION_DATE": "Aanmaakdatum", + "REPEAT": "Herhalen", + "EDIT": "Bewerken", + "DELETE": "Verwijderen", + "FINISHED": "Archief", + "NOTES": "Notities", + "ADD_PHOTO": "Foto toevoegen", + "CANCEL": "Annuleren", + "GENERATE": "Genereren", + "SAVE": "Opslaan", + "ADD_SOMETHING": "Toevoegen", + "CONTACT": "Contact", + "NAME": "Naam", + "IMPORT": "import", + "EXPORT": "Export", + "VIEW": "Weergave", + "ARCHIVE": "archief", + "CURRENT": "huidig", + "BACK": "Terug", + "CLOSE": "sluiten", + "DAY": "Dag", + "BREW_DATA_TEMPERATURE_TIME": "Temperatuur Tijd", + "BREW_DATA_SURF_TIME": "Surftijd", + "BREW_DATA_TIME": "Tijd", + "BREW_DATA_GRIND_SIZE": "Maalinstelling", + "BREW_DATA_GRIND_WEIGHT": "Gemalen koffie (gr)", + "BREW_DATA_IN_OUT_BR": "In\/Uit (BR)", + "BREW_DATA_NOTES": "Notities", + "BREW_DATA_PREPARATION_METHOD": "Bereidingswijze", + "BREW_DATA_MILL": "Maler", + "BREW_DATA_MILL_SPEED": "Maalsnelheid (rpm)", + "BREW_DATA_MILL_TIMER": "Maaltijd", + "BREW_DATA_BREW_QUANTITY": "Hoeveelheid water", + "BREW_DATA_BEAN_TYPE": "Boon type", + "BREW_DATA_BREW_TEMPERATURE": "Brouwtemperatuur", + "BREW_DATA_PRESSURE_PROFILE": "Profiel", + "BREW_DATA_COFFEE_TYPE": "Type koffie", + "BREW_DATA_COFFEE_CONCENTRATION": "Koffie Concentratie", + "BREW_DATA_COFFEE_FIRST_DRIP_TIME": "Eerste druppeltijd", + "BREW_DATA_COFFEE_BLOOMING_TIME": "Bloeitijd \/ Pre-infusie", + "BREW_DATA_ATTACHMENTS": "Bijlagen \/ Foto's", + "BREW_DATA_RATING": "Beoordeling", + "BREW_DATA_CALCULATED_COFFEE_BREW_TIME": "Koffiezettijd", + "BREW_DATA_TDS": "Totaal opgeloste vaste stoffen %", + "BREW_DATA_CALCULATED_EXTRACTION_YIELD": "Extractie Opbrengst %", + "BREW_INFORMATION_BREW_RATIO": "Brouw Verhouding", + "BREW_INFORMATION_BEAN_AGE": "Boon leeftijd", + "BREW_INFORMATION_BREW_QUANTITY_TYPE_NAME": "Hoeveelheidstype", + "BREW_DATA_TDS_EY": "TDS \/ %EY", + "BREW_DATA_BREW_BEVERAGE_QUANTITY": "Hoeveelheid drank", + "BREW_DATA_PREPARATION_METHOD_TOOL": "bereidings gereedschappen", + "BREW_DATA_WATER": "Water", + "BREW_DATA_BEAN_WEIGHT_IN": "Bonengewicht (g)", + "BREW_DATA_VESSEL": "Serveerkan", + "BREW_DATA_VESSEL_WEIGHT": "Serveerkan gewicht", + "BREW_DATA_VESSEL_NAME": "Serveerkan naam", + "BREW_DATA_FLAVOR": "Smaak", + "BREW_DATA_FLOW_PROFILE": "Stroom", + "ONE_DAY": "Dag", + "DAYS": "Dagen", + "ONE_HOUR": "uur", + "HOURS": "uren", + "ONE_MINUTE": "minuut", + "MINUTES": "minuten", + "WITHOUT_COFFEE": "zonder koffie", + "NOT_FOUND": "Niet gevonden", + "INVALID_FILE_FORMAT": "Ongeldig bestandsformaat", + "FILE_NOT_FOUND_INFORMATION": "Bestand niet gevonden", + "ERROR_ON_FILE_READING": "Fout bij het lezen van bestandsgegevens", + "IMPORT_SUCCESSFULLY": "Importeren succesvol", + "IMPORT_UNSUCCESSFULLY_DATA_NOT_CHANGED": "Import mislukt, er zijn geen gegevens gewijzigd", + "INVALID_FILE_DATA": "Ongeldige bestandsinhoud", + "DOWNLOADED": "Gedownload", + "FILE_DOWNLOADED_SUCCESSFULLY": "Bestand ' {{fileName}} ' is succesvol gedownload naar uw downloadmap!", + "NO": "Nee", + "YES": "Ja", + "SURE_QUESTION": "Weet je het zeker?", + "DELETE_BREW_QUESTION": "Brouwsel verwijderen?", + "DELETE_BEAN_QUESTION": "Boon verwijderen? Alle bijbehorende brouwsels worden ook verwijderd!", + "DELETE_GREEN_BEAN_QUESTION": "Groene bonen verwijderen? Alle bijbehorende geroosterde bonen, evenals brouwsels worden verwijderd!", + "DELETE_MILL_QUESTION": "Grinder verwijderen? Alle bijbehorende brouwsels worden ook verwijderd!", + "DELETE_PREPARATION_METHOD_QUESTION": "Bereidingswijze verwijderen? Alle bijbehorende brouwsels worden ook verwijderd!", + "DELETE_PREPARATION_TOOL_QUESTION": "Bereidingstool verwijderen? Alle brouwsels die aan deze tool zijn gekoppeld, worden bijgewerkt.", + "APP_COULD_NOT_STARTED_CORRECTLY_BECAUSE_MISSING_FILESYSTEM": "App kon niet correct worden gestart vanwege ontbrekend bestandssysteem", + "CARE": "Zorg", + "ERROR_OCCURED": "Er is een fout opgetreden", + "CSV_FILE_NOT_DOWNLOADED": "CSV-bestand kon niet worden gedownload!", + "CSV_FILE_DOWNLOADED_SUCCESSFULLY": "CSV-bestand ' {{fileName}} ' is succesvol gedownload naar uw downloadmap!", + "ADD_BREW": "Voeg brouwsel toe", + "CHOOSE": "Kies", + "CHOOSE_PHOTO_OR_LIBRARY": "Maak een foto of kies uit de fotogalerij.", + "RECORD": "Maak een foto", + "PAGE_BEAN_INFORMATION": "Boon informatie", + "PAGE_BEAN_INFORMATION_GOOD_BREWS": "Goed", + "PAGE_BEAN_INFORMATION_BAD_BREWS": "Slecht", + "PAGE_BEAN_INFORMATION_COUNT_BREWS": "Totaal aantal brouwsels", + "INFORMATION": "Informatie", + "PAGE_BEAN_BREW_CHART_TITLE": "Brouwoverzicht voor deze boon", + "PAGE_BEAN_INFORMATION_AWESOME_BREWS": "Geweldig", + "PAGE_BEAN_INFORMATION_NORMAL_BREWS": "Normaal", + "PAGE_BEAN_INFORMATION_NOT_RATED_BREWS": "Niet beoordeeld", + "PAGE_PREPARATION_INFORMATION_BREWS_DONE": "Brouwsels met deze bereidingswijze", + "PAGE_PREPARATION_INFORMATION_BREWED_QUANTITY": "Gebrouwen hoeveelheid", + "PAGE_PREPARATION_INFORMATION_GRIND_WEIGHT": "Verbruikt gewicht van bonen", + "PAGE_PREPARATION_INFORMATION_TIME_SPENT_BREWING": "Totale bereidingstijd", + "PAGE_PREPARATION_INFORMATION": "Informatie over de bereiding", + "SECONDS": "Seconden", + "PAGE_MILL_INFORMATION": "Maler informatie", + "PAGE_MILL_INFORMATION_BREWS_DONE": "Brouwsels met deze maler", + "PAGE_MILL_INFORMATION_GRIND_WEIGHT": "Verbruikt gewicht van bonen", + "PAGE_HELPER_WATER_HARDNESS": "Waterhardheid", + "PAGE_HELPER_WATER_HARDNESS_CA_CONTENTS": "Calciumgehalte mg\/l", + "PAGE_HELPER_WATER_HARDNESS_MG_CONTENTS": "Magnesiumgehalte mg\/l", + "PAGE_HELPER_WATER_HARDNESS_GERMAN_HARDNESS": "Duitse hardheidsgraad", + "PAGE_HELPER_WATER_HARDNESS_TOTAL_HARDNESS": "Totale hardheid", + "PAGE_HELPER_BREW_RATIO": "Brouw Verhouding", + "PAGE_HELPER_BREW_RATIO_GROUND_COFFEE": "Gemalen koffie (gr)", + "PAGE_HELPER_BREW_RATIO_WATER": "Vloeistof (gr\/ml)", + "PAGE_HELPER_BREW_RATIO_CALCULATED": "Berekende brouwverhouding", + "PAGE_SETTINGS_SHOW_ARCHIVED_BREWS": "Gearchiveerde brouwsels weergeven", + "PAGE_SETTINGS_SHOW_ARCHIVED_BEANS": "Gearchiveerde bonen weergeven", + "PAGE_SETTINGS_SHOW_ARCHIVED_GREEN_BEANS": "Toon gearchiveerde groene bonen", + "CUPPING_SCORE": "Score", + "CUPPING_SCORE_DRY_FRAGRANCE": "Droge geur", + "CUPPING_SCORE_WET_AROMA": "Nat aroma", + "CUPPING_SCORE_BRIGHTNESS": "Helderheid", + "CUPPING_SCORE_FLAVOR": "Smaak", + "CUPPING_SCORE_BODY": "Body", + "CUPPING_SCORE_FINISH": "Afwerking", + "CUPPING_SCORE_SWEETNESS": "Zoetheid", + "CUPPING_SCORE_CLEAN_CUP": "Clean Cup", + "CUPPING_SCORE_COMPLEXITY": "Complexiteit", + "CUPPING_SCORE_UNIFORMITY": "Uniformiteit", + "CUPPING_SCORE_CUPPERS_CORRECTION": "Cuppers-correctie", + "CUPPING_SCORE_DRY_FRAGRANCE_TOOLTIP": "Verwijst naar het aroma van de droge gemalen koffie voordat er heet water aan wordt toegevoegd.", + "CUPPING_SCORE_WET_AROMA_TOOLTIP": "De geur van nat koffiedik, nadat er heet water aan is toegevoegd.", + "CUPPING_SCORE_BRIGHTNESS_TOOLTIP": "Zuurgraad is de smaak van scherpe hoge tonen in de koffie, veroorzaakt door een set van chlorogeen, citroenzuur, kininezuur, azijnzuur en andere, voornamelijk waargenomen in de voorkant van de mond en op de tong. (Het is een goede kwaliteit; niet gerelateerd aan bitterheid in koffie, en niet direct verantwoordelijk voor maagklachten). Zuurgraad wordt gewaardeerd door veel kopers, en is direct gerelateerd aan de kwaliteit van de kop, aangezien zuurgraad het product is van aanplantingen op grote hoogte.", + "CUPPING_SCORE_FLAVOR_TOOLTIP": "Dit is de algehele indruk in de mond, inclusief alle andere beoordelingen. Er zijn 4 \"primaire smaak\"-groeperingen (zuur, zoet, zout, bitter) en veel \"secundaire smaken\".", + "CUPPING_SCORE_BODY_TOOLTIP": "Vaak \"mondgevoel\" genoemd, is body het gevoel van gewicht en dikte van de gezette koffie, veroorzaakt door het percentage oplosbare vaste stoffen in de kop, inclusief alle organische verbindingen die worden geëxtraheerd (de zetmethode en de hoeveelheid gemalen koffie die wordt gebruikt, hebben hier grote invloed op). We beoordelen Body op een lagere schaal omdat licht gebodyde koffiesoorten zeker niet slecht zijn en in sommige origines past de lichtere body het beste bij het algehele karakter van de kop.", + "CUPPING_SCORE_FINISH_TOOLTIP": "De aanhoudende of opkomende smaken die komen nadat de mond is schoongemaakt. Dit omvat de tijd dat de koffie je mond verlaat tot minuten daarna...een reden dat je veel cuppers zult vinden die hun nasmaakscores herzien terwijl ze een minuut of twee later nog steeds een positieve smaak ervaren.", + "CUPPING_SCORE_SWEETNESS_TOOLTIP": "Zoetheid is bijna altijd een gewenste kwaliteit in koffie, zelfs als het op eufemistische wijze wordt beschreven, zoals \"rustieke zoetheid\" of \"bitterzoetheid\". U zult merken dat verfijnde zoetheid (denk aan Europees gebak, fijn snoepgoed, witte suiker, pure zoetheid) hoog scoort, evenals complexe zoetheid van fruitsuikers (fructose). Moutige zoetheid (maltose) is minder traditioneel, maar wel wenselijk en honing kan variëren van heel puur en schoon tot complex, rustiek, bijna gistachtig. Kortom, als zoetheid een sleutel is tot de kop, zal het goed worden beoordeeld.", + "CUPPING_SCORE_CLEAN_CUP_TOOLTIP": "Let op dat \"clean cup\" niet letterlijk betekent dat er geen vuil op de koffie zit. Het gaat alleen om smaak en rauwe, funky koffies die \"onrein\" zijn en de smaak kan ook heel wenselijk zijn, zoals nat-gepelde Indonesische koffies uit Sumatra, of droog verwerkte Ethiopische en Jemenitische types.", + "CUPPING_SCORE_COMPLEXITY_TOOLTIP": "Complexiteit complimenteert de scores voor \"smaak\" en \"afwerking\", om een veelheid of gelaagdheid van veel smaken te communiceren. Het betekent dat er veel te ontdekken valt in de kop. Aan de andere kant kunnen simpele koffies een opluchting zijn na overmatige blootstelling aan veel krachtige, intense, complexe koffies.", + "CUPPING_SCORE_UNIFORMITY_TOOLTIP": "Uniformiteit verwijst naar verschillen tussen kopjes. Koffiesoorten die met een droog proces zijn bereid, kunnen van nature minder uniform zijn dan koffiesoorten die met een nat proces zijn bereid. We zouden nooit een partij met fantastische smaken vermijden als deze af en toe afwijkt. Dit wordt gescoord tijdens het cuppingprotocol, waarbij meerdere kopjes worden gemaakt van elke partij die wordt beoordeeld.", + "CUPPING_SCORE_CUPPERS_CORRECTION_TOOLTIP": "Dit is aangepast van het SCAA-systeem en de Cup of Excellence-score (soms noemen ze het \"Overall Points\"). Het stelt een cupper in staat om ervoor te zorgen dat de totale score de algehele indruk van de cup correct weergeeft. U zou deze aanpak kunnen bekritiseren en het \"vervalsen\" van het totaal kunnen beschouwen. In zekere zin zou u gelijk hebben ... maar het zou veel erger zijn om de categoriescores te veranderen om het gewenste totaal te bereiken (om een koffie een 9 te geven voor zuurgraad terwijl u weet dat het een 7 is), of omgekeerd om een koffie die absoluut een 90 verdient, op 84 te laten eindigen. Het specifieke Cupper's Correction-nummer doet er niet toe, of het nu een 5 of een 8 is ... het idee is dat de totale score een correcte indruk geeft van de kwaliteit van de koffie.", + "CUPPING_SCORE_TOOLTIP": "100-95 = Verbazingwekkend, 90-94 = Uitstekend, 85-89 = Zeer goed, 80-84 = Goed, 75-79 = Redelijk, 70-74 = Slecht", + "DETAIL_BREW": "Brouw details", + "DETAIL_BEAN": "Boon details", + "DETAIL_MILL": "Maler details", + "DETAIL_PREPARATION": "Bereidings details", + "EDIT_BREW": "Bewerk brouwsel", + "ADD_BEAN": "Boon toevoegen", + "EDIT_BEAN": "Bewerken boon", + "ADD_PREPARATION": "Bereidingsmethode toevoegen", + "EDIT_PREPARATION": "Bewerk bereidingsmethode", + "ADD_MILL": "Voeg maler toe", + "EDIT_MILL": "Maler bewerken", + "USE_FILTER": "Filter toepassen", + "RESET_FILTER": "Filter resetten", + "COFFEE_GRAMS_GRINDED": "Gram gemalen", + "BEANS_USED": "Bonen geconsumeerd", + "BREW_HEADER_BEFORE_BREW": "Voor het brouwen", + "BREW_HEADER_WHILE_BREW": "Tijdens het brouwen", + "BREW_HEADER_AFTER_BREW": "Na het brouwen", + "BREW_HEADER_CUPPING": "Proeven", + "BEANS_CONSUMED": "Archieveer", + "NAV_MANAGE_PARAMETERS": "Parameters beheren", + "NAV_SORT_PARAMETERS": "Sorteer parameters", + "NAV_DEFAULT_PARAMETERS": "Standaardparameters definiëren", + "PAGE_SORT_PARAMETERS_DESCRIPTION": "Versleep parameters om te bepalen in welke volgorde ze worden weergegeven.", + "PAGE_MANAGE_PARAMETERS_DESCRIPTION": "Geef aan welke parameters moeten worden weergegeven bij het bewerken van brouwinformatie.", + "PAGE_DEFAULT_PARAMETERS_DESCRIPTION": "Markeer welke parameters standaard moeten worden ingesteld op de laatst gebruikte waarde.", + "SORT_PARAMETERS_BEFORE": "Voor het brouwen", + "SORT_PARAMETERS_MEANWHILE": "Tijdens het brouwen", + "SORT_PARAMETERS_AFTER": "Na het brouwen", + "MORE_INFORMATION": "Meer informatie", + "UNDERSTOOD": "Begrepen", + "WELCOME_PAGE_ACTIVATE_ANALYTICS_TITLE": "Analyse en tracking", + "WELCOME_PAGE_ACTIVATE_ANALYTICS_DESCRIPTION": "We willen de app, de website en onze toekomstige diensten voor u voortdurend verbeteren. Om dit te doen, moeten we wat gegevens bijhouden over hoe u de app en de functies ervan gebruikt. Maar we beloven dat we nooit persoonlijke gegevens zullen bijhouden. Om deze beloften waar te maken, gebruiken we Matomo, een open source service gericht op gegevensbeveiliging en privacy die op onze eigen server wordt gehost - dit zorgt ervoor dat alleen wij eigenaar zijn van de gegevens. Onze website biedt alle informatie over de parameters die we bijhouden en bovendien kunt u de broncode bekijken die 100% open source is. Heeft u vragen? Neem dan gerust contact met ons op.", + "ANALYTICS_INFORMATION_TITLE": "Analyse en tracking", + "ANALYTICS_INFORMATION_DESCRIPTION": "Zoals u weet, is de veiligheid van uw gegevens en uw privacy onze topprioriteit. Daarom zijn we overgestapt van Google Analytics naar de open source service Matomo, die zich richt op gegevensbeveiliging en privacy. Deze service wordt gehost op onze eigen server. Dit betekent dat wij volledig eigenaar zijn van de gegevens. De bijgehouden parameters zijn niet gewijzigd en we beloven nog steeds dat we nooit persoonlijke gegevens zullen bijhouden. Op onze website vindt u alle informatie over de parameters die we bijhouden. Bovendien kunt u de broncode bekijken, die 100% open source is. Heeft u vragen? Neem dan gerust contact met ons op.", + "ACTIVATE": "Activeren", + "DO_NOT_ACTIVE": "Niet activeren", + "WELCOME_PAGE_BEAN_TITLE": "Boon", + "WELCOME_PAGE_BEAN_DESCRIPTION": "Koffie zetten zonder bonen is een beetje lastig. Voeg je eerste boon toe om te beginnen!", + "WELCOME_PAGE_BEAN_ADD": "Boon toevoegen", + "SKIP": "Overslaan", + "WELCOME_PAGE_PREPARATION_TITLE": "Bereidingswijze", + "WELCOME_PAGE_PREPARATION_DESCRIPTION": "V60, Aeropress, Espresso - er zijn veel manieren om koffie te zetten. Voeg er minstens één toe.", + "WELCOME_PAGE_PREPARATION_ADD": "Bereidingswijze toevoegen", + "WELCOME_PAGE_MILL_TITLE": "Maler", + "WELCOME_PAGE_MILL_DESCRIPTION": "Bijna klaar, maar je hebt iets nodig om je bonen te malen! Voeg minstens één maler toe.", + "WELCOME_PAGE_MILL_ADD": "Voeg Maler toe", + "WELCOME_PAGE_TITLE": "Welkom!", + "WELCOME_PAGE_BEAN_HEADLINE": "Eerste boon", + "WELCOME_PAGE_PREPARATION_HEADLINE": "Bereidingswijze toevoegen", + "WELCOME_PAGE_MILL_HEADLINE": "Eerste Maler", + "WELCOME_PAGE_LETS_START_HEADLINE": "Nu kunnen we beginnen met brouwen!", + "WELCOME_PAGE_LETS_START_TITLE": "Nu kunnen we beginnen met brouwen!", + "WELCOME_PAGE_LETS_START_DESCRIPTION": "Gefeliciteerd, je bent klaar om de beste koffie van je leven te zetten. Veel plezier en verspreid de liefde voor goede koffie!", + "PREPARATION_TYPE": "Bereidingsmethode", + "PREPARATION_TYPE_NAME": "Naam", + "ARCHIVED": "Gearchiveerd", + "PAGE_SETTINGS_SHOW_ARCHIVED_PREPARATIONS": "Toon gearchiveerde bereidingsmethoden", + "PAGE_SETTINGS_SHOW_ARCHIVED_MILLS": "Toon gearchiveerde malers", + "PAGE_MILL_LIST_NO_ARCHIVED_MILL_EXISTING": "Er zijn nog geen malers gearchiveerd.", + "PAGE_PREPARATION_LIST_NO_ARCHIVED_PREPARATION_EXISTING": "Je hebt geen gearchiveerde bereidingswijzen.", + "TOAST_BREW_ADDED_SUCCESSFULLY": "Brouwsel succesvol toegevoegd", + "TOAST_BREW_REPEATED_SUCCESSFULLY": "Brouwsel succesvol herhaald", + "TOAST_BEAN_ADDED_SUCCESSFULLY": "Boon succesvol toegevoegd", + "TOAST_MILL_ADDED_SUCCESSFULLY": "Maler succesvol toegevoegd", + "TOAST_PREPARATION_ADDED_SUCCESSFULLY": "Bereidingswijze succesvol toegevoegd", + "TOAST_WATER_ADDED_SUCCESSFULLY": "Water succesvol toegevoegd", + "TOAST_BREW_DELETED_SUCCESSFULLY": "Brouwsel is verwijderd", + "TOAST_BEAN_DELETED_SUCCESSFULLY": "Boon is verwijderd", + "TOAST_GREEN_BEAN_DELETED_SUCCESSFULLY": "Groene boon is verwijderd", + "TOAST_MILL_DELETED_SUCCESSFULLY": "Maler is verwijderd", + "TOAST_WATER_DELETED_SUCCESSFULLY": "Water is verwijderd", + "TOAST_PREPARATION_DELETED_SUCCESSFULLY": "Bereidingswijze is verwijderd", + "TOAST_BREW_EDITED_SUCCESSFULLY": "Brouwsel is bewerkt", + "TOAST_BEAN_EDITED_SUCCESSFULLY": "Boon is bewerkt", + "TOAST_MILL_EDITED_SUCCESSFULLY": "Maler is bewerkt", + "TOAST_PREPARATION_EDITED_SUCCESSFULLY": "Bereiding is bewerkt", + "TOAST_WATER_EDITED_SUCCESSFULLY": "Water is bewerkt", + "TOAST_BEAN_ARCHIVED_SUCCESSFULLY": "Boon is gearchiveerd", + "TOAST_MILL_ARCHIVED_SUCCESSFULLY": "Maler is gearchiveerd", + "TOAST_PREPARATION_ARCHIVED_SUCCESSFULLY": "Bereidingsmethode is gearchiveerd", + "TOAST_WATER_ARCHIVED_SUCCESSFULLY": "Water is gearchiveerd", + "BEAN_WEIGHT_ALREADY_USED": "{{gramUsed}} g van {{gramTotal}} g ( {{leftOver}} g)", + "PREPARATION_TYPE_CUSTOM_PREPARATION": "Aangepaste bereidingsmethode", + "PREPARATION_TYPE_AEROPRESS": "Aeropress", + "PREPARATION_TYPE_V60": "V60", + "PREPARATION_TYPE_CHEMEX": "Chemex", + "PREPARATION_TYPE_BIALETTI": "Bialetti", + "PREPARATION_TYPE_PORTAFILTER": "Espresso machine", + "PREPARATION_TYPE_KALITA_WAVE": "Kalita Wave", + "PREPARATION_TYPE_FRENCH_PRESS": "French Press", + "PREPARATION_TYPE_SWANNECK": "Swan Neck", + "PREPARATION_TYPE_DRIPPER": "Dripper", + "PREPARATION_TYPE_DELTER_PRESS": "Delter Press", + "PREPARATION_TYPE_COLD_BREW": "Koud brouwsel", + "PREPARATION_TYPE_AEROPRESS_INVERTED": "Aeropress Omgekeerd", + "PREPARATION_TYPE_TURKISH": "Turks", + "PREPARATION_TYPE_BLUE_DRIPPER": "Blue Dripper", + "PREPARATION_TYPE_ADD_CUSTOM": "Aangepaste methode toevoegen", + "PREPARATION_TYPE_GINA": "Gina", + "PREPARATION_TYPE_KONO": "Kono", + "PREPARATION_TYPE_ORIGAMI": "Origami", + "PREPARATION_TYPE_CAFELAT": "Cafelat", + "PREPARATION_TYPE_OREA": "Orea", + "PREPARATION_TYPE_COLD_DRIP": "Cold Drip", + "PREPARATION_TYPE_HAND_LEVER": "Handmatige hendel", + "PREPARATION_TYPE_FLAIR": "Flair", + "PREPARATION_TYPE_APRIL_BREWER": "April Brewer", + "PREPARATION_TYPE_ESPRO_BLOOM": "Espreo Bloom", + "PREPARATION_TYPE_FELLOW_STAGG": "Fellow Stagg", + "PREPARATION_TYPE_HSIAO_50": "Hsiao 50", + "PREPARATION_TYPE_KARLSBADER_KANNE": "Karlsbader", + "PREPARATION_TYPE_MOCCA_MASTER": "Mocca Master", + "PREPARATION_TYPE_SIPHON": "Sifon", + "CHOOSE_BEANS": "Selecteer bonen", + "CHOOSE_BEAN": "Selecteer boon", + "CHOOSE_WATERS": "Selecteer wateren", + "CHOOSE_WATER": "Selecteer water", + "CHOOSE_PREPARATIONS": "Selecteer bereidingsmethoden", + "CHOOSE_PREPARATION": "Selecteer bereidingsmethode", + "CHOOSE_MILLS": "Selecteer molens", + "CHOOSE_MILL": "Selecteer molen", + "BEAN": { + "PLACE_HOLDER": { + "BEAN_DATA_NAME": "Naam toevoegen", + "BEAN_DATA_ROAST_NAME": "Voeg de mate van branding toe", + "BEAN_DATA_ROASTING_DATE": "Wanneer werden de bonen geroosterd?", + "BEAN_DATA_ROASTER": "Wie heeft de bonen geroosterd?", + "BEAN_DATA_VARIETY": "Voeg de koffievariant toe", + "BEAN_DATA_PROCESSING": "Koffieverwerking, bijvoorbeeld gewassen", + "BEAN_DATA_COUNTRY": "Waar komt de boon vandaan?", + "BEAN_DATA_MIX": "Wat is de melange ratio?", + "BEAN_DATA_AROMATICS": "Voeg smaken toe", + "BEAN_DATA_WEIGHT": "Gewicht van de bonen", + "BEAN_DATA_COST": "Hoeveel hebben de bonen gekost?", + "BEAN_DATA_REGION": "Regio toevoegen", + "BEAN_DATA_FARM": "Voeg de boerderij toe", + "BEAN_DATA_FARMER": "Voeg de boer toe", + "BEAN_DATA_ELEVATION": "Op welke hoogte werden de bonen geteeld", + "BEAN_DATA_HARVEST_TIME": "wanneer zijn de bonen geoogst?", + "BEAN_DATA_BUY_DATE": "Wanneer zijn de bonen gekocht?", + "BEAN_DATA_PERCENTAGE": "Voeg het percentage van deze boon in de melange toe", + "BEAN_DATA_CERTIFICATION": "Voeg de bonencertificering toe (bijv. fairtrade, bio)", + "BEAN_DATA_ROASTING_TYPE": "Voeg brand type toe", + "BEAN_DATA_DECAFFEINATED": "Is deze koffie cafeïnevrij", + "BEAN_DATA_URL": "Voeg de website-url toe", + "BEAN_DATA_EAN": "Voeg het EAN- of artikelnummer toe", + "BEAN_DATA_CUPPING_POINTS": "Voeg de cuppingpunten toe", + "BEAN_DATA_PURCHASING_PRICE": "Voeg de aankoopprijs toe", + "BEAN_DATA_FOB_PRICE": "Voeg de FOB-prijs toe", + "NOTES": "Voeg notities toe over deze bonen", + "CHOOSE_DATA_ROASTER": "Kies een brander", + "CHOOSE_DATA_ROASTING_TYPE": "Kies type branding", + "BEAN_DATA_BEST_DATE": "Wanneer is de beste datum om de bonen te gebruiken?", + "BEAN_DATA_OPEN_DATE": "Wanneer is de zak bonen geopend?", + "FROZEN_NOTES": "Zijn er opmerkingen over de bevroren koffie? Bijvoorbeeld in welke vriezer je het hebt bewaard." + } + }, + "PREPARATION": { + "PLACE_HOLDER": { + "PREPARATION_TYPE_NAME": "Voeg een naam toe", + "NOTES": "Voeg notities toe over deze bereidingsmethode" + } + }, + "MILL": { + "PLACE_HOLDER": { + "NAME": "Voeg een naam toe", + "NOTES": "Voeg notities toe over deze grinder" + } + }, + "BREW": { + "PLACE_HOLDER": { + "BREW_DATA_GRIND_SIZE": "Voer de maalinstelling in", + "BREW_DATA_GRIND_WEIGHT": "Vul de hoeveelheid koffie in (gr)", + "BREW_DATA_BREW_TEMPERATURE": "Voer de brouwtemperatuur in", + "BREW_DATA_PREPARATION_METHOD": "Selecteer bereidingsmethode", + "BREW_DATA_BEAN_TYPE": "Selecteer bonen", + "BREW_DATA_MILL": "Selecteer een molen", + "BREW_DATA_MILL_SPEED": "Voer de maalsnelheid in", + "BREW_DATA_MILL_TIMER": "Voer de maal tijd in", + "BREW_DATA_PRESSURE_PROFILE": "Druk\/stroomprofiel, brouwmethodologie", + "BREW_DATA_TEMPERATURE_TIME": "Voer de tijd in die de machine heeft mogen opwarmen", + "BREW_DATA_COFFEE_BLOOMING_TIME": "Hoe lang is de bloom?", + "BREW_DATA_COFFEE_FIRST_DRIP_TIME": "Wanneer verscheen de eerste druppel?", + "BREW_DATA_BREW_QUANTITY": "Hoeveel water heb je gebruikt om te brouwen?", + "BREW_DATA_COFFEE_TYPE": "Voer de koffiestijl in (bijv. ristretto)", + "BREW_DATA_COFFEE_CONCENTRATION": "Voer de koffie concentratie in", + "BREW_DATA_TDS": "Wat was de gemeten hoeveelheid opgeloste vaste stoffen (TDS)?", + "BREW_DATA_NOTES": "Voeg notities toe over dit brouwsel", + "BREW_DATA_BREW_BEVERAGE_QUANTITY": "Voeg totale drank hoeveelheid toe", + "BREW_DATA_PREPARATION_METHOD_TOOL": "Kies je bereiding tools", + "BREW_DATA_WATER": "Kies het gebruikte water", + "BREW_DATA_BEAN_WEIGHT_IN": "Welk gewicht aan bonen heb je gebruikt?" + } + }, + "ROASTED_BEFORE": "Geroosterd voor", + "DAY_OLD": "dag oud", + "DAYS_OLD": "dagen oud", + "BEANS_AMOUNT_USED": "Verbruikt", + "CUPPING_BREW": "Proeven", + "COFFEE_DRUNKEN_QUANTITY": "Koffie dronken", + "IMAGE_DELETED": "Afbeelding is verwijderd", + "IMAGE_NOT_DELETED": "Afbeelding kon niet worden verwijderd", + "EXTERNAL_STORAGE_NOT_SUPPORTED": "Sorry, externe opslag wordt niet ondersteund", + "BEANS_ARCHIVED": "Gearchiveerd", + "TAB_ARCHIVE": "Archief", + "TODAY": "Vandaag", + "PLEASE_WAIT": "Even geduld aub...", + "PREPARATION_STYLE_POUR_OVER": "Pourover", + "PREPARATION_STYLE_ESPRESSO": "Espresso", + "PREPARATION_STYLE_FULL_IMMERSION": "Immersie", + "PREPARATION_STYLE_PERCOLATION": "Percolatie", + "PREPARATION_TYPE_STYLE": "Bereidingsstijl", + "PAGE_SETTINGS_FAST_REPEAT_DESCRIPTION": "Activeert een nieuw menu-item - hiermee kun je een brouwsel kopieren.", + "PAGE_SETTINGS_TRACK_BREW_COORDINATES_DESCRIPTION": "Sla geolocatie gegevens op voor elk brouwsel.", + "PAGE_SETTINGS_TRACK_CAFFEINE_CONSUMPTION_DESCRIPTION": "Bewaar de hoeveelheid geconsumeerde cafeïne", + "UPDATE_TITLE": "Wat is er nieuw?", + "NEXT": "Volgende", + "CUSTOM_PARAMETERS": "Parameters aanpassen", + "CUSTOM_DEFAULT_PARAMETERS": "Standaard", + "CUSTOM_MANAGE_PARAMETERS": "Beheer", + "CUSTOM_SORT_PARAMETERS": "Sorteer", + "BREW_PARAMETER_CUSTOMIZE_TITLE": "Aangepaste parameters voor elke bereidingsmethode", + "BREW_PARAMETER_CUSTOMIZE_DESCRIPTION": "Wilt u voor elke bereidingsmethode aangepaste parameters kiezen? Navigeer naar \"Methoden\", open het menu van de specifieke bereidingsmethode en kies \"Parameters aanpassen\". Daar kunt u kiezen welke parameters voor deze bereiding worden gebruikt!", + "BREW_DATA_BREW_QUANTITY_TOOLTIP": "Hoeveelheid water (niet bruikbaar voor espresso)", + "BREW_DATA_COFFEE_FIRST_DRIP_TIME_TOOLTIP": "Eerste druppel tijd van de koffie (alleen espresso)", + "BREW_DATA_PREPARATION_METHOD_TOOLTIP": "Voorbereiding (alleen aanpasbaar indien actief)", + "PAGE_SETTINGS_GENERAL": "Algemene instellingen", + "EDIT_PREPARATION_CUSTOM_PARAMETERS": "Parameters aanpassen", + "ENABLE_PREPARATION_CUSTOM_PARAMETERS": "Aangepaste parameters gebruiken", + "BEAN_ADD_ANOTHER_SORT": "Voeg een andere boon type toe", + "BEAN_SORT": "Boon Type", + "BEAN_SORT_INFORMATION": "Informatie over de variëteit", + "BEAN_SORT_MORE_INFORMATION": "Meer informatie", + "NAVIGATE_TO_PREPARATION_METHODS": "Navigeer naar bereidingsmethoden", + "PREPARATION_TOOLS": "bereidingsmiddelen", + "PREPARATION_TOOLS_INFORMATION": "Voeg verschillende mandjes toe, WDT-gereedschappen voor uw espressomachine; stoffen, papieren of gaasfilters voor uw filterkoffie, enz.", + "PREPARATION_TOOLS_PLACEHOLDER": "Papieren of stoffen filter, VST 20g, 14g mandje, enz.", + "PREPARATION_PARAMETERS_CUSTOMIZED": "Aangepaste parameters", + "BEANS_WEIGHT_AVAILABLE": "Beschikbare bonen", + "SORT_ORDER": "Sorteervolgorde wijzigen", + "ASCENDING": "Oplopend", + "DESCENDING": "Aflopend", + "SORT_AFTER": "Sorteren na", + "BEAN_SORT_NAME_OF_BEAN": "Boon naam", + "BEAN_SORT_ROASTER": "Brander", + "BEAN_SORT_ROASTING_DATE": "Datum van roosteren", + "BEAN_TAB_ROAST_INFORMATION": "Geroosterde informatie", + "BEAN_TAB_GENERAL_INFORMATION": "Algemeen", + "BEAN_TAB_SORT_INFORMATION": "Informatie over de variëteit", + "PAGE_SETTINGS_MANAGE_ARCHIVE": "Archief beheren", + "LAST_USE": "Laatst gebruikt", + "SEARCH": "Zoeken", + "OVERVIEW": "Overzicht", + "BEAN_HEADER_ADDITIONALE_INFORMATION": "Aanvullende informatie", + "THREE_DEE_TOUCH_ACTION_BREW": "Brouw", + "THREE_DEE_TOUCH_ACTION_BEAN": "Boon", + "THREE_DEE_TOUCH_ACTION_PREPARATION": "Bereidingsmethode", + "THREE_DEE_TOUCH_ACTION_MILL": "Koffiemolen", + "PAGE_CREDITS_NOT_EXISTING": "Geen inhoud", + "TIMER_HOUR": "Uren", + "TIMER_MINUTES": "Minuten", + "TIMER_SECONDS": "Seconden", + "EXCEL": { + "BEAN": { + "CREATION_DATE": "Aanmaakdatum", + "ID": "Bonen-ID" + }, + "PREPARATION": { + "CREATION_DATE": "Aanmaakdatum", + "ID": "bereidingsmiddelen" + }, + "GRINDER": { + "CREATION_DATE": "Aanmaakdatum", + "ID": "Molen-ID" + } + }, + "EXCEL_EXPORT": "Excel-export", + "HEALTH_KIT_QUESTION_TITLE": "cafeïneconsumptie opslaan", + "HEALTH_KIT_QUESTION_MESSAGE": "Na activering wordt de geschatte cafeïne van elk brouwsel automatisch opgeslagen in Apple Health.", + "NAV_ROASTING_SECTION": "Bonen roosteren", + "ROASTING_SECTION": { + "NAV_GREEN_BEANS": "Groene bonen", + "NAV_ROASTING_MACHINE": "Roostermachine", + "ROASTING_MACHINE": { + "TOTAL_ROAST_QUANTITY": "Totaalgewicht geroosterd", + "TOTAL_ROAST_COUNT": "Aantal brandingen" + }, + "GREEN_BEAN": { + "ADD": "Toevoegen", + "EDIT": "Bewerking", + "DETAIL": "Groene bonen details", + "ROASTABLE": "Roosterbaar", + "NO_ROASTS_YET": "Nog geen gebrande bonen" + }, + "BEAN": { + "DROP_TEMPERATURE": "Eindtemperatuur van de gebrande boon", + "ROAST_LENGTH": "brand lengte", + "ROASTER_MACHINE": "Roostermachine", + "GREEN_BEAN_WEIGHT": "Gewicht van groene bonen", + "OUTSIDE_TEMPERATURE": "Omgevingstemperatuur", + "HUMIDITY": "Vochtigheid", + "FIRST_CRACK_MINUTE": "eerst kraak minuut", + "FIRST_CRACK_TEMPERATURE": "eerste kraak temperatuur", + "SECOND_CRACK_MINUTE": "Tweede kraak minuut", + "SECOND_CRACK_TEMPERATURE": "tweede kraak temperatuur", + "PLACE_HOLDER": { + "DROP_TEMPERATURE": "Eindtemperatuur van de gebrande boon", + "ROAST_LENGTH": "brand lengte", + "ROASTER_MACHINE": "Roostermachine", + "GREEN_BEAN_WEIGHT": "Gewicht van groene bonen", + "OUTSIDE_TEMPERATURE": "Omgevingstemperatuur", + "HUMIDITY": "Vochtigheid", + "FIRST_CRACK_MINUTE": "Eerste kraak minuut", + "FIRST_CRACK_TEMPERATURE": "Eerste kraak temperatuur", + "SECOND_CRACK_TEMPERATURE": "Tweede kraak temperatuur", + "SECOND_CRACK_MINUTE": "Tweede kraak minuut" + } + } + }, + "PAGE_SETTINGS_MANAGE_SECTIONS": "Meer secties", + "PAGE_SETTINGS_SHOW_ROASTING_SECTION": "Activeer brandersectie", + "PAGE_SETTINGS_SHOW_WATER_SECTION": "Activeer water sectie", + "PAGE_SETTINGS_SHOW_CUPPING_SECTION": "Activeer cupping-sectie", + "BEAN_DATA_BUY_DATE": "Aankoopdatum", + "BEAN_SORT_CREATION_DATE": "Aanmaakdatum", + "BEAN_SORT_PURCHASE_DATE": "Aankoopdatum", + "BEAN_ROAST_COUNT": "Roost aantal", + "TRANSFER_ROAST": "Geroosterd", + "BEAN_TAB_LINKED_ROASTS": "Geroosterd", + "BEAN_DATA_WEIGHT_AFTER_ROASTING": "Gewicht na het roosteren", + "TOAST_GREEN_BEAN_ADDED_SUCCESSFULLY": "Groene bonen toegevoegd", + "TOAST_GREEN_BEAN_EDITED_SUCCESSFULLY": "Groene boon bewerkt", + "TOAST_GREEN_BEAN_ARCHIVED_SUCCESSFULLY": "Groene bonen gearchiveerd", + "TOAST_ROASTING_MACHINE_ADDED_SUCCESSFULLY": "Roostermachine toegevoegd", + "TOAST_ROASTING_MACHINE_EDITED_SUCCESSFULLY": "Brandmachine bewerkt", + "TOAST_ROASTING_MACHINE_ARCHIVED_SUCCESSFULLY": "Brander gearchiveerd", + "DELETE_ROASTING_MACHINE_QUESTION": "Brander verwijderen? Alle gebrande bonen waarnaar verwezen wordt, worden bijgewerkt en niet verwijderd.", + "TOAST_ROASTING_MACHINE_DELETED_SUCCESSFULLY": "Brander verwijderd", + "EDIT_ROASTING_MACHINE": "Bewerk", + "DETAIL_ROASTING_MACHINE": "Details van de brander", + "DELETE_WATER_QUESTION": "Water verwijderen? Alle gerefereerde brouwsels worden bijgewerkt en niet verwijderd", + "ROASTING_MACHINE": { + "PLACE_HOLDER": { + "NAME": "Voeg een naam toe", + "NOTES": "Opmerkingen toevoegen voor deze brander" + } + }, + "NAV_ROASTING_MACHINE": "koffierooster machines", + "PAGE_ROASTING_MACHINE_LIST_NO_MACHINES_EXISTING": "U hebt geen koffiebrander toegevoegd", + "PAGE_ROASTING_MACHINE_LIST_NO_ARCHIVED_MACHINES_EXISTING": "je hebt geen koffiebrander gearchiveerd", + "CHOOSE_ROASTING_MACHINES": "koffiebrander", + "CHOOSE_ROASTING_MACHINE": "koffiebrander", + "POPOVER_BREWS_OPTION_TOGGLE_FAVOURITE": "Favoriet", + "TOAST_BREW_FAVOURITE_ADDED": "Favoriet toegevoegd", + "TOAST_BREW_FAVOURITE_REMOVED": "Favoriet verwijderd", + "BREW_FILTER_JUST_FAVOURITE": "Favorieten", + "STATISTICS_PREPARATION_USAGES": "Bereidingsmethode gebruik", + "STATISTICS_PREPARATION_TIMELINE_USAGES": "Gebruiksgeschiedenis bereidingsmethode", + "STATISTICS_GRINDER_TIMELINE_USAGES": "Gebruiksgeschiedenis van de Maler", + "ACCEPT": "Accepteer", + "STATISTIC_TAB_GENERAL": "Algemeen", + "STATISTIC_TAB_BREWS": "Brouwsels", + "STATISTIC_TAB_BEANS": "Bonen", + "STATISTIC_TAB_PREPARATIONS": "Voorbereidingen", + "STATISTIC_TAB_GRINDERS": "Malers", + "PAGE_STATISTICS_BREW_PER_DAYPROCESSES": "Brouwsels per dag", + "PAGE_STATISTICS_BREW_TIME": "Brouwtijd", + "PAGE_STATISTICS_PHOTOS_TAKEN": "Foto's gemaakt", + "PAGE_SETTINGS_IMAGE_QUALITY": "afbeelding kwaliteit", + "PAGE_SETTINGS_IMAGE_QUALITY_TOOLTIP": "Bepaal in welke kwaliteit uw afbeeldingen moeten worden opgeslagen. Dit kan uw dataverbruik verlagen.", + "PAGE_SETTINGS_BREW_RATING": "Brouw beoordeling", + "PAGE_SETTINGS_BREW_RATING_TOOLTIP": "Is de standaard '-1 tot 5' niet de juiste beoordeling voor u? U kunt in plaats daarvan een '-1 tot 100' schaal gebruiken", + "UPDATE_ENTRY_OF": "Update item {{index}} van {{count}}", + "WEBSITE": "Website", + "SHARE": "Deel", + "ANDROID_FILE_ACCESS_NEEDED_TITLE": "Toegang tot gedeeld bestand vereist", + "ANDROID_FILE_ACCESS_NEEDED_DESCRIPTION": "Om de app volledig te laten werken, vragen we u om bestandstoegang te autoriseren. Anders ontstaan er problemen bij het gebruik van de app. Dit is specifiek nodig voor het automatische back-upsysteem.", + "COULD_NOT_ACCESS_FILE": "We konden het gekozen bestand niet openen", + "WRONG_FILE_FORMAT": "U hebt een niet-ondersteund bestandsformaat gekozen", + "SCAN_BEAN": "Scan pakket", + "CLEAR": "leeg maken", + "BEAN_LOOKS_LIKE_CONSUMED": "Het lijkt erop dat deze bonen op zijn. Wil je ze archiveren?", + "CUPPING_1": "Fruit", + "CUPPING_2": "Citrus", + "CUPPING_3": "Citroen & limonade", + "CUPPING_4": "Limoen", + "CUPPING_5": "Grapefruit", + "CUPPING_6": "Clementine", + "CUPPING_7": "Mandarijn", + "CUPPING_8": "mandarijn sinaasappel", + "CUPPING_9": "sinasappel", + "CUPPING_10": "Appel\/peer", + "CUPPING_11": "Groene appel", + "CUPPING_12": "Rode appel", + "CUPPING_13": "Meloen", + "CUPPING_14": "Watermeloen", + "CUPPING_15": "Honingdauw meloen", + "CUPPING_16": "Cantaloupe meloen", + "CUPPING_17": "Druif", + "CUPPING_18": "Witte druif", + "CUPPING_19": "Groene druif", + "CUPPING_20": "Rode druif", + "CUPPING_21": "Concord-druif", + "CUPPING_22": "Tropisch fruit", + "CUPPING_23": "Litchi", + "CUPPING_24": "Stervrucht", + "CUPPING_25": "Tamarinde", + "CUPPING_26": "Passievrucht", + "CUPPING_27": "Ananas", + "CUPPING_28": "Mango", + "CUPPING_29": "Papaja", + "CUPPING_30": "Kiwi", + "CUPPING_31": "Banaan", + "CUPPING_32": "Kokosnoot", + "CUPPING_33": "Steenvrucht", + "CUPPING_34": "Perzik", + "CUPPING_35": "Nectarine", + "CUPPING_36": "Abrikoos", + "CUPPING_37": "Pruim", + "CUPPING_38": "Kers", + "CUPPING_39": "Zwarte kers", + "CUPPING_40": "Bes", + "CUPPING_41": "Cranberry", + "CUPPING_42": "Framboos", + "CUPPING_43": "Aardbei", + "CUPPING_44": "Bosbes", + "CUPPING_45": "Rode bes", + "CUPPING_46": "Zwarte bes", + "CUPPING_47": "Gedroogd fruit", + "CUPPING_48": "Gouden rozijn", + "CUPPING_49": "Rozijn", + "CUPPING_50": "Gedroogde vijg", + "CUPPING_51": "Gedroogde dadels", + "CUPPING_52": "Gedroogde Pruim", + "CUPPING_53": "Zoet & gebrand", + "CUPPING_54": "Chocolade", + "CUPPING_55": "Cacaonibs", + "CUPPING_56": "Donkere chocolade", + "CUPPING_57": "Bakkers chocolade", + "CUPPING_58": "Bitterzoete chocolade", + "CUPPING_59": "Cacaopoeder", + "CUPPING_60": "Melkchocolade", + "CUPPING_61": "Noot", + "CUPPING_62": "Walnoot", + "CUPPING_63": "Pinda", + "CUPPING_64": "Cashew", + "CUPPING_65": "Pecannoot", + "CUPPING_66": "Hazelnoot", + "CUPPING_67": "Amandel", + "CUPPING_68": "Graan & Graanproducten", + "CUPPING_69": "Zoet broodgebak", + "CUPPING_70": "Granola", + "CUPPING_71": "Graham-cracker", + "CUPPING_72": "Rogge", + "CUPPING_73": "Tarwe", + "CUPPING_74": "Gerst", + "CUPPING_75": "Vers brood", + "CUPPING_76": "Zoet & Suikerachtig", + "CUPPING_77": "Vanille", + "CUPPING_78": "Noga", + "CUPPING_79": "Honing", + "CUPPING_80": "Boter", + "CUPPING_81": "Room", + "CUPPING_82": "Marshmallow", + "CUPPING_83": "Rietsuiker", + "CUPPING_84": "Bruine suiker", + "CUPPING_85": "Karamel", + "CUPPING_86": "Ahornsiroop", + "CUPPING_87": "Melasse", + "CUPPING_88": "Cola", + "CUPPING_89": "Geroosterd", + "CUPPING_90": "Toast", + "CUPPING_91": "Verbrande suiker", + "CUPPING_92": "Rokerig", + "CUPPING_93": "Koolstof", + "CUPPING_94": "Plantaardig, hartig en kruidig", + "CUPPING_95": "Kruiden", + "CUPPING_96": "Zwarte peper", + "CUPPING_97": "Witte peper", + "CUPPING_98": "Kaneel", + "CUPPING_99": "Koriander", + "CUPPING_100": "Gember", + "CUPPING_101": "Nootmuskaat", + "CUPPING_102": "Kerrie", + "CUPPING_103": "Drop-anijs", + "CUPPING_104": "Kruidnagel", + "CUPPING_105": "Hartig", + "CUPPING_106": "Leerachtig", + "CUPPING_107": "Vleesachtig", + "CUPPING_108": "Sojasaus", + "CUPPING_109": "Zongedroogde tomaat", + "CUPPING_110": "Tomaat", + "CUPPING_111": "Plantaardig aards kruid", + "CUPPING_112": "Grond", + "CUPPING_113": "Vers hout", + "CUPPING_114": "Ceder", + "CUPPING_115": "Tabak", + "CUPPING_116": "Hooi \/ stro", + "CUPPING_117": "Bladgroenten", + "CUPPING_118": "Olijf", + "CUPPING_119": "Groene peper", + "CUPPING_120": "Pompoen", + "CUPPING_121": "Paddestoel", + "CUPPING_122": "Zoete erwt", + "CUPPING_123": "Sneeuwerwt", + "CUPPING_124": "Grassig", + "CUPPING_125": "Dille", + "CUPPING_126": "Salie", + "CUPPING_127": "Munt", + "CUPPING_128": "Groene thee", + "CUPPING_129": "Zwarte thee", + "CUPPING_130": "Hop", + "CUPPING_131": "Bergamot", + "CUPPING_132": "Bloemrijk", + "CUPPING_133": "Bloemen", + "CUPPING_134": "Hibiscus", + "CUPPING_135": "Rozenbottels", + "CUPPING_136": "Lavendel", + "CUPPING_137": "Magnolia", + "CUPPING_138": "Jasmijn kamperfoelie", + "CUPPING_139": "Oranjebloesem", + "CUPPING_140": "Citroengras", + "WATER_SECTION": { + "NAV_WATER": "Water", + "YOU_GOT_NO_ARCHIVED_WATER": "Je hebt nog geen water gearchiveerd", + "YOU_GOT_NO_WATER": "Je hebt nog geen water toegevoegd", + "CATEGORY_INFORMATION": "Water informatie", + "CATEGORY_GENERAL": "Algemeen", + "WATER_BOTTLE_EXPLANATION": "Waterflessen vermelden de concentratie meestal in eenheden van ppm = mg\/L", + "USED_TIMES": "Aantal keren gebruikt", + "AMOUNT": "Gebruikte hoeveelheid", + "WATER": { + "GENERAL_HARDNESS": "Algemene hardheid (GH)", + "TOTAL_ALKALINITY": "Totale alkaliteit (KH)", + "CALCIUM": "Kalium (Ca)", + "MAGNESIUM": "Magnesium (Mg)", + "SODIUM": "Natrium (Na)", + "TDS": "Totaal opgeloste vaste stoffen (TDS)", + "UNITS": "Eenheden", + "PLACE_HOLDER": { + "GENERAL_HARDNESS": "Algemene hardheid", + "TOTAL_ALKALINITY": "Totale alkaliteit", + "CALCIUM": "Kalium (Ca)", + "MAGNESIUM": "Magnesium (Mg)", + "SODIUM": "Natrium (Na)", + "TDS": "Totaal opgeloste vaste stoffen (TDS)", + "NAME": "Voeg waternaam toe", + "NOTES": "Voeg wat notities toe voor je water", + "POTASSIUM": "Kalium (K)", + "CHLORIDE": "Chloride (Cl)", + "SULFATE": "Sulfaat (SO4)" + }, + "WATER_UNIT": { + "UNKNOWN": "Onbekend", + "PPM": "ppm als CaCO3", + "MG_L": "mg\/L", + "MMOL_L": "mmol\/L", + "DH": "°dH" + }, + "POTASSIUM": "Kalium (K)", + "CHLORIDE": "Chloride (Cl)", + "SULFATE": "Sulfaat (SO4)" + } + }, + "BREW_BRIX_CALCULATION": "Graden Brix", + "SET_TDS": "TDS instellen", + "TOTAL_WEIGHT": "Totaal gewicht", + "CALCULATED_WEIGHT": "Berekend gewicht", + "SET_WEIGHT": "Gewicht instellen", + "ADD_FLAVORS_AROMAS_TITLE": "Aroma's \/ Smaken", + "CUSTOM_FLAVORS_AROMAS": "Individueel aroma", + "CUSTOM_FLAVORS_AROMAS_PLACEHOLDER": "Voeg je individuele aroma toe", + "PREDEFINED_FLAVORS_AROMAS": "Veel voorkomende aroma's", + "ADD_AROMA_FLAVOR": "Aroma's\/smaken toevoegen", + "BEAN_WEIGHT_IN_PLACEHOLDER": "Bonen uit de verpakking gehaald", + "VESSEL_PLACEHOLDER": "Naam van het Serveerkan en het leeggewicht ervan", + "GRIND_WEIGHT_PLACEHOLDER": "Gewicht van de gemalen bonen die voor het brouwen worden gebruikt", + "PRESET_BREW_TITLE": "Gebruik laatste brouwsel als voorinstelling", + "CUPPING_BREW_TAB_AROMA": "Aroma", + "CUPPING_BREW_TAB_TASTING": "Systematische cupping", + "WATER_PLACEHOLDER": "Activeer het watergedeelte in het instellingenmenu voor volledige functionaliteit", + "PAGE_SETTINGS_SCALES": "Weegschalen", + "CONNECT": "Verbinden", + "DISCONNECT": "Loskoppelen", + "SCALE": { + "BLUETOOTH_SCAN_RUNNING": "Zoeken naar weegschaal tot 60s", + "BLUETOOTH_NOT_ENABLED": "Bluetooth niet geactiveerd, activeer het om het goed te laten werken", + "CONNECTION_NOT_ESTABLISHED": "Weegschaal niet gevonden, of verbinding kon niet tot stand worden gebracht", + "CONNECTED_SUCCESSFULLY": "Weegschaal verbonden", + "DISCONNECTED_SUCCESSFULLY": "Weegschaal losgekoppeld", + "DISCONNECTED_UNPLANNED": "Weegschaal onverwachts losgekoppeld", + "REQUEST_PERMISSION": { + "LOCATION": "Om Bluetooth-weegschalen te vinden, heeft de app toegang nodig tot de locatie.", + "BLUETOOTH": "Om bluetooth-weegschalen te vinden, heeft de app toegang tot bluetooth nodig" + }, + "INFORMATION_DESCRIPTION": "Ondersteunde weegschalen zijn: Decent Scale, Acaia, Felicita, Hiroia Jimmy, Skale 2, DiFluid Microbalance, Smartchef Scale, Blackcoffee.io, Bookoo Mini Scale en Eureka Precisa. Let op: Als de Eureka Precisa een negatieve waarde ontvangt, stopt de timer" + }, + "QR": { + "WRONG_QRCODE_DESCRIPTION": "Ongeldige QR-code of onbekende inhoud", + "WRONG_QRCODE_TITLE": "Fout", + "WRONG_LINK_DESCRIPTION": "Ongeldige QR-link", + "WRONG_LINK_TITLE": "Fout", + "SERVER": { + "ERROR_OCCURED": "Er is een fout opgetreden. De QR-code kon niet worden gelezen. Probeer het opnieuw.", + "BEAN_NOT_APPROVED": "Boon is nog niet goedgekeurd, probeer het later nog eens" + }, + "BEAN_SUCCESSFULLY_SCANNED": "Bean succesvol gescand", + "BEAN_SUCCESSFULLY_REFRESHED": "Boon succesvol bijgewerkt", + "IMAGES_GETTING_DOWNLOADED": "Afbeeldingen downloaden" + }, + "RETRY_CONNECT": "Verbinding opnieuw proberen", + "SMART_SCALE_STAY_CONNECTED_ON_APP_MINIMIZE": "Houd de weegschaal verbonden, zelfs als de app op de achtergrond draait", + "BREW_FLOW_WEIGHT": "Gewicht", + "BREW_FLOW_WEIGHT_PER_SECOND": "Stroom (afgevlakt)", + "ROAST_TYPE_UNKNOWN": "onbekend", + "ROAST_TYPE_CINNAMON_ROAST": "Cinnamon Branding", + "ROAST_TYPE_AMERICAN_ROAST": "Amerikaans Branding", + "ROAST_TYPE_NEW_ENGLAND_ROAST": "Nieuw-Engeland Branding", + "ROAST_TYPE_HALF_CITY_ROAST": "Half City Branding", + "ROAST_TYPE_MODERATE_LIGHT_ROAST": "Matig-lichte branding", + "ROAST_TYPE_CITY_ROAST": "City Branding", + "ROAST_TYPE_CITY_PLUS_ROAST": "City+ Branding", + "ROAST_TYPE_FULL_CITY_ROAST": "Full City Branding", + "ROAST_TYPE_FULL_CITY_PLUS_ROAST": "Full City + Branding", + "ROAST_TYPE_ITALIAN_ROAST": "Ialiaanse Branding", + "ROAST_TYPE_VIEANNA_ROAST": "Weense Branding", + "ROAST_TYPE_FRENCH_ROAST": "Franse Branding", + "ROAST_TYPE_CUSTOM_ROAST": "Aangepast", + "BEAN_MIX_UNKNOWN": "onbekend", + "BEAN_MIX_SINGLE_ORIGIN": "Single Origin", + "BEAN_MIX_BLEND": "Melange", + "BEAN_ROASTING_TYPE_FILTER": "Filter", + "BEAN_ROASTING_TYPE_ESPRESSO": "Espresso", + "BEAN_ROASTING_TYPE_OMNI": "Omni", + "BEAN_ROASTING_TYPE_UNKNOWN": "Onbekend", + "SMART_SCALE_LOG": "Activeer logbestanden voor slimme weegschaal (alleen voor foutopsporing)", + "TOAST_PREPARATION_TOOL_EDITED_SUCCESSFULLY": "Voorbereidingstool bewerkt", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE": "Bluetooth-weegschaal", + "PAGE_SETTINGS_TAB_GENERAL": "Algemeen", + "SMART_SCALE_TARE_ON_BREW": "Tarraweegschaal op nieuw brouwsel", + "SMART_SCALE_TARE_ON_START_TIMER": "Tarra weegschaal bij het starten van de timer", + "PAGE_SETTINGS_BREW_RATING_STEPS": "Beoordelingsstappen", + "BREW_AVG_FLOW_WEIGHT_PER_SECOND": "Ø Stroom", + "CUSTOM_LIST_VIEW_PARAMETERS": "Parameters voor lijstweergave", + "NAV_LIST_VIEW_CUSTOM_PARAMETERS": "Parameters voor lijstweergave", + "PAGE_LIST_VIEW_CUSTOM_PARAMETERS_DESCRIPTION": "Bepaal welke parameters moeten worden weergegeven op de tegels van de lijstweergave", + "BREW_DATA_VESSEL_NAME_WEIGHT": "Serveerkan Naam\/Gewicht", + "IGNORE_NEGATIVE_VALUES": "Negeer negatieve gewichtswaarden", + "IGNORE_ANOMALY_VALUES": "Negeer afwijkende waarden", + "IGNORE_ANOMALY_VALUES_TOOLTIP": "Bijvoorbeeld: Een kopje rond bewegen op de weegschaal", + "TOAST_BEAN_FAVOURITE_ADDED": "Favoriet toegevoegd", + "TOAST_BEAN_FAVOURITE_REMOVED": "Favoriet verwijderd", + "QR_CODE_SCANNER_INFORMATION_TITLE": "QR-code", + "QR_CODE_SCANNER_INFORMATION_DESCRIPTION": "Alle gescande boneninformatie komt rechtstreeks van de branderij. Mocht u onjuiste of misleidende informatie aantreffen, laat het mij dan weten via e-mail: info@beanconqueror.com.", + "DONT_SHOW_AGAIN": "Niet meer weergeven", + "ARCHIVED_TOOLS": "Gearchiveerd gereedschap", + "UNARCHIVE": "Herstel", + "PAGE_SETTINGS_HIDE_ARCHIVED_BREWS_DASHBOARD": "Toon gearchiveerde brouwsels op dashboard", + "PAGE_SETTINGS_HIDE_ARCHIVED_BREWS_DASHBOARD_DESCRIPTION": "Moeten gearchiveerde brouwsels op de startpagina worden weergegeven?", + "COPY": "Kopiëren", + "TOAST_PREPARATION_METHOD_REPEATED_SUCCESSFULLY": "Bereidingsmethode succesvol gekopieerd", + "PREPARATION_TYPE_CAFEC_FLOWER": "Cafec Flower", + "PREPARATION_TYPE_DECEMBER_DRIPPER": "December Dripper", + "PREPARATION_TYPE_DECENT_ESPRESSO": "Decent Espresso", + "PREPARATION_TYPE_HARIO_SWITCH": "Hario Switch", + "PREPARATION_TYPE_HARIO_WOODNECK": "Hario Woodneck", + "PREPARATION_TYPE_RATIO_SIX_COFFEE_BREWER": "Ratio Six-koffiezetapparaat", + "PREPARATION_TYPE_ROK": "ROK", + "PREPARATION_TYPE_TORNADO_DUO": "Tornado-duo", + "PREPARATION_TYPE_TRICOLATE": "Tricolate", + "QR_CODE_REFRESH_DATA_MESSAGE": "Alle informatie voor deze boon wordt overschreven, doorgaan?", + "POPOVER_QR_CODE_REFRESH": "Gegevens opnieuw laden", + "BREW_FLOW_WEIGHT_REALTIME": "Stroom (realtime)", + "SMART_SCALE_STOP_TIMER_ON_BREW": "Stop de timer van de weegschaal bij een nieuw brouwsel", + "SMART_SCALE_RESET_TIMER_ON_BREW": "Reset de weegschaaltimer bij een nieuw brouwsel", + "BREW_PRESSURE_FLOW": "Druk", + "BREW_TEMPERATURE_REALTIME": "Temperatuur", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_SHOW_GRAPHS_FILTER": "Grafieken weergeven voor filter", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_SHOW_GRAPHS_ESPRESSO": "Toon grafieken voor espresso", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE": "Drukapparaat", + "PAGE_SETTINGS_TAB_BLUETOOTH_TEMPERATURE": "Temperatuur apparaat", + "PRESSURE_LOG": "Activeer logbestanden voor drukapparaat", + "PRESSURE_THRESHOLD_ACTIVE": "Starttimer met vooraf gedefinieerde druk", + "PRESSURE_THRESHOLD_BAR": "Drempelwaarde druk", + "TIMER_MILLISECONDS": "MS", + "PAGE_SETTINGS_BREW_ENABLE_MILLISECONDS": "Milliseconden?", + "PAGE_SETTINGS_BREW_ENABLE_MILLISECONDS_DESCRIPTION": "Gebruik milliseconden om uw brouwsels nog nauwkeuriger te analyseren", + "PAGE_SETTINGS_BEAN_RATING": "Bonen beoordeling", + "PAGE_SETTINGS_BEAN_RATING_TOOLTIP": "Is de standaard '0 tot 5' niet de juiste beoordeling voor u? U kunt in plaats daarvan een '0 tot 100'-schaal gebruiken", + "PAGE_SETTINGS_BEAN_RATING_STEPS": "Stappen voor het beoordelen van bonen", + "COFFEE_GRAMS_BREWED": "grammen gebrouwen", + "SMART_SCALE_MAXIMIZE_ON_START_TIMER": "Maximaliseer realtime-grafiek bij het starten van de timer", + "PRESSURE": { + "CONNECTION_NOT_ESTABLISHED": "Drukapparaat niet gevonden, of verbinding kon niet tot stand worden gebracht", + "CONNECTED_SUCCESSFULLY": "Drukapparaat aangesloten", + "DISCONNECTED_SUCCESSFULLY": "Drukapparaat losgekoppeld", + "DISCONNECTED_UNPLANNED": "Drukapparaat onverwachts losgekoppeld", + "STAY_CONNECTED_ON_APP_MINIMIZE": "Houd het drukapparaat aangesloten, zelfs als de app op de achtergrond draait", + "INFORMATION_DESCRIPTION": "Ondersteunde apparaten zijn: Popsicle, Pressensor, Smart Espresso Profiler, Bookoo Espresso Monitor", + "BLUETOOTH_SCAN_RUNNING": "Zoeken naar drukapparaat gedurende maximaal 60 seconden", + "BLUETOOTH_NOT_ENABLED": "Bluetooth niet geactiveerd, activeer het om het goed te laten werken", + "REQUEST_PERMISSION": { + "LOCATION": "Om drukapparaten te vinden, heeft de app toegang nodig tot de locatie.", + "BLUETOOTH": "Om drukapparaten te vinden, heeft de app toegang tot Bluetooth nodig" + } + }, + "POPOVER_BLUETOOTH_ACTION_RECONNECT_SCALE": "Weegschaal opnieuw verbinden", + "POPOVER_BLUETOOTH_ACTION_RECONNECT_PRESSURE_DEVICE": "Drukapparaat opnieuw aansluiten", + "POPOVER_SHOW_BREWS": "Toon brouwsels", + "LAST_USED_GRIND_SIZE_SETTING": "Laatste maalstand", + "LAST_USED_BEAN": "Laatste boon", + "PAGE_SETTINGS_BREW_MILLISECONDS_DECIMAL_PLACES_DESCRIPTION": "Hoeveel decimalen moeten er worden weergegeven?", + "SMART_SCALE_COMMAND_DELAY": "Opdracht vertraging", + "SMART_SCALE_COMMAND_DELAY_TOOLTIP": "Hoeveel tijd moet er verstrijken tussen elk bluetooth-commando?", + "SUPPORT_ME": "Donatie", + "PAGE_SETTINGS_BREW_DISPLAY_BEAN_IMAGE": "Bonenafbeeldingen op brouwsels weergeven?", + "PAGE_SETTINGS_BREW_DISPLAY_BEAN_IMAGE_DESCRIPTION": "Als er een bonenafbeelding bestaat, wordt deze gebruikt in plaats van de afbeelding van de bereidingsmethode", + "DOWNLOAD_XLSX": "Excel downloaden", + "DOWNLOAD_JSON": "JSON downloaden", + "NAV_BEAN_PARAMS": "Boon Parameters", + "BEAN_DATA_ROAST_NAME_TYPE": "Brand graad", + "ENABLE_BEAN_SORT_INFORMATION": "Variëteit-informatie inschakelen", + "BEAN_SORT_MIX": "Bonenmix", + "PAGE_MANAGE_BEAN_PARAMETERS_DESCRIPTION": "Markeer welke parameters moeten worden weergegeven bij het bewerken van bean-informatie.", + "PAGE_BEAN_LIST_VIEW_CUSTOM_PARAMETERS_DESCRIPTION": "Bepaal de parameters die moeten worden weergegeven in lijstweergaven voor bonen", + "BEAN_DATA_NAME_TOOLTIP": "De naam van de bonen", + "BEAN_DATA_ROASTER_TOOLTIP": "Wie is de brander", + "BEAN_DATA_BUY_DATE_TOOLTIP": "Wanneer heb je de bonen gekocht?", + "BEAN_DATA_ROASTING_DATE_TOOLTIP": "Wat is de branddatum van de bonen?", + "BEAN_DATA_ROASTING_TYPE_TOOLTIP": "Voor welke bereidingswijze is deze boon geroosterd, bijvoorbeeld Filter", + "BEAN_DATA_ROAST_NAME_TOOLTIP": "Wat is de brandingsgraad van de bonen?", + "BEAN_DATA_ROAST_NAME_TYPE_TOOLTIP": "Welk type branding (Cinnamon, New England, etc.)", + "BREW_DATA_RATING_TOOLTIP": "Je waardering", + "BEAN_SORT_MIX_TOOLTIP": "Zijn de bonen een melange?", + "BEAN_DATA_WEIGHT_TOOLTIP": "Hoeveel wegen de bonen?", + "BEAN_DATA_COST_TOOLTIP": "Hoeveel hebben de bonen gekost?", + "BEAN_DATA_AROMATICS_TOOLTIP": "Welke aroma's\/smaken hebben de bonen?", + "BEAN_DATA_CUPPING_POINTS_TOOLTIP": "Hoeveel cupping-punten heeft de boon?", + "BEAN_DATA_DECAFFEINATED_TOOLTIP": "Is het cafeïnevrij?", + "BEAN_DATA_URL_TOOLTIP": "URL naar de winkel", + "BEAN_DATA_EAN_TOOLTIP": "Het EAN\/artikelnummer", + "NOTES_TOOLTIP": "Aantekeningen gemaakt", + "BREW_DATA_ATTACHMENTS_TOOLTIP": "Foto's toevoegen?", + "ENABLE_BEAN_SORT_INFORMATION_TOOLTIP": "Meer informatie over de bonen: waar komen ze vandaan, wanneer zijn ze geoogst, etc.", + "BEAN_DATA_COUNTRY_TOOLTIP": "Uit welk land komen de bonen?", + "BEAN_DATA_REGION_TOOLTIP": "Uit welke regio komen de bonen?", + "BEAN_DATA_FARM_TOOLTIP": "Van welke boerderij komen de bonen?", + "BEAN_DATA_FARMER_TOOLTIP": "Wie verbouwde deze bonen", + "BEAN_DATA_ELEVATION_TOOLTIP": "Op welke hoogte groeiden de bonen?", + "BEAN_DATA_PROCESSING_TOOLTIP": "Hoe zijn de bonen verwerkt (natuurlijk, gewassen, enz.)", + "BEAN_DATA_VARIETY_TOOLTIP": "De Koffieboon Variëteit (SL28 bv.)", + "BEAN_DATA_HARVEST_TIME_TOOLTIP": "In welk jaar\/maand werden deze bonen geoogst?", + "BEAN_DATA_PERCENTAGE_TOOLTIP": "Hoeveel procent van deze bonen zit er in de zak?", + "BEAN_DATA_CERTIFICATION_TOOLTIP": "Welke certificeringen hebben de bonen (bijvoorbeeld fairtrade)", + "BEAN_DATA_PURCHASING_PRICE_TOOLTIP": "Wat was de aankoopprijs van de koffiebrander", + "BEAN_DATA_FOB_PRICE_TOOLTIP": "Wat was de Free-On-Board (FOB) prijs toen de brander het kocht?", + "BEAN_PARAMETER_CUSTOMIZE_TITLE": "Pas de informatie aan die u voor bonen wilt gebruiken", + "BEAN_PARAMETER_CUSTOMIZE_DESCRIPTION": "Er kan veel informatie over de bonen worden ingevoerd of gebruikt. Kies zelf de parameters die u wilt invullen en welke u wilt weergeven", + "BREW_CANT_START_BECAUSE_TIMER_NOT_RESETTED_TITLE": "Timer resetten!", + "BREW_CANT_START_BECAUSE_TIMER_NOT_RESETTED_DESCRIPTION": "Omdat je een Bluetooth-apparaat hebt aangesloten, moet je eerst je timer resetten voordat je kunt beginnen.", + "BREW_FILTER_JUST_CHART_DATA": "Alleen grafieken", + "SCALE_RESET_TRIGGERED_DESCRIPTION": "De bluetooth weegschaal wil zowel de app-timer als de brouwgrafiek resetten, wil je doorgaan?", + "SCALE_RESET_TRIGGERED_TITLE": "Opnieuw instellen?", + "NAV_REPEAT_PARAMETERS": "Herhalingsparameters definiëren", + "PAGE_REPEAT_PARAMETERS_DESCRIPTION": "Markeer welke parameters vooraf moeten worden ingesteld wanneer u een specifieke brouwbeurt herhaalt", + "CUSTOM_REPEAT_PARAMETERS": "Herhalen", + "CUSTOM_REPEAT_PARAMETERS_DESCRIPTION": "Aangepaste herhaling activeren?", + "PAGE_SETTINGS_USE_NUERMIC_KEYBOARD_FOR_GRIND_SIZE": "Numeriek toetsenbord voor maalinstelling?", + "PAGE_SETTINGS_USE_NUERMIC_KEYBOARD_FOR_GRIND_SIZE_DESCRIPTION": "Wilt u een numeriek toetsenbord gebruiken voor de maalinstelling, in plaats van het hele toetsenbord?", + "PREPARATION_DEVICE": { + "TYPE": { + "NONE": "Geen", + "XENIA": "Xenia", + "METICULOUS": "Meticulous", + "SANREMO_YOU": "" + }, + "URL": "URL-adres", + "CHOOSE_DEVICE": "Kies apparaat", + "CONNECTION": { + "UNSUCCESFULLY": "Er kon geen verbinding met de machine worden gemaakt", + "SUCCESFULLY": "Verbinding met machine was succesvol" + }, + "TYPE_XENIA": { + "TITLE": "Xenia machine", + "PRESS_START_SCRIPT": "Start script bij start", + "FIRST_DRIP_SCRIPT": "Start script bij eerste druppel", + "SCRIPT_AT_WEIGHT": "Gewicht voor scriptuitvoering", + "SCRIPT_LIST_GENERAL_0": "Niets", + "SCRIPT_LIST_GENERAL_1": "Espresso, 25 seconden", + "SCRIPT_LIST_GENERAL_2": "Espresso, eindeloos", + "SCRIPT_LIST_GENERAL_STOP": "Schot stoppen", + "CHOOSE_SCRIPT_AT_WEIGHT": "Kies script voor gewicht bereikt", + "ERROR_NOT_ALL_SCRIPTS_FOUND": "Een of meer scripts konden niet worden gevonden, ze zijn gereset", + "ERROR_CONNECTION_COULD_NOT_BE_ESTABLISHED": "De verbinding met de xenia espressomachine kon niet tot stand worden gebracht. Controleer of u zich op het juiste netwerk (LAN) bevindt, of u het juiste IP-adres hebt ingevoerd, etc.", + "CHECKING_CONNECTION_TO_PORTAFILTER": "Verbinding met Xenia wordt gecontroleerd", + "GRABING_SCRIPTS": "Scripts laden vanuit Xenia", + "BREW_BY_WEIGHT_ACTIVE": "Brouwen op gewicht actief" + }, + "API_VERSION": "Api-versie", + "RESIDUAL_LAG_TIME": "Resterende vertragingstijd", + "RESIDUAL_LAG_TIME_DESCRIPTION": "Stel de tijd in die overeenkomt met het resterende water. Met een naakte portafilter heb je een lagere tijd nodig, met een tuit een hogere. Hoe langzamer uw weegschaal het gewicht rapporteert, hoe meer tijd u nodig heeft", + "TYPE_METICULOUS": { + "TITLE": "Meticulous machine", + "NO_PROFILE": "Geen profiel", + "CHOOSE_PROFILE": "Profiel kiezen", + "SHOT_STARTED": "Brouwen gestart", + "SHOT_ENDED": "Brouwen gestopt" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "", + "STOP_AT_WEIGHT": "", + "SELECT_MODE": "", + "MODE_LISTENING": "", + "MODE_CONTROL": "", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" + } + }, + "DEVICE_CONNECTION": "Apparaatverbinding", + "PREPARATION_DEVICE_CONNECTION": "Apparaatverbinding", + "MANUAL_EXPORT_TO_VISUALIZER": "Exporteren naar Visualizer", + "ONLY_FAVOURITES": "Alleen favorieten", + "TEMPERATURE": { + "CONNECTION_NOT_ESTABLISHED": "Temperatuurapparaat niet gevonden, of verbinding kon niet tot stand worden gebracht", + "CONNECTED_SUCCESSFULLY": "Temperatuurapparaat aangesloten", + "DISCONNECTED_SUCCESSFULLY": "Temperatuurapparaat losgekoppeld", + "DISCONNECTED_UNPLANNED": "Temperatuurapparaat onverwachts losgekoppeld", + "STAY_CONNECTED_ON_APP_MINIMIZE": "Houd de thermometer verbonden, zelfs als de app op de achtergrond staat", + "INFORMATION_DESCRIPTION": "Ondersteunde apparaten zijn: ETI Ltd BLE-thermometers (ThermaQ Blue, BlueTherm, enz.), Combustion Inc., Meater (niet Meater+ of Meater 2)", + "BLUETOOTH_SCAN_RUNNING": "Zoeken naar temperatuurapparaat gedurende maximaal 60 seconden", + "BLUETOOTH_NOT_ENABLED": "Bluetooth niet geactiveerd, activeer het om het goed te laten werken", + "REQUEST_PERMISSION": { + "LOCATION": "Om temperatuurmeters te vinden, heeft de app toegang nodig tot de locatie.", + "BLUETOOTH": "Om temperatuurapparaten te vinden, heeft de app toegang tot Bluetooth nodig" + }, + "LOG": "Logbestanden voor temperatuurapparaat activeren", + "THRESHOLD_ACTIVE": "Start timer met vooraf ingestelde temperatuur", + "THRESHOLD_TEMP": "Drempel temperatuur" + }, + "POPOVER_BLUETOOTH_ACTION_RECONNECT_TEMPERATURE_DEVICE": "Temperatuurapparaat opnieuw aansluiten", + "PRESSURE_DEVICE_JUST_VISIBLE_ON_ESPRESSO": "Drukapparaat is alleen bruikbaar bij de bereidingswijze 'Espresso'", + "PRESSURE_MESSAGE_AFTER_CONNECTION": "Bekend gedrag (in analyse): Houd er rekening mee dat het langer kan duren om verbinding te maken nadat u uw telefoon opnieuw hebt opgestart of wanneer u de app langere tijd niet hebt gebruikt. Na deze tijd zou het verbindingsprobleem opgelost moeten zijn.", + "SMART_SCALE_ACAIA_HEARTBEAT_TIMER": "Hartslagtimer - Speciaal voor Acaia Scales", + "SMART_SCALE_ACAIA_HEARTBEAT_TIMER_TOOLTIP": "Alleen gebruikt voor Acaia-weegschalen! - Oudere Acaia-weegschalen hebben een hartslagsignaal nodig. Als u problemen ondervindt, probeer dan de hartslag op een hogere frequentie in te stellen.", + "SHARE_BEAN_URL": "Delen als URL", + "SHARE_BEAN_IMAGE": "Delen als afbeelding", + "SMART_SCALE_ESPRESSO_STOP_ON_NO_WEIGHT_CHANGE": "Espresso - Automatisch stoppen met brouwen", + "SMART_SCALE_ESPRESSO_STOP_ON_NO_WEIGHT_CHANGE_DESCRIPTION": "Deze instelling wordt alleen gebruikt voor brouwsels van het type 'espresso'. Het brouwsel wordt automatisch gestopt wanneer er geen flow wordt gedetecteerd.", + "BREW_DATA_BREW_QUANTITY_TOOLTIP_BREW_RATIO": "De zetverhouding wordt berekend met deze waarde voor alle brouwsels, maar niet voor type 'espresso'.", + "BREW_DATA_BREW_BEVERAGE_QUANTITY_TOOLTIP_BREW_RATIO": "De zetverhouding wordt alleen met deze waarde berekend voor brouwsels van het type 'espresso'", + "SMART_SCALE_DID_NOT_SEND_ANY_WEIGHT_DESCRIPTION": "Het lijkt erop dat de bluetooth weegschaal niet goed is aangesloten. Dit gebeurt meestal op iOS-apparaten met Acaia weegschalen, sluit de weegschaal opnieuw aan en zorg ervoor dat de gewichtstegel wordt bijgewerkt.", + "SMART_SCALE_DID_NOT_SEND_ANY_WEIGHT_TITLE": "Geen gewichtswaarden?", + "SMART_SCALE_ESPRESSO_STOP_ON_NO_WEIGHT_CHANGE_MIN_FLOW_DESCRIPTION": "De stroomsnelheid waarbij het zetten moet worden gestopt. Het zetten wordt pas gestopt als er minimaal 5 seconden zijn verstreken, er 5 gram in de kop zit of als u het gewicht van de gemalen koffie hebt ingevoerd - er is minimaal een zetverhouding van 1:1 bereikt (bijv.: 18 g erin, 18 g eruit). Nadat aan deze voorwaarden is voldaan, wordt de hier ingestelde minimale stroomsnelheid gecontroleerd", + "ONLY_BEST_BREWS": "Alleen de beste brouwsels", + "POPOVER_BEST_BREW": "Beste brouwsel", + "PAGE_SETTINGS_BEST_BREW": "Beste brouwsels activeren", + "PAGE_SETTINGS_BEST_BREW_DESCRIPTION": "Markeer je beste brouwsel voor een specifieke boon. Elke boon kan één beste brouwsel hebben in plaats van meerdere favorieten.", + "PAGE_SETTINGS_TAB_BLUETOOTH_REFRACTOMETER": "Refractometer apparaat", + "REFRACTOMETER": { + "CONNECTION_NOT_ESTABLISHED": "Refractometerapparaat niet gevonden, of verbinding kon niet tot stand worden gebracht", + "CONNECTED_SUCCESSFULLY": "Refractometerapparaat aangesloten", + "DISCONNECTED_SUCCESSFULLY": "Refractometerapparaat losgekoppeld", + "DISCONNECTED_UNPLANNED": "Refractometerapparaat onverwachts losgekoppeld", + "STAY_CONNECTED_ON_APP_MINIMIZE": "Houd de refractometer verbonden, zelfs als de app op de achtergrond draait", + "INFORMATION_DESCRIPTION": "Ondersteunde apparaten zijn: DiFluid R2", + "BLUETOOTH_SCAN_RUNNING": "Zoeken naar refractometer-apparaat gedurende maximaal 60 seconden", + "BLUETOOTH_NOT_ENABLED": "Bluetooth niet geactiveerd, activeer het om het goed te laten werken", + "REQUEST_PERMISSION": { + "LOCATION": "Om refractometerapparaten te vinden, heeft de app toegang nodig tot de locatie.", + "BLUETOOTH": "Om refractometerapparaten te vinden, heeft de app toegang tot Bluetooth nodig" + }, + "LOG": "Activeer logbestanden voor refractometerapparaat", + "READ_END": "Test voltooid - resultaat ontvangen" + }, + "COPIED_TO_CLIPBOARD_SUCCESSFULLY": "Toegevoegd aan klembord", + "COPIED_TO_CLIPBOARD_UNSUCCESSFULLY": "Kan niet worden toegevoegd aan het klembord", + "PAGE_SETTINGS_LANGUAGE_FRENCH": "Frans", + "ANDROID_EXTERNAL_FILE_ACCESS_NOT_POSSIBLE_TITLE": "Gegevens kunnen niet worden opgeslagen vanwege Android-beperkingen", + "ANDROID_EXTERNAL_FILE_ACCESS_NEEDED_DESCRIPTION": "Je Android-telefoon ondersteunt geen externe bestandssystemen, dus je moet het ZIP-bestand downloaden zonder mediabestanden. Ga voor meer informatie naar https:\/\/beanconqueror.com\/faq.", + "PAGE_SETTINGS_SECURITY_CHECK_WHEN_GOING_BACK": "Beveiligingsbericht voor afsluiten?", + "PAGE_SETTINGS_SECURITY_CHECK_WHEN_GOING_BACK_DESCRIPTION": "Controleer of de gegevens zijn gewijzigd bij het toevoegen\/bewerken van bonen of brouwsels; als dat het geval is, wordt er een veiligheidswaarschuwing weergegeven bij het teruggaan.", + "PAGE_BEANS_DISCARD_CONFIRM": "Boon informatie is gewijzigd. Weet je zeker dat je de pagina wilt verlaten zonder op te slaan?", + "PAGE_BREW_DISCARD_CONFIRM": "De brouw informatie is gewijzigd. Weet je zeker dat je de pagina wilt verlaten zonder op te slaan?", + "NO_ENTRIES_FOUND": "Geen vermeldingen gevonden", + "POPOVER_BEANS_OPTION_REPEAT": "Herhaal laatste brouwsel", + "REPEAT_LAST_BREW": "Herhaal laatste brouwsel", + "REPEAT_BEST_BREW": "Herhaal beste brouwsel", + "PAGE_SETTINGS_VISUALIZER_SECTION": "Visualizer", + "VISUALIZER": { + "ACTIVATE": "Visualizer activeren", + "CHOOSE_SERVER": "Server kiezen", + "CONNECTION": { + "UNSUCCESSFULLY": "Er kon geen verbinding tot stand worden gebracht.", + "SUCCESSFULLY": "Verbinding kon tot stand worden gebracht." + }, + "SERVER": { + "VISUALIZER": "Visualizer-server", + "CUSTOM": "Aangepaste server" + }, + "SHOT": { + "UPLOAD_SUCCESSFULLY": "Brouwen geüpload naar visualizer.", + "UPLOAD_UNSUCCESSFULLY": "Brouwen kon niet worden geüpload naar de Visualizer." + }, + "URL": "Server-URL", + "USERNAME": "Gebruikersnaam", + "PASSWORD": "Wachtwoord", + "UPLOAD_AUTOMATIC": "Elke brouwsessie automatisch uploaden?", + "UPLOAD_AUTOMATIC_TOOLTIP": "Zorg ervoor dat u een actieve internetverbinding hebt wanneer u uw brouwsel opslaat.", + "UPLOAD_ALL": "Alle brouwsels uploaden", + "NOT_ALL_SHOTS_UPLOADED": "Niet alle brouwsels konden worden geüpload", + "ALL_SHOTS_UPLOADED": "Alle brouwsels zijn geüpload" + }, + "SMART_SCALE_AUTO_START_LISTENING": "Timer automatisch starten?", + "SMART_SCALE_AUTO_START_LISTENING_DESCRIPTION": "Start de timer automatisch bij het bereiken van de volgende gewichtswaarde", + "CHOOSE_REFERENCE_GRAPH": "Referentiegrafiek selecteren", + "RESET": "Opnieuw instellen", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_AXIS": "Definieer de beginassen van de grafiek", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_AXIS_DESCRIPTION": "Stel de begingrootte van de assen in voor zowel filter- als espressokoffie.", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_FILTER_WEIGHT": "Filter - Gewicht", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_FILTER_FLOW": "Filter - Doorstroming", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_ESPRESSO_WEIGHT": "Espresso - Gewicht", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_ESPRESSO_FLOW": "Espresso - Doorstroming", + "SMART_SCALE_IGNORE_INCOMING_WEIGHT": "Negeer huidige gewichtsoverdracht ", + "SMART_SCALE_IGNORE_INCOMING_WEIGHT_TOOLTIP": "Er verschijnt een nieuwe knop in het brouwgedeelte, die nieuwe gewichtswaarden van de bluetooth weegschaal zal negeren.", + "BREWS_ACTIVE": "Actieve brouwsels", + "BREWS_ARCHIVED": "Gearchiveerde brouwsels", + "GRAPHS": "Grafieken", + "GRAPH_SECTION": { + "NAV_GRAPH": "Grafieken", + "NO_ARCHIVED_ENTRIES": "Geen gearchiveerde vermeldingen", + "NO_ENTRIES": "Geen vermeldingen", + "SECTION_HAS_BEEN_ACTIVATED": "Grafieksectie is geactiveerd" + }, + "TOAST_GRAPH_ARCHIVED_SUCCESSFULLY": "Grafiek gearchiveerd", + "TOAST_GRAPH_DELETED_SUCCESSFULLY": "Grafiek verwijderd", + "TOAST_GRAPH_EDITED_SUCCESSFULLY": "Grafiek bewerkt", + "TOAST_GRAPH_ADD_SUCCESSFULLY": "Grafiek toegevoegd", + "NAV_GRAPH_SECTION": "Grafieken", + "DELETE_GRAPH_QUESTION": "Wilt u deze grafiek verwijderen?", + "PAGE_SETTINGS_SHOW_ARCHIVED_GRAPHS": "Gearchiveerde grafieken weergeven", + "PAGE_SETTINGS_SHOW_GRAPH_SECTION": "Grafieksectie activeren", + "EDIT_GRAPH": "Grafiek bewerken", + "ADD_GRAPH": "Grafiek toevoegen", + "GRAPH": { + "PLACE_HOLDER": { + "NAME": "Grafiek naam", + "NOTES": "Notities" + }, + "UPLOAD": "Grafiek uploaden", + "DELETE": "Grafiek verwijderen", + "UPLOAD_DESCRIPTION": "Importeer een .JSON-bestand dat u kunt downloaden in de brew-detailweergave. U kunt ook gedeelde grafieken van de community importeren die van het type .JSON zijn." + }, + "SHOW_VISUALIZER": "Visualiseerder weergeven", + "NO_BREWS_FOUND": "Geen brouwsels gevonden", + "NO_GRAPHS_FOUND": "Geen grafieken gevonden", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_TIME_AXIS": "Definieer de maximale tijdsas van de grafiek", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_TIME_DESCRIPTION": "Stel het maximum aantal seconden in op de tijd-as voor zowel filterkoffie als espresso.", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_TIME_FILTER_AXIS_NORMAL_SCREEN": "Filter - Normaal scherm", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_TIME_FILTER_AXIS_FULL_SCREEN_SCREEN": "Filter - Volledig scherm", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_TIME_ESPRESSO_AXIS_NORMAL_SCREEN": "Espresso - Normaal scherm", + "PAGE_SETTINGS_TAB_BLUETOOTH_SCALE_GRAPHS_TIME_ESPRESSO_AXIS_FULL_SCREEN_SCREEN": "Espresso - Volledig scherm", + "PAGE_SETTINGS_DATE_FORMAT": "Datum notatie", + "PAGE_SETTINGS_LANGUAGE_FRANCE": "Frans", + "PAGE_SETTINGS_LANGUAGE_INDONESIA": "Indonesisch", + "EXTRACTION_CHART_TITLE": "Extractie grafiek", + "PAGE_SETTINGS_SHOW_BACKUP_ISSUES": "Back-up problemen weergeven", + "PAGE_SETTINGS_SHOW_BACKUP_ISSUES_DESCRIPTION": "Geef een pop-up weer als er geen back-ups naar het bestandssysteem kunnen worden geschreven", + "AUTOMATIC_BACKUP_DID_FAIL": "Automatische back-up werkte niet, zorg ervoor dat u werkende back-ups hebt! Deze informatie kan worden uitgeschakeld in de instellingen", + "INTERNAL_BACKUP_DID_FAIL": "Interne back-up werkte niet, zorg ervoor dat u werkende back-ups hebt! Deze informatie kan worden uitgeschakeld in de instellingen", + "ZIP_BACKUP_FILE_COULD_NOT_BE_BUILD": "ZIP-bestand kon niet worden opgeslagen! Deze informatie kan worden uitgeschakeld in de instellingen", + "SEND_LOGS": "Logboeken verzenden", + "POPOVER_BLUETOOTH_ACTION_RECONNECT_REFRACTOMETER": "Sluit refractometer apparaat opnieuw aan", + "PAGE_SETTINGS_LANGUAGE_ITALIAN": "Italiaans", + "PAGE_SETTINGS_LANGUAGE_POLISH": "Pools", + "BREW_CANT_START_BECAUSE_TIMER_NOT_RESETTED_GENERAL_DESCRIPTION": "Je moet eerst je timer resetten voordat je kunt beginnen.", + "SMART_SCALE_FIRST_DRIP_THRESHOLD": "Eerste druppel drempel", + "SMART_SCALE_FIRST_DRIP_THRESHOLD_TOOLTIP": "Bij welk weegschaalgewicht moet de eerste druppel worden geteld? Standaard: >= 0,1g", + "PAGE_SETTINGS_BREW_TIMER_START_DELAY_ACTIVE": "Zetvertraging starten", + "PAGE_SETTINGS_BREW_TIMER_START_DELAY_ACTIVE_DESCRIPTION": "Stel een vertragingstijd in die een laadspinner weergeeft totdat het brouwen begint", + "STARTING_IN": "Beginnend in ... {{time}}", + "IOS_DATABASE_ISSUE_TITLE": "ATTENTIE !!!!! - DATABASEVERBINDING VERLOREN", + "IOS_DATABASE_ISSUE_DESCRIPTION": "Het spijt ons u te moeten meedelen dat de verbinding met de database is verbroken. Dit probleem is het gevolg van een onopgeloste bug in het systeem van Apple, waarover Beanconqueror geen controle heeft. Om mogelijk gegevensverlies te voorkomen, verzoeken wij u vriendelijk de Beanconqueror-applicatie onmiddellijk geforceerd af te sluiten en opnieuw te openen.", + "RELOAD_APP": "Herstart de app", + "WATER_TYPE_ADD_CUSTOM": "Water op maat", + "WATER_TYPE_THIRD_WAVE_WATER_CLASSIC_LIGHT_ROAST_PROFILE": "Third Wave Water - Klassiek licht geroosterd profiel", + "WATER_TYPE_THIRD_WAVE_WATER_MEDIUM_ROAST_PROFILE": "Third Wave Water - Middel geroosterd profiel", + "WATER_TYPE_THIRD_WAVE_WATER_DARK_ROAST_PROFILE": "Third Wave Water - Donker geroosterd profiel", + "WATER_TYPE_THIRD_WAVE_WATER_ESPRESSO_MACHINE_PROFILE": "Third Wave Water - Espressomachine Profiel", + "WATER_TYPE_THIRD_WAVE_WATER_COLD_BREW_PROFILE": "Third Wave Water - koud brouwprofiel", + "WATER_TYPE_THIRD_WAVE_WATER_LOW_ACID_PROFILE": "Third Wave Water - Laag Zuurprofiel", + "ADD_WATER": "Voeg water toe", + "EXTRACTION_CHART_LABEL_STRONG_UNDEREXTRACTED": "STERK
onderextractie", + "EXTRACTION_CHART_LABEL_STRONG": "Strong
", + "EXTRACTION_CHART_LABEL_STRONG_HARSH": "STERK
ruw", + "EXTRACTION_CHART_LABEL_UNDEREXTRACTED": "ondergeëxtraheerd", + "EXTRACTION_CHART_LABEL_IDEAL": "IDEAAL", + "EXTRACTION_CHART_LABEL_HARSH": "ruw", + "EXTRACTION_CHART_LABEL_WEAK_UNDEREXTRACTED": "ZWAK
onderextractie", + "EXTRACTION_CHART_LABEL_WEAK": "ZWAK
", + "EXTRACTION_CHART_LABEL_WEAK_HARSH": "ZWAK
ruw", + "PAGE_SETTINGS_TEXT_TO_SPEECH_SECTION": "Tekst naar spraak", + "TEXT_TO_SPEECH": { + "ACTIVATE": "Activeer tekst naar spraak", + "BREW_STARTED": "Brouwen gestart", + "BREW_ENDED": "Einde brouwen", + "TIME": "Tijd", + "SPEAK_EVERY_MS": "Spreek elke geselecteerde milliseconde", + "FOLLOWING_NUMBERS_WILL_BE_TEST_SPOKEN": "De volgende nummers worden als test uitgesproken", + "TEST_SPEAK": "Start test spraak", + "PITCH": "Toonhoogte", + "RATE": "beoordeling" + }, + "PAGE_SETTINGS_HAPTIC_FEEDBACK_SECTION": "Haptische feedback", + "HAPTIC_FEEDBACK": { + "ACTIVATE": "Activeer haptische feedback", + "BREW_STARTED": "Trillen bij het starten van het brouwen", + "BREW_STOPPED": "Trillen bij het stoppen van het brouwen", + "TARE": "Trillen bij tarra van de weegschaal" + }, + "CONNECTED": "Verbonden", + "DISCONNECTED": "Losgekoppeld", + "EXPERIMENTAL_FEATURE_DISCLAIMER": "Dit is een experimentele functie", + "SHOW_HOURS": "Toon uren", + "SHOW_MINUTES": "Toon Minuten", + "BEANS_UNARCHIVE": "uit het archief halen", + "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Boon is uit het archief gehaald", + "WATER_TYPE_PURE_COFFEE_WATER": "Zuiver Koffiewater", + "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "empirical water GLACIAL", + "WATER_TYPE_EMPIRICAL_WATER_SPRING": "empirical water SPRING", + "SORT_PREPARATION_TOOLS": "Sorteer bereidingsmiddelen", + "PREPARATION_TYPE_METICULOUS": "Meticulous", + "EXPORT_CAUTION": "Exporteren exporteert alleen de database, geen afbeeldingen, geen stromingsprofielen . Als deze nodig zijn, ga dan naar Gitbook voor meer informatie", + "POPOVER_FREEZE_COFFEE_BEAN": "Bevries koffieboon", + "POPOVER_UNFREEZE_COFFEE_BEAN": "Koffieboon ontdooien", + "BEAN_POPOVER_EDIT_FREEZE_DATE": "invries datum bewerken", + "BEAN_POPOVER_EDIT_UNFREEZE_DATE": "ontdooi datum bewerken", + "BEAN_POPOVER_LEFT_UNFROZEN": "Niet ingevroren", + "BEAN_POPOVER_FREEZE_PARTIAL_BAG": "Gedeelte zak invriezen (g)", + "FROZEN_BEANS": "Bevroren", + "BEAN_TAB_FROZEN_INFORMATION": "Bevroren notities", + "BEAN_POPOVER_FROZEN_BAGS": "Bevroren zakken", + "BEAN_POPOVER_FROZEN_DELETE_BEAN_MESSAGE": "Je gaat de hele zak invriezen en we hebben geen brouwsels gevonden die ermee gemaakt zijn, wil je de originele zak verwijderen?", + "CREATE_FROZEN_BEANS": "creëer bonen", + "BEAN_POPOVER_COPY_ATTACHMENTS": "Kopieer bijlagen", + "BEAN_POPOVER_COPY_ATTACHMENTS_DESCRIPTION": "Het kopiëren van bijlagen is aan het begin van deze functie gedeactiveerd", + "BEAN_DATA_FROZEN_DATE": "Invries datum", + "BEAN_DATA_UNFROZEN_DATE": "ontdooi datum", + "PAGE_BEANS_LIST_YOU_GOT_NO_FROZEN_BEANS": "Je hebt geen ingevroren bonen", + "BEAN_DATA_FROZEN_ID": "Ingevroren id", + "PAGE_SETTINGS_MANAGE_FEATURES": "Functies beheren", + "ACTIVE_BEAN_FREEZING_FEATURE": "Activeer het invriezen van bonen", + "NAV_FROZEN_BEANS_LIST": "Lijst met ingevroren bonen", + "BEAN_BREW_LIST_VIEW_PARAMETERS": "Boon informatie voor brouwen", + "BEAN_AGE_BY_BREW_DATE": "Leeftijd van de bonen op brouwdatum", + "BEAN_POPOVER_FROZEN_BEAN_WILL_BE_ARCHIVED_NOW_MESSAGE": "Je diepvrieszakken hebben als resultaat dat je originele zak geen gewicht meer heeft, maar je hebt er wel mee gebrouwen, dus beoordeel en stuur naar het archief.", + "BEAN_POPOVER_YOU_CANT_FREEZE_WITH_ZERO_WEIGHT_LEFT": "Je kunt niet bevriezen, want het restgewicht van je zak is nul", + "BEAN_DATA_BEST_DATE": "Beste Bonen Datum", + "BEAN_DATA_BEST_DATE_TOOLTIP": "Wanneer is de beste datum om de bonen te gebruiken?", + "BEAN_DATA_OPEN_DATE": "Openingsdatum van de zak", + "BEAN_DATA_OPEN_DATE_TOOLTIP": "Wanneer heb je de zak bonen geopend?", + "BEAN_FREEZING_STORAGE_TYPE_UNKNOWN": "Onbekend", + "BEAN_FREEZING_STORAGE_TYPE_COFFEE_BAG": "Koffiezak", + "BEAN_FREEZING_STORAGE_TYPE_COFFEE_JAR": "Koffie pot", + "BEAN_FREEZING_STORAGE_TYPE_ZIP_LOCK": "Ritssluiting", + "BEAN_FREEZING_STORAGE_TYPE_VACUUM_SEALED": "Vacuüm verzegeld", + "BEAN_FREEZING_STORAGE_TYPE_TUBE": "Buis", + "BEAN_DATA_FROZEN_STORAGE_TYPE": "Vriezer opslagtype", + "PAGE_SETTINGS_BREW_RATING_CHANGED_BREWS_NOT_VISIBLE": "U heeft de maximale beoordeling van uw brouwsels gewijzigd, maar u heeft uw brouwsels al hoger beoordeeld dan uw werkelijke maximum. Deze worden dus niet weergegeven bij de normale filterinstellingen.", + "PAGE_SETTINGS_BEAN_RATING_CHANGED_BEANS_NOT_VISIBLE": "U heeft de maximale beoordeling voor bonen gewijzigd, maar u heeft bonen al hoger beoordeeld dan uw werkelijke maximum. Ze worden dus niet weergegeven met de normale filterinstellingen.", + "BEAN_DATA_FROZEN_NOTE": "Bevroren notities", + "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "Als je dit activeert, loopt de grafiek één seconde achter", + "IGNORE_ANOMALY_VALUES_DESCRIPTION": "Als je dit activeert, loopt de grafiek één seconde achter", + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Bewaar de machine logs bij het opslaan van een brouwsel", + "IMPORT_SHOT_FROM_METICULOUS": "Importeer een shot", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Alle gevonden items in de Excel-lijst zijn omgezet in nieuwe bonen. Geniet van een smakelijk kopje koffie!", + "IMPORT_UNSUCCESSFULLY": "Importeren is niet gelukt", + "BEAN_LIST": "Bonenlijst", + "IMPORT_ROASTED_BEANS_EXCEL": "Gebrande bonen importeren via Excel", + "IMPORT_GREEN_BEANS_EXCEL": "Groene bonen importeren via excel", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "Het lijkt erop dat het gekozen excel-bestand corrupt is, verkeerde gegevens bevat, verkeerd is gestructureerd of verkeerd is gekozen. Hierdoor konden er geen beans worden geïmporteerd.", + "OK": "OK", + "BEAN_SORT_BEAN_AGE": "Bonen leeftijd", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Alle gevonden items in de Excel-lijst zijn omgezet in nieuwe groene bonen, success met koffie roosteren!", + "PREPARATION_TYPE_SANREMO_YOU": "", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "Je voegt een bean toe die variëteitsinformatie bevat, maar die parameters zijn niet geactiveerd. Wil je ze nu activeren?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Definieer de assen voor de drukgrafiek", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Stel de begin- en eindgrootte van de assen voor druk in", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Druk assen", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Nederlands", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "Download importsjablonen", + "SHOW_GRAPH": "Grafiek weergeven", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Versie 7.5.0: Wat is er nieuw", + "DESCRIPTION": [ + "Nieuwe taal<\/b>", + "Nederlandse ondersteuning - Met dank aan Ygg", + "", + " Brouwsels <\/b>", + "Het is nu mogelijk om een brouwlijst samen te vouwen", + "Weergeven of er een bevroren boon is gebruikt", + "Bonen afbeelding nu zichtbaar bij selectie", + "Lees en importeer een geschiedenisshot van de Meticulous", + "De vermeldingen in de brouwlijst zijn nu veegbaar als ze een grafiek hebben", + "", + " Bonen <\/b>", + "Het is nu mogelijk om de bonenlijst in te klappen", + "Bonen kunnen nu worden gesorteerd op boonleeftijd", + "Sortering wordt niet meer gereset bij het archiveren van een boon", + "", + " Groene bonen <\/b>", + "Groene bonen zijn nu importeerbaar - Zie Instellingen", + "", + " Instellingen <\/b>", + "Stel de start-as voor de druk in", + "Importeer groene bonen of geroosterde bonen", + "", + "Detectie van gegevenscorruptie<\/b>", + "Beanconqueror controleert nu of er op de een of andere manier een datacorruptie is opgetreden, en als dat zo is, toont het een pop-up waarmee u een back-up kunt importeren", + "", + "Anders<\/b>", + "Het oplossen van de sortering van bereidingsgereedschappen", + "Berekening van bevroren bonen om afrondingsproblemen op te lossen", + "Enkele technische wijzigingen in de code", + "Kleine aanpassingen" + ] + } + } +} \ No newline at end of file diff --git a/src/assets/i18n/pl.json b/src/assets/i18n/pl.json index ca541d8e2..72041c945 100644 --- a/src/assets/i18n/pl.json +++ b/src/assets/i18n/pl.json @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "Nic", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "" }, "URL": "Adres URL", "CHOOSE_DEVICE": "Wybierz urządzenie", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "Wybierz profil", "SHOT_STARTED": "Strzał rozpoczęty", "SHOT_ENDED": "Strzał zakończony" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "", + "STOP_AT_WEIGHT": "", + "SELECT_MODE": "", + "MODE_LISTENING": "", + "MODE_CONTROL": "", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" } }, "DEVICE_CONNECTION": "Połączenie urządzenia", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "Pokaż minuty", "BEANS_UNARCHIVE": "Cofnij archiwizację", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Ziarno nie zostało zarchiwizowane", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "Wersja 7.4.0: Co nowego", - "DESCRIPTION": [ - "Ziarna<\/b>", - "Teraz możliwe jest zamrożenie ziaren kawy, aktywując tę funkcję w ustawieniach.", - "Dodano najlepszą i otwartą datę dla fasoli, należy aktywować ten parametr", - "Ziarna można teraz dodawać bezpośrednio z przeglądu wyboru.", - "", - "Termometr<\/b>", - "Wsparcie Combustion Inc. Termometr - Dziękujemy za urządzenie!", - "Wsparcie dla termometru Meater (nie Meater 2 lub Meater +) - dzięki Yannick!", - "", - "Sekcja wodna<\/b>", - "Dodano czystą wodę kawową", - "Dodana woda empiryczna", - "", - "Narzędzia przygotowawcze<\/b>", - "Posortuj teraz swoje narzędzia przygotowawcze", - "", - "Młynek<\/b>", - "Obrazy młynka są teraz wyświetlane w przeglądzie wyboru", - "", - "Ustawienia<\/b>", - "Komunikat bezpieczeństwa, jeśli zmieniono ocenę dla naparów lub ziaren, a maksymalna ocena jest niższa niż już oceniony wpis.", - "", - "Inne<\/b>", - "Przywrócono bezpośrednie ustawianie ostrości w sekundach podczas otwierania timera.", - "Kilka zmian technicznych w kodzie", - "Drobne poprawki" - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Czysta woda kawowa", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "woda empiryczna GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "woda empiryczna SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "Zamrożone notatki", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "Aktywacja tej opcji spowoduje, że wykres będzie opóźniony o jedną sekundę.", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "Aktywacja tej opcji spowoduje, że wykres będzie opóźniony o jedną sekundę.", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Zapisywanie dzienników urządzenia podczas zapisywania parzenia" + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Zapisywanie dzienników urządzenia podczas zapisywania parzenia", + "IMPORT_SHOT_FROM_METICULOUS": "Importuj strzał", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Wszystkie znalezione wpisy na liście w pliku Excel zostały przekształcone w nowe ziarna. Ciesz się smacznym naparem!", + "IMPORT_UNSUCCESSFULLY": "Import nie powiódł się", + "BEAN_LIST": "Lista ziaren", + "IMPORT_ROASTED_BEANS_EXCEL": "Importuj wyrażone ziarna z pliku Excel", + "IMPORT_GREEN_BEANS_EXCEL": "Importuj zielone ziarna z pliku Excel", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "Wygląda na to, że wybrany plik Excel jest uszkodzony, ma złe dane, jest źle ustrukturyzowany lub został źle wybrany. Z tego powodu nie można było zaimportować żadnych ziaren.", + "OK": "Ok", + "BEAN_SORT_BEAN_AGE": "Wiek ziarna", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Wszystkie znalezione pozycje na liście excel, zostały przekształcone w nowe zielone ziarna, mają świetne pieczenie!", + "PREPARATION_TYPE_SANREMO_YOU": "", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "Dodajesz ziarno, które zawiera informacje o odmianie, ale te parametry nie są aktywowane. Czy chcesz je teraz aktywować?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Zdefiniuj osie wykresu ciśnienia", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Ustaw początkowy i końcowy rozmiar osi dla ciśnienia", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Osie ciśnienia", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Holenderski", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "Pobierz szablony importu", + "SHOW_GRAPH": "Pokaż wykres", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Wersja 7.5.0: Co nowego", + "DESCRIPTION": [ + "Nowy język<\/b>", + "Wsparcie języka holenderskiego - podziękowania dla Ygg", + "", + "Napary<\/b>", + "Zwinięcie listy naparów jest teraz możliwe", + "Wyświetlanie, czy użyto zamrożonego ziarna", + "Obraz ziarna jest teraz widoczny po zaznaczeniu.", + "Odczytywanie i importowanie ujęcia historii z Meticulous", + "Wpisy na liście na parów można teraz przesuwać, jeśli mają wykres", + "", + "Ziarna<\/b>", + "Zwijanie listy fasoli jest teraz możliwe", + "Ziarna można teraz sortować według ich wieku", + "Sortowanie nie jest już resetowane podczas archiwizowania ziarna", + "", + "Zielone ziarna<\/b>", + "Zielone ziarna można teraz importować - patrz Ustawienia", + "", + "Ustawienia<\/b>", + "Ustaw oś początkową dla ciśnienia", + "Importuj zielone ziarna lub wypalone", + "", + "Wykrywanie uszkodzeń danych<\/b>", + "Beanconqueror sprawdza teraz, czy w jakiś sposób nie doszło do uszkodzenia danych i jeśli tak, wyświetla okno podręczne umożliwiające zaimportowanie kopii zapasowej", + "", + "Inne<\/b>", + "Naprawianie sortowania narzędzi przygotowawczych", + "Naprawa obliczeń zamrożonej fasoli w celu usunięcia problemów z zaokrąglaniem", + "Kilka zmian technicznych w kodzie", + "Drobne poprawki" + ] + } + } } \ No newline at end of file diff --git a/src/assets/i18n/tr.json b/src/assets/i18n/tr.json index cfb0c409b..8ae503068 100644 --- a/src/assets/i18n/tr.json +++ b/src/assets/i18n/tr.json @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "Hiçbiri", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "" }, "URL": "Url", "CHOOSE_DEVICE": "Cihaz seç", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "Profil seçin", "SHOT_STARTED": "Akış başladı", "SHOT_ENDED": "Akış sona erdi" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "", + "STOP_AT_WEIGHT": "", + "SELECT_MODE": "", + "MODE_LISTENING": "", + "MODE_CONTROL": "", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" } }, "DEVICE_CONNECTION": "Cihaz bağlantısı", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "Dakikayı göster", "BEANS_UNARCHIVE": "Arşivden çıkar", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "Çekirdek arşivden çıkarıldı", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "Sürüm 7.4.0: Yenilikler", - "DESCRIPTION": [ - " Fasulye <\/b>", - "Artık kahve çekirdeklerinizi dondurmak mümkün, bu özelliği ayarlardan etkinleştirin", - "Çekirdek için en iyi ve açık tarih eklendi, bu parametreyi etkinleştirmeniz gerekir", - "Çekirdekler artık doğrudan seçime genel bakıştan eklenebilir", - "", - "Termometre<\/b>", - "Combustion Inc. Termometre Desteği - Cihaz için teşekkürler!", - "Meater Termometre desteği (Meater 2 veya Meater + değil) - Yannick'e teşekkürler!", - "", - "Su Bölümü<\/b>", - "Saf Kahve Suyu Eklendi", - "Ampirik su eklendi", - "", - "Hazırlık Araçları<\/b>", - "Hazırlık araçlarınızı şimdi sıralayın", - "", - "Öğütücü<\/b>", - "Öğütücü görüntüleri artık seçim genel görünümünde gösteriliyor", - "", - "Ayarlar<\/b>", - "Demleme veya çekirdek derecelendirmesini değiştirdiyseniz ve maksimum değer, halihazırda derecelendirilmiş bir girişten düşükse güvenlik mesajı", - "", - "Diğer<\/b>", - "Zamanlayıcıyı açarken doğrudan saniye odağı geri döndürüldü", - "Koddaki bazı teknik değişiklikler", - "Küçük değişiklikler" - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Saf Kahve Suyu", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "ampirik su GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "ampirik su SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "Dondurulmuş notlar", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "Bunu etkinleştirdiğinizde bir saniye geride olacak bir grafik ortaya çıkar", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "Bunu etkinleştirdiğinizde bir saniye geride olacak bir grafik ortaya çıkar", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Bir demlemeyi kaydederken makine günlüklerini kaydedin" + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "Bir demlemeyi kaydederken makine günlüklerini kaydedin", + "IMPORT_SHOT_FROM_METICULOUS": "Çekimi içe aktar", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Excel listesinde bulunan tüm girişler yeni çekirdeklere dönüştürüldü, lezzetli bir demlemenin tadını çıkarın!", + "IMPORT_UNSUCCESSFULLY": "İçe aktarma başarısız oldu", + "BEAN_LIST": "Çekirdek listesi", + "IMPORT_ROASTED_BEANS_EXCEL": "Kavrulmuş çekirdekleri excel ile içe aktarın", + "IMPORT_GREEN_BEANS_EXCEL": "Yeşil çekirdekleri excel ile içe aktarın", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "Seçilen excel dosyasının bozuk, yanlış veri içerdiği, yanlış yapılandırılmış olduğu veya yanlış seçildiği anlaşılıyor. Bu nedenle hiçbir çekirdek içe aktarılamadı.", + "OK": "Tamam", + "BEAN_SORT_BEAN_AGE": "Çekirdek yaşı", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "Excel listesinde bulunan tüm girdiler yeni yeşil çekirdeklere dönüştürüldü, harika kavrulmuşlar!", + "PREPARATION_TYPE_SANREMO_YOU": "", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "İçinde çeşitlilik bilgisi olan bir çekirdek ekliyorsunuz, ancak bu parametreler etkinleştirilmemiş. Bunları şimdi etkinleştirmek ister misiniz?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "Basınç grafiği için eksenleri tanımlayın", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "Basınç için eksenlerin başlangıç ve bitiş boyutunu ayarlayın", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "Basınç eksenleri", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "Hollandaca", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "İçe aktarma şablonlarını indirin", + "SHOW_GRAPH": "Grafiği göster", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "Sürüm 7.5.0: Yenilikler", + "DESCRIPTION": [ + "Yeni dil<\/b>", + "Hollandaca desteği için - Ygg'ye teşekkürler", + "", + "Demlemeler<\/b>", + "Bir demleme listesini daraltmak artık mümkün", + "Dondurulmuş çekirdeğin kullanılıp kullanılmadığının görüntülenmesi", + "Çekirdek görüntüsü artık seçimde görünür", + "Meticulous'tan bir geçmiş görüntüsünü okuyun ve içe aktarın", + "Demleme listesi girişleri artık grafikleri olduğunda kaydırılabilir", + "", + "Çekirdekler<\/b>", + "Çekirdek listesini daraltmak artık mümkün", + "Çekirdekler artık çekirdek yaşına göre sıralanabiliyor", + "Bir çekirdeği arşivlerken sıralama artık sıfırlanmıyor", + "", + "Yeşil çekirdek<\/b>", + "Yeşil çekirdek artık içe aktarılabilir - Ayarlara Bakın", + "", + " Ayarlar <\/b>", + "Basınç için başlangıç eksenini ayarlayın", + "Yeşil çekirdek veya kavrulmuş çekirdek içe aktarın", + "", + "Veri bozulması tespiti<\/b>", + "Beanconqueror şimdi bir şekilde bir veri bozulması olup olmadığını kontrol ediyor ve evet ise, bir yedeği içe aktarmayı mümkün kılmak için size bir açılır pencere gösteriyor", + "", + "Diğerleri<\/b>", + "Hazırlık araçlarının sıralamasının düzeltilmesi", + "Yuvarlama sorunlarını gidermek için dondurulmuş çekirdek hesaplamasının düzeltilmesi", + "Koddaki bazı teknik değişiklikler", + "Küçük değişiklikler" + ] + } + } } \ No newline at end of file diff --git a/src/assets/i18n/zh.json b/src/assets/i18n/zh.json index b7c5bd637..dd248ec42 100644 --- a/src/assets/i18n/zh.json +++ b/src/assets/i18n/zh.json @@ -1030,7 +1030,8 @@ "TYPE": { "NONE": "None", "XENIA": "Xenia", - "METICULOUS": "Meticulous" + "METICULOUS": "Meticulous", + "SANREMO_YOU": "" }, "URL": "Url", "CHOOSE_DEVICE": "选择设备", @@ -1063,6 +1064,19 @@ "CHOOSE_PROFILE": "选择配置文件", "SHOT_STARTED": "萃取开始", "SHOT_ENDED": "萃取结束" + }, + "TYPE_SANREMO_YOU": { + "TITLE": "", + "STOP_AT_WEIGHT": "", + "SELECT_MODE": "", + "MODE_LISTENING": "", + "MODE_CONTROL": "", + "MANUAL_CONTROLLING": "", + "PROFILE_P1_CONTROLLING": "", + "PROFILE_P2_CONTROLLING": "", + "PROFILE_P3_CONTROLLING": "", + "NO_PROFILE_TARGET_WEIGHT_INFORMATION": "", + "NO_MANUAL_TARGET_WEIGHT_INFORMATION": "" } }, "DEVICE_CONNECTION": "设备连接", @@ -1273,39 +1287,6 @@ "SHOW_MINUTES": "显示分钟", "BEANS_UNARCHIVE": "取消归档", "TOAST_BEAN_UNARCHIVED_SUCCESSFULLY": "咖啡豆已经取消归档", - "UPDATE_TEXT_TITLE_TITLE": { - "7.4.0": { - "TITLE": "版本7.4.0:有什么新功能?", - "DESCRIPTION": [ - "豆子<\/b>", - "现在可以将您的咖啡豆冷冻保存,您可以在设置中激活这个功能", - "为咖啡豆添加了最佳和开封日期,您需要激活此参数", - "现在可以直接从选择概览中添加豆子", - "空的", - "温度计<\/b>", - "支持 Combustion Inc. 温度计 - 感谢提供设备!", - "支持 Meater 温度计(不支持 Meater 2 或 Meater +) - 感谢 Yannick!", - "空的", - "水分区<\/b>", - "添加Pure Coffee Water", - "添加Empirical水", - "空的", - "准备工具<\/b>", - "整理您的准备工具", - "空的", - "磨豆机<\/b>", - "磨豆机的图片现在在选择概览中显示", - "空的", - "设置<\/b>", - "如果您更改了冲泡或咖啡豆的评分,并且最高评分低于已评分条目的评分,则显示安全提示信息", - "空的", - "其他<\/b>", - "在打开计时器时,恢复到以秒数计时为准", - "代码中的一些技术修改", - "小调整" - ] - } - }, "WATER_TYPE_PURE_COFFEE_WATER": "Pure Coffee Water", "WATER_TYPE_EMPIRICAL_WATER_GLACIAL": "empirical water GLACIAL", "WATER_TYPE_EMPIRICAL_WATER_SPRING": "empirical water SPRING", @@ -1352,5 +1333,61 @@ "BEAN_DATA_FROZEN_NOTE": "冰冻风味叙述", "IGNORE_NEGATIVE_VALUES_DESCRIPTION": "激活此功能后,图表将落后一秒", "IGNORE_ANOMALY_VALUES_DESCRIPTION": "激活此功能后,图表将落后一秒", - "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "保存冲泡时保存机器日志" + "SAVE_LOGFILES_TO_NOTES_FROM_MACHINE": "保存冲泡时保存机器日志", + "IMPORT_SHOT_FROM_METICULOUS": "导入一个冲泡记录", + "BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "所有在excel列表中找到的条目,都被转换成新的豆子,享受美味的冲泡!", + "IMPORT_UNSUCCESSFULLY": "导入不成功", + "BEAN_LIST": "豆子列表", + "IMPORT_ROASTED_BEANS_EXCEL": "通过excel导入已经烘焙好的豆子", + "IMPORT_GREEN_BEANS_EXCEL": "通过excel导入生豆", + "BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE": "看起来所选择的excel文件是损坏的,有错误的数据,错误的结构,或者是错误的选择。因此,没有豆子可以导入。", + "OK": "可以", + "BEAN_SORT_BEAN_AGE": "豆龄", + "GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION": "所有在excel列表中找到的条目,已经转化为新的生豆,祝你烘焙成功!", + "PREPARATION_TYPE_SANREMO_YOU": "", + "PREPARATION_TYPE_XENIA": "Xenia", + "BEAN_POPUP_YOU_DONT_SEE_EVERYTHING_DESCRIPTION": "您正在添加一个包含各种信息的豆子,但是这些参数没有被激活。你想现在激活它们吗?", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS": "定义压力图表的坐标轴", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_DESCRIPTION": "设置坐标轴的起始大小和末端的压力", + "PAGE_SETTINGS_TAB_BLUETOOTH_PRESSURE_GRAPHS_AXIS_RANGE": "压力坐标轴", + "PAGE_SETTINGS_LANGUAGE_DUTCH": "荷兰语", + "DOWNLOAD_IMPORT_EXCEL_TEMPLATES": "下载导入模板", + "SHOW_GRAPH": "显示图表", + "UPDATE_TEXT_TITLE_TITLE": { + "7.5.0": { + "TITLE": "版本7.5.0:更新了什么", + "DESCRIPTION": [ + "新的语言<\/b>", + "荷兰语的支持-感谢Ygg", + "空的", + "冲泡<\/b>", + "现在可以折叠冲泡列表", + "显示是否使用了冷冻豆子", + "豆子的图像现在在选择时可见", + "读取并导入从“Meticulous”冲泡历史", + "当冲煮列表有图表时,它们的条目可以滑动", + "空的", + "豆子<\/b>", + "现在可以折叠豆子列表了", + "豆子现在是按豆龄分类的", + "归档豆子时不再重置排序", + "空的", + "生豆<\/b>", + "生豆现在是可导入的-见设置", + "空的", + "设置<\/b>", + "设置压力的起始轴", + "导入生豆或者烘焙过的豆子", + "空的", + "数据损坏检测<\/b>", + "Beanconqueror现在会检查是否发生了数据损坏,如果是,它会向您显示一个弹出窗口,以便导入备份", + "空的", + "其他<\/b>", + "修复准备工具的排序", + "修复冷冻豆子的计算以解决舍入问题", + "代码中的一些技术修改", + "小调整" + ] + } + } } \ No newline at end of file diff --git a/src/assets/js/matomo.js b/src/assets/js/matomo.js index ec939f985..dcb2a84c2 100644 --- a/src/assets/js/matomo.js +++ b/src/assets/js/matomo.js @@ -394,41 +394,60 @@ if (typeof window.Matomo !== 'object') { av = String(av); if ( av.indexOf('?' + aw + '=') === -1 && - av.indexOf('&' + aw + '=') === -1 + av.indexOf('&' + aw + '=') === -1 && + av.indexOf('#' + aw + '=') === -1 ) { return av; } + var aB = ''; + var aD = av.indexOf('#'); + if (aD !== -1) { + aB = av.substr(aD + 1); + av = av.substr(0, aD); + } var ax = av.indexOf('?'); - if (ax === -1) { - return av; + var au = ''; + var aA = av; + if (ax > -1) { + au = av.substr(ax + 1); + aA = av.substr(0, ax); } - var au = av.substr(ax + 1); - var aB = av.substr(0, ax); + var az = function (aF) { + var aH; + var aG = aF.length - 1; + for (aG; aG >= 0; aG--) { + aH = aF[aG].split('=')[0]; + if (aH === aw) { + aF.splice(aG, 1); + } + } + return aF; + }; if (au) { - var aC = ''; - var aE = au.indexOf('#'); - if (aE !== -1) { - aC = au.substr(aE + 1); - au = au.substr(0, aE); + var aC = az(au.split('&')).join('&'); + if (aC) { + aA += '?' + aC; } - var ay; - var aA = au.split('&'); - var az = aA.length - 1; - for (az; az >= 0; az--) { - ay = aA[az].split('=')[0]; - if (ay === aw) { - aA.splice(az, 1); - } + } + if (aB && aB.indexOf('=') > 0) { + var ay = aB.charAt(0) === '?'; + if (ay) { + aB = aB.substr(1); } - var aD = aA.join('&'); - if (aD) { - aB = aB + '?' + aD; + var aE = az(aB.split('&')).join('&'); + if (aE) { + aA += '#'; + if (ay) { + aA += '?'; + } + aA += aE; } - if (aC) { - aB += '#' + aC; + } else { + if (aB) { + aA += '#' + aB; } } - return aB; + return aA; } function e(aw, av) { var au = '[\\?&#]' + av + '=([^&#]*)'; @@ -1497,27 +1516,27 @@ if (typeof window.Matomo !== 'object') { function U(ct, cn) { var bV = this, bo = 'mtm_consent', - c0 = 'mtm_cookie_consent', - c9 = 'mtm_consent_removed', + c1 = 'mtm_cookie_consent', + da = 'mtm_consent_removed', ch = af(K.domain, X.location.href, O()), - dh = P(ch[0]), + di = P(ch[0]), bZ = p(ch[1]), bA = p(ch[2]), - df = false, + dg = false, cx = 'GET', - dA = cx, + dC = cx, aQ = 'application/x-www-form-urlencoded; charset=UTF-8', cR = aQ, aM = ct || '', bU = '', - dp = '', + dr = '', cD = '', cj = cn || '', bL = '', b0 = '', bf, bu = '', - dw = [ + dy = [ '7z', 'aac', 'apk', @@ -1601,15 +1620,15 @@ if (typeof window.Matomo !== 'object') { 'z', 'zip', ], - aG = [dh], + aG = [di], bM = [], cS = ['.paypal.com'], cy = [], bY = [], bj = [], bW = 500, - dk = true, - c6, + dl = true, + c7, bg, b4, b1, @@ -1624,20 +1643,54 @@ if (typeof window.Matomo !== 'object') { 'utm_medium', ], bT = ['pk_kwd', 'mtm_kwd', 'piwik_kwd', 'matomo_kwd', 'utm_term'], + cV = [ + 'mtm_campaign', + 'matomo_campaign', + 'mtm_cpn', + 'pk_campaign', + 'piwik_campaign', + 'pk_cpn', + 'utm_campaign', + 'mtm_keyword', + 'matomo_kwd', + 'mtm_kwd', + 'pk_keyword', + 'piwik_kwd', + 'pk_kwd', + 'utm_term', + 'mtm_source', + 'pk_source', + 'utm_source', + 'mtm_medium', + 'pk_medium', + 'utm_medium', + 'mtm_content', + 'pk_content', + 'utm_content', + 'mtm_cid', + 'pk_cid', + 'utm_id', + 'mtm_clid', + 'mtm_group', + 'pk_group', + 'mtm_placement', + 'pk_placement', + ], bv = '_pk_', aD = 'pk_vid', ba = 180, - dm, + dp, bC, b5 = false, aR = 'Lax', bx = false, - dd, + de, bp, + dm = true, bI, - c7 = 33955200000, + c8 = 33955200000, cE = 1800000, - dv = 15768000000, + dx = 15768000000, bd = true, bR = false, bs = false, @@ -1649,22 +1702,22 @@ if (typeof window.Matomo !== 'object') { bz = {}, bG = 200, cN = {}, - dq = {}, - dx = {}, + ds = {}, + dz = {}, a3 = {}, co = [], by = false, ck = false, cp = [], cu = false, - cY = false, + cZ = false, ax = false, - dy = false, - da = false, + dA = false, + db = false, aW = false, bn = w(), cT = null, - dn = null, + dq = null, a0, bO, cl = ar, @@ -1673,210 +1726,221 @@ if (typeof window.Matomo !== 'object') { bN = false, cK = 0, bH = ['id', 'ses', 'cvar', 'ref'], - cX = false, + cY = false, bP = null, - c8 = [], + c9 = [], cM = [], aF = Y++, aE = false, - dl = true, - cV = false; + dn = true, + cW = false; try { bu = K.title; } catch (cU) { bu = ''; } - function aL(dL) { - if (bx && dL !== c9) { + function aL(dN) { + if (bx && dN !== da) { return 0; } - var dJ = new RegExp('(^|;)[ ]*' + dL + '=([^;]*)'), - dK = dJ.exec(K.cookie); - return dK ? W(dK[2]) : 0; + var dL = new RegExp('(^|;)[ ]*' + dN + '=([^;]*)'), + dM = dL.exec(K.cookie); + return dM ? W(dM[2]) : 0; } - bP = !aL(c9); - function dE(dN, dO, dR, dQ, dL, dM, dP) { - if (bx && dN !== c9) { + bP = !aL(da); + function dG(dP, dQ, dT, dS, dN, dO, dR) { + if (bx && dP !== da) { return; } - var dK; - if (dR) { - dK = new Date(); - dK.setTime(dK.getTime() + dR); + var dM; + if (dT) { + dM = new Date(); + dM.setTime(dM.getTime() + dT); } - if (!dP) { - dP = 'Lax'; + if (!dR) { + dR = 'Lax'; } K.cookie = - dN + + dP + '=' + - u(dO) + - (dR ? ';expires=' + dK.toGMTString() : '') + + u(dQ) + + (dT ? ';expires=' + dM.toGMTString() : '') + ';path=' + - (dQ || '/') + - (dL ? ';domain=' + dL : '') + - (dM ? ';secure' : '') + + (dS || '/') + + (dN ? ';domain=' + dN : '') + + (dO ? ';secure' : '') + ';SameSite=' + - dP; - if ((!dR || dR >= 0) && aL(dN) !== String(dO)) { - var dJ = + dR; + if ((!dT || dT >= 0) && aL(dP) !== String(dQ)) { + var dL = 'There was an error setting cookie `' + - dN + + dP + '`. Please check domain and path.'; - ap(dJ); + ap(dL); } } - function cf(dJ) { - var dL, dK; - dJ = j(dJ, aD); - dJ = j(dJ, 'ignore_referrer'); - dJ = j(dJ, 'ignore_referer'); - for (dK = 0; dK < cy.length; dK++) { - dJ = j(dJ, cy[dK]); + function cf(dL) { + var dN, dM; + if (dm !== true && !cY) { + for (dM = 0; dM < cH.length; dM++) { + dL = j(dL, cH[dM]); + } + for (dM = 0; dM < bT.length; dM++) { + dL = j(dL, bT[dM]); + } + for (dM = 0; dM < cV.length; dM++) { + dL = j(dL, cV[dM]); + } + } + dL = j(dL, aD); + dL = j(dL, 'ignore_referrer'); + dL = j(dL, 'ignore_referer'); + for (dM = 0; dM < cy.length; dM++) { + dL = j(dL, cy[dM]); } if (b1) { - dL = new RegExp('#.*'); - return dJ.replace(dL, ''); + dN = new RegExp('#.*'); + return dL.replace(dN, ''); } - return dJ; + return dL; } - function b8(dL, dJ) { - var dM = t(dJ), - dK; - if (dM) { - return dJ; + function b8(dN, dL) { + var dO = t(dL), + dM; + if (dO) { + return dL; } - if (dJ.slice(0, 1) === '/') { - return t(dL) + '://' + d(dL) + dJ; + if (dL.slice(0, 1) === '/') { + return t(dN) + '://' + d(dN) + dL; } - dL = cf(dL); - dK = dL.indexOf('?'); - if (dK >= 0) { - dL = dL.slice(0, dK); + dN = cf(dN); + dM = dN.indexOf('?'); + if (dM >= 0) { + dN = dN.slice(0, dM); } - dK = dL.lastIndexOf('/'); - if (dK !== dL.length - 1) { - dL = dL.slice(0, dK + 1); + dM = dN.lastIndexOf('/'); + if (dM !== dN.length - 1) { + dN = dN.slice(0, dM + 1); } - return dL + dJ; + return dN + dL; } - function c4(dL, dJ) { - var dK; + function c5(dN, dL) { + var dM; + dN = String(dN).toLowerCase(); dL = String(dL).toLowerCase(); - dJ = String(dJ).toLowerCase(); - if (dL === dJ) { + if (dN === dL) { return true; } - if (dJ.slice(0, 1) === '.') { - if (dL === dJ.slice(1)) { + if (dL.slice(0, 1) === '.') { + if (dN === dL.slice(1)) { return true; } - dK = dL.length - dJ.length; - if (dK > 0 && dL.slice(dK) === dJ) { + dM = dN.length - dL.length; + if (dM > 0 && dN.slice(dM) === dL) { return true; } } return false; } - function cB(dJ) { - var dK = document.createElement('a'); - if (dJ.indexOf('//') !== 0 && dJ.indexOf('http') !== 0) { - if (dJ.indexOf('*') === 0) { - dJ = dJ.substr(1); + function cB(dL) { + var dM = document.createElement('a'); + if (dL.indexOf('//') !== 0 && dL.indexOf('http') !== 0) { + if (dL.indexOf('*') === 0) { + dL = dL.substr(1); } - if (dJ.indexOf('.') === 0) { - dJ = dJ.substr(1); + if (dL.indexOf('.') === 0) { + dL = dL.substr(1); } - dJ = 'http://' + dJ; + dL = 'http://' + dL; } - dK.href = x.toAbsoluteUrl(dJ); - if (dK.pathname) { - return dK.pathname; + dM.href = x.toAbsoluteUrl(dL); + if (dM.pathname) { + return dM.pathname; } return ''; } - function be(dK, dJ) { - if (!ao(dJ, '/')) { - dJ = '/' + dJ; + function be(dM, dL) { + if (!ao(dL, '/')) { + dL = '/' + dL; } - if (!ao(dK, '/')) { - dK = '/' + dK; + if (!ao(dM, '/')) { + dM = '/' + dM; } - var dL = dJ === '/' || dJ === '/*'; - if (dL) { + var dN = dL === '/' || dL === '/*'; + if (dN) { return true; } - if (dK === dJ) { + if (dM === dL) { return true; } - dJ = String(dJ).toLowerCase(); - dK = String(dK).toLowerCase(); - if (V(dJ, '*')) { - dJ = dJ.slice(0, -1); - dL = !dJ || dJ === '/'; - if (dL) { + dL = String(dL).toLowerCase(); + dM = String(dM).toLowerCase(); + if (V(dL, '*')) { + dL = dL.slice(0, -1); + dN = !dL || dL === '/'; + if (dN) { return true; } - if (dK === dJ) { + if (dM === dL) { return true; } - return dK.indexOf(dJ) === 0; + return dM.indexOf(dL) === 0; } - if (!V(dK, '/')) { - dK += '/'; + if (!V(dM, '/')) { + dM += '/'; } - if (!V(dJ, '/')) { - dJ += '/'; + if (!V(dL, '/')) { + dL += '/'; } - return dK.indexOf(dJ) === 0; + return dM.indexOf(dL) === 0; } - function aA(dN, dP) { - var dK, dJ, dL, dM, dO; - for (dK = 0; dK < aG.length; dK++) { - dM = P(aG[dK]); - dO = cB(aG[dK]); - if (c4(dN, dM) && be(dP, dO)) { + function aA(dP, dR) { + var dM, dL, dN, dO, dQ; + for (dM = 0; dM < aG.length; dM++) { + dO = P(aG[dM]); + dQ = cB(aG[dM]); + if (c5(dP, dO) && be(dR, dQ)) { return true; } } return false; } - function a6(dM) { - var dK, dJ, dL; - for (dK = 0; dK < aG.length; dK++) { - dJ = P(aG[dK].toLowerCase()); - if (dM === dJ) { + function a6(dO) { + var dM, dL, dN; + for (dM = 0; dM < aG.length; dM++) { + dL = P(aG[dM].toLowerCase()); + if (dO === dL) { return true; } - if (dJ.slice(0, 1) === '.') { - if (dM === dJ.slice(1)) { + if (dL.slice(0, 1) === '.') { + if (dO === dL.slice(1)) { return true; } - dL = dM.length - dJ.length; - if (dL > 0 && dM.slice(dL) === dJ) { + dN = dO.length - dL.length; + if (dN > 0 && dO.slice(dN) === dL) { return true; } } } return false; } - function cJ(dJ) { - var dK, dM, dO, dL, dN; - if (!dJ.length || !cS.length) { + function cJ(dL) { + var dM, dO, dQ, dN, dP; + if (!dL.length || !cS.length) { return false; } - dM = d(dJ); - dO = cB(dJ); - if (dM.indexOf('www.') === 0) { - dM = dM.substr(4); + dO = d(dL); + dQ = cB(dL); + if (dO.indexOf('www.') === 0) { + dO = dO.substr(4); } - for (dK = 0; dK < cS.length; dK++) { - dL = P(cS[dK]); - dN = cB(cS[dK]); - if (dL.indexOf('www.') === 0) { - dL = dL.substr(4); + for (dM = 0; dM < cS.length; dM++) { + dN = P(cS[dM]); + dP = cB(cS[dM]); + if (dN.indexOf('www.') === 0) { + dN = dN.substr(4); } - if (c4(dM, dL) && be(dO, dN)) { + if (c5(dO, dN) && be(dQ, dP)) { return true; } } @@ -1894,28 +1958,28 @@ if (typeof window.Matomo !== 'object') { X.close(); } } - function cF(dJ, dL) { - dJ = dJ.replace('send_image=0', 'send_image=1'); - var dK = new Image(1, 1); - dK.onload = function () { + function cF(dL, dN) { + dL = dL.replace('send_image=0', 'send_image=1'); + var dM = new Image(1, 1); + dM.onload = function () { I = 0; - if (typeof dL === 'function') { - dL({ request: dJ, trackerUrl: aM, success: true }); + if (typeof dN === 'function') { + dN({ request: dL, trackerUrl: aM, success: true }); } }; - dK.onerror = function () { - if (typeof dL === 'function') { - dL({ request: dJ, trackerUrl: aM, success: false }); + dM.onerror = function () { + if (typeof dN === 'function') { + dN({ request: dL, trackerUrl: aM, success: false }); } }; - dK.src = aM + (aM.indexOf('?') < 0 ? '?' : '&') + dJ; + dM.src = aM + (aM.indexOf('?') < 0 ? '?' : '&') + dL; cI(); } - function c1(dJ) { - if (dA === 'POST') { + function c2(dL) { + if (dC === 'POST') { return true; } - return dJ && (dJ.length > 2000 || dJ.indexOf('{"requests"') === 0); + return dL && (dL.length > 2000 || dL.indexOf('{"requests"') === 0); } function aT() { return ( @@ -1924,66 +1988,66 @@ if (typeof window.Matomo !== 'object') { 'function' === typeof Blob ); } - function bh(dN, dQ, dP) { - var dL = aT(); - if (!dL) { + function bh(dP, dS, dR) { + var dN = aT(); + if (!dN) { return false; } - var dM = { type: 'application/x-www-form-urlencoded; charset=UTF-8' }; - var dR = false; - var dK = aM; + var dO = { type: 'application/x-www-form-urlencoded; charset=UTF-8' }; + var dT = false; + var dM = aM; try { - var dJ = new Blob([dN], dM); - if (dP && !c1(dN)) { - dJ = new Blob([], dM); - dK = dK + (dK.indexOf('?') < 0 ? '?' : '&') + dN; + var dL = new Blob([dP], dO); + if (dR && !c2(dP)) { + dL = new Blob([], dO); + dM = dM + (dM.indexOf('?') < 0 ? '?' : '&') + dP; } - dR = g.sendBeacon(dK, dJ); - } catch (dO) { + dT = g.sendBeacon(dM, dL); + } catch (dQ) { return false; } - if (dR && typeof dQ === 'function') { - dQ({ - request: dN, + if (dT && typeof dS === 'function') { + dS({ + request: dP, trackerUrl: aM, success: true, isSendBeacon: true, }); } cI(); - return dR; + return dT; } - function du(dK, dL, dJ) { - if (!N(dJ) || null === dJ) { - dJ = true; + function dw(dM, dN, dL) { + if (!N(dL) || null === dL) { + dL = true; } - if (m && bh(dK, dL, dJ)) { + if (m && bh(dM, dN, dL)) { return; } setTimeout(function () { - if (m && bh(dK, dL, dJ)) { + if (m && bh(dM, dN, dL)) { return; } - var dO; + var dQ; try { - var dN = X.XMLHttpRequest + var dP = X.XMLHttpRequest ? new X.XMLHttpRequest() : X.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : null; - dN.open('POST', aM, true); - dN.onreadystatechange = function () { + dP.open('POST', aM, true); + dP.onreadystatechange = function () { if ( this.readyState === 4 && !(this.status >= 200 && this.status < 300) ) { - var dP = m && bh(dK, dL, dJ); - if (!dP && dJ) { - cF(dK, dL); + var dR = m && bh(dM, dN, dL); + if (!dR && dL) { + cF(dM, dN); } else { - if (typeof dL === 'function') { - dL({ - request: dK, + if (typeof dN === 'function') { + dN({ + request: dM, trackerUrl: aM, success: false, xhr: this, @@ -1991,49 +2055,49 @@ if (typeof window.Matomo !== 'object') { } } } else { - if (this.readyState === 4 && typeof dL === 'function') { - dL({ request: dK, trackerUrl: aM, success: true, xhr: this }); + if (this.readyState === 4 && typeof dN === 'function') { + dN({ request: dM, trackerUrl: aM, success: true, xhr: this }); } } }; - dN.setRequestHeader('Content-Type', cR); - dN.withCredentials = true; - dN.send(dK); - } catch (dM) { - dO = m && bh(dK, dL, dJ); - if (!dO && dJ) { - cF(dK, dL); + dP.setRequestHeader('Content-Type', cR); + dP.withCredentials = true; + dP.send(dM); + } catch (dO) { + dQ = m && bh(dM, dN, dL); + if (!dQ && dL) { + cF(dM, dN); } else { - if (typeof dL === 'function') { - dL({ request: dK, trackerUrl: aM, success: false }); + if (typeof dN === 'function') { + dN({ request: dM, trackerUrl: aM, success: false }); } } } cI(); }, 50); } - function cv(dK) { - var dJ = new Date(); - var dL = dJ.getTime() + dK; - if (!s || dL > s) { - s = dL; + function cv(dM) { + var dL = new Date(); + var dN = dL.getTime() + dM; + if (!s || dN > s) { + s = dN; } } function bl() { bn = true; cT = new Date().getTime(); } - function dD() { - var dJ = new Date().getTime(); - return !cT || dJ - cT > bg; + function dF() { + var dL = new Date().getTime(); + return !cT || dL - cT > bg; } function aH() { - if (dD()) { + if (dF()) { b4(); } } function a5() { - if (K.visibilityState === 'hidden' && dD()) { + if (K.visibilityState === 'hidden' && dF()) { b4(); } else { if (K.visibilityState === 'visible') { @@ -2041,7 +2105,7 @@ if (typeof window.Matomo !== 'object') { } } } - function dH() { + function dJ() { if (aW || !bg) { return; } @@ -2052,31 +2116,31 @@ if (typeof window.Matomo !== 'object') { ag++; v.addPlugin('HeartBeat' + ag, { unload: function () { - if (aW && dD()) { + if (aW && dF()) { b4(); } }, }); } - function cZ(dN) { - var dK = new Date(); - var dJ = dK.getTime(); - dn = dJ; - if (cY && dJ < cY) { - var dL = cY - dJ; - setTimeout(dN, dL); - cv(dL + 50); - cY += 50; + function c0(dP) { + var dM = new Date(); + var dL = dM.getTime(); + dq = dL; + if (cZ && dL < cZ) { + var dN = cZ - dL; + setTimeout(dP, dN); + cv(dN + 50); + cZ += 50; return; } - if (cY === false) { - var dM = 800; - cY = dJ + dM; + if (cZ === false) { + var dO = 800; + cZ = dL + dO; } - dN(); + dP(); } function aX() { - if (aL(c9)) { + if (aL(da)) { bP = false; } else { if (aL(bo)) { @@ -2084,31 +2148,31 @@ if (typeof window.Matomo !== 'object') { } } } - function b2(dM) { - var dL, - dK = '', - dJ = ''; - for (dL in dx) { - if (Object.prototype.hasOwnProperty.call(dx, dL)) { - dJ += '&' + dL + '=' + dx[dL]; + function b2(dO) { + var dN, + dM = '', + dL = ''; + for (dN in dz) { + if (Object.prototype.hasOwnProperty.call(dz, dN)) { + dL += '&' + dN + '=' + dz[dN]; } } if (a3) { - dK = '&uadata=' + u(X.JSON.stringify(a3)); + dM = '&uadata=' + u(X.JSON.stringify(a3)); } - if (dM instanceof Array) { - for (dL = 0; dL < dM.length; dL++) { - dM[dL] += dK + dJ; + if (dO instanceof Array) { + for (dN = 0; dN < dO.length; dN++) { + dO[dN] += dM + dL; } } else { - dM += dK + dJ; + dO += dM + dL; } - return dM; + return dO; } function av() { return N(g.userAgentData) && D(g.userAgentData.getHighEntropyValues); } - function cG(dJ) { + function cG(dL) { if (by || ck) { return; } @@ -2127,111 +2191,111 @@ if (typeof window.Matomo !== 'object') { 'fullVersionList', ]) .then( - function (dL) { - var dK; - if (dL.fullVersionList) { - delete dL.brands; - delete dL.uaFullVersion; + function (dN) { + var dM; + if (dN.fullVersionList) { + delete dN.brands; + delete dN.uaFullVersion; } - a3 = dL; + a3 = dN; by = true; ck = false; - dJ(); + dL(); }, - function (dK) { + function (dM) { by = true; ck = false; - dJ(); + dL(); } ); } - function bS(dK, dJ, dL) { + function bS(dM, dL, dN) { aX(); if (!bP) { - c8.push([dK, dL]); + c9.push([dM, dN]); return; } - if (dl && !by && av()) { - co.push([dK, dL]); + if (dn && !by && av()) { + co.push([dM, dN]); return; } aE = true; - if (!dd && dK) { - if (cX && bP) { - dK += '&consent=1'; + if (!de && dM) { + if (cY && bP) { + dM += '&consent=1'; } - dK = b2(dK); - cZ(function () { - if (dk && bh(dK, dL, true)) { + dM = b2(dM); + c0(function () { + if (dl && bh(dM, dN, true)) { cv(100); return; } - if (c1(dK)) { - du(dK, dL); + if (c2(dM)) { + dw(dM, dN); } else { - cF(dK, dL); + cF(dM, dN); } - cv(dJ); + cv(dL); }); } if (!aW) { - dH(); + dJ(); } } - function cA(dJ) { - if (dd) { + function cA(dL) { + if (de) { return false; } - return dJ && dJ.length; + return dL && dL.length; } - function dt(dJ, dN) { - if (!dN || dN >= dJ.length) { - return [dJ]; + function dv(dL, dP) { + if (!dP || dP >= dL.length) { + return [dL]; } - var dK = 0; - var dL = dJ.length; - var dM = []; - for (dK; dK < dL; dK += dN) { - dM.push(dJ.slice(dK, dK + dN)); + var dM = 0; + var dN = dL.length; + var dO = []; + for (dM; dM < dN; dM += dP) { + dO.push(dL.slice(dM, dM + dP)); } - return dM; + return dO; } - function dF(dK, dJ) { - if (!cA(dK)) { + function dH(dM, dL) { + if (!cA(dM)) { return; } - if (dl && !by && av()) { - co.push([dK, null]); + if (dn && !by && av()) { + co.push([dM, null]); return; } if (!bP) { - c8.push([dK, null]); + c9.push([dM, null]); return; } aE = true; - cZ(function () { - var dN = dt(dK, 50); - var dL = 0, - dM; - for (dL; dL < dN.length; dL++) { - dM = + c0(function () { + var dP = dv(dM, 50); + var dN = 0, + dO; + for (dN; dN < dP.length; dN++) { + dO = '{"requests":["?' + - b2(dN[dL]).join('","?') + + b2(dP[dN]).join('","?') + '"],"send_image":0}'; - if (dk && bh(dM, null, false)) { + if (dl && bh(dO, null, false)) { cv(100); } else { - du(dM, null, false); + dw(dO, null, false); } } - cv(dJ); + cv(dL); }); } - function a2(dJ) { - return bv + dJ + '.' + cj + '.' + bB; + function a2(dL) { + return bv + dL + '.' + cj + '.' + bB; } - function cc(dL, dK, dJ) { - dE(dL, '', -129600000, dK, dJ); + function cc(dN, dM, dL) { + dG(dN, '', -129600000, dM, dL); } function ci() { if (bx) { @@ -2240,42 +2304,42 @@ if (typeof window.Matomo !== 'object') { if (!N(X.showModalDialog) && N(g.cookieEnabled)) { return g.cookieEnabled ? '1' : '0'; } - var dJ = bv + 'testcookie'; - dE(dJ, '1', undefined, bC, dm, b5, aR); - var dK = aL(dJ) === '1' ? '1' : '0'; - cc(dJ); - return dK; + var dL = bv + 'testcookie'; + dG(dL, '1', undefined, bC, dp, b5, aR); + var dM = aL(dL) === '1' ? '1' : '0'; + cc(dL); + return dM; } function bt() { - bB = cl((dm || dh) + (bC || '/')).slice(0, 4); + bB = cl((dp || di) + (bC || '/')).slice(0, 4); } function ay() { - var dK, dJ; - for (dK = 0; dK < co.length; dK++) { - dJ = typeof co[dK][0]; - if (dJ === 'string') { - bS(co[dK][0], bW, co[dK][1]); + var dM, dL; + for (dM = 0; dM < co.length; dM++) { + dL = typeof co[dM][0]; + if (dL === 'string') { + bS(co[dM][0], bW, co[dM][1]); } else { - if (dJ === 'object') { - dF(co[dK][0], bW); + if (dL === 'object') { + dH(co[dM][0], bW); } } } co = []; } - function c5() { - if (!dl) { + function c6() { + if (!dn) { return {}; } if (av()) { cG(ay); } - if (N(dx.res)) { - return dx; + if (N(dz.res)) { + return dz; } - var dK, - dM, - dN = { + var dM, + dO, + dP = { pdf: 'application/pdf', qt: 'video/quicktime', realp: 'audio/x-pn-realaudio-plugin', @@ -2286,10 +2350,10 @@ if (typeof window.Matomo !== 'object') { }; if (!new RegExp('MSIE').test(g.userAgent)) { if (g.mimeTypes && g.mimeTypes.length) { - for (dK in dN) { - if (Object.prototype.hasOwnProperty.call(dN, dK)) { - dM = g.mimeTypes[dN[dK]]; - dx[dK] = dM && dM.enabledPlugin ? '1' : '0'; + for (dM in dP) { + if (Object.prototype.hasOwnProperty.call(dP, dM)) { + dO = g.mimeTypes[dP[dM]]; + dz[dM] = dO && dO.enabledPlugin ? '1' : '0'; } } } @@ -2299,1005 +2363,1005 @@ if (typeof window.Matomo !== 'object') { N(g.javaEnabled) && g.javaEnabled() ) { - dx.java = '1'; + dz.java = '1'; } if (!N(X.showModalDialog) && N(g.cookieEnabled)) { - dx.cookie = g.cookieEnabled ? '1' : '0'; + dz.cookie = g.cookieEnabled ? '1' : '0'; } else { - dx.cookie = ci(); + dz.cookie = ci(); } } - var dL = parseInt(ac.width, 10); - var dJ = parseInt(ac.height, 10); - dx.res = parseInt(dL, 10) + 'x' + parseInt(dJ, 10); - return dx; + var dN = parseInt(ac.width, 10); + var dL = parseInt(ac.height, 10); + dz.res = parseInt(dN, 10) + 'x' + parseInt(dL, 10); + return dz; } function ca() { - var dK = a2('cvar'), - dJ = aL(dK); - if (dJ && dJ.length) { - dJ = X.JSON.parse(dJ); - if (aa(dJ)) { - return dJ; + var dM = a2('cvar'), + dL = aL(dM); + if (dL && dL.length) { + dL = X.JSON.parse(dL); + if (aa(dL)) { + return dL; } } return {}; } - function c2() { + function c3() { if (aZ === false) { aZ = ca(); } } - function de() { - var dJ = c5(); + function df() { + var dL = c6(); return cl( (g.userAgent || '') + (g.platform || '') + - X.JSON.stringify(dJ) + + X.JSON.stringify(dL) + new Date().getTime() + Math.random() ).slice(0, 16); } function aJ() { - var dJ = c5(); + var dL = c6(); return cl( - (g.userAgent || '') + (g.platform || '') + X.JSON.stringify(dJ) + (g.userAgent || '') + (g.platform || '') + X.JSON.stringify(dL) ).slice(0, 6); } function bq() { return Math.floor(new Date().getTime() / 1000); } function aS() { - var dK = bq(); - var dL = aJ(); - var dJ = String(dK) + dL; - return dJ; + var dM = bq(); + var dN = aJ(); + var dL = String(dM) + dN; + return dL; } - function ds(dL) { - dL = String(dL); - var dO = aJ(); - var dM = dO.length; - var dN = dL.substr(-1 * dM, dM); - var dK = parseInt(dL.substr(0, dL.length - dM), 10); - if (dK && dN && dN === dO) { - var dJ = bq(); + function du(dN) { + dN = String(dN); + var dQ = aJ(); + var dO = dQ.length; + var dP = dN.substr(-1 * dO, dO); + var dM = parseInt(dN.substr(0, dN.length - dO), 10); + if (dM && dP && dP === dQ) { + var dL = bq(); if (ba <= 0) { return true; } - if (dJ >= dK && dJ <= dK + ba) { + if (dL >= dM && dL <= dM + ba) { return true; } } return false; } - function dG(dJ) { - if (!da) { + function dI(dL) { + if (!db) { return ''; } - var dN = e(dJ, aD); - if (!dN) { + var dP = e(dL, aD); + if (!dP) { return ''; } - dN = String(dN); - var dL = new RegExp('^[a-zA-Z0-9]+$'); - if (dN.length === 32 && dL.test(dN)) { - var dK = dN.substr(16, 32); - if (ds(dK)) { - var dM = dN.substr(0, 16); - return dM; + dP = String(dP); + var dN = new RegExp('^[a-zA-Z0-9]+$'); + if (dP.length === 32 && dN.test(dP)) { + var dM = dP.substr(16, 32); + if (du(dM)) { + var dO = dP.substr(0, 16); + return dO; } } return ''; } - function db() { + function dc() { if (!b0) { - b0 = dG(bZ); + b0 = dI(bZ); } - var dL = new Date(), - dJ = Math.round(dL.getTime() / 1000), - dK = a2('id'), - dO = aL(dK), - dN, - dM; - if (dO) { - dN = dO.split('.'); - dN.unshift('0'); + var dN = new Date(), + dL = Math.round(dN.getTime() / 1000), + dM = a2('id'), + dQ = aL(dM), + dP, + dO; + if (dQ) { + dP = dQ.split('.'); + dP.unshift('0'); if (b0.length) { - dN[1] = b0; + dP[1] = b0; } - return dN; + return dP; } if (b0.length) { - dM = b0; + dO = b0; } else { if ('0' === ci()) { - dM = ''; + dO = ''; } else { - dM = de(); + dO = df(); } } - dN = ['1', dM, dJ]; - return dN; + dP = ['1', dO, dL]; + return dP; } function a9() { - var dM = db(), - dK = dM[0], - dL = dM[1], - dJ = dM[2]; - return { newVisitor: dK, uuid: dL, createTs: dJ }; + var dO = dc(), + dM = dO[0], + dN = dO[1], + dL = dO[2]; + return { newVisitor: dM, uuid: dN, createTs: dL }; } function aP() { - var dM = new Date(), - dK = dM.getTime(), - dN = a9().createTs; - var dJ = parseInt(dN, 10); - var dL = dJ * 1000 + c7 - dK; - return dL; + var dO = new Date(), + dM = dO.getTime(), + dP = a9().createTs; + var dL = parseInt(dP, 10); + var dN = dL * 1000 + c8 - dM; + return dN; } - function aV(dJ) { + function aV(dL) { if (!cj) { return; } - var dL = new Date(), - dK = Math.round(dL.getTime() / 1000); - if (!N(dJ)) { - dJ = a9(); + var dN = new Date(), + dM = Math.round(dN.getTime() / 1000); + if (!N(dL)) { + dL = a9(); } - var dM = dJ.uuid + '.' + dJ.createTs + '.'; - dE(a2('id'), dM, aP(), bC, dm, b5, aR); + var dO = dL.uuid + '.' + dL.createTs + '.'; + dG(a2('id'), dO, aP(), bC, dp, b5, aR); } function bX() { - var dJ = aL(a2('ref')); - if (dJ.length) { + var dL = aL(a2('ref')); + if (dL.length) { try { - dJ = X.JSON.parse(dJ); - if (aa(dJ)) { - return dJ; + dL = X.JSON.parse(dL); + if (aa(dL)) { + return dL; } - } catch (dK) {} + } catch (dM) {} } return ['', '', 0, '']; } - function bJ(dL) { - var dK = bv + 'testcookie_domain'; - var dJ = 'testvalue'; - dE(dK, dJ, 10000, null, dL, b5, aR); - if (aL(dK) === dJ) { - cc(dK, null, dL); + function bJ(dN) { + var dM = bv + 'testcookie_domain'; + var dL = 'testvalue'; + dG(dM, dL, 10000, null, dN, b5, aR); + if (aL(dM) === dL) { + cc(dM, null, dN); return true; } return false; } function aN() { - var dK = bx; + var dM = bx; bx = false; - var dJ, dL; - for (dJ = 0; dJ < bH.length; dJ++) { - dL = a2(bH[dJ]); - if (dL !== c9 && dL !== bo && 0 !== aL(dL)) { - cc(dL, bC, dm); + var dL, dN; + for (dL = 0; dL < bH.length; dL++) { + dN = a2(bH[dL]); + if (dN !== da && dN !== bo && 0 !== aL(dN)) { + cc(dN, bC, dp); } } - bx = dK; + bx = dM; } - function cg(dJ) { - cj = dJ; + function cg(dL) { + cj = dL; } - function dI(dN) { - if (!dN || !aa(dN)) { + function dK(dP) { + if (!dP || !aa(dP)) { return; } - var dM = []; - var dL; - for (dL in dN) { - if (Object.prototype.hasOwnProperty.call(dN, dL)) { - dM.push(dL); + var dO = []; + var dN; + for (dN in dP) { + if (Object.prototype.hasOwnProperty.call(dP, dN)) { + dO.push(dN); } } - var dO = {}; - dM.sort(); - var dJ = dM.length; - var dK; - for (dK = 0; dK < dJ; dK++) { - dO[dM[dK]] = dN[dM[dK]]; + var dQ = {}; + dO.sort(); + var dL = dO.length; + var dM; + for (dM = 0; dM < dL; dM++) { + dQ[dO[dM]] = dP[dO[dM]]; } - return dO; + return dQ; } function cs() { - dE(a2('ses'), '1', cE, bC, dm, b5, aR); + dG(a2('ses'), '1', cE, bC, dp, b5, aR); } function br() { - var dM = ''; - var dK = + var dO = ''; + var dM = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - var dL = dK.length; - var dJ; - for (dJ = 0; dJ < 6; dJ++) { - dM += dK.charAt(Math.floor(Math.random() * dL)); + var dN = dM.length; + var dL; + for (dL = 0; dL < 6; dL++) { + dO += dM.charAt(Math.floor(Math.random() * dN)); } - return dM; + return dO; } - function aI(dK) { + function aI(dM) { if (cD !== '') { - dK += cD; + dM += cD; bs = true; - return dK; + return dM; } if (!h) { - return dK; + return dM; } - var dL = + var dN = typeof h.timing === 'object' && h.timing ? h.timing : undefined; - if (!dL) { - dL = + if (!dN) { + dN = typeof h.getEntriesByType === 'function' && h.getEntriesByType('navigation') ? h.getEntriesByType('navigation')[0] : undefined; } - if (!dL) { - return dK; + if (!dN) { + return dM; } - var dJ = ''; - if (dL.connectEnd && dL.fetchStart) { - if (dL.connectEnd < dL.fetchStart) { - return dK; + var dL = ''; + if (dN.connectEnd && dN.fetchStart) { + if (dN.connectEnd < dN.fetchStart) { + return dM; } - dJ += '&pf_net=' + Math.round(dL.connectEnd - dL.fetchStart); + dL += '&pf_net=' + Math.round(dN.connectEnd - dN.fetchStart); } - if (dL.responseStart && dL.requestStart) { - if (dL.responseStart < dL.requestStart) { - return dK; + if (dN.responseStart && dN.requestStart) { + if (dN.responseStart < dN.requestStart) { + return dM; } - dJ += '&pf_srv=' + Math.round(dL.responseStart - dL.requestStart); + dL += '&pf_srv=' + Math.round(dN.responseStart - dN.requestStart); } - if (dL.responseStart && dL.responseEnd) { - if (dL.responseEnd < dL.responseStart) { - return dK; + if (dN.responseStart && dN.responseEnd) { + if (dN.responseEnd < dN.responseStart) { + return dM; } - dJ += '&pf_tfr=' + Math.round(dL.responseEnd - dL.responseStart); + dL += '&pf_tfr=' + Math.round(dN.responseEnd - dN.responseStart); } - if (N(dL.domLoading)) { - if (dL.domInteractive && dL.domLoading) { - if (dL.domInteractive < dL.domLoading) { - return dK; + if (N(dN.domLoading)) { + if (dN.domInteractive && dN.domLoading) { + if (dN.domInteractive < dN.domLoading) { + return dM; } - dJ += '&pf_dm1=' + Math.round(dL.domInteractive - dL.domLoading); + dL += '&pf_dm1=' + Math.round(dN.domInteractive - dN.domLoading); } } else { - if (dL.domInteractive && dL.responseEnd) { - if (dL.domInteractive < dL.responseEnd) { - return dK; + if (dN.domInteractive && dN.responseEnd) { + if (dN.domInteractive < dN.responseEnd) { + return dM; } - dJ += '&pf_dm1=' + Math.round(dL.domInteractive - dL.responseEnd); + dL += '&pf_dm1=' + Math.round(dN.domInteractive - dN.responseEnd); } } - if (dL.domComplete && dL.domInteractive) { - if (dL.domComplete < dL.domInteractive) { - return dK; + if (dN.domComplete && dN.domInteractive) { + if (dN.domComplete < dN.domInteractive) { + return dM; } - dJ += '&pf_dm2=' + Math.round(dL.domComplete - dL.domInteractive); + dL += '&pf_dm2=' + Math.round(dN.domComplete - dN.domInteractive); } - if (dL.loadEventEnd && dL.loadEventStart) { - if (dL.loadEventEnd < dL.loadEventStart) { - return dK; + if (dN.loadEventEnd && dN.loadEventStart) { + if (dN.loadEventEnd < dN.loadEventStart) { + return dM; } - dJ += '&pf_onl=' + Math.round(dL.loadEventEnd - dL.loadEventStart); + dL += '&pf_onl=' + Math.round(dN.loadEventEnd - dN.loadEventStart); } - return dK + dJ; + return dM + dL; } - function cr(dJ) { + function cr(dL) { return ( - e(dJ, 'ignore_referrer') === '1' || e(dJ, 'ignore_referer') === '1' + e(dL, 'ignore_referrer') === '1' || e(dL, 'ignore_referer') === '1' ); } - function dz() { - var dT, - dM = new Date(), - dN = Math.round(dM.getTime() / 1000), - dY, - dL, - dO = 1024, - dV, - dP, - dK = a2('ses'), - dS = a2('ref'), - dR = aL(dK), - dJ = bX(), - dX = bf || bZ, - dU, - dQ, - dW = {}; - dU = dJ[0]; - dQ = dJ[1]; - dY = dJ[2]; - dL = dJ[3]; - if (!cr(dX) && !dR) { - if (!bI || !dU.length) { - for (dT in cH) { - if (Object.prototype.hasOwnProperty.call(cH, dT)) { - dU = e(dX, cH[dT]); - if (dU.length) { + function dB() { + var dV, + dO = new Date(), + dP = Math.round(dO.getTime() / 1000), + d0, + dN, + dQ = 1024, + dX, + dR, + dM = a2('ses'), + dU = a2('ref'), + dT = aL(dM), + dL = bX(), + dZ = bf || bZ, + dW, + dS, + dY = {}; + dW = dL[0]; + dS = dL[1]; + d0 = dL[2]; + dN = dL[3]; + if (!cr(dZ) && !dT) { + if ((!bI || !dW.length) && (dm || cY)) { + for (dV in cH) { + if (Object.prototype.hasOwnProperty.call(cH, dV)) { + dW = e(dZ, cH[dV]); + if (dW.length) { break; } } } - for (dT in bT) { - if (Object.prototype.hasOwnProperty.call(bT, dT)) { - dQ = e(dX, bT[dT]); - if (dQ.length) { + for (dV in bT) { + if (Object.prototype.hasOwnProperty.call(bT, dV)) { + dS = e(dZ, bT[dV]); + if (dS.length) { break; } } } } - dV = d(bA); - dP = dL.length ? d(dL) : ''; + dX = d(bA); + dR = dN.length ? d(dN) : ''; if ( - dV.length && - !a6(dV) && + dX.length && + !a6(dX) && !cJ(bA) && - (!bI || !dP.length || a6(dP) || cJ(dL)) + (!bI || !dR.length || a6(dR) || cJ(dN)) ) { - dL = bA; + dN = bA; } - if (dL.length || dU.length) { - dY = dN; - dJ = [dU, dQ, dY, cf(dL.slice(0, dO))]; - dE(dS, X.JSON.stringify(dJ), dv, bC, dm, b5, aR); + if (dN.length || dW.length) { + d0 = dP; + dL = [dW, dS, d0, cf(dN.slice(0, dQ))]; + dG(dU, X.JSON.stringify(dL), dx, bC, dp, b5, aR); } } - if (dU.length) { - dW._rcn = u(dU); + if (dW.length) { + dY._rcn = u(dW); } - if (dQ.length) { - dW._rck = u(dQ); + if (dS.length) { + dY._rck = u(dS); } - dW._refts = dY; - if (String(dL).length) { - dW._ref = u(cf(dL.slice(0, dO))); + dY._refts = d0; + if (String(dN).length) { + dY._ref = u(cf(dN.slice(0, dQ))); } - return dW; + return dY; } - function cL(dK, dW, dX) { - var dV, - dJ = new Date(), - dU = aZ, - dQ = a2('cvar'), - dZ = bf || bZ, - dL = cr(dZ); + function cL(dM, dY, dZ) { + var dX, + dL = new Date(), + dW = aZ, + dS = a2('cvar'), + d1 = bf || bZ, + dN = cr(d1); if (bx) { aN(); } - if (dd) { + if (de) { return ''; } - var dY = new RegExp('^file://', 'i'); - if (!cV && (X.location.protocol === 'file:' || dY.test(dZ))) { + var d0 = new RegExp('^file://', 'i'); + if (!cW && (X.location.protocol === 'file:' || d0.test(d1))) { return ''; } - c5(); - var dR = a9(); - var dO = K.characterSet || K.charset; - if (!dO || dO.toLowerCase() === 'utf-8') { - dO = null; + c6(); + var dT = a9(); + var dQ = K.characterSet || K.charset; + if (!dQ || dQ.toLowerCase() === 'utf-8') { + dQ = null; } - dK += + dM += '&idsite=' + cj + '&rec=1&r=' + String(Math.random()).slice(2, 8) + '&h=' + - dJ.getHours() + + dL.getHours() + '&m=' + - dJ.getMinutes() + + dL.getMinutes() + '&s=' + - dJ.getSeconds() + + dL.getSeconds() + '&url=' + - u(cf(dZ)) + - (bA.length && !cJ(bA) && !dL ? '&urlref=' + u(cf(bA)) : '') + + u(cf(d1)) + + (bA.length && !cJ(bA) && !dN ? '&urlref=' + u(cf(bA)) : '') + (ad(bL) ? '&uid=' + u(bL) : '') + '&_id=' + - dR.uuid + + dT.uuid + '&_idn=' + - dR.newVisitor + - (dO ? '&cs=' + u(dO) : '') + + dT.newVisitor + + (dQ ? '&cs=' + u(dQ) : '') + '&send_image=0'; - var dT = dz(); - for (dV in dT) { - if (Object.prototype.hasOwnProperty.call(dT, dV)) { - dK += '&' + dV + '=' + dT[dV]; + var dV = dB(); + for (dX in dV) { + if (Object.prototype.hasOwnProperty.call(dV, dX)) { + dM += '&' + dX + '=' + dV[dX]; } } - var d1 = []; - if (dW) { - for (dV in dW) { + var d3 = []; + if (dY) { + for (dX in dY) { if ( - Object.prototype.hasOwnProperty.call(dW, dV) && - /^dimension\d+$/.test(dV) + Object.prototype.hasOwnProperty.call(dY, dX) && + /^dimension\d+$/.test(dX) ) { - var dM = dV.replace('dimension', ''); - d1.push(parseInt(dM, 10)); - d1.push(String(dM)); - dK += '&' + dV + '=' + u(dW[dV]); - delete dW[dV]; + var dO = dX.replace('dimension', ''); + d3.push(parseInt(dO, 10)); + d3.push(String(dO)); + dM += '&' + dX + '=' + u(dY[dX]); + delete dY[dX]; } } } - if (dW && E(dW)) { - dW = null; + if (dY && E(dY)) { + dY = null; } - for (dV in cN) { - if (Object.prototype.hasOwnProperty.call(cN, dV)) { - dK += '&' + dV + '=' + u(cN[dV]); + for (dX in cN) { + if (Object.prototype.hasOwnProperty.call(cN, dX)) { + dM += '&' + dX + '=' + u(cN[dX]); } } - for (dV in bz) { - if (Object.prototype.hasOwnProperty.call(bz, dV)) { - var dP = -1 === Q(d1, dV); - if (dP) { - dK += '&dimension' + dV + '=' + u(bz[dV]); + for (dX in bz) { + if (Object.prototype.hasOwnProperty.call(bz, dX)) { + var dR = -1 === Q(d3, dX); + if (dR) { + dM += '&dimension' + dX + '=' + u(bz[dX]); } } } - if (dW) { - dK += '&data=' + u(X.JSON.stringify(dW)); + if (dY) { + dM += '&data=' + u(X.JSON.stringify(dY)); } else { if (aw) { - dK += '&data=' + u(X.JSON.stringify(aw)); + dM += '&data=' + u(X.JSON.stringify(aw)); } } - function dN(d2, d3) { - var d4 = X.JSON.stringify(d2); - if (d4.length > 2) { - return '&' + d3 + '=' + u(d4); + function dP(d4, d5) { + var d6 = X.JSON.stringify(d4); + if (d6.length > 2) { + return '&' + d5 + '=' + u(d6); } return ''; } - var d0 = dI(b9); - var dS = dI(cC); - dK += dN(d0, 'cvar'); - dK += dN(dS, 'e_cvar'); + var d2 = dK(b9); + var dU = dK(cC); + dM += dP(d2, 'cvar'); + dM += dP(dU, 'e_cvar'); if (aZ) { - dK += dN(aZ, '_cvar'); - for (dV in dU) { - if (Object.prototype.hasOwnProperty.call(dU, dV)) { - if (aZ[dV][0] === '' || aZ[dV][1] === '') { - delete aZ[dV]; + dM += dP(aZ, '_cvar'); + for (dX in dW) { + if (Object.prototype.hasOwnProperty.call(dW, dX)) { + if (aZ[dX][0] === '' || aZ[dX][1] === '') { + delete aZ[dX]; } } } if (b3) { - dE(dQ, X.JSON.stringify(aZ), cE, bC, dm, b5, aR); + dG(dS, X.JSON.stringify(aZ), cE, bC, dp, b5, aR); } } if (bd && bR && !bs) { - dK = aI(dK); + dM = aI(dM); bs = true; } if (aU) { - dK += '&pv_id=' + aU; + dM += '&pv_id=' + aU; } - aV(dR); + aV(dT); cs(); - dK += ah(dX, { tracker: bV, request: dK }); - if (dp.length) { - dK += '&' + dp; + dM += ah(dZ, { tracker: bV, request: dM }); + if (dr.length) { + dM += '&' + dr; } if (au()) { - dK += '&tracker_install_check=' + q; + dM += '&tracker_install_check=' + q; } if (D(cq)) { - dK = cq(dK); + dM = cq(dM); } - return dK; + return dM; } b4 = function bi() { - var dJ = new Date(); - dJ = dJ.getTime(); - if (!dn) { + var dL = new Date(); + dL = dL.getTime(); + if (!dq) { return false; } - if (dn + bg <= dJ) { + if (dq + bg <= dL) { bV.ping(); return true; } return false; }; - function bD(dM, dL, dQ, dN, dJ, dT) { - var dP = 'idgoal=0', - dK = new Date(), - dR = [], - dS, - dO = String(dM).length; - if (dO) { - dP += '&ec_id=' + u(dM); + function bD(dO, dN, dS, dP, dL, dV) { + var dR = 'idgoal=0', + dM = new Date(), + dT = [], + dU, + dQ = String(dO).length; + if (dQ) { + dR += '&ec_id=' + u(dO); } - dP += '&revenue=' + dL; - if (String(dQ).length) { - dP += '&ec_st=' + dQ; + dR += '&revenue=' + dN; + if (String(dS).length) { + dR += '&ec_st=' + dS; } - if (String(dN).length) { - dP += '&ec_tx=' + dN; + if (String(dP).length) { + dR += '&ec_tx=' + dP; } - if (String(dJ).length) { - dP += '&ec_sh=' + dJ; + if (String(dL).length) { + dR += '&ec_sh=' + dL; } - if (String(dT).length) { - dP += '&ec_dt=' + dT; + if (String(dV).length) { + dR += '&ec_dt=' + dV; } - if (dq) { - for (dS in dq) { - if (Object.prototype.hasOwnProperty.call(dq, dS)) { - if (!N(dq[dS][1])) { - dq[dS][1] = ''; + if (ds) { + for (dU in ds) { + if (Object.prototype.hasOwnProperty.call(ds, dU)) { + if (!N(ds[dU][1])) { + ds[dU][1] = ''; } - if (!N(dq[dS][2])) { - dq[dS][2] = ''; + if (!N(ds[dU][2])) { + ds[dU][2] = ''; } - if (!N(dq[dS][3]) || String(dq[dS][3]).length === 0) { - dq[dS][3] = 0; + if (!N(ds[dU][3]) || String(ds[dU][3]).length === 0) { + ds[dU][3] = 0; } - if (!N(dq[dS][4]) || String(dq[dS][4]).length === 0) { - dq[dS][4] = 1; + if (!N(ds[dU][4]) || String(ds[dU][4]).length === 0) { + ds[dU][4] = 1; } - dR.push(dq[dS]); + dT.push(ds[dU]); } } - dP += '&ec_items=' + u(X.JSON.stringify(dR)); + dR += '&ec_items=' + u(X.JSON.stringify(dT)); } - dP = cL(dP, aw, 'ecommerce'); - bS(dP, bW); - if (dO) { - dq = {}; + dR = cL(dR, aw, 'ecommerce'); + bS(dR, bW); + if (dQ) { + ds = {}; } } - function cb(dJ, dN, dM, dL, dK, dO) { - if (String(dJ).length && N(dN)) { - bD(dJ, dN, dM, dL, dK, dO); + function cb(dL, dP, dO, dN, dM, dQ) { + if (String(dL).length && N(dP)) { + bD(dL, dP, dO, dN, dM, dQ); } } - function bF(dJ) { - if (N(dJ)) { - bD('', dJ, '', '', '', ''); + function bF(dL) { + if (N(dL)) { + bD('', dL, '', '', '', ''); } } - function cd(dK, dM, dL) { + function cd(dM, dO, dN) { if (!bN) { aU = br(); } - var dJ = cL('action_name=' + u(aq(dK || bu)), dM, 'log'); + var dL = cL('action_name=' + u(aq(dM || bu)), dO, 'log'); if (bd && !bs) { - dJ = aI(dJ); + dL = aI(dL); } - bS(dJ, bW, dL); + bS(dL, bW, dN); } - function bb(dL, dK) { - var dM, - dJ = '(^| )(piwik[_-]' + dK + '|matomo[_-]' + dK; - if (dL) { - for (dM = 0; dM < dL.length; dM++) { - dJ += '|' + dL[dM]; + function bb(dN, dM) { + var dO, + dL = '(^| )(piwik[_-]' + dM + '|matomo[_-]' + dM; + if (dN) { + for (dO = 0; dO < dN.length; dO++) { + dL += '|' + dN[dO]; } } - dJ += ')( |$)'; - return new RegExp(dJ); + dL += ')( |$)'; + return new RegExp(dL); } - function a4(dJ) { - return aM && dJ && 0 === String(dJ).indexOf(aM); + function a4(dL) { + return aM && dL && 0 === String(dL).indexOf(aM); } - function cP(dN, dJ, dO, dK) { - if (a4(dJ)) { + function cP(dP, dL, dQ, dM) { + if (a4(dL)) { return 0; } - var dM = bb(bY, 'download'), - dL = bb(bj, 'link'), - dP = new RegExp('\\.(' + dw.join('|') + ')([?&#]|$)', 'i'); - if (dL.test(dN)) { + var dO = bb(bY, 'download'), + dN = bb(bj, 'link'), + dR = new RegExp('\\.(' + dy.join('|') + ')([?&#]|$)', 'i'); + if (dN.test(dP)) { return 'link'; } - if (dK || dM.test(dN) || dP.test(dJ)) { + if (dM || dO.test(dP) || dR.test(dL)) { return 'download'; } - if (dO) { + if (dQ) { return 0; } return 'link'; } - function aC(dK) { - var dJ; - dJ = dK.parentNode; - while (dJ !== null && N(dJ)) { - if (aj.isLinkElement(dK)) { + function aC(dM) { + var dL; + dL = dM.parentNode; + while (dL !== null && N(dL)) { + if (aj.isLinkElement(dM)) { break; } - dK = dJ; - dJ = dK.parentNode; + dM = dL; + dL = dM.parentNode; } - return dK; + return dM; } - function dC(dO) { - dO = aC(dO); - if (!aj.hasNodeAttribute(dO, 'href')) { + function dE(dQ) { + dQ = aC(dQ); + if (!aj.hasNodeAttribute(dQ, 'href')) { return; } - if (!N(dO.href)) { + if (!N(dQ.href)) { return; } - var dN = aj.getAttributeValueFromNode(dO, 'href'); - var dK = dO.pathname || cB(dO.href); - var dP = dO.hostname || d(dO.href); - var dQ = dP.toLowerCase(); - var dL = dO.href.replace(dP, dQ); - var dM = new RegExp( + var dP = aj.getAttributeValueFromNode(dQ, 'href'); + var dM = dQ.pathname || cB(dQ.href); + var dR = dQ.hostname || d(dQ.href); + var dS = dR.toLowerCase(); + var dN = dQ.href.replace(dR, dS); + var dO = new RegExp( '^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto|tel):', 'i' ); - if (!dM.test(dL)) { - var dJ = cP( - dO.className, - dL, - aA(dQ, dK), - aj.hasNodeAttribute(dO, 'download') + if (!dO.test(dN)) { + var dL = cP( + dQ.className, + dN, + aA(dS, dM), + aj.hasNodeAttribute(dQ, 'download') ); - if (dJ) { - return { type: dJ, href: dL }; + if (dL) { + return { type: dL, href: dN }; } } } - function aY(dJ, dK, dL, dM) { - var dN = x.buildInteractionRequestParams(dJ, dK, dL, dM); - if (!dN) { + function aY(dL, dM, dN, dO) { + var dP = x.buildInteractionRequestParams(dL, dM, dN, dO); + if (!dP) { return; } - return cL(dN, null, 'contentInteraction'); + return cL(dP, null, 'contentInteraction'); } - function bm(dJ, dK) { - if (!dJ || !dK) { + function bm(dL, dM) { + if (!dL || !dM) { return false; } - var dL = x.findTargetNode(dJ); - if (x.shouldIgnoreInteraction(dL)) { + var dN = x.findTargetNode(dL); + if (x.shouldIgnoreInteraction(dN)) { return false; } - dL = x.findTargetNodeNoDefault(dJ); - if (dL && !Z(dL, dK)) { + dN = x.findTargetNodeNoDefault(dL); + if (dN && !Z(dN, dM)) { return false; } return true; } - function cO(dL, dK, dN) { - if (!dL) { + function cO(dN, dM, dP) { + if (!dN) { return; } - var dJ = x.findParentContentNode(dL); - if (!dJ) { + var dL = x.findParentContentNode(dN); + if (!dL) { return; } - if (!bm(dJ, dL)) { + if (!bm(dL, dN)) { return; } - var dM = x.buildContentBlock(dJ); - if (!dM) { + var dO = x.buildContentBlock(dL); + if (!dO) { return; } - if (!dM.target && dN) { - dM.target = dN; + if (!dO.target && dP) { + dO.target = dP; } return x.buildInteractionRequestParams( - dK, - dM.name, - dM.piece, - dM.target + dM, + dO.name, + dO.piece, + dO.target ); } - function a7(dK) { + function a7(dM) { if (!cp || !cp.length) { return false; } - var dJ, dL; - for (dJ = 0; dJ < cp.length; dJ++) { - dL = cp[dJ]; + var dL, dN; + for (dL = 0; dL < cp.length; dL++) { + dN = cp[dL]; if ( - dL && - dL.name === dK.name && - dL.piece === dK.piece && - dL.target === dK.target + dN && + dN.name === dM.name && + dN.piece === dM.piece && + dN.target === dM.target ) { return true; } } return false; } - function a8(dJ) { - return function (dN) { - if (!dJ) { + function a8(dL) { + return function (dP) { + if (!dL) { return; } - var dL = x.findParentContentNode(dJ); - var dK; - if (dN) { - dK = dN.target || dN.srcElement; + var dN = x.findParentContentNode(dL); + var dM; + if (dP) { + dM = dP.target || dP.srcElement; } - if (!dK) { - dK = dJ; + if (!dM) { + dM = dL; } - if (!bm(dL, dK)) { + if (!bm(dN, dM)) { return; } - if (!dL) { + if (!dN) { return false; } - var dO = x.findTargetNode(dL); - if (!dO || x.shouldIgnoreInteraction(dO)) { + var dQ = x.findTargetNode(dN); + if (!dQ || x.shouldIgnoreInteraction(dQ)) { return false; } - var dM = dC(dO); - if (dy && dM && dM.type) { - return dM.type; + var dO = dE(dQ); + if (dA && dO && dO.type) { + return dO.type; } - return bV.trackContentInteractionNode(dK, 'click'); + return bV.trackContentInteractionNode(dM, 'click'); }; } - function ce(dL) { - if (!dL || !dL.length) { + function ce(dN) { + if (!dN || !dN.length) { return; } - var dJ, dK; - for (dJ = 0; dJ < dL.length; dJ++) { - dK = x.findTargetNode(dL[dJ]); - if (dK && !dK.contentInteractionTrackingSetupDone) { - dK.contentInteractionTrackingSetupDone = true; - at(dK, 'click', a8(dK)); + var dL, dM; + for (dL = 0; dL < dN.length; dL++) { + dM = x.findTargetNode(dN[dL]); + if (dM && !dM.contentInteractionTrackingSetupDone) { + dM.contentInteractionTrackingSetupDone = true; + at(dM, 'click', a8(dM)); } } } - function bK(dL, dM) { - if (!dL || !dL.length) { + function bK(dN, dO) { + if (!dN || !dN.length) { return []; } - var dJ, dK; - for (dJ = 0; dJ < dL.length; dJ++) { - if (a7(dL[dJ])) { - dL.splice(dJ, 1); - dJ--; + var dL, dM; + for (dL = 0; dL < dN.length; dL++) { + if (a7(dN[dL])) { + dN.splice(dL, 1); + dL--; } else { - cp.push(dL[dJ]); + cp.push(dN[dL]); } } - if (!dL || !dL.length) { + if (!dN || !dN.length) { return []; } - ce(dM); - var dN = []; - for (dJ = 0; dJ < dL.length; dJ++) { - dK = cL( + ce(dO); + var dP = []; + for (dL = 0; dL < dN.length; dL++) { + dM = cL( x.buildImpressionRequestParams( - dL[dJ].name, - dL[dJ].piece, - dL[dJ].target + dN[dL].name, + dN[dL].piece, + dN[dL].target ), undefined, 'contentImpressions' ); - if (dK) { - dN.push(dK); + if (dM) { + dP.push(dM); } } - return dN; + return dP; } - function cW(dK) { - var dJ = x.collectContent(dK); - return bK(dJ, dK); + function cX(dM) { + var dL = x.collectContent(dM); + return bK(dL, dM); } - function bk(dK) { - if (!dK || !dK.length) { + function bk(dM) { + if (!dM || !dM.length) { return []; } - var dJ; - for (dJ = 0; dJ < dK.length; dJ++) { - if (!x.isNodeVisible(dK[dJ])) { - dK.splice(dJ, 1); - dJ--; + var dL; + for (dL = 0; dL < dM.length; dL++) { + if (!x.isNodeVisible(dM[dL])) { + dM.splice(dL, 1); + dL--; } } - if (!dK || !dK.length) { + if (!dM || !dM.length) { return []; } - return cW(dK); + return cX(dM); } - function aO(dL, dJ, dK) { - var dM = x.buildImpressionRequestParams(dL, dJ, dK); - return cL(dM, null, 'contentImpression'); + function aO(dN, dL, dM) { + var dO = x.buildImpressionRequestParams(dN, dL, dM); + return cL(dO, null, 'contentImpression'); } - function dB(dM, dK) { - if (!dM) { + function dD(dO, dM) { + if (!dO) { return; } - var dJ = x.findParentContentNode(dM); - var dL = x.buildContentBlock(dJ); - if (!dL) { + var dL = x.findParentContentNode(dO); + var dN = x.buildContentBlock(dL); + if (!dN) { return; } - if (!dK) { - dK = 'Unknown'; + if (!dM) { + dM = 'Unknown'; } - return aY(dK, dL.name, dL.piece, dL.target); + return aY(dM, dN.name, dN.piece, dN.target); } - function dc(dK, dM, dJ, dL) { + function dd(dM, dO, dL, dN) { return ( 'e_c=' + - u(dK) + - '&e_a=' + u(dM) + - (N(dJ) ? '&e_n=' + u(dJ) : '') + - (N(dL) ? '&e_v=' + u(dL) : '') + + '&e_a=' + + u(dO) + + (N(dL) ? '&e_n=' + u(dL) : '') + + (N(dN) ? '&e_v=' + u(dN) : '') + '&ca=1' ); } - function aB(dL, dN, dJ, dM, dP, dO) { - if (!ad(dL) || !ad(dN)) { + function aB(dN, dP, dL, dO, dR, dQ) { + if (!ad(dN) || !ad(dP)) { ap( 'Error while logging event: Parameters `category` and `action` must not be empty or filled with whitespaces' ); return false; } - var dK = cL(dc(dL, dN, dJ, dM), dP, 'event'); - bS(dK, bW, dO); + var dM = cL(dd(dN, dP, dL, dO), dR, 'event'); + bS(dM, bW, dQ); } - function cm(dJ, dM, dK, dN) { - var dL = cL( + function cm(dL, dO, dM, dP) { + var dN = cL( 'search=' + - u(dJ) + - (dM ? '&search_cat=' + u(dM) : '') + - (N(dK) ? '&search_count=' + dK : ''), - dN, + u(dL) + + (dO ? '&search_cat=' + u(dO) : '') + + (N(dM) ? '&search_count=' + dM : ''), + dP, 'sitesearch' ); - bS(dL, bW); + bS(dN, bW); } - function dg(dJ, dN, dM, dL) { - var dK = cL('idgoal=' + dJ + (dN ? '&revenue=' + dN : ''), dM, 'goal'); - bS(dK, bW, dL); + function dh(dL, dP, dO, dN) { + var dM = cL('idgoal=' + dL + (dP ? '&revenue=' + dP : ''), dO, 'goal'); + bS(dM, bW, dN); } - function dr(dM, dJ, dQ, dP, dL) { - var dO = dJ + '=' + u(cf(dM)); - var dK = cO(dL, 'click', dM); - if (dK) { - dO += '&' + dK; + function dt(dO, dL, dS, dR, dN) { + var dQ = dL + '=' + u(cf(dO)); + var dM = cO(dN, 'click', dO); + if (dM) { + dQ += '&' + dM; } - var dN = cL(dO, dQ, 'link'); - bS(dN, bW, dP); + var dP = cL(dQ, dS, 'link'); + bS(dP, bW, dR); } - function b7(dK, dJ) { - if (dK !== '') { - return dK + dJ.charAt(0).toUpperCase() + dJ.slice(1); + function b7(dM, dL) { + if (dM !== '') { + return dM + dL.charAt(0).toUpperCase() + dL.slice(1); } - return dJ; + return dL; } - function cw(dO) { - var dN, - dJ, - dM = ['', 'webkit', 'ms', 'moz'], - dL; + function cw(dQ) { + var dP, + dL, + dO = ['', 'webkit', 'ms', 'moz'], + dN; if (!bp) { - for (dJ = 0; dJ < dM.length; dJ++) { - dL = dM[dJ]; - if (Object.prototype.hasOwnProperty.call(K, b7(dL, 'hidden'))) { - if (K[b7(dL, 'visibilityState')] === 'prerender') { - dN = true; + for (dL = 0; dL < dO.length; dL++) { + dN = dO[dL]; + if (Object.prototype.hasOwnProperty.call(K, b7(dN, 'hidden'))) { + if (K[b7(dN, 'visibilityState')] === 'prerender') { + dP = true; } break; } } } - if (dN) { - at(K, dL + 'visibilitychange', function dK() { - K.removeEventListener(dL + 'visibilitychange', dK, false); - dO(); + if (dP) { + at(K, dN + 'visibilitychange', function dM() { + K.removeEventListener(dN + 'visibilitychange', dM, false); + dQ(); }); return; } - dO(); + dQ(); } function bE() { - var dK = bV.getVisitorId(); - var dJ = aS(); - return dK + dJ; + var dM = bV.getVisitorId(); + var dL = aS(); + return dM + dL; } - function cz(dJ) { - if (!dJ) { + function cz(dL) { + if (!dL) { return; } - if (!aj.hasNodeAttribute(dJ, 'href')) { + if (!aj.hasNodeAttribute(dL, 'href')) { return; } - var dK = aj.getAttributeValueFromNode(dJ, 'href'); - if (!dK || a4(dK)) { + var dM = aj.getAttributeValueFromNode(dL, 'href'); + if (!dM || a4(dM)) { return; } if (!bV.getVisitorId()) { return; } - dK = j(dK, aD); - var dL = bE(); - dK = J(dK, aD, dL); - aj.setAnyAttribute(dJ, 'href', dK); + dM = j(dM, aD); + var dN = bE(); + dM = J(dM, aD, dN); + aj.setAnyAttribute(dL, 'href', dM); } - function bw(dM) { - var dN = aj.getAttributeValueFromNode(dM, 'href'); - if (!dN) { + function bw(dO) { + var dP = aj.getAttributeValueFromNode(dO, 'href'); + if (!dP) { return false; } - dN = String(dN); - var dK = - dN.indexOf('//') === 0 || - dN.indexOf('http://') === 0 || - dN.indexOf('https://') === 0; - if (!dK) { + dP = String(dP); + var dM = + dP.indexOf('//') === 0 || + dP.indexOf('http://') === 0 || + dP.indexOf('https://') === 0; + if (!dM) { return false; } - var dJ = dM.pathname || cB(dM.href); - var dL = (dM.hostname || d(dM.href)).toLowerCase(); - if (aA(dL, dJ)) { - if (!c4(dh, P(dL))) { + var dL = dO.pathname || cB(dO.href); + var dN = (dO.hostname || d(dO.href)).toLowerCase(); + if (aA(dN, dL)) { + if (!c5(di, P(dN))) { return true; } return false; } return false; } - function c3(dJ) { - var dK = dC(dJ); - if (dK && dK.type) { - dK.href = p(dK.href); - dr(dK.href, dK.type, undefined, null, dJ); + function c4(dL) { + var dM = dE(dL); + if (dM && dM.type) { + dM.href = p(dM.href); + dt(dM.href, dM.type, undefined, null, dL); return; } - if (da) { - dJ = aC(dJ); - if (bw(dJ)) { - cz(dJ); + if (db) { + dL = aC(dL); + if (bw(dL)) { + cz(dL); } } } function cQ() { return K.all && !K.addEventListener; } - function di(dJ) { - var dL = dJ.which; - var dK = typeof dJ.button; - if (!dL && dK !== 'undefined') { + function dj(dL) { + var dN = dL.which; + var dM = typeof dL.button; + if (!dN && dM !== 'undefined') { if (cQ()) { - if (dJ.button & 1) { - dL = 1; + if (dL.button & 1) { + dN = 1; } else { - if (dJ.button & 2) { - dL = 3; + if (dL.button & 2) { + dN = 3; } else { - if (dJ.button & 4) { - dL = 2; + if (dL.button & 4) { + dN = 2; } } } } else { - if (dJ.button === 0 || dJ.button === '0') { - dL = 1; + if (dL.button === 0 || dL.button === '0') { + dN = 1; } else { - if (dJ.button & 1) { - dL = 2; + if (dL.button & 1) { + dN = 2; } else { - if (dJ.button & 2) { - dL = 3; + if (dL.button & 2) { + dN = 3; } } } } } - return dL; + return dN; } - function b6(dJ) { - switch (di(dJ)) { + function b6(dL) { + switch (dj(dL)) { case 1: return 'left'; case 2: @@ -3306,122 +3370,122 @@ if (typeof window.Matomo !== 'object') { return 'right'; } } - function bc(dJ) { - return dJ.target || dJ.srcElement; + function bc(dL) { + return dL.target || dL.srcElement; } - function dj(dJ) { - return dJ === 'A' || dJ === 'AREA'; + function dk(dL) { + return dL === 'A' || dL === 'AREA'; } - function aK(dJ) { - function dK(dM) { - var dN = bc(dM); - var dO = dN.nodeName; - var dL = bb(bM, 'ignore'); - while (!dj(dO) && dN && dN.parentNode) { - dN = dN.parentNode; - dO = dN.nodeName; + function aK(dL) { + function dM(dO) { + var dP = bc(dO); + var dQ = dP.nodeName; + var dN = bb(bM, 'ignore'); + while (!dk(dQ) && dP && dP.parentNode) { + dP = dP.parentNode; + dQ = dP.nodeName; } - if (dN && dj(dO) && !dL.test(dN.className)) { - return dN; + if (dP && dk(dQ) && !dN.test(dP.className)) { + return dP; } } - return function (dN) { - dN = dN || X.event; - var dO = dK(dN); - if (!dO) { + return function (dP) { + dP = dP || X.event; + var dQ = dM(dP); + if (!dQ) { return; } - var dM = b6(dN); - if (dN.type === 'click') { - var dL = false; - if (dJ && dM === 'middle') { - dL = true; + var dO = b6(dP); + if (dP.type === 'click') { + var dN = false; + if (dL && dO === 'middle') { + dN = true; } - if (dO && !dL) { - c3(dO); + if (dQ && !dN) { + c4(dQ); } } else { - if (dN.type === 'mousedown') { - if (dM === 'middle' && dO) { - a0 = dM; - bO = dO; + if (dP.type === 'mousedown') { + if (dO === 'middle' && dQ) { + a0 = dO; + bO = dQ; } else { a0 = bO = null; } } else { - if (dN.type === 'mouseup') { - if (dM === a0 && dO === bO) { - c3(dO); + if (dP.type === 'mouseup') { + if (dO === a0 && dQ === bO) { + c4(dQ); } a0 = bO = null; } else { - if (dN.type === 'contextmenu') { - c3(dO); + if (dP.type === 'contextmenu') { + c4(dQ); } } } } }; } - function az(dM, dL, dJ) { - var dK = typeof dL; - if (dK === 'undefined') { - dL = true; + function az(dO, dN, dL) { + var dM = typeof dN; + if (dM === 'undefined') { + dN = true; } - at(dM, 'click', aK(dL), dJ); - if (dL) { - at(dM, 'mouseup', aK(dL), dJ); - at(dM, 'mousedown', aK(dL), dJ); - at(dM, 'contextmenu', aK(dL), dJ); + at(dO, 'click', aK(dN), dL); + if (dN) { + at(dO, 'mouseup', aK(dN), dL); + at(dO, 'mousedown', aK(dN), dL); + at(dO, 'contextmenu', aK(dN), dL); } } - function a1(dK, dN, dO) { + function a1(dM, dP, dQ) { if (cu) { return true; } cu = true; - var dP = false; - var dM, dL; - function dJ() { - dP = true; + var dR = false; + var dO, dN; + function dL() { + dR = true; } n(function () { - function dQ(dS) { + function dS(dU) { setTimeout(function () { if (!cu) { return; } - dP = false; - dO.trackVisibleContentImpressions(); - dQ(dS); - }, dS); + dR = false; + dQ.trackVisibleContentImpressions(); + dS(dU); + }, dU); } - function dR(dS) { + function dT(dU) { setTimeout(function () { if (!cu) { return; } - if (dP) { - dP = false; - dO.trackVisibleContentImpressions(); + if (dR) { + dR = false; + dQ.trackVisibleContentImpressions(); } - dR(dS); - }, dS); + dT(dU); + }, dU); } - if (dK) { - dM = ['scroll', 'resize']; - for (dL = 0; dL < dM.length; dL++) { + if (dM) { + dO = ['scroll', 'resize']; + for (dN = 0; dN < dO.length; dN++) { if (K.addEventListener) { - K.addEventListener(dM[dL], dJ, false); + K.addEventListener(dO[dN], dL, false); } else { - X.attachEvent('on' + dM[dL], dJ); + X.attachEvent('on' + dO[dN], dL); } } - dR(100); + dT(100); } - if (dN && dN > 0) { - dN = parseInt(dN, 10); - dQ(dN); + if (dP && dP > 0) { + dP = parseInt(dP, 10); + dS(dP); } }); } @@ -3431,36 +3495,36 @@ if (typeof window.Matomo !== 'object') { timeout: null, interval: 2500, sendRequests: function () { - var dJ = this.requests; + var dL = this.requests; this.requests = []; - if (dJ.length === 1) { - bS(dJ[0], bW); + if (dL.length === 1) { + bS(dL[0], bW); } else { - dF(dJ, bW); + dH(dL, bW); } }, canQueue: function () { return !m && this.enabled; }, - pushMultiple: function (dK) { + pushMultiple: function (dM) { if (!this.canQueue()) { - dF(dK, bW); + dH(dM, bW); return; } - var dJ; - for (dJ = 0; dJ < dK.length; dJ++) { - this.push(dK[dJ]); + var dL; + for (dL = 0; dL < dM.length; dL++) { + this.push(dM[dL]); } }, - push: function (dJ) { - if (!dJ) { + push: function (dL) { + if (!dL) { return; } if (!this.canQueue()) { - bS(dJ, bW); + bS(dL, bW); return; } - bQ.requests.push(dJ); + bQ.requests.push(dL); if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; @@ -3469,9 +3533,9 @@ if (typeof window.Matomo !== 'object') { bQ.timeout = null; bQ.sendRequests(); }, bQ.interval); - var dK = 'RequestQueue' + aF; - if (!Object.prototype.hasOwnProperty.call(b, dK)) { - b[dK] = { + var dM = 'RequestQueue' + aF; + if (!Object.prototype.hasOwnProperty.call(b, dM)) { + b[dM] = { unload: function () { if (bQ.timeout) { clearTimeout(bQ.timeout); @@ -3490,7 +3554,7 @@ if (typeof window.Matomo !== 'object') { if (!aL(a2('id'))) { aV(); } - return db(); + return dc(); }; this.getVisitorId = function () { return this.getVisitorInfo()[1]; @@ -3510,8 +3574,8 @@ if (typeof window.Matomo !== 'object') { this.getAttributionReferrerUrl = function () { return bX()[3]; }; - this.setTrackerUrl = function (dJ) { - aM = dJ; + this.setTrackerUrl = function (dL) { + aM = dL; }; this.getTrackerUrl = function () { return aM; @@ -3522,153 +3586,153 @@ if (typeof window.Matomo !== 'object') { this.getPiwikUrl = function () { return this.getMatomoUrl(); }; - this.addTracker = function (dL, dK) { - if (!N(dL) || null === dL) { - dL = this.getTrackerUrl(); + this.addTracker = function (dN, dM) { + if (!N(dN) || null === dN) { + dN = this.getTrackerUrl(); } - var dJ = new U(dL, dK); - M.push(dJ); + var dL = new U(dN, dM); + M.push(dL); v.trigger('TrackerAdded', [this]); - return dJ; + return dL; }; this.getSiteId = function () { return cj; }; - this.setSiteId = function (dJ) { - cg(dJ); + this.setSiteId = function (dL) { + cg(dL); }; this.resetUserId = function () { bL = ''; }; - this.setUserId = function (dJ) { - if (ad(dJ)) { - bL = dJ; + this.setUserId = function (dL) { + if (ad(dL)) { + bL = dL; } }; - this.setVisitorId = function (dK) { - var dJ = /[0-9A-Fa-f]{16}/g; - if (y(dK) && dJ.test(dK)) { - b0 = dK; + this.setVisitorId = function (dM) { + var dL = /[0-9A-Fa-f]{16}/g; + if (y(dM) && dL.test(dM)) { + b0 = dM; } else { - ap('Invalid visitorId set' + dK); + ap('Invalid visitorId set' + dM); } }; this.getUserId = function () { return bL; }; - this.setCustomData = function (dJ, dK) { - if (aa(dJ)) { - aw = dJ; + this.setCustomData = function (dL, dM) { + if (aa(dL)) { + aw = dL; } else { if (!aw) { aw = {}; } - aw[dJ] = dK; + aw[dL] = dM; } }; this.getCustomData = function () { return aw; }; - this.setCustomRequestProcessing = function (dJ) { - cq = dJ; + this.setCustomRequestProcessing = function (dL) { + cq = dL; }; - this.appendToTrackingUrl = function (dJ) { - dp = dJ; + this.appendToTrackingUrl = function (dL) { + dr = dL; }; - this.getRequest = function (dJ) { - return cL(dJ); + this.getRequest = function (dL) { + return cL(dL); }; - this.addPlugin = function (dJ, dK) { - b[dJ] = dK; + this.addPlugin = function (dL, dM) { + b[dL] = dM; }; - this.setCustomDimension = function (dJ, dK) { - dJ = parseInt(dJ, 10); - if (dJ > 0) { - if (!N(dK)) { - dK = ''; + this.setCustomDimension = function (dL, dM) { + dL = parseInt(dL, 10); + if (dL > 0) { + if (!N(dM)) { + dM = ''; } - if (!y(dK)) { - dK = String(dK); + if (!y(dM)) { + dM = String(dM); } - bz[dJ] = dK; + bz[dL] = dM; } }; - this.getCustomDimension = function (dJ) { - dJ = parseInt(dJ, 10); - if (dJ > 0 && Object.prototype.hasOwnProperty.call(bz, dJ)) { - return bz[dJ]; + this.getCustomDimension = function (dL) { + dL = parseInt(dL, 10); + if (dL > 0 && Object.prototype.hasOwnProperty.call(bz, dL)) { + return bz[dL]; } }; - this.deleteCustomDimension = function (dJ) { - dJ = parseInt(dJ, 10); - if (dJ > 0) { - delete bz[dJ]; + this.deleteCustomDimension = function (dL) { + dL = parseInt(dL, 10); + if (dL > 0) { + delete bz[dL]; } }; - this.setCustomVariable = function (dK, dJ, dN, dL) { - var dM; - if (!N(dL)) { - dL = 'visit'; + this.setCustomVariable = function (dM, dL, dP, dN) { + var dO; + if (!N(dN)) { + dN = 'visit'; } - if (!N(dJ)) { + if (!N(dL)) { return; } - if (!N(dN)) { - dN = ''; + if (!N(dP)) { + dP = ''; } - if (dK > 0) { - dJ = !y(dJ) ? String(dJ) : dJ; - dN = !y(dN) ? String(dN) : dN; - dM = [dJ.slice(0, bG), dN.slice(0, bG)]; - if (dL === 'visit' || dL === 2) { - c2(); - aZ[dK] = dM; + if (dM > 0) { + dL = !y(dL) ? String(dL) : dL; + dP = !y(dP) ? String(dP) : dP; + dO = [dL.slice(0, bG), dP.slice(0, bG)]; + if (dN === 'visit' || dN === 2) { + c3(); + aZ[dM] = dO; } else { - if (dL === 'page' || dL === 3) { - b9[dK] = dM; + if (dN === 'page' || dN === 3) { + b9[dM] = dO; } else { - if (dL === 'event') { - cC[dK] = dM; + if (dN === 'event') { + cC[dM] = dO; } } } } }; - this.getCustomVariable = function (dK, dL) { - var dJ; - if (!N(dL)) { - dL = 'visit'; + this.getCustomVariable = function (dM, dN) { + var dL; + if (!N(dN)) { + dN = 'visit'; } - if (dL === 'page' || dL === 3) { - dJ = b9[dK]; + if (dN === 'page' || dN === 3) { + dL = b9[dM]; } else { - if (dL === 'event') { - dJ = cC[dK]; + if (dN === 'event') { + dL = cC[dM]; } else { - if (dL === 'visit' || dL === 2) { - c2(); - dJ = aZ[dK]; + if (dN === 'visit' || dN === 2) { + c3(); + dL = aZ[dM]; } } } - if (!N(dJ) || (dJ && dJ[0] === '')) { + if (!N(dL) || (dL && dL[0] === '')) { return false; } - return dJ; + return dL; }; - this.deleteCustomVariable = function (dJ, dK) { - if (this.getCustomVariable(dJ, dK)) { - this.setCustomVariable(dJ, '', '', dK); + this.deleteCustomVariable = function (dL, dM) { + if (this.getCustomVariable(dL, dM)) { + this.setCustomVariable(dL, '', '', dM); } }; - this.deleteCustomVariables = function (dJ) { - if (dJ === 'page' || dJ === 3) { + this.deleteCustomVariables = function (dL) { + if (dL === 'page' || dL === 3) { b9 = {}; } else { - if (dJ === 'event') { + if (dL === 'event') { cC = {}; } else { - if (dJ === 'visit' || dJ === 2) { + if (dL === 'visit' || dL === 2) { aZ = {}; } } @@ -3677,113 +3741,113 @@ if (typeof window.Matomo !== 'object') { this.storeCustomVariablesInCookie = function () { b3 = true; }; - this.setLinkTrackingTimer = function (dJ) { - bW = dJ; + this.setLinkTrackingTimer = function (dL) { + bW = dL; }; this.getLinkTrackingTimer = function () { return bW; }; - this.setDownloadExtensions = function (dJ) { - if (y(dJ)) { - dJ = dJ.split('|'); + this.setDownloadExtensions = function (dL) { + if (y(dL)) { + dL = dL.split('|'); } - dw = dJ; + dy = dL; }; - this.addDownloadExtensions = function (dK) { - var dJ; - if (y(dK)) { - dK = dK.split('|'); + this.addDownloadExtensions = function (dM) { + var dL; + if (y(dM)) { + dM = dM.split('|'); } - for (dJ = 0; dJ < dK.length; dJ++) { - dw.push(dK[dJ]); + for (dL = 0; dL < dM.length; dL++) { + dy.push(dM[dL]); } }; - this.removeDownloadExtensions = function (dL) { - var dK, - dJ = []; - if (y(dL)) { - dL = dL.split('|'); + this.removeDownloadExtensions = function (dN) { + var dM, + dL = []; + if (y(dN)) { + dN = dN.split('|'); } - for (dK = 0; dK < dw.length; dK++) { - if (Q(dL, dw[dK]) === -1) { - dJ.push(dw[dK]); + for (dM = 0; dM < dy.length; dM++) { + if (Q(dN, dy[dM]) === -1) { + dL.push(dy[dM]); } } - dw = dJ; + dy = dL; }; - this.setDomains = function (dJ) { - aG = y(dJ) ? [dJ] : dJ; - var dN = false, - dL = 0, - dK; - for (dL; dL < aG.length; dL++) { - dK = String(aG[dL]); - if (c4(dh, P(dK))) { - dN = true; + this.setDomains = function (dL) { + aG = y(dL) ? [dL] : dL; + var dP = false, + dN = 0, + dM; + for (dN; dN < aG.length; dN++) { + dM = String(aG[dN]); + if (c5(di, P(dM))) { + dP = true; break; } - var dM = cB(dK); - if (dM && dM !== '/' && dM !== '/*') { - dN = true; + var dO = cB(dM); + if (dO && dO !== '/' && dO !== '/*') { + dP = true; break; } } - if (!dN) { - aG.push(dh); + if (!dP) { + aG.push(di); } }; - this.setExcludedReferrers = function (dJ) { - cS = y(dJ) ? [dJ] : dJ; + this.setExcludedReferrers = function (dL) { + cS = y(dL) ? [dL] : dL; }; this.enableCrossDomainLinking = function () { - da = true; + db = true; }; this.disableCrossDomainLinking = function () { - da = false; + db = false; }; this.isCrossDomainLinkingEnabled = function () { - return da; + return db; }; - this.setCrossDomainLinkingTimeout = function (dJ) { - ba = dJ; + this.setCrossDomainLinkingTimeout = function (dL) { + ba = dL; }; this.getCrossDomainLinkingUrlParameter = function () { return u(aD) + '=' + u(bE()); }; - this.setIgnoreClasses = function (dJ) { - bM = y(dJ) ? [dJ] : dJ; + this.setIgnoreClasses = function (dL) { + bM = y(dL) ? [dL] : dL; }; - this.setRequestMethod = function (dJ) { - if (dJ) { - dA = String(dJ).toUpperCase(); + this.setRequestMethod = function (dL) { + if (dL) { + dC = String(dL).toUpperCase(); } else { - dA = cx; + dC = cx; } - if (dA === 'GET') { + if (dC === 'GET') { this.disableAlwaysUseSendBeacon(); } }; - this.setRequestContentType = function (dJ) { - cR = dJ || aQ; + this.setRequestContentType = function (dL) { + cR = dL || aQ; }; - this.setGenerationTimeMs = function (dJ) { + this.setGenerationTimeMs = function (dL) { ap( 'setGenerationTimeMs is no longer supported since Matomo 4. The call will be ignored. The replacement is setPagePerformanceTiming.' ); }; - this.setPagePerformanceTiming = function (dN, dP, dO, dK, dQ, dL) { - var dM = { - pf_net: dN, - pf_srv: dP, - pf_tfr: dO, - pf_dm1: dK, - pf_dm2: dQ, - pf_onl: dL, + this.setPagePerformanceTiming = function (dP, dR, dQ, dM, dS, dN) { + var dO = { + pf_net: dP, + pf_srv: dR, + pf_tfr: dQ, + pf_dm1: dM, + pf_dm2: dS, + pf_onl: dN, }; try { - dM = R(dM, N); - dM = C(dM); - cD = l(dM); + dO = R(dO, N); + dO = C(dO); + cD = l(dO); if (cD === '') { ap( 'setPagePerformanceTiming() called without parameters. This function needs to be called with at least one performance parameter.' @@ -3792,137 +3856,137 @@ if (typeof window.Matomo !== 'object') { } bs = false; bR = true; - } catch (dJ) { - ap('setPagePerformanceTiming: ' + dJ.toString()); + } catch (dL) { + ap('setPagePerformanceTiming: ' + dL.toString()); } }; - this.setReferrerUrl = function (dJ) { - bA = dJ; + this.setReferrerUrl = function (dL) { + bA = dL; }; - this.setCustomUrl = function (dJ) { - bf = b8(bZ, dJ); + this.setCustomUrl = function (dL) { + bf = b8(bZ, dL); }; this.getCurrentUrl = function () { return bf || bZ; }; - this.setDocumentTitle = function (dJ) { - bu = dJ; + this.setDocumentTitle = function (dL) { + bu = dL; }; - this.setPageViewId = function (dJ) { - aU = dJ; + this.setPageViewId = function (dL) { + aU = dL; bN = true; }; this.getPageViewId = function () { return aU; }; - this.setAPIUrl = function (dJ) { - bU = dJ; + this.setAPIUrl = function (dL) { + bU = dL; }; - this.setDownloadClasses = function (dJ) { - bY = y(dJ) ? [dJ] : dJ; + this.setDownloadClasses = function (dL) { + bY = y(dL) ? [dL] : dL; }; - this.setLinkClasses = function (dJ) { - bj = y(dJ) ? [dJ] : dJ; + this.setLinkClasses = function (dL) { + bj = y(dL) ? [dL] : dL; }; - this.setCampaignNameKey = function (dJ) { - cH = y(dJ) ? [dJ] : dJ; + this.setCampaignNameKey = function (dL) { + cH = y(dL) ? [dL] : dL; }; - this.setCampaignKeywordKey = function (dJ) { - bT = y(dJ) ? [dJ] : dJ; + this.setCampaignKeywordKey = function (dL) { + bT = y(dL) ? [dL] : dL; }; - this.discardHashTag = function (dJ) { - b1 = dJ; + this.discardHashTag = function (dL) { + b1 = dL; }; - this.setCookieNamePrefix = function (dJ) { - bv = dJ; + this.setCookieNamePrefix = function (dL) { + bv = dL; if (aZ) { aZ = ca(); } }; - this.setCookieDomain = function (dJ) { - var dK = P(dJ); - if (!bx && !bJ(dK)) { - ap("Can't write cookie on domain " + dJ); + this.setCookieDomain = function (dL) { + var dM = P(dL); + if (!bx && !bJ(dM)) { + ap("Can't write cookie on domain " + dL); } else { - dm = dK; + dp = dM; bt(); } }; - this.setExcludedQueryParams = function (dJ) { - cy = y(dJ) ? [dJ] : dJ; + this.setExcludedQueryParams = function (dL) { + cy = y(dL) ? [dL] : dL; }; this.getCookieDomain = function () { - return dm; + return dp; }; this.hasCookies = function () { return '1' === ci(); }; - this.setSessionCookie = function (dL, dK, dJ) { - if (!dL) { + this.setSessionCookie = function (dN, dM, dL) { + if (!dN) { throw new Error('Missing cookie name'); } - if (!N(dJ)) { - dJ = cE; + if (!N(dL)) { + dL = cE; } - bH.push(dL); - dE(a2(dL), dK, dJ, bC, dm, b5, aR); + bH.push(dN); + dG(a2(dN), dM, dL, bC, dp, b5, aR); }; - this.getCookie = function (dK) { - var dJ = aL(a2(dK)); - if (dJ === 0) { + this.getCookie = function (dM) { + var dL = aL(a2(dM)); + if (dL === 0) { return null; } - return dJ; + return dL; }; - this.setCookiePath = function (dJ) { - bC = dJ; + this.setCookiePath = function (dL) { + bC = dL; bt(); }; this.getCookiePath = function () { return bC; }; - this.setVisitorCookieTimeout = function (dJ) { - c7 = dJ * 1000; + this.setVisitorCookieTimeout = function (dL) { + c8 = dL * 1000; }; - this.setSessionCookieTimeout = function (dJ) { - cE = dJ * 1000; + this.setSessionCookieTimeout = function (dL) { + cE = dL * 1000; }; this.getSessionCookieTimeout = function () { return cE; }; - this.setReferralCookieTimeout = function (dJ) { - dv = dJ * 1000; + this.setReferralCookieTimeout = function (dL) { + dx = dL * 1000; }; - this.setConversionAttributionFirstReferrer = function (dJ) { - bI = dJ; + this.setConversionAttributionFirstReferrer = function (dL) { + bI = dL; }; - this.setSecureCookie = function (dJ) { - if (dJ && location.protocol !== 'https:') { + this.setSecureCookie = function (dL) { + if (dL && location.protocol !== 'https:') { ap('Error in setSecureCookie: You cannot use `Secure` on http.'); return; } - b5 = dJ; + b5 = dL; }; - this.setCookieSameSite = function (dJ) { - dJ = String(dJ); - dJ = dJ.charAt(0).toUpperCase() + dJ.toLowerCase().slice(1); - if (dJ !== 'None' && dJ !== 'Lax' && dJ !== 'Strict') { + this.setCookieSameSite = function (dL) { + dL = String(dL); + dL = dL.charAt(0).toUpperCase() + dL.toLowerCase().slice(1); + if (dL !== 'None' && dL !== 'Lax' && dL !== 'Strict') { ap( 'Ignored value for sameSite. Please use either Lax, None, or Strict.' ); return; } - if (dJ === 'None') { + if (dL === 'None') { if (location.protocol === 'https:') { this.setSecureCookie(true); } else { ap( 'sameSite=None cannot be used on http, reverted to sameSite=Lax.' ); - dJ = 'Lax'; + dL = 'Lax'; } } - aR = dJ; + aR = dL; }; this.disableCookies = function () { bx = true; @@ -3934,15 +3998,15 @@ if (typeof window.Matomo !== 'object') { return !bx; }; this.setCookieConsentGiven = function () { - if (bx && !dd) { + if (bx && !de) { bx = false; - if (!dl) { + if (!dn) { this.enableBrowserFeatureDetection(); } if (cj && aE) { aV(); - var dJ = cL('ping=1', null, 'ping'); - bS(dJ, bW); + var dL = cL('ping=1', null, 'ping'); + bS(dL, bW); } } }; @@ -3954,73 +4018,76 @@ if (typeof window.Matomo !== 'object') { return true; }; this.getRememberedCookieConsent = function () { - return aL(c0); + return aL(c1); }; this.forgetCookieConsentGiven = function () { - cc(c0, bC, dm); + cc(c1, bC, dp); this.disableCookies(); }; - this.rememberCookieConsentGiven = function (dK) { - if (dK) { - dK = dK * 60 * 60 * 1000; + this.rememberCookieConsentGiven = function (dM) { + if (dM) { + dM = dM * 60 * 60 * 1000; } else { - dK = 30 * 365 * 24 * 60 * 60 * 1000; + dM = 30 * 365 * 24 * 60 * 60 * 1000; } this.setCookieConsentGiven(); - var dJ = new Date().getTime(); - dE(c0, dJ, dK, bC, dm, b5, aR); + var dL = new Date().getTime(); + dG(c1, dL, dM, bC, dp, b5, aR); }; this.deleteCookies = function () { aN(); }; - this.setDoNotTrack = function (dK) { - var dJ = g.doNotTrack || g.msDoNotTrack; - dd = dK && (dJ === 'yes' || dJ === '1'); - if (dd) { + this.setDoNotTrack = function (dM) { + var dL = g.doNotTrack || g.msDoNotTrack; + de = dM && (dL === 'yes' || dL === '1'); + if (de) { this.disableCookies(); } }; + this.disableCampaignParameters = function () { + dm = false; + }; this.alwaysUseSendBeacon = function () { - dk = true; + dl = true; }; this.disableAlwaysUseSendBeacon = function () { - dk = false; + dl = false; }; - this.addListener = function (dK, dJ) { - az(dK, dJ, false); + this.addListener = function (dM, dL) { + az(dM, dL, false); }; - this.enableLinkTracking = function (dK) { - if (dy) { + this.enableLinkTracking = function (dM) { + if (dA) { return; } - dy = true; - var dJ = this; + dA = true; + var dL = this; r(function () { ax = true; - var dL = K.body; - az(dL, dK, true); + var dN = K.body; + az(dN, dM, true); }); }; this.enableJSErrorTracking = function () { - if (df) { + if (dg) { return; } - df = true; - var dJ = X.onerror; - X.onerror = function (dO, dM, dL, dN, dK) { + dg = true; + var dL = X.onerror; + X.onerror = function (dQ, dO, dN, dP, dM) { cw(function () { - var dP = 'JavaScript Errors'; - var dQ = dM + ':' + dL; - if (dN) { - dQ += ':' + dN; + var dR = 'JavaScript Errors'; + var dS = dO + ':' + dN; + if (dP) { + dS += ':' + dP; } - if (Q(cM, dP + dQ + dO) === -1) { - cM.push(dP + dQ + dO); - aB(dP, dQ, dO); + if (Q(cM, dR + dS + dQ) === -1) { + cM.push(dR + dS + dQ); + aB(dR, dS, dQ); } }); - if (dJ) { - return dJ(dO, dM, dL, dN, dK); + if (dL) { + return dL(dQ, dO, dN, dP, dM); } return false; }; @@ -4028,11 +4095,11 @@ if (typeof window.Matomo !== 'object') { this.disablePerformanceTracking = function () { bd = false; }; - this.enableHeartBeatTimer = function (dJ) { - dJ = Math.max(dJ || 15, 5); - bg = dJ * 1000; - if (dn !== null) { - dH(); + this.enableHeartBeatTimer = function (dL) { + dL = Math.max(dL || 15, 5); + bg = dL * 1000; + if (dq !== null) { + dJ(); } }; this.disableHeartBeatTimer = function () { @@ -4057,30 +4124,30 @@ if (typeof window.Matomo !== 'object') { X.top.location = X.location; } }; - this.redirectFile = function (dJ) { + this.redirectFile = function (dL) { if (X.location.protocol === 'file:') { - X.location = dJ; + X.location = dL; } }; - this.setCountPreRendered = function (dJ) { - bp = dJ; + this.setCountPreRendered = function (dL) { + bp = dL; }; - this.trackGoal = function (dJ, dM, dL, dK) { + this.trackGoal = function (dL, dO, dN, dM) { cw(function () { - dg(dJ, dM, dL, dK); + dh(dL, dO, dN, dM); }); }; - this.trackLink = function (dK, dJ, dM, dL) { + this.trackLink = function (dM, dL, dO, dN) { cw(function () { - dr(dK, dJ, dM, dL); + dt(dM, dL, dO, dN); }); }; this.getNumTrackedPageViews = function () { return cK; }; - this.trackPageView = function (dJ, dL, dK) { + this.trackPageView = function (dL, dN, dM) { cp = []; - c8 = []; + c9 = []; cM = []; if (S(cj)) { cw(function () { @@ -4089,20 +4156,20 @@ if (typeof window.Matomo !== 'object') { } else { cw(function () { cK++; - cd(dJ, dL, dK); + cd(dL, dN, dM); }); } }; this.disableBrowserFeatureDetection = function () { - dl = false; - dx = {}; + dn = false; + dz = {}; if (av()) { ay(); } }; this.enableBrowserFeatureDetection = function () { - dl = true; - c5(); + dn = true; + c6(); }; this.trackAllContentImpressions = function () { if (S(cj)) { @@ -4110,176 +4177,176 @@ if (typeof window.Matomo !== 'object') { } cw(function () { r(function () { - var dJ = x.findContentNodes(); - var dK = cW(dJ); - bQ.pushMultiple(dK); + var dL = x.findContentNodes(); + var dM = cX(dL); + bQ.pushMultiple(dM); }); }); }; - this.trackVisibleContentImpressions = function (dJ, dK) { + this.trackVisibleContentImpressions = function (dL, dM) { if (S(cj)) { return; } - if (!N(dJ)) { - dJ = true; + if (!N(dL)) { + dL = true; } - if (!N(dK)) { - dK = 750; + if (!N(dM)) { + dM = 750; } - a1(dJ, dK, this); + a1(dL, dM, this); cw(function () { n(function () { - var dL = x.findContentNodes(); - var dM = bk(dL); - bQ.pushMultiple(dM); + var dN = x.findContentNodes(); + var dO = bk(dN); + bQ.pushMultiple(dO); }); }); }; - this.trackContentImpression = function (dL, dJ, dK) { + this.trackContentImpression = function (dN, dL, dM) { if (S(cj)) { return; } + dN = a(dN); dL = a(dL); - dJ = a(dJ); - dK = a(dK); - if (!dL) { + dM = a(dM); + if (!dN) { return; } - dJ = dJ || 'Unknown'; + dL = dL || 'Unknown'; cw(function () { - var dM = aO(dL, dJ, dK); - bQ.push(dM); + var dO = aO(dN, dL, dM); + bQ.push(dO); }); }; - this.trackContentImpressionsWithinNode = function (dJ) { - if (S(cj) || !dJ) { + this.trackContentImpressionsWithinNode = function (dL) { + if (S(cj) || !dL) { return; } cw(function () { if (cu) { n(function () { - var dK = x.findContentNodesWithinNode(dJ); - var dL = bk(dK); - bQ.pushMultiple(dL); + var dM = x.findContentNodesWithinNode(dL); + var dN = bk(dM); + bQ.pushMultiple(dN); }); } else { r(function () { - var dK = x.findContentNodesWithinNode(dJ); - var dL = cW(dK); - bQ.pushMultiple(dL); + var dM = x.findContentNodesWithinNode(dL); + var dN = cX(dM); + bQ.pushMultiple(dN); }); } }); }; - this.trackContentInteraction = function (dL, dM, dJ, dK) { + this.trackContentInteraction = function (dN, dO, dL, dM) { if (S(cj)) { return; } + dN = a(dN); + dO = a(dO); dL = a(dL); dM = a(dM); - dJ = a(dJ); - dK = a(dK); - if (!dL || !dM) { + if (!dN || !dO) { return; } - dJ = dJ || 'Unknown'; + dL = dL || 'Unknown'; cw(function () { - var dN = aY(dL, dM, dJ, dK); - if (dN) { - bQ.push(dN); + var dP = aY(dN, dO, dL, dM); + if (dP) { + bQ.push(dP); } }); }; - this.trackContentInteractionNode = function (dL, dK) { - if (S(cj) || !dL) { + this.trackContentInteractionNode = function (dN, dM) { + if (S(cj) || !dN) { return; } - var dJ = null; + var dL = null; cw(function () { - dJ = dB(dL, dK); - if (dJ) { - bQ.push(dJ); + dL = dD(dN, dM); + if (dL) { + bQ.push(dL); } }); - return dJ; + return dL; }; this.logAllContentBlocksOnPage = function () { - var dL = x.findContentNodes(); - var dJ = x.collectContent(dL); - var dK = typeof console; - if (dK !== 'undefined' && console && console.log) { - console.log(dJ); + var dN = x.findContentNodes(); + var dL = x.collectContent(dN); + var dM = typeof console; + if (dM !== 'undefined' && console && console.log) { + console.log(dL); } }; - this.trackEvent = function (dK, dM, dJ, dL, dO, dN) { + this.trackEvent = function (dM, dO, dL, dN, dQ, dP) { cw(function () { - aB(dK, dM, dJ, dL, dO, dN); + aB(dM, dO, dL, dN, dQ, dP); }); }; - this.trackSiteSearch = function (dJ, dL, dK, dM) { + this.trackSiteSearch = function (dL, dN, dM, dO) { cp = []; cw(function () { - cm(dJ, dL, dK, dM); + cm(dL, dN, dM, dO); }); }; - this.setEcommerceView = function (dN, dJ, dL, dK) { + this.setEcommerceView = function (dP, dL, dN, dM) { cN = {}; - if (ad(dL)) { - dL = String(dL); + if (ad(dN)) { + dN = String(dN); } - if (!N(dL) || dL === null || dL === false || !dL.length) { - dL = ''; + if (!N(dN) || dN === null || dN === false || !dN.length) { + dN = ''; } else { - if (dL instanceof Array) { - dL = X.JSON.stringify(dL); + if (dN instanceof Array) { + dN = X.JSON.stringify(dN); } } - var dM = '_pkc'; - cN[dM] = dL; - if (N(dK) && dK !== null && dK !== false && String(dK).length) { - dM = '_pkp'; - cN[dM] = dK; + var dO = '_pkc'; + cN[dO] = dN; + if (N(dM) && dM !== null && dM !== false && String(dM).length) { + dO = '_pkp'; + cN[dO] = dM; } - if (!ad(dN) && !ad(dJ)) { + if (!ad(dP) && !ad(dL)) { return; } - if (ad(dN)) { - dM = '_pks'; - cN[dM] = dN; + if (ad(dP)) { + dO = '_pks'; + cN[dO] = dP; } - if (!ad(dJ)) { - dJ = ''; + if (!ad(dL)) { + dL = ''; } - dM = '_pkn'; - cN[dM] = dJ; + dO = '_pkn'; + cN[dO] = dL; }; this.getEcommerceItems = function () { - return JSON.parse(JSON.stringify(dq)); + return JSON.parse(JSON.stringify(ds)); }; - this.addEcommerceItem = function (dN, dJ, dL, dK, dM) { - if (ad(dN)) { - dq[dN] = [String(dN), dJ, dL, dK, dM]; + this.addEcommerceItem = function (dP, dL, dN, dM, dO) { + if (ad(dP)) { + ds[dP] = [String(dP), dL, dN, dM, dO]; } }; - this.removeEcommerceItem = function (dJ) { - if (ad(dJ)) { - dJ = String(dJ); - delete dq[dJ]; + this.removeEcommerceItem = function (dL) { + if (ad(dL)) { + dL = String(dL); + delete ds[dL]; } }; this.clearEcommerceCart = function () { - dq = {}; + ds = {}; }; - this.trackEcommerceOrder = function (dJ, dN, dM, dL, dK, dO) { - cb(dJ, dN, dM, dL, dK, dO); + this.trackEcommerceOrder = function (dL, dP, dO, dN, dM, dQ) { + cb(dL, dP, dO, dN, dM, dQ); }; - this.trackEcommerceCartUpdate = function (dJ) { - bF(dJ); + this.trackEcommerceCartUpdate = function (dL) { + bF(dL); }; - this.trackRequest = function (dK, dM, dL, dJ) { + this.trackRequest = function (dM, dO, dN, dL) { cw(function () { - var dN = cL(dK, dM, dJ); - bS(dN, bW, dL); + var dP = cL(dM, dO, dL); + bS(dP, bW, dN); }); }; this.ping = function () { @@ -4288,39 +4355,39 @@ if (typeof window.Matomo !== 'object') { this.disableQueueRequest = function () { bQ.enabled = false; }; - this.setRequestQueueInterval = function (dJ) { - if (dJ < 1000) { + this.setRequestQueueInterval = function (dL) { + if (dL < 1000) { throw new Error('Request queue interval needs to be at least 1000ms'); } - bQ.interval = dJ; + bQ.interval = dL; }; - this.queueRequest = function (dK, dJ) { + this.queueRequest = function (dM, dL) { cw(function () { - var dL = dJ ? dK : cL(dK); - bQ.push(dL); + var dN = dL ? dM : cL(dM); + bQ.push(dN); }); }; this.isConsentRequired = function () { - return cX; + return cY; }; this.getRememberedConsent = function () { - var dJ = aL(bo); - if (aL(c9)) { - if (dJ) { - cc(bo, bC, dm); + var dL = aL(bo); + if (aL(da)) { + if (dL) { + cc(bo, bC, dp); } return null; } - if (!dJ || dJ === 0) { + if (!dL || dL === 0) { return null; } - return dJ; + return dL; }; this.hasRememberedConsent = function () { return !!this.getRememberedConsent(); }; this.requireConsent = function () { - cX = true; + cY = true; bP = this.hasRememberedConsent(); if (!bP) { bx = true; @@ -4334,47 +4401,47 @@ if (typeof window.Matomo !== 'object') { }, }; }; - this.setConsentGiven = function (dK) { + this.setConsentGiven = function (dM) { bP = true; - if (!dl) { + if (!dn) { this.enableBrowserFeatureDetection(); } - cc(c9, bC, dm); - var dL, dJ; - for (dL = 0; dL < c8.length; dL++) { - dJ = typeof c8[dL][0]; - if (dJ === 'string') { - bS(c8[dL][0], bW, c8[dL][1]); + cc(da, bC, dp); + var dN, dL; + for (dN = 0; dN < c9.length; dN++) { + dL = typeof c9[dN][0]; + if (dL === 'string') { + bS(c9[dN][0], bW, c9[dN][1]); } else { - if (dJ === 'object') { - dF(c8[dL][0], bW); + if (dL === 'object') { + dH(c9[dN][0], bW); } } } - c8 = []; - if (!N(dK) || dK) { + c9 = []; + if (!N(dM) || dM) { this.setCookieConsentGiven(); } }; - this.rememberConsentGiven = function (dL) { - if (dL) { - dL = dL * 60 * 60 * 1000; + this.rememberConsentGiven = function (dN) { + if (dN) { + dN = dN * 60 * 60 * 1000; } else { - dL = 30 * 365 * 24 * 60 * 60 * 1000; + dN = 30 * 365 * 24 * 60 * 60 * 1000; } - var dJ = true; - this.setConsentGiven(dJ); - var dK = new Date().getTime(); - dE(bo, dK, dL, bC, dm, b5, aR); + var dL = true; + this.setConsentGiven(dL); + var dM = new Date().getTime(); + dG(bo, dM, dN, bC, dp, b5, aR); }; - this.forgetConsentGiven = function (dJ) { - if (dJ) { - dJ = dJ * 60 * 60 * 1000; + this.forgetConsentGiven = function (dL) { + if (dL) { + dL = dL * 60 * 60 * 1000; } else { - dJ = 30 * 365 * 24 * 60 * 60 * 1000; + dL = 30 * 365 * 24 * 60 * 60 * 1000; } - cc(bo, bC, dm); - dE(c9, new Date().getTime(), dJ, bC, dm, b5, aR); + cc(bo, bC, dp); + dG(da, new Date().getTime(), dL, bC, dp, b5, aR); this.forgetCookieConsentGiven(); this.requireConsent(); }; @@ -4386,7 +4453,7 @@ if (typeof window.Matomo !== 'object') { this.setConsentGiven(false); }; this.enableFileTracking = function () { - cV = true; + cW = true; }; n(function () { setTimeout(function () { @@ -4402,7 +4469,7 @@ if (typeof window.Matomo !== 'object') { } if (!aE) { aV(); - dz(); + dB(); } }, }); @@ -4446,6 +4513,7 @@ if (typeof window.Matomo !== 'object') { 'forgetCookieConsentGiven', 'requireCookieConsent', 'disableBrowserFeatureDetection', + 'disableCampaignParameters', 'disableCookies', 'setTrackerUrl', 'setAPIUrl', diff --git a/src/classes/bean/bean.ts b/src/classes/bean/bean.ts index 8c0b28a6d..633b48a3a 100755 --- a/src/classes/bean/bean.ts +++ b/src/classes/bean/bean.ts @@ -156,6 +156,13 @@ export class Bean implements IBean { return false; } } + public isUnfrozen() { + if (this.frozenDate && this.unfrozenDate) { + return true; + } else { + return false; + } + } public initializeByObject(beanObj: IBean): void { Object.assign(this, beanObj); diff --git a/src/classes/devices/argosThermometer.ts b/src/classes/devices/argosThermometer.ts new file mode 100644 index 000000000..a812905ec --- /dev/null +++ b/src/classes/devices/argosThermometer.ts @@ -0,0 +1,79 @@ +import { PeripheralData } from './ble.types'; +import { Logger } from './common/logger'; + +import { TemperatureDevice } from './temperatureBluetoothDevice'; + +declare var ble: any; + +export class ArgosThermometer extends TemperatureDevice { + public static DEVICE_NAME = 'ARGOS'; + public static TEMPERATURE_SERVICE_UUID = + '6a521c59-55b5-4384-85c0-6534e63fb09e'; + public static TEMPERATURE_CHAR_UUID = '6a521c62-55b5-4384-85c0-6534e63fb09e'; + + private logger: Logger; + + constructor(data: PeripheralData) { + super(data); + this.connect(); + this.logger = new Logger('ArgosTemperatureSensor'); + } + + public static test(device: any): boolean { + return ( + device && + device.name && + device.name + .toLowerCase() + .startsWith(ArgosThermometer.DEVICE_NAME.toLowerCase()) + ); + } + + public connect() { + this.attachNotification(); + } + + public disconnect() { + this.deattachNotification(); + } + + private attachNotification() { + ble.startNotification( + this.device_id, + ArgosThermometer.TEMPERATURE_SERVICE_UUID, + ArgosThermometer.TEMPERATURE_CHAR_UUID, + + async (_data: any) => { + const rawData = _data; //new Uint8Array(_data.slice(0, -1)); + this.parseStatusUpdate(rawData); + }, + + (_data: any) => {} + ); + } + + private parseStatusUpdate(temperatureRawStatus: any) { + this.logger.log( + 'temperatureRawStatus received is: ' + temperatureRawStatus + ); + const formatNumber = new Intl.NumberFormat(undefined, { + minimumIntegerDigits: 2, + }).format; + + const setPoint = + ((temperatureRawStatus.getUint16(5, true) / 127) * 5) / 9 - 32; // Convert from F to C + const data = formatNumber(setPoint); + + this.setTemperature(Number(data), temperatureRawStatus); + } + + private deattachNotification() { + ble.stopNotification( + this.device_id, + ArgosThermometer.TEMPERATURE_SERVICE_UUID, + ArgosThermometer.TEMPERATURE_CHAR_UUID, + (e: any) => {}, + (e: any) => {} + ); + } +} diff --git a/src/classes/devices/bokooScale.ts b/src/classes/devices/bokooScale.ts index ad3144985..a20520730 100644 --- a/src/classes/devices/bokooScale.ts +++ b/src/classes/devices/bokooScale.ts @@ -49,6 +49,16 @@ export class BookooScale extends BluetoothScale { await this.write(new Uint8Array([0x03, 0x0a, 0x01, 0x00, 0x00, 0x08])); } + public async tareAndStartTimerModeAuto() { + this.weight.smoothed = 0; + this.weight.actual = 0; + this.weight.oldSmoothed = 0; + this.weight.old = 0; + this.setWeight(0); + + await this.write(new Uint8Array([0x03, 0x0a, 0x07, 0x00, 0x00, 0x00])); + } + public override disconnectTriggered(): void { this.logger.log('Disconnecting...'); this.deattachNotification(); diff --git a/src/classes/devices/index.ts b/src/classes/devices/index.ts index b18f3a7e9..34c3e63b5 100644 --- a/src/classes/devices/index.ts +++ b/src/classes/devices/index.ts @@ -25,6 +25,7 @@ import { BookooScale } from './bokooScale'; import { BasicGrillThermometer } from './basicGrillThermometer'; import { MeaterThermometer } from './meaterThermometer'; import { CombustionThermometer } from './combustionThermometer'; +import { ArgosThermometer } from './argosThermometer'; export { BluetoothScale, SCALE_TIMER_COMMAND } from './bluetoothDevice'; export * from './common'; @@ -56,6 +57,7 @@ export enum TemperatureType { BASICGRILL = 'BASICGRILL', MEATER = 'MEATER', COMBUSTION = 'COMBUSTION', + ARGOS = 'ARGOS', } export enum RefractometerType { @@ -129,6 +131,8 @@ export function makeTemperatureDevice( return new MeaterThermometer(data); case TemperatureType.COMBUSTION: return new CombustionThermometer(data); + case TemperatureType.ARGOS: + return new ArgosThermometer(data); default: return null; } diff --git a/src/classes/preparation/preparation.ts b/src/classes/preparation/preparation.ts index d18e46396..3394961ba 100755 --- a/src/classes/preparation/preparation.ts +++ b/src/classes/preparation/preparation.ts @@ -12,6 +12,7 @@ import { UIHelper } from '../../services/uiHelper'; import { ListViewBrewParameter } from '../parameter/listViewBrewParameter'; import { RepeatBrewParameter } from '../parameter/repeatBrewParameter'; import { ConnectedPreparationDevice } from '../preparationDevice/connectedPreparationDevice'; +import { PreparationDeviceType } from '../preparationDevice'; export class Preparation implements IPreparation { public name: string; @@ -163,6 +164,10 @@ export class Preparation implements IPreparation { return PREPARATION_STYLE_TYPE.POUR_OVER; case PREPARATION_TYPES.METICULOUS: return PREPARATION_STYLE_TYPE.ESPRESSO; + case PREPARATION_TYPES.XENIA: + return PREPARATION_STYLE_TYPE.ESPRESSO; + case PREPARATION_TYPES.SANREMO_YOU: + return PREPARATION_STYLE_TYPE.ESPRESSO; default: return PREPARATION_STYLE_TYPE.POUR_OVER; } @@ -249,6 +254,10 @@ export class Preparation implements IPreparation { return 'beanconqueror-preparation-tricolate'; case PREPARATION_TYPES.METICULOUS: return 'beanconqueror-preparation-meticulous'; + case PREPARATION_TYPES.SANREMO_YOU: + return 'beanconqueror-preparation-sanremo-you'; + case PREPARATION_TYPES.XENIA: + return 'beanconqueror-preparation-xenia'; default: return 'beanconqueror-preparation-custom'; } @@ -305,4 +314,7 @@ export class Preparation implements IPreparation { public hasPhotos(): boolean { return this.attachments && this.attachments.length > 0; } + public hasDeviceConnection(): boolean { + return this.connectedPreparationDevice?.type !== PreparationDeviceType.NONE; + } } diff --git a/src/classes/preparationDevice/index.ts b/src/classes/preparationDevice/index.ts index ad1960ce2..729697c2f 100644 --- a/src/classes/preparationDevice/index.ts +++ b/src/classes/preparationDevice/index.ts @@ -3,11 +3,13 @@ import { PreparationDevice } from './preparationDevice'; import { HttpClient } from '@angular/common/http'; import { Preparation } from '../preparation/preparation'; import { MeticulousDevice } from './meticulous/meticulousDevice'; +import { SanremoYOUDevice } from './sanremo/sanremoYOUDevice'; export enum PreparationDeviceType { NONE = 'NONE', XENIA = 'XENIA', METICULOUS = 'METICULOUS', + SANREMO_YOU = 'SANREMO_YOU', } export function makePreparationDevice( @@ -20,6 +22,8 @@ export function makePreparationDevice( return new XeniaDevice(_http, _preparation); case PreparationDeviceType.METICULOUS: return new MeticulousDevice(_http, _preparation); + case PreparationDeviceType.SANREMO_YOU: + return new SanremoYOUDevice(_http, _preparation); default: return null; } diff --git a/src/classes/preparationDevice/meticulous/meticulousDevice.ts b/src/classes/preparationDevice/meticulous/meticulousDevice.ts index ee5022dec..25a0daf64 100644 --- a/src/classes/preparationDevice/meticulous/meticulousDevice.ts +++ b/src/classes/preparationDevice/meticulous/meticulousDevice.ts @@ -1,15 +1,22 @@ import { PreparationDevice } from '../preparationDevice'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Preparation } from '../../preparation/preparation'; import { MeticulousShotData } from './meticulousShotData'; -import Api, { - ApiResponseError, - ProfileIdent, - ActionType, -} from 'meticulous-api'; +import Api, { ActionType, ProfileIdent } from '@meticulous-home/espresso-api'; import { IMeticulousParams } from '../../../interfaces/preparationDevices/meticulous/iMeticulousParams'; import { Profile } from 'meticulous-typescript-profile'; +import { catchError, timeout } from 'rxjs/operators'; +import { of } from 'rxjs'; +import { HistoryListingEntry } from '@meticulous-home/espresso-api/dist/types'; +import moment from 'moment'; +import { + BrewFlow, + IBrewPressureFlow, + IBrewRealtimeWaterFlow, + IBrewTemperatureFlow, + IBrewWeightFlow, +} from '../../brew/brewFlow'; declare var cordova; declare var io; @@ -22,6 +29,55 @@ export class MeticulousDevice extends PreparationDevice { private _profiles: Array = []; + private serverURL: string = ''; + + public static returnBrewFlowForShotData(_shotData) { + const newMoment = moment(new Date()).startOf('day'); + const newBrewFlow = new BrewFlow(); + + for (const entry of _shotData as any) { + const shotEntry: any = entry.shot; + const shotEntryTime = newMoment.clone().add('milliseconds', entry.time); + const timestamp = shotEntryTime.format('HH:mm:ss.SSS'); + + const realtimeWaterFlow: IBrewRealtimeWaterFlow = + {} as IBrewRealtimeWaterFlow; + + realtimeWaterFlow.brew_time = ''; + realtimeWaterFlow.timestamp = timestamp; + realtimeWaterFlow.smoothed_weight = 0; + realtimeWaterFlow.flow_value = shotEntry.flow; + realtimeWaterFlow.timestampdelta = 0; + + newBrewFlow.realtimeFlow.push(realtimeWaterFlow); + + const brewFlow: IBrewWeightFlow = {} as IBrewWeightFlow; + brewFlow.timestamp = timestamp; + brewFlow.brew_time = ''; + brewFlow.actual_weight = shotEntry.weight; + brewFlow.old_weight = 0; + brewFlow.actual_smoothed_weight = 0; + brewFlow.old_smoothed_weight = 0; + brewFlow.not_mutated_weight = 0; + newBrewFlow.weight.push(brewFlow); + + const pressureFlow: IBrewPressureFlow = {} as IBrewPressureFlow; + pressureFlow.timestamp = timestamp; + pressureFlow.brew_time = ''; + pressureFlow.actual_pressure = shotEntry.pressure; + pressureFlow.old_pressure = 0; + newBrewFlow.pressureFlow.push(pressureFlow); + + const temperatureFlow: IBrewTemperatureFlow = {} as IBrewTemperatureFlow; + temperatureFlow.timestamp = timestamp; + temperatureFlow.brew_time = ''; + temperatureFlow.actual_temperature = shotEntry.temperature; + temperatureFlow.old_temperature = 0; + newBrewFlow.temperatureFlow.push(temperatureFlow); + } + return newBrewFlow; + } + constructor(protected httpClient: HttpClient, _preparation: Preparation) { super(httpClient, _preparation); this.meticulousShotData = undefined; @@ -29,10 +85,53 @@ export class MeticulousDevice extends PreparationDevice { undefined, _preparation.connectedPreparationDevice.url ); + this.serverURL = _preparation.connectedPreparationDevice.url; if (typeof cordova !== 'undefined') { } } + public getHistory() { + const promise = new Promise((resolve, reject) => { + const httpOptions = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json', + }), + }; + this.httpClient + .post( + this.serverURL + '/api/v1/history', + { + sort: 'desc', + max_results: 20, + }, + httpOptions + ) + .pipe( + timeout(10000), + catchError((e) => { + return of(null); + }) + ) + .toPromise() + .then( + (data: any) => { + console.log(data); + if (data && data.history) { + resolve(data.history); + } + }, + (error) => { + console.log(error); + reject(); + } + ) + .catch((error) => { + console.log(error); + reject(); + }); + }); + return promise; + } public getActualShotData() { return this.meticulousShotData; @@ -45,24 +144,44 @@ export class MeticulousDevice extends PreparationDevice { try { const profileResponse = await this.metApi.getProfile(_profileId); - if (profileResponse instanceof ApiResponseError) { + const profile = profileResponse.data as unknown as Profile; + return profile; + /* if (profileResponse instanceof Profile) { } else { - const profile = profileResponse.data as unknown as Profile; - return profile; - } + + }*/ } catch (ex) {} return undefined; } + public async getHistoryShortListing() { + const response = await this.metApi.getHistoryShortListing(); + if (response && response.data && response.data.history) { + return response.data.history as Array; + } + } + + public async searchHistory() { + const response = await this.metApi.searchHistory({ + query: '', + ids: [], + start_date: '', + end_date: '', + order_by: ['date'], + sort: 'desc', + max_results: 10, + dump_data: true, + }); + if (response && response.data && response.data.history) { + return response.data.history as Array; + } + } + public async loadProfileByID(_profileId: string) { try { const loadProfile = await this.metApi.loadProfileByID(_profileId); - - if (loadProfile instanceof ApiResponseError) { - } else { - const profile = loadProfile.data as unknown as Profile; - return profile; - } + const profile = loadProfile.data as unknown as Profile; + return profile; } catch (ex) { console.log(ex.message); } @@ -87,11 +206,7 @@ export class MeticulousDevice extends PreparationDevice { try { if (this._profiles.length <= 0) { const profiles = await this.metApi.listProfiles(); - - if (profiles instanceof ApiResponseError) { - } else { - this._profiles = profiles.data as Array; - } + this._profiles = profiles.data as Array; } } catch (ex) {} } @@ -101,8 +216,9 @@ export class MeticulousDevice extends PreparationDevice { } public override async deviceConnected(): Promise { - const promise = new Promise((resolve, reject) => { - if (this.socket !== undefined) { + const promise = new Promise(async (resolve, reject) => { + const settings: any = await this.metApi.getSettings(); + if (settings?.data?.config) { resolve(true); } else { resolve(false); diff --git a/src/classes/preparationDevice/sanremo/sanremoYOUDevice.ts b/src/classes/preparationDevice/sanremo/sanremoYOUDevice.ts new file mode 100644 index 000000000..062092222 --- /dev/null +++ b/src/classes/preparationDevice/sanremo/sanremoYOUDevice.ts @@ -0,0 +1,208 @@ +import { PreparationDevice } from '../preparationDevice'; +import { HttpClient } from '@angular/common/http'; +import { Preparation } from '../../preparation/preparation'; + +import { ISanremoYOUParams } from '../../../interfaces/preparationDevices/sanremoYOU/iSanremoYOUParams'; +import { SanremoYOUMode } from '../../../enums/preparationDevice/sanremo/sanremoYOUMode'; + +declare var cordova; +export class SanremoYOUDevice extends PreparationDevice { + public scriptList: Array<{ INDEX: number; TITLE: string }> = []; + + private connectionURL: string = ''; + private statusPhase: number = 0; + constructor(protected httpClient: HttpClient, _preparation: Preparation) { + super(httpClient, _preparation); + + this.connectionURL = this.getPreparation().connectedPreparationDevice.url; + } + + public async deviceConnected(): Promise { + const promise = new Promise((resolve, reject) => { + const options = { + method: 'get', + }; + let urlAdding = '/api/runtime'; + + cordova.plugin.http.sendRequest( + this.connectionURL + urlAdding, + options, + (response) => { + try { + const parsedJSON = JSON.parse(response.data); + resolve(true); + return; + + if (parsedJSON && 'id' in parsedJSON) { + resolve(true); + } else { + reject(''); + } + } catch (e) { + // alert("Error in Resolve " + JSON.stringify(e)); + reject(JSON.stringify(e)); + } + }, + (response) => { + // prints 403 + // alert("Error " + JSON.stringify(response)); + reject(JSON.stringify(response)); + } + ); + }); + return promise; + } + + public getPressure() { + return this.pressure; + } + + public getResidualLagTime() { + const connectedPreparationDevice = + this.getPreparation().connectedPreparationDevice; + if ( + connectedPreparationDevice.customParams && + connectedPreparationDevice.customParams.residualLagTime + ) { + return connectedPreparationDevice.customParams.residualLagTime; + } else { + // Fixed value. + return 1.35; + } + } + + public getSaveLogfilesFromMachine(): boolean { + const connectedPreparationDevice = + this.getPreparation().connectedPreparationDevice; + if ( + connectedPreparationDevice.customParams && + connectedPreparationDevice.customParams.saveLogfilesFromMachine + ) { + return connectedPreparationDevice.customParams.saveLogfilesFromMachine; + } else { + // Fixed value. + return false; + } + } + + public getTemperature() { + return this.temperature; + } + public getDevicetemperature() { + return this.deviceTemperature; + } + + public fetchRuntimeData(_callback: any = null) { + const options = { + method: 'get', + }; + const urlAdding = '/api/runtime'; + + cordova.plugin.http.sendRequest( + this.connectionURL + urlAdding, + options, + (response) => { + try { + /**{"status":1,"description":"ON","statusPhase":0,"alarms":0,"warnings":2,"tempBoilerCoffe":82.1,"tempBolierServices":100.2,"pumpServicesPress":0.2,"pumpPress":0.0,"counterVol":0,"realtimeFlow":0,"setPressPaddle":0.0}**/ + const parsedJSON = JSON.parse(response.data); + const temp = parsedJSON.tempBoilerCoffe; + const press = parsedJSON.pumpPress * 10; + const statusPhase = parsedJSON.statusPhase; + + this.temperature = temp; + this.pressure = press; + this.statusPhase = statusPhase; + if (_callback) { + _callback(); + } + } catch (e) {} + }, + (response) => { + // prints 403 + } + ); + } + + public startShot(_mode: SanremoYOUMode) { + const promise = new Promise((resolve, reject) => { + const options = { + method: 'get', + }; + + let urlAdding = ''; + if (_mode === SanremoYOUMode.MANUAL_CONTROLLING) { + urlAdding = '/api/action/man'; + } else if (_mode === SanremoYOUMode.PROFILE_P1_CONTROLLING) { + urlAdding = '/api/action/p1'; + } else if (_mode === SanremoYOUMode.PROFILE_P2_CONTROLLING) { + urlAdding = '/api/action/p2'; + } else if (_mode === SanremoYOUMode.PROFILE_P3_CONTROLLING) { + urlAdding = '/api/action/p3'; + } + + cordova.plugin.http.sendRequest( + this.getPreparation().connectedPreparationDevice.url + urlAdding, + options, + (response) => { + try { + const parsedJSON = JSON.parse(response.data); + resolve(parsedJSON); + } catch (e) { + reject(JSON.stringify(e)); + } + }, + (response) => { + // prints 403 + reject(JSON.stringify(response)); + } + ); + }); + return promise; + } + public stopShot(_mode: SanremoYOUMode) { + const promise = new Promise(async (resolve, reject) => { + const options = { + method: 'get', + }; + + let urlAdding = ''; + if (_mode === SanremoYOUMode.MANUAL_CONTROLLING) { + urlAdding = '/api/action/man'; + } else if (_mode === SanremoYOUMode.PROFILE_P1_CONTROLLING) { + urlAdding = '/api/action/p1'; + } else if (_mode === SanremoYOUMode.PROFILE_P2_CONTROLLING) { + urlAdding = '/api/action/p2'; + } else if (_mode === SanremoYOUMode.PROFILE_P3_CONTROLLING) { + urlAdding = '/api/action/p3'; + } + + cordova.plugin.http.sendRequest( + this.getPreparation().connectedPreparationDevice.url + urlAdding, + options, + (response) => { + try { + const parsedJSON = JSON.parse(response.data); + resolve(parsedJSON); + } catch (e) { + reject(JSON.stringify(e)); + } + }, + (response) => { + // prints 403 + reject(JSON.stringify(response)); + } + ); + }); + return promise; + } +} + +export class SanremoYOUParams implements ISanremoYOUParams { + public stopAtWeight: number = 0; + public residualLagTime: number = 0.9; + public selectedMode: SanremoYOUMode = SanremoYOUMode.LISTENING; + constructor() { + this.residualLagTime = 0.9; + this.selectedMode = SanremoYOUMode.LISTENING; + } +} diff --git a/src/classes/preparationDevice/xenia/xeniaDevice.ts b/src/classes/preparationDevice/xenia/xeniaDevice.ts index ef5d9578e..2b699b24a 100644 --- a/src/classes/preparationDevice/xenia/xeniaDevice.ts +++ b/src/classes/preparationDevice/xenia/xeniaDevice.ts @@ -337,6 +337,7 @@ export class XeniaParams implements IXeniaParams { public scriptAtFirstDripId: number = 0; public scriptAtWeightReachedId: number = 0; public scriptAtWeightReachedNumber: number = 0; + public residualLagTime: number = 0; constructor() {} } diff --git a/src/classes/settings/settings.ts b/src/classes/settings/settings.ts index fdb5c42c4..1adf7cb10 100755 --- a/src/classes/settings/settings.ts +++ b/src/classes/settings/settings.ts @@ -88,6 +88,15 @@ export class Settings implements ISettings { ARCHIVED: IBeanPageSort; FROZEN: IBeanPageSort; }; + public bean_collapsed: { + OPEN: boolean; + ARCHIVED: boolean; + FROZEN: boolean; + }; + public brew_collapsed: { + OPEN: boolean; + ARCHIVED: boolean; + }; public green_bean_sort: { OPEN: IBeanPageSort; @@ -131,6 +140,11 @@ export class Settings implements ISettings { }; }; + public graph_pressure: { + upper: number; + lower: number; + }; + public wake_lock: boolean; public security_check_when_going_back: boolean; @@ -317,6 +331,15 @@ export class Settings implements ISettings { FROZEN: {} as IBeanPageSort, }; + this.bean_collapsed = { + OPEN: false, + ARCHIVED: false, + FROZEN: false, + }; + this.brew_collapsed = { + OPEN: false, + ARCHIVED: false, + }; this.green_bean_sort = { OPEN: {} as IBeanPageSort, ARCHIVED: {} as IBeanPageSort, @@ -363,6 +386,11 @@ export class Settings implements ISettings { }, }; + this.graph_pressure = { + lower: 0, + upper: 9, + }; + this.brew_rating = 5; this.brew_rating_steps = 1; this.bean_rating = 5; @@ -505,14 +533,7 @@ export class Settings implements ISettings { ); } - public resetFilter() { - this.brew_filter = { - OPEN: {} as IBrewPageFilter, - ARCHIVED: {} as IBrewPageFilter, - }; - this.brew_filter.OPEN = this.GET_BREW_FILTER(); - this.brew_filter.ARCHIVED = this.GET_BREW_FILTER(); - + public resetBeanFilter() { this.bean_filter = { OPEN: {} as IBeanPageFilter, ARCHIVED: {} as IBeanPageFilter, @@ -521,7 +542,23 @@ export class Settings implements ISettings { this.bean_filter.OPEN = this.GET_BEAN_FILTER(); this.bean_filter.ARCHIVED = this.GET_BEAN_FILTER(); this.bean_filter.FROZEN = this.GET_BEAN_FILTER(); + } + + public resetBrewFilter() { + this.brew_filter = { + OPEN: {} as IBrewPageFilter, + ARCHIVED: {} as IBrewPageFilter, + }; + this.brew_filter.OPEN = this.GET_BREW_FILTER(); + this.brew_filter.ARCHIVED = this.GET_BREW_FILTER(); + } + + public resetFilter() { + this.resetBrewFilter(); + this.resetBeanFilter(); + } + public resetBeanSort() { this.bean_sort = { OPEN: {} as IBeanPageSort, ARCHIVED: {} as IBeanPageSort, diff --git a/src/classes/version/iVersion.ts b/src/classes/version/iVersion.ts index 79b29aa4f..7acefb509 100755 --- a/src/classes/version/iVersion.ts +++ b/src/classes/version/iVersion.ts @@ -60,7 +60,7 @@ export class Version implements IVersion { * We dont set this to a variable, else it would be stored in DB and wrongly overwritten */ private getUpdatedVersions() { - return ['7.4.0']; + return ['7.5.0']; } private versionCompare(_actualAppVersion, _updateVersion) { diff --git a/src/components/bean-information/bean-information.component.html b/src/components/bean-information/bean-information.component.html index a4831a2f8..4ebadc640 100644 --- a/src/components/bean-information/bean-information.component.html +++ b/src/components/bean-information/bean-information.component.html @@ -43,14 +43,15 @@ - +
+
- + @@ -144,7 +145,7 @@ {{"BEANS_AMOUNT_USED" | translate}}
- {{daysOld()}}
@@ -161,7 +162,7 @@ {{'BEAN_DATA_COST' | translate}}
- {{bean?.cost}} + {{bean?.cost}} ({{calculateCostPerKG()}} {{getCurrencySymbol()}}/kg)
{{'BEAN_DATA_CUPPING_POINTS' | translate}}
diff --git a/src/components/bean-information/bean-information.component.ts b/src/components/bean-information/bean-information.component.ts index ecc2130fa..e31dbffdb 100644 --- a/src/components/bean-information/bean-information.component.ts +++ b/src/components/bean-information/bean-information.component.ts @@ -36,11 +36,11 @@ import { BeanMapper } from '../../mapper/bean/beanMapper'; import { ServerCommunicationService } from '../../services/serverCommunication/server-communication.service'; import { UIHelper } from '../../services/uiHelper'; import { TranslateService } from '@ngx-translate/core'; -import BREW_TRACKING from '../../data/tracking/brewTracking'; import * as htmlToImage from 'html-to-image'; import { UIBrewHelper } from '../../services/uiBrewHelper'; import moment from 'moment/moment'; import { BEAN_FREEZING_STORAGE_ENUM } from '../../enums/beans/beanFreezingStorage'; +import { CurrencyService } from '../../services/currencyService/currency.service'; @Component({ selector: 'bean-information', @@ -51,7 +51,7 @@ export class BeanInformationComponent implements OnInit { @Input() public bean: Bean; @Input() public showActions: boolean = true; @Input() public disabled: boolean = false; - + @Input() public collapsed: boolean = false; @ViewChild('card', { read: ElementRef }) public cardEl: ElementRef; @ViewChild('beanStars', { read: NgxStarsComponent, static: false }) @@ -81,7 +81,8 @@ export class BeanInformationComponent implements OnInit { private readonly translate: TranslateService, private readonly platform: Platform, private readonly uiBrewHelper: UIBrewHelper, - private actionSheetCtrl: ActionSheetController + private actionSheetCtrl: ActionSheetController, + private readonly currencyService: CurrencyService ) { this.settings = this.uiSettingsStorage.getSettings(); } @@ -498,7 +499,7 @@ export class BeanInformationComponent implements OnInit { private async resetSettings() { const settings: Settings = this.uiSettingsStorage.getSettings(); - settings.resetFilter(); + settings.resetBeanFilter(); await this.uiSettingsStorage.saveSettings(settings); } @@ -542,4 +543,32 @@ export class BeanInformationComponent implements OnInit { } return false; } + + public showCostPerKG(): boolean { + if ( + this.bean.weight && + this.bean.weight > 0 && + this.bean.weight !== 1000 && + this.bean.cost && + this.bean.cost > 0 + ) { + return true; + } + return false; + } + public getCurrencySymbol() { + return this.currencyService.getActualCurrencySymbol(); + } + + public calculateCostPerKG() { + const beanWeight = this.bean.weight; + const beanCost = this.bean.cost; + + const costPerGramm = this.uiHelper.toFixedIfNecessary( + beanCost / beanWeight, + 2 + ); + const kgCost = costPerGramm * 1000; + return kgCost; + } } diff --git a/src/components/beans/bean-freeze-information/bean-freeze-information.component.html b/src/components/beans/bean-freeze-information/bean-freeze-information.component.html index 4238b14a4..175b0079f 100644 --- a/src/components/beans/bean-freeze-information/bean-freeze-information.component.html +++ b/src/components/beans/bean-freeze-information/bean-freeze-information.component.html @@ -1,10 +1,10 @@ - + - + { let component: BeanFreezeInformationComponent; @@ -10,13 +16,22 @@ describe('BeanFreezeInformationComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [BeanFreezeInformationComponent], - imports: [IonicModule.forRoot()], + imports: [ + IonicModule.forRoot(), + IonicStorageModule.forRoot(), + TranslateModule.forRoot(), + PipesModule, + ], + providers: [{ provide: UIHelper, useClass: UIHelperMock }], }).compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(BeanFreezeInformationComponent); component = fixture.componentInstance; + component.data = new Bean(); fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/components/beans/bean-general-information/bean-general-information.component.spec.ts b/src/components/beans/bean-general-information/bean-general-information.component.spec.ts index 3dd74c0ad..7394397b4 100644 --- a/src/components/beans/bean-general-information/bean-general-information.component.spec.ts +++ b/src/components/beans/bean-general-information/bean-general-information.component.spec.ts @@ -8,8 +8,8 @@ import { UIHelper } from '../../../services/uiHelper'; import { UIHelperMock } from '../../../classes/mock'; import { Bean } from '../../../classes/bean/bean'; import { FormsModule } from '@angular/forms'; -import { KeysPipe } from '../../../pipes/keys'; import { Config } from '../../../classes/objectConfig/objectConfig'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BeanGeneralInformationComponent', () => { let component: BeanGeneralInformationComponent; @@ -17,8 +17,13 @@ describe('BeanGeneralInformationComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [BeanGeneralInformationComponent, KeysPipe], - imports: [IonicModule.forRoot(), TranslateModule.forRoot(), FormsModule], + declarations: [BeanGeneralInformationComponent], + imports: [ + IonicModule.forRoot(), + TranslateModule.forRoot(), + FormsModule, + PipesModule, + ], providers: [ { provide: Storage }, { provide: UIHelper, useClass: UIHelperMock }, diff --git a/src/components/brew-graph-reference-card/brew-graph-reference-card.component.spec.ts b/src/components/brew-graph-reference-card/brew-graph-reference-card.component.spec.ts index 0c859cc16..63b391609 100644 --- a/src/components/brew-graph-reference-card/brew-graph-reference-card.component.spec.ts +++ b/src/components/brew-graph-reference-card/brew-graph-reference-card.component.spec.ts @@ -7,11 +7,11 @@ import { TranslateModule } from '@ngx-translate/core'; import { UIHelper } from '../../services/uiHelper'; import { UIHelperMock } from '../../classes/mock'; import { Brew } from '../../classes/brew/brew'; -import { FormatDatePipe } from '../../pipes/formatDate'; import { IBrew } from '../../interfaces/brew/iBrew'; import { Bean } from '../../classes/bean/bean'; import { Preparation } from '../../classes/preparation/preparation'; import { Mill } from '../../classes/mill/mill'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BrewGraphReferenceCardComponent', () => { let component: BrewGraphReferenceCardComponent; @@ -19,8 +19,8 @@ describe('BrewGraphReferenceCardComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [BrewGraphReferenceCardComponent, FormatDatePipe], - imports: [IonicModule.forRoot(), TranslateModule.forRoot()], + declarations: [BrewGraphReferenceCardComponent], + imports: [IonicModule.forRoot(), TranslateModule.forRoot(), PipesModule], providers: [ { provide: Storage }, { provide: UIHelper, useClass: UIHelperMock }, diff --git a/src/components/brew-information/brew-information.component.html b/src/components/brew-information/brew-information.component.html index cdf9d92a5..85e0b0650 100644 --- a/src/components/brew-information/brew-information.component.html +++ b/src/components/brew-information/brew-information.component.html @@ -1,251 +1,330 @@ -
- - - - - - - - -
- -
-
- -
- -
-
- - - - - {{brew.config.unix_timestamp | formatDate:[settings?.date_format]}}{{brew.config.unix_timestamp | formatDate:["HH:mm"]}} +
+ + + + + + + + - - - - + +
+ +
-
- -
{{bean?.name}} ({{bean?.roaster}})
- - - + +
+ +
- + + + + + + +
+ + +
+ + () + + {{ bean?.name }} ({{ bean?.roaster }}) +
+
+ + + + + + {{ this.uiHelper.toFixedIfNecessary(brew.rating, 2) }} + +
+
+ + +
+ + +
- - - - - -
-
-
- - - - + + + {{ "BREW_DATA_PREPARATION_METHOD" | translate }} +
+ {{ preparation?.name }} +
+ - - {{brew?.grind_weight | number : '.0-2'}}gr / {{brew.brew_beverage_quantity | number : '.0-2'}}{{brewQuantityEnum[brew?.brew_beverage_quantity_type]}} ({{brew?.getBrewRatio()}}) - - - +
+ {{ mill?.name }} +
+ + {{ "BREW_DATA_IN_OUT_BR" | translate }} +
+ + {{ brew?.grind_weight | number : '.0-2' }}gr + / {{ brew.brew_quantity | number : '.0-2' }}{{ brewQuantityEnum[brew?.brew_quantity_type] }} ({{ brew?.getBrewRatio() }} + ) + + + {{ brew?.grind_weight | number : '.0-2' }}gr + / {{ brew.brew_beverage_quantity | number : '.0-2' }}{{ brewQuantityEnum[brew?.brew_beverage_quantity_type] }} ({{ brew?.getBrewRatio() }} + ) + +
+
- {{brew?.getFormattedTotalCoffeeBrewTime()}} -
- 0 && uiBrewHelper.fieldVisible(settings.visible_list_view_parameters.mill_speed, preparation?.visible_list_view_parameters.mill_speed, - preparation?.use_custom_parameters))" size="6"> - {{'BREW_DATA_GRIND_SIZE' | translate}}
- + {{ 'BREW_DATA_GRIND_SIZE' | translate }} +
+ {{brew?.grind_size}}{{ brew?.grind_size }}
- {{brew?.tds}}, %{{brew?.getExtractionYield()}} -
- - {{'BREW_DATA_BREW_TEMPERATURE' | translate}}
- {{brew?.brew_temperature}} -
- - {{'BREW_DATA_PRESSURE_PROFILE' | translate}}
- {{brew?.pressure_profile}} -
- - {{"BREW_DATA_BEAN_WEIGHT_IN" | translate}}
- {{brew?.bean_weight_in}} -
- +
+ {{ brew?.bean_weight_in }} +
+ + {{ "BREW_DATA_MILL_TIMER" | translate }} +
+ {{ brew?.mill_timer }} +
+ - {{"BREW_DATA_VESSEL_NAME_WEIGHT" | translate}}
- {{brew?.vessel_name}} / {{brew?.vessel_weight}} -
- +
+ {{ brew?.vessel_name }} / {{ brew?.vessel_weight }} +
+ + {{ "BREW_DATA_TEMPERATURE_TIME" | translate }} +
+ {{ brew?.getFormattedTotalCoffeeTemperatureTime() }} +
+ + {{ "BREW_DATA_COFFEE_BLOOMING_TIME" | translate }} +
+ {{ brew?.getFormattedTotalCoffeeBloomingTime() }} +
+ 0)'> + {{ "BREW_DATA_COFFEE_FIRST_DRIP_TIME" | translate }} +
+ {{ brew?.getFormattedTotalCoffeeFirstDripTime() }} +
+ - {{"BREW_DATA_WATER" | translate}}
- {{brew?.getWater().name}} -
- +
+ {{ brew?.getWater().name }} +
+ - {{"BREW_DATA_COFFEE_TYPE" | translate}}
- {{brew?.coffee_type}} -
- +
+ {{ brew?.coffee_type }} +
+ - {{"BREW_DATA_COFFEE_CONCENTRATION" | translate}}
- {{brew?.coffee_concentration}} -
- +
+ {{ brew?.coffee_concentration }} +
+ + {{ "BREW_INFORMATION_BEAN_AGE" | translate }} +
+ {{ brew?.getCalculatedBeanAge() }} +
+
- {{getCuppedBrewFlavors()}} -
- +
+ {{ brew?.getPreparationToolName(uuid) }} + +
+
+ + {{ 'BREW_DATA_FLAVOR' | translate }} +
+ {{ getCuppedBrewFlavors() }} +
+ - {{"BREW_DATA_NOTES" | translate}}
-
{{brew?.note}}
-
+ {{ "BREW_DATA_NOTES" | translate }} +
+ +
{{ brew?.note }}
+
+
-
-
-
-
-
+ + + + +
+ + + + + + + + + +
+ +
- - - - - + + + + + -
-
- {{brew.config.unix_timestamp | formatDate:["DD"]}}.
- - {{brew.config.unix_timestamp | formatDate:["MMMM"]}}
{{brew.config.unix_timestamp | formatDate:["YYYY"]}} +
+
+ {{ brew.config.unix_timestamp | formatDate:["DD"] }}.
+ + {{ brew.config.unix_timestamp | formatDate:["MMMM"] }} +
{{ brew.config.unix_timestamp | formatDate:["YYYY"] }}
-
+
-
-
- - -
- - - - - - - {{bean?.name}} - - - - {{preparation?.name}} - - - - - {{mill?.name}} - - - - - - - - - - - - - -
-
- - +
+
+
+ +
+ + + + + + + + + + + () + + {{ bean?.name }} + + + + + {{ preparation?.name }} + + + + + {{ mill?.name }} + + + + + + + + + + + + + + +
+
+
+
diff --git a/src/components/brew-information/brew-information.component.scss b/src/components/brew-information/brew-information.component.scss index 655feba5f..6ec6155ba 100644 --- a/src/components/brew-information/brew-information.component.scss +++ b/src/components/brew-information/brew-information.component.scss @@ -9,6 +9,7 @@ max-height:60px!important; } ion-card.brew-layout { + width:100%; margin-top:0px; margin-bottom:0px; margin-left:10px; @@ -54,6 +55,7 @@ top: -5px; } } + ion-card.dashboard-layout { @@ -143,6 +145,22 @@ max-height: 60px; object-fit:cover; } + + swiper-slide { + display: flex; + position: relative; + + width: 100%; + + height: auto; + } + + .add-bottom-spacing{ + margin-bottom:20px; + } + + + } diff --git a/src/components/brew-information/brew-information.component.spec.ts b/src/components/brew-information/brew-information.component.spec.ts index 0ea528ee2..1f962c9ab 100644 --- a/src/components/brew-information/brew-information.component.spec.ts +++ b/src/components/brew-information/brew-information.component.spec.ts @@ -13,12 +13,13 @@ import { UIImage } from '../../services/uiImage'; import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; -import { FormatDatePipe } from '../../pipes/formatDate'; import { Brew } from '../../classes/brew/brew'; import { IBrew } from '../../interfaces/brew/iBrew'; import { Bean } from '../../classes/bean/bean'; import { Preparation } from '../../classes/preparation/preparation'; import { Mill } from '../../classes/mill/mill'; +import { PipesModule } from 'src/pipes/pipes.module'; +import { FileChooser } from '@awesome-cordova-plugins/file-chooser/ngx'; describe('BrewInformationComponent', () => { let component: BrewInformationComponent; @@ -26,11 +27,12 @@ describe('BrewInformationComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [BrewInformationComponent, FormatDatePipe], + declarations: [BrewInformationComponent], imports: [ IonicModule.forRoot(), TranslateModule.forRoot(), HttpClientTestingModule, + PipesModule, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [ @@ -41,6 +43,7 @@ describe('BrewInformationComponent', () => { { provide: UIImage, useClass: UIImageMock }, { provide: SocialSharing }, { provide: FileTransfer }, + { provide: FileChooser }, ], }).compileComponents(); })); diff --git a/src/components/brew-information/brew-information.component.ts b/src/components/brew-information/brew-information.component.ts index e333a98f6..715893514 100644 --- a/src/components/brew-information/brew-information.component.ts +++ b/src/components/brew-information/brew-information.component.ts @@ -10,7 +10,7 @@ import { } from '@angular/core'; import { Brew } from '../../classes/brew/brew'; import { UISettingsStorage } from '../../services/uiSettingsStorage'; -import { ModalController, Platform } from '@ionic/angular'; +import { MenuController, ModalController, Platform } from '@ionic/angular'; import { BREW_ACTION } from '../../enums/brews/brewAction'; import { BrewPopoverActionsComponent } from '../../app/brew/brew-popover-actions/brew-popover-actions.component'; import { Bean } from '../../classes/bean/bean'; @@ -33,13 +33,13 @@ import { TranslateService } from '@ngx-translate/core'; import { BrewTrackingService } from '../../services/brewTracking/brew-tracking.service'; import { UIHealthKit } from '../../services/uiHealthKit'; import * as htmlToImage from 'html-to-image'; -import { Visualizer } from '../../classes/visualizer/visualizer'; import { UIFileHelper } from '../../services/uiFileHelper'; import { BrewFlow } from '../../classes/brew/brewFlow'; -import { UIBeanStorage } from '../../services/uiBeanStorage'; + import { UIBeanHelper } from '../../services/uiBeanHelper'; import { VisualizerService } from '../../services/visualizerService/visualizer-service.service'; +import { UIGraphHelper } from '../../services/uiGraphHelper'; declare var window; @Component({ selector: 'brew-information', @@ -48,9 +48,24 @@ declare var window; }) export class BrewInformationComponent implements OnInit { @Input() public brew: Brew; + public _collapsed: boolean = undefined; @Input() public layout: string = 'brew'; @ViewChild('card', { read: ElementRef }) public cardEl: ElementRef; + + public slideOpts = { + allowTouchMove: false, + speed: 400, + slide: 4, + }; + + @ViewChild('swiper', { static: false }) public brewInformationSlider: + | ElementRef + | undefined; + + @ViewChild('brewInformationContainer', { read: ElementRef, static: false }) + public brewInformationContainer: ElementRef; + @ViewChild('brewStars', { read: NgxStarsComponent, static: false }) public brewStars: NgxStarsComponent; @@ -64,6 +79,9 @@ export class BrewInformationComponent implements OnInit { public settings: Settings = null; + public informationContainerHeight: number = undefined; + public informationContainerWidth: number = undefined; + constructor( private readonly uiSettingsStorage: UISettingsStorage, public readonly uiBrewHelper: UIBrewHelper, @@ -80,17 +98,74 @@ export class BrewInformationComponent implements OnInit { private readonly uiHealthKit: UIHealthKit, private readonly platform: Platform, private readonly uiFileHelper: UIFileHelper, - private readonly uiBeanStorage: UIBeanStorage, private readonly uiBeanHelper: UIBeanHelper, - private readonly visualizerService: VisualizerService + private readonly visualizerService: VisualizerService, + private readonly uiGraphHelper: UIGraphHelper, + private readonly menu: MenuController ) {} + @Input() set collapsed(value: boolean) { + let retrigger: boolean = false; + if (value !== this._collapsed && this._collapsed !== undefined) { + //Retrigger + retrigger = true; + } + this._collapsed = value; + + if (retrigger) { + //When setting the container to undefined, the *ngIf removes the graph, and setting after that the new height, the element will be spawned correctly. + this.informationContainerWidth = undefined; + setTimeout(() => { + this.calculcationInformationContainer(); + }, 50); + } + } + + get collapsed(): boolean { + return this._collapsed; + } + + private calculcationInformationContainer() { + /**We calculcate the information here, to avoid expression-changed in angular, because it always triggered while scrolling cause of calucation functions**/ + this.informationContainerHeight = + this.brewInformationContainer?.nativeElement?.offsetHeight - 50; + this.informationContainerWidth = + this.brewInformationContainer?.nativeElement?.offsetWidth - 50; + } public ngOnInit() { if (this.brew) { this.settings = this.uiSettingsStorage.getSettings(); this.bean = this.brew.getBean(); this.preparation = this.brew.getPreparation(); this.mill = this.brew.getMill(); + + /**On Android we somehow need a bit more ms for the calc... specific on older once**/ + let timeoutMS = 350; + if (this.platform.is('ios')) { + timeoutMS = 150; + } + setTimeout(() => { + this.calculcationInformationContainer(); + + if (this.brew.flow_profile) { + /**If we slide on a bigger tablet, somehow ionic triggering the menu when sliding from right to left, thats why we need to attach us to touchstart/end and to ignore the slide...**/ + this.brewInformationSlider?.nativeElement.swiper.on( + 'touchStart', + () => { + //We got two slides + this.menu.swipeGesture(false); + } + ); + this.brewInformationSlider?.nativeElement.swiper.on( + 'touchEnd', + () => { + this.menu.swipeGesture(true); + } + ); + } else { + this.brewInformationSlider?.nativeElement.swiper.disable(); + } + }, timeoutMS); } } @@ -129,6 +204,10 @@ export class BrewInformationComponent implements OnInit { this.brewAction.emit([BREW_ACTION.DETAIL, this.brew]); } + public async showBrewGraph() { + await this.uiGraphHelper.detailBrewGraph(this.brew); + } + public async showBrewActions(event): Promise { event.stopPropagation(); event.stopImmediatePropagation(); @@ -354,6 +433,9 @@ export class BrewInformationComponent implements OnInit { case BREW_ACTION.SHOW_VISUALIZER: await this.showVisualizerShot(); break; + case BREW_ACTION.SHOW_GRAPH: + await this.showBrewGraph(); + break; default: break; } @@ -400,12 +482,12 @@ export class BrewInformationComponent implements OnInit { await this.uiAlert.showLoadingSpinner(); if (this.platform.is('ios')) { htmlToImage - .toJpeg(this.cardEl.nativeElement) + .toPng(this.cardEl.nativeElement) .then((_dataURL) => { // On iOS we need to do this a second time, because the rendering doesn't render everything (strange thing) setTimeout(() => { htmlToImage - .toJpeg(this.cardEl.nativeElement) + .toPng(this.cardEl.nativeElement) .then(async (_dataURLSecond) => { await this.uiAlert.hideLoadingSpinner(); setTimeout(() => { @@ -422,7 +504,7 @@ export class BrewInformationComponent implements OnInit { }); } else { htmlToImage - .toJpeg(this.cardEl.nativeElement) + .toPng(this.cardEl.nativeElement) .then(async (_dataURL) => { await this.uiAlert.hideLoadingSpinner(); setTimeout(() => { diff --git a/src/components/brew-timer/brew-timer.component.spec.ts b/src/components/brew-timer/brew-timer.component.spec.ts index 1ec526294..f80d3373a 100644 --- a/src/components/brew-timer/brew-timer.component.spec.ts +++ b/src/components/brew-timer/brew-timer.component.spec.ts @@ -6,6 +6,7 @@ import { ModalController } from '@ionic/angular'; import { CoffeeBluetoothDevicesService } from '../../services/coffeeBluetoothDevices/coffee-bluetooth-devices.service'; import { UISettingsStorage } from '../../services/uiSettingsStorage'; import { Settings } from '../../classes/settings/settings'; +import { Device } from '@awesome-cordova-plugins/device/ngx'; describe('BrewTimerComponent', () => { let component: BrewTimerComponent; @@ -32,6 +33,9 @@ describe('BrewTimerComponent', () => { }, }, }, + { + provide: Device, + }, ], }).compileComponents(); })); diff --git a/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.html b/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.html index 3c6db90c7..11fd6cebb 100644 --- a/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.html +++ b/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.html @@ -37,7 +37,7 @@
? g
-
({{data.getBrewRatio()}})
+
({{data.getBrewRatio()}})
@@ -48,8 +48,8 @@ style="padding-right:5px;"> -
? g/s
-
? g/s
+
? g/s
+
? g/s
diff --git a/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.scss b/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.scss index 12eb90d4b..eb6fc3c8f 100644 --- a/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.scss +++ b/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.scss @@ -106,4 +106,9 @@ font-size: 30px; } } + .information-tile-no-overflow { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } } diff --git a/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.ts b/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.ts index cd4f50964..ad9faba44 100644 --- a/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.ts +++ b/src/components/brews/brew-brewing-graph/brew-brewing-graph.component.ts @@ -62,6 +62,8 @@ import { Graph } from '../../../classes/graph/graph'; import { UIGraphStorage } from '../../../services/uiGraphStorage.service'; import regression from 'regression'; import { TextToSpeechService } from '../../../services/textToSpeech/text-to-speech.service'; +import { SanremoYOUDevice } from '../../../classes/preparationDevice/sanremo/sanremoYOUDevice'; +import { SanremoYOUMode } from '../../../enums/preparationDevice/sanremo/sanremoYOUMode'; declare var Plotly; @@ -133,6 +135,7 @@ export class BrewBrewingGraphComponent implements OnInit { private temperatureThresholdWasHit: boolean = false; private xeniaOverviewInterval: any = undefined; private meticulousInterval: any = undefined; + private sanremoYOUFetchingInterval: any = undefined; public lastChartLayout: any = undefined; public lastChartRenderingInstance: number = 0; @@ -153,6 +156,7 @@ export class BrewBrewingGraphComponent implements OnInit { public textToSpeechWeightInterval: any = undefined; public textToSpeechTimerInterval: any = undefined; + constructor( private readonly platform: Platform, private readonly bleManager: CoffeeBluetoothDevicesService, @@ -873,6 +877,21 @@ export class BrewBrewingGraphComponent implements OnInit { this.getChartConfig() ); + setTimeout(() => { + //Do this after the plot. + if ( + this.brewComponent?.brewBrewingPreparationDeviceEl?.preparationDeviceConnected() + ) { + if ( + this.brewComponent?.brewBrewingPreparationDeviceEl?.hasTargetWeightActive() + ) { + this.drawTargetWeight( + this.brewComponent?.brewBrewingPreparationDeviceEl?.getTargetWeight() + ); + } + } + }, 50); + this.lastChartRenderingInstance = -1; if (this.brewComponent.maximizeFlowGraphIsShown) { //After we don't know how long all scale events take, dispatch the resize event, after the flow component will then grab on and stretch the canva. @@ -925,6 +944,7 @@ export class BrewBrewingGraphComponent implements OnInit { if (this.isDetail === false) { let graph_weight_settings; let graph_flow_settings; + if ( this.data.getPreparation().style_type === PREPARATION_STYLE_TYPE.ESPRESSO @@ -1033,6 +1053,9 @@ export class BrewBrewingGraphComponent implements OnInit { this.brewComponent?.brewBrewingPreparationDeviceEl?.preparationDeviceConnected() || !this.platform.is('cordova') ) { + const graph_pressure_settings = this.settings.graph_pressure; + const suggestedMinPressure: number = graph_pressure_settings.lower; + const suggestedMaxPressure: number = graph_pressure_settings.upper; layout['yaxis4'] = { title: '', titlefont: { color: '#05C793' }, @@ -1043,7 +1066,7 @@ export class BrewBrewingGraphComponent implements OnInit { showgrid: false, position: 0.91, fixedrange: true, - range: [0, 10], + range: [suggestedMinPressure, suggestedMaxPressure], }; } const temperatureDevice = this.bleManager.getTemperatureDevice(); @@ -1105,6 +1128,16 @@ export class BrewBrewingGraphComponent implements OnInit { }, }; + const graph_pressure_settings = this.settings.graph_pressure; + const suggestedMinPressure = graph_pressure_settings.lower; + let suggestedMaxPressure = graph_pressure_settings.upper; + try { + if (this.pressureTrace?.y.length > 0) { + suggestedMaxPressure = Math.max(...this.pressureTrace.y); + suggestedMaxPressure = Math.ceil(suggestedMaxPressure + 1); + } + } catch (ex) {} + layout['yaxis4'] = { title: '', titlefont: { color: '#05C793' }, @@ -1114,7 +1147,7 @@ export class BrewBrewingGraphComponent implements OnInit { side: 'right', showgrid: false, position: 0.93, - range: [0, 12], + range: [suggestedMinPressure, suggestedMaxPressure], visible: true, }; @@ -1155,6 +1188,51 @@ export class BrewBrewingGraphComponent implements OnInit { return layout; } + public drawTargetWeight(_targetWeight: number) { + if ( + this.brewComponent?.brewBrewingPreparationDeviceEl?.getPreparationDeviceType() === + PreparationDeviceType.SANREMO_YOU + ) { + if (!('shapes' in this.lastChartLayout)) { + this.lastChartLayout['shapes'] = []; + } + let didWeAlreadyHaveAshape: boolean = false; + for (const [index, shape] of this.lastChartLayout['shapes'].entries()) { + if (shape['customId'] === 'targetWeightLine') { + if (_targetWeight > 0) { + //We have already one shape. + shape['y0'] = _targetWeight; + shape['y1'] = _targetWeight; + } else { + this.lastChartLayout['shapes'].splice(index, 1); + } + didWeAlreadyHaveAshape = true; + + break; + } + } + if (didWeAlreadyHaveAshape === false && _targetWeight > 0) { + this.lastChartLayout['shapes'].push({ + type: 'line', + x0: 0, // Anfang der x-Achse (in Bezug auf die Daten) + x1: 1, // Ende der x-Achse (1 = 100% der Breite) + y0: _targetWeight, // Y-Position der Linie (auf der Datenachse) + y1: _targetWeight, // gleiche Y-Position, um eine horizontale Linie zu erzeugen + xref: 'paper', // Verweise auf das gesamte Diagramm (nicht nur den Datenbereich) + yref: 'y', // Y-Achse bezieht sich auf den Datenwert + line: { + color: 'rgb(193, 160, 80)', + width: 2, + dash: 'dot', // Stil der Linie (optional) + }, + customId: 'targetWeightLine', + }); + } + + Plotly.relayout(this.profileDiv.nativeElement, this.lastChartLayout); + } + } + private getChartConfig() { if (this.isDetail === true) { const config = { @@ -1577,6 +1655,18 @@ export class BrewBrewingGraphComponent implements OnInit { newLayoutIsNeeded = true; } } + if (this.pressureTrace?.y?.length > 0) { + // #783 + const lastPressureData: number = + this.pressureTrace.y[this.pressureTrace.y.length - 1]; + if (lastPressureData > this.lastChartLayout.yaxis4.range[1]) { + this.lastChartLayout.yaxis4.range[1] = Math.ceil( + lastPressureData + 1 + ); + newLayoutIsNeeded = true; + } + } + if (newLayoutIsNeeded) { Plotly.relayout( this.profileDiv.nativeElement, @@ -1585,18 +1675,7 @@ export class BrewBrewingGraphComponent implements OnInit { } }, 25); } else { - const delay = moment(new Date()) - .startOf('day') - .add('seconds', 0) - .toDate() - .getTime(); - const delayedTime: number = moment(new Date()) - .startOf('day') - .add('seconds', this.brewComponent.timer.getSeconds() + 5) - .toDate() - .getTime(); - this.lastChartLayout.xaxis.range = [delay, delayedTime]; - Plotly.relayout(this.profileDiv.nativeElement, this.lastChartLayout); + // Not needed anymore } } catch (ex) {} }); @@ -1614,6 +1693,16 @@ export class BrewBrewingGraphComponent implements OnInit { // If users rests, we reset also drip time, else the script would not recognize it. this.data.coffee_first_drip_time = 0; this.data.coffee_first_drip_time_milliseconds = 0; + + const deviceType = + this.brewComponent?.brewBrewingPreparationDeviceEl?.getDataPreparationDeviceType(); + if (deviceType === PreparationDeviceType.XENIA) { + this.stopFetchingDataFromSanremoYOU(); + } else if (deviceType === PreparationDeviceType.METICULOUS) { + this.stopFetchingDataFromMeticulous(); + } else if (deviceType === PreparationDeviceType.SANREMO_YOU) { + this.stopFetchingDataFromSanremoYOU(); + } } if (scale || pressureDevice || temperatureDevice) { @@ -1753,6 +1842,7 @@ export class BrewBrewingGraphComponent implements OnInit { PreparationDeviceType.XENIA && this.machineStopScriptWasTriggered === false ) { + this.machineStopScriptWasTriggered = true; // If the event is not xenia, we pressed buttons, if the event was triggerd by xenia, timer already stopped. // If we press pause, stop scripts. this.uiLog.log(`Xenia Script - Pause button pressed, stop script`); @@ -1774,6 +1864,33 @@ export class BrewBrewingGraphComponent implements OnInit { ); this.stopFetchingAndSettingDataFromXenia(); } + if ( + this.brewComponent?.brewBrewingPreparationDeviceEl?.preparationDeviceConnected() && + this.brewComponent?.brewBrewingPreparationDeviceEl?.getPreparationDeviceType() === + PreparationDeviceType.SANREMO_YOU && + _event !== 'sanremo_you' && + _event !== 'shot_ended' && + this.machineStopScriptWasTriggered === false && + this.data.preparationDeviceBrew.params.selectedMode !== + SanremoYOUMode.LISTENING + ) { + //User pressed the pause button, or the stopAtWeight is bigger then 0 + this.machineStopScriptWasTriggered = true; + this.uiLog.log(`Sanremo YOU - Pause button pressed, stop shot`); + const prepDeviceCall: SanremoYOUDevice = this.brewComponent + ?.brewBrewingPreparationDeviceEl?.preparationDevice as SanremoYOUDevice; + prepDeviceCall + .stopShot(this.data.preparationDeviceBrew.params.selectedMode) + .catch((_msg) => { + this.uiToast.showInfoToast( + 'We could not stop - manual triggered: ' + _msg, + false + ); + this.uiLog.log('We could not stop - manual triggered: ' + _msg); + }); + this.stopFetchingDataFromSanremoYOU(); + } + if ( this.brewComponent?.brewBrewingPreparationDeviceEl?.preparationDeviceConnected() && this.brewComponent?.brewBrewingPreparationDeviceEl?.getPreparationDeviceType() === @@ -1826,6 +1943,34 @@ export class BrewBrewingGraphComponent implements OnInit { } } + public startFetchingDataFromSanremoYOU() { + const prepDeviceCall: SanremoYOUDevice = this.brewComponent + .brewBrewingPreparationDeviceEl.preparationDevice as SanremoYOUDevice; + + this.stopFetchingDataFromSanremoYOU(); + + const setSanremoData = () => { + const temp = prepDeviceCall.getTemperature(); + const press = prepDeviceCall.getPressure(); + this.__setPressureFlow({ actual: press, old: press }); + this.__setTemperatureFlow({ actual: temp, old: temp }); + }; + prepDeviceCall.fetchRuntimeData(() => { + // before we start the interval, we fetch the data once to overwrite, and set them. + setSanremoData(); + }); + + this.sanremoYOUFetchingInterval = setInterval(async () => { + try { + // We don't use the callback function to make sure we don't have to many performance issues + prepDeviceCall.fetchRuntimeData(() => { + //before we start the interval, we fetch the data once to overwrite, and set them. + setSanremoData(); + }); + } catch (ex) {} + }, 100); + } + public startFetchingDataFromMeticulous() { const prepDeviceCall: MeticulousDevice = this.brewComponent .brewBrewingPreparationDeviceEl.preparationDevice as MeticulousDevice; @@ -1961,6 +2106,13 @@ export class BrewBrewingGraphComponent implements OnInit { } } + public stopFetchingDataFromSanremoYOU() { + if (this.sanremoYOUFetchingInterval !== undefined) { + clearInterval(this.sanremoYOUFetchingInterval); + this.sanremoYOUFetchingInterval = undefined; + } + } + public stopFetchingDataFromMeticulous() { if (this.meticulousInterval !== undefined) { clearInterval(this.meticulousInterval); @@ -2065,7 +2217,7 @@ export class BrewBrewingGraphComponent implements OnInit { ); weight = weight + genRand(0.1, 2, 2); pressure = Math.floor( - (crypto.getRandomValues(new Uint8Array(1))[0] / Math.pow(2, 8)) * 11 + (crypto.getRandomValues(new Uint8Array(1))[0] / Math.pow(2, 8)) * 16 ); temperature = Math.floor( (crypto.getRandomValues(new Uint8Array(1))[0] / Math.pow(2, 8)) * 90 @@ -2225,36 +2377,62 @@ export class BrewBrewingGraphComponent implements OnInit { this.brewComponent?.brewBrewingPreparationDeviceEl?.getPreparationDeviceType() === PreparationDeviceType.METICULOUS ) { + const prepDeviceCall: MeticulousDevice = this.brewComponent + .brewBrewingPreparationDeviceEl.preparationDevice as MeticulousDevice; + + if (this.data.preparationDeviceBrew.params.chosenProfileId !== '') { + this.uiLog.log( + `A Meticulous profile was choosen, execute it - ${this.data.preparationDeviceBrew.params.chosenProfileId}` + ); + await prepDeviceCall.loadProfileByID( + this.data.preparationDeviceBrew.params.chosenProfileId + ); + await prepDeviceCall.startExecute(); + } else { + this.uiLog.log( + 'No Meticulous profile was selected, just listen for the start' + ); + } + if ( - this.brewComponent?.brewBrewingPreparationDeviceEl?.getPreparationDeviceType() === - PreparationDeviceType.METICULOUS + this.settings.bluetooth_scale_maximize_on_start_timer === true && + this.brewComponent.maximizeFlowGraphIsShown === false ) { - const prepDeviceCall: MeticulousDevice = this.brewComponent - .brewBrewingPreparationDeviceEl.preparationDevice as MeticulousDevice; + this.brewComponent.maximizeFlowGraph(); + } - if (this.data.preparationDeviceBrew.params.chosenProfileId !== '') { - this.uiLog.log( - `A Meticulous profile was choosen, execute it - ${this.data.preparationDeviceBrew.params.chosenProfileId}` - ); - await prepDeviceCall.loadProfileByID( - this.data.preparationDeviceBrew.params.chosenProfileId - ); - await prepDeviceCall.startExecute(); - } else { - this.uiLog.log( - 'No Meticulous profile was selected, just listen for the start' - ); - } + this.startFetchingDataFromMeticulous(); + } else if ( + this.brewComponent?.brewBrewingPreparationDeviceEl?.preparationDeviceConnected() && + this.brewComponent?.brewBrewingPreparationDeviceEl?.getPreparationDeviceType() === + PreparationDeviceType.SANREMO_YOU + ) { + const prepDeviceCall: SanremoYOUDevice = this.brewComponent + .brewBrewingPreparationDeviceEl.preparationDevice as SanremoYOUDevice; - if ( - this.settings.bluetooth_scale_maximize_on_start_timer === true && - this.brewComponent.maximizeFlowGraphIsShown === false - ) { - this.brewComponent.maximizeFlowGraph(); - } + if ( + this.data.preparationDeviceBrew?.params.selectedMode !== + SanremoYOUMode.LISTENING + ) { + prepDeviceCall + .startShot(this.data.preparationDeviceBrew?.params.selectedMode) + .catch((_msg) => { + this.uiLog.log('We could not start shot on sanremo: ' + _msg); + this.uiToast.showInfoToast( + 'We could not start shot on sanremo: ' + _msg, + false + ); + }); + } - this.startFetchingDataFromMeticulous(); + if ( + this.settings.bluetooth_scale_maximize_on_start_timer === true && + this.brewComponent.maximizeFlowGraphIsShown === false + ) { + this.brewComponent.maximizeFlowGraph(); } + + this.startFetchingDataFromSanremoYOU(); } } @@ -2292,6 +2470,11 @@ export class BrewBrewingGraphComponent implements OnInit { await this.timerReset(undefined); await this.brewComponent.timer.resetWithoutEmit(false); + if (scale.getScaleType() === ScaleType.BOKOOSCALE) { + //const bookooScale: BookooScale = scale as BookooScale; + //await bookooScale.tareAndStartTimerModeAuto(); + } + this.brewComponent.timer.checkChanges(); this.checkChanges(); @@ -2422,8 +2605,8 @@ export class BrewBrewingGraphComponent implements OnInit { const isEspressoBrew: boolean = this.data.getPreparation().style_type === PREPARATION_STYLE_TYPE.ESPRESSO; - this.textToSpeechWeightInterval = setInterval(() => { - this.ngZone.runOutsideAngular(() => { + this.ngZone.runOutsideAngular(() => { + this.textToSpeechWeightInterval = setInterval(() => { if (this.flowProfileTempAll.length > 0) { const actualScaleWeight = this.flowProfileTempAll.slice(-1)[0].weight; @@ -2443,18 +2626,15 @@ export class BrewBrewingGraphComponent implements OnInit { } } } - }); - }, this.settings.text_to_speech_interval_rate); - - this.textToSpeechTimerInterval = setInterval(() => { - this.ngZone.runOutsideAngular(() => { + }, this.settings.text_to_speech_interval_rate); + this.textToSpeechTimerInterval = setInterval(() => { this.textToSpeech.speak( this.translate.instant('TEXT_TO_SPEECH.TIME') + ' ' + this.data.brew_time ); - }); - }, 5000); + }, 5000); + }); } } @@ -2656,8 +2836,9 @@ export class BrewBrewingGraphComponent implements OnInit { * We try to match also turbo-shots which are like 7-8 grams. * Scales with just 3 values per second would be like 7 / 3 values per second = 2.33g increase each tick. * So we won't get jump from like 1 to 10 gram, then to like 40 grams + * Update 26.08.24 - We change from 5 to 10, because we had one shot where the value jumped from 0 to 5,5 and we didn't track anymore */ - const plausibleEspressoWeightIncreaseBound: number = 5; + const plausibleEspressoWeightIncreaseBound: number = 10; risingFactorOK = entryBeforeVal + plausibleEspressoWeightIncreaseBound >= weight; @@ -2691,6 +2872,169 @@ export class BrewBrewingGraphComponent implements OnInit { }; } + private calculateBrewByWeight( + _currentWeightValue: number, + _residualLagTime: number, + _targetWeight: number, + _brewByWeightActive: boolean, + _scale: BluetoothScale + ) { + let weight: number = this.uiHelper.toFixedIfNecessary( + _currentWeightValue, + 1 + ); + if (this.ignoreScaleWeight === true) { + if (this.flowProfileTempAll.length > 0) { + const oldFlowProfileTemp = + this.flowProfileTempAll[this.flowProfileTempAll.length - 1]; + weight = this.uiHelper.toFixedIfNecessary(oldFlowProfileTemp.weight, 1); + } + } + + if ( + this.flow_profile_raw.realtimeFlow && + this.flow_profile_raw.realtimeFlow.length > 0 + ) { + const targetWeight = _targetWeight; + + const brewByWeightActive: boolean = _brewByWeightActive; + + let n = 3; + if (this.flowNCalculation > 0) { + n = this.flowNCalculation; + } else { + n = this.flowProfileTempAll.length; + } + const lag_time = this.uiHelper.toFixedIfNecessary(1 / n, 2); + + let average_flow_rate = 0; + let lastFlowValue = 0; + + const linearArray = []; + + const weightFlowCalc: Array = + this.flow_profile_raw.weight.slice(-(n - 1)); + + for (let i = 0; i < weightFlowCalc.length; i++) { + if (weightFlowCalc[i] && weightFlowCalc[i].actual_weight) { + const linearArrayEntry = [i, weightFlowCalc[i].actual_weight]; + linearArray.push(linearArrayEntry); + } + } + linearArray.push([n - 1, weight]); + + const linearRegressionCalc = regression.linear(linearArray); + average_flow_rate = linearRegressionCalc.equation[0] * n; + + const scaleType = _scale.getScaleType(); + + this.pushBrewByWeight( + targetWeight, + lag_time, + this.flowTime + '.' + this.flowSecondTick, + lastFlowValue, + weight, + lag_time + _residualLagTime, + weight + average_flow_rate * (lag_time + _residualLagTime) >= + targetWeight, + average_flow_rate * (lag_time + _residualLagTime), + _residualLagTime, + average_flow_rate, + scaleType + ); + let thresholdHit: boolean = false; + if (brewByWeightActive) { + thresholdHit = + weight + average_flow_rate * (lag_time + _residualLagTime) >= + targetWeight; + } else { + thresholdHit = weight >= targetWeight; + } + return thresholdHit; + } + return false; + } + + private triggerStopShotOnXenia(_actualScaleWeight) { + const prepDeviceCall: XeniaDevice = this.brewComponent + .brewBrewingPreparationDeviceEl.preparationDevice as XeniaDevice; + if (this.data.preparationDeviceBrew.params.scriptAtWeightReachedId > 0) { + this.uiLog.log( + `Xenia Script - Weight Reached: ${_actualScaleWeight} - Trigger custom script` + ); + prepDeviceCall + .startScript( + this.data.preparationDeviceBrew.params.scriptAtWeightReachedId + ) + .catch((_msg) => { + this.uiToast.showInfoToast( + 'We could not start script at weight: ' + _msg, + false + ); + this.uiLog.log('We could not start script at weight: ' + _msg); + }); + this.writeExecutionTimeToNotes( + 'Weight reached script', + this.data.preparationDeviceBrew.params.scriptAtWeightReachedId, + this.getScriptName( + this.data.preparationDeviceBrew.params.scriptAtWeightReachedId + ) + ); + } else { + this.uiLog.log(`Xenia Script - Weight Reached - Trigger stop script`); + prepDeviceCall.stopScript().catch((_msg) => { + this.uiToast.showInfoToast( + 'We could not stop script at weight: ' + _msg, + false + ); + this.uiLog.log('We could not stop script at weight: ' + _msg); + }); + this.writeExecutionTimeToNotes( + 'Stop script (Weight reached)', + 0, + this.translate.instant( + 'PREPARATION_DEVICE.TYPE_XENIA.SCRIPT_LIST_GENERAL_STOP' + ) + ); + } + + // This will be just called once, we stopped the shot and now we check if we directly shall stop or not + if ( + this.settings.bluetooth_scale_espresso_stop_on_no_weight_change === false + ) { + this.stopFetchingAndSettingDataFromXenia(); + this.brewComponent.timer.pauseTimer('xenia'); + } else { + // We weight for the normal "setFlow" to stop the detection of the graph, there then aswell is the stop fetch of the xenia triggered. + } + } + + private triggerStopShotOnSanremoYOU(_actualScaleWeight) { + const prepDeviceCall: SanremoYOUDevice = this.brewComponent + .brewBrewingPreparationDeviceEl.preparationDevice as SanremoYOUDevice; + + this.uiLog.log(`Sanremo YOU Stop: ${_actualScaleWeight}`); + prepDeviceCall + .stopShot(this.data.preparationDeviceBrew.params.selectedMode) + .catch((_msg) => { + this.uiToast.showInfoToast( + 'We could not stop at weight: ' + _msg, + false + ); + this.uiLog.log('We could not stop script at weight: ' + _msg); + }); + + // This will be just called once, we stopped the shot and now we check if we directly shall stop or not + if ( + this.settings.bluetooth_scale_espresso_stop_on_no_weight_change === false + ) { + this.stopFetchingDataFromSanremoYOU(); + this.brewComponent.timer.pauseTimer('sanremo_you'); + } else { + // We weight for the normal "setFlow" to stop the detection of the graph, there then aswell is the stop fetch of the xenia triggered. + } + } + public attachToScaleWeightChange() { const scale: BluetoothScale = this.bleManager.getScale(); const preparationStyleType = this.data.getPreparation().style_type; @@ -2703,6 +3047,39 @@ export class BrewBrewingGraphComponent implements OnInit { } this.machineStopScriptWasTriggered = false; + + const prepDeviceConnected = + this.brewComponent.brewBrewingPreparationDeviceEl.preparationDeviceConnected(); + let residual_lag_time = 1.35; + let targetWeight = 0; + let brewByWeightActive: boolean = false; + let preparationDeviceType: PreparationDeviceType; + + if (prepDeviceConnected) { + preparationDeviceType = + this.brewComponent.brewBrewingPreparationDeviceEl.getPreparationDeviceType(); + switch (preparationDeviceType) { + case PreparationDeviceType.XENIA: + const prepXeniaDeviceCall: XeniaDevice = this.brewComponent + .brewBrewingPreparationDeviceEl.preparationDevice as XeniaDevice; + residual_lag_time = prepXeniaDeviceCall.getResidualLagTime(); + targetWeight = + this.data.preparationDeviceBrew.params + .scriptAtWeightReachedNumber; + brewByWeightActive = + this.data.preparationDeviceBrew?.params?.brew_by_weight_active; + break; + case PreparationDeviceType.SANREMO_YOU: + const prepSanremoDeviceCall: SanremoYOUDevice = this.brewComponent + .brewBrewingPreparationDeviceEl + .preparationDevice as SanremoYOUDevice; + residual_lag_time = prepSanremoDeviceCall.getResidualLagTime(); + targetWeight = this.data.preparationDeviceBrew.params.stopAtWeight; + brewByWeightActive = true; + break; + } + } + this.scaleFlowSubscription = scale.flowChange.subscribe((_valChange) => { let _val; if (this.ignoreScaleWeight === false) { @@ -2715,190 +3092,47 @@ export class BrewBrewingGraphComponent implements OnInit { _val = _valChange; } - if ( - this.brewComponent.timer.isTimerRunning() && - this.brewComponent.brewBrewingPreparationDeviceEl.preparationDeviceConnected() && - this.brewComponent.brewBrewingPreparationDeviceEl.getPreparationDeviceType() === - PreparationDeviceType.XENIA && - this.data.preparationDeviceBrew.params.scriptAtWeightReachedNumber > 0 - ) { - if (this.isFirstXeniaScriptSet()) { - let weight: number = this.uiHelper.toFixedIfNecessary( + if (this.brewComponent.timer.isTimerRunning() && prepDeviceConnected) { + if ( + preparationDeviceType === PreparationDeviceType.XENIA && + this.data.preparationDeviceBrew.params.scriptAtWeightReachedNumber > + 0 && + this.isFirstXeniaScriptSet() + ) { + /**We call this function before the if, because we still log the data**/ + const thresholdHit = this.calculateBrewByWeight( _val.actual, - 1 + residual_lag_time, + targetWeight, + brewByWeightActive, + scale ); - if (this.ignoreScaleWeight === true) { - if (this.flowProfileTempAll.length > 0) { - const oldFlowProfileTemp = - this.flowProfileTempAll[this.flowProfileTempAll.length - 1]; - weight = this.uiHelper.toFixedIfNecessary( - oldFlowProfileTemp.weight, - 1 - ); - } - } - - if ( - this.flow_profile_raw.realtimeFlow && - this.flow_profile_raw.realtimeFlow.length > 0 - ) { - const prepDeviceCall: XeniaDevice = this.brewComponent - .brewBrewingPreparationDeviceEl - .preparationDevice as XeniaDevice; - - const targetWeight = - this.data.preparationDeviceBrew.params - .scriptAtWeightReachedNumber; - - const brewByWeightActive: boolean = - this.data.preparationDeviceBrew?.params?.brew_by_weight_active; - - let n = 3; - if (this.flowNCalculation > 0) { - n = this.flowNCalculation; - } else { - n = this.flowProfileTempAll.length; - } - const lag_time = this.uiHelper.toFixedIfNecessary(1 / n, 2); - const residual_lag_time = prepDeviceCall.getResidualLagTime(); - - let average_flow_rate = 0; - let lastFlowValue = 0; - - const linearArray = []; - - const weightFlowCalc: Array = - this.flow_profile_raw.weight.slice(-(n - 1)); - for (let i = 0; i < weightFlowCalc.length; i++) { - if (weightFlowCalc[i] && weightFlowCalc[i].actual_weight) { - const linearArrayEntry = [i, weightFlowCalc[i].actual_weight]; - linearArray.push(linearArrayEntry); - } + if (this.machineStopScriptWasTriggered === false) { + if (thresholdHit) { + this.machineStopScriptWasTriggered = true; + this.triggerStopShotOnXenia(_val.actual); } - linearArray.push([n - 1, weight]); - - const linearRegressionCalc = regression.linear(linearArray); - average_flow_rate = linearRegressionCalc.equation[0] * n; - - /** Old calculcation - try { - lastFlowValue = - this.flow_profile_raw.realtimeFlow[ - this.flow_profile_raw.realtimeFlow.length - 1 - ].flow_value; - - const avgFlowValCalc: Array = - this.flow_profile_raw.realtimeFlow.slice(-n); - - for (let i = 0; i < avgFlowValCalc.length; i++) { - if (avgFlowValCalc[i] && avgFlowValCalc[i].flow_value) { - average_flow_rate = - average_flow_rate + avgFlowValCalc[i].flow_value; - } - } - if (average_flow_rate > 0) { - average_flow_rate = this.uiHelper.toFixedIfNecessary( - average_flow_rate / n, - 2 - ); - } - } catch (ex) {}**/ - - const scaleType = scale.getScaleType(); - - this.pushBrewByWeight( - this.data.preparationDeviceBrew.params - .scriptAtWeightReachedNumber, - lag_time, - this.flowTime + '.' + this.flowSecondTick, - lastFlowValue, - weight, - lag_time + residual_lag_time, - weight + average_flow_rate * (lag_time + residual_lag_time) >= - targetWeight, - average_flow_rate * (lag_time + residual_lag_time), - residual_lag_time, - average_flow_rate, - scaleType - ); - - if (this.machineStopScriptWasTriggered === false) { - let thresholdHit: boolean = false; - if (brewByWeightActive) { - thresholdHit = - weight + - average_flow_rate * (lag_time + residual_lag_time) >= - targetWeight; - } else { - thresholdHit = weight >= targetWeight; - } - - if (thresholdHit) { - if ( - this.data.preparationDeviceBrew.params - .scriptAtWeightReachedId > 0 - ) { - this.uiLog.log( - `Xenia Script - Weight Reached: ${weight} - Trigger custom script` - ); - prepDeviceCall - .startScript( - this.data.preparationDeviceBrew.params - .scriptAtWeightReachedId - ) - .catch((_msg) => { - this.uiToast.showInfoToast( - 'We could not start script at weight: ' + _msg, - false - ); - this.uiLog.log( - 'We could not start script at weight: ' + _msg - ); - }); - this.writeExecutionTimeToNotes( - 'Weight reached script', - this.data.preparationDeviceBrew.params - .scriptAtWeightReachedId, - this.getScriptName( - this.data.preparationDeviceBrew.params - .scriptAtWeightReachedId - ) - ); - } else { - this.uiLog.log( - `Xenia Script - Weight Reached - Trigger stop script` - ); - prepDeviceCall.stopScript().catch((_msg) => { - this.uiToast.showInfoToast( - 'We could not stop script at weight: ' + _msg, - false - ); - this.uiLog.log( - 'We could not stop script at weight: ' + _msg - ); - }); - this.writeExecutionTimeToNotes( - 'Stop script (Weight reached)', - 0, - this.translate.instant( - 'PREPARATION_DEVICE.TYPE_XENIA.SCRIPT_LIST_GENERAL_STOP' - ) - ); - } - this.machineStopScriptWasTriggered = true; - // This will be just called once, we stopped the shot and now we check if we directly shall stop or not - if ( - this.settings - .bluetooth_scale_espresso_stop_on_no_weight_change === - false - ) { - this.stopFetchingAndSettingDataFromXenia(); - this.brewComponent.timer.pauseTimer('xenia'); - } else { - // We weight for the normal "setFlow" to stop the detection of the graph, there then aswell is the stop fetch of the xenia triggered. - } - } + } + } else if ( + this.brewComponent.brewBrewingPreparationDeviceEl.getPreparationDeviceType() === + PreparationDeviceType.SANREMO_YOU && + this.data.preparationDeviceBrew.params.selectedMode !== + SanremoYOUMode.LISTENING + ) { + /**We call this function before the if, because we still log the data**/ + const thresholdHit = this.calculateBrewByWeight( + _val.actual, + residual_lag_time, + targetWeight, + brewByWeightActive, + scale + ); + if (this.machineStopScriptWasTriggered === false) { + //Don't stop the machine when the target weight is 0 + if (thresholdHit && targetWeight > 0) { + this.machineStopScriptWasTriggered = true; + this.triggerStopShotOnSanremoYOU(_val.actual); } } } @@ -2940,8 +3174,9 @@ export class BrewBrewingGraphComponent implements OnInit { this.deattachToScaleStartTareListening(); this.stopFetchingAndSettingDataFromXenia(); this.stopFetchingDataFromMeticulous(); + this.stopFetchingDataFromSanremoYOU(); - if (this.settings.text_to_speech_active) { + if (this.settings?.text_to_speech_active) { this.textToSpeech.end(); } } @@ -3514,6 +3749,13 @@ export class BrewBrewingGraphComponent implements OnInit { ) { this.stopFetchingAndSettingDataFromXenia(); } + if ( + this.brewComponent.brewBrewingPreparationDeviceEl.preparationDeviceConnected() && + this.brewComponent.brewBrewingPreparationDeviceEl.getPreparationDeviceType() === + PreparationDeviceType.SANREMO_YOU + ) { + this.stopFetchingDataFromSanremoYOU(); + } let isMeticulous: boolean = false; @@ -3529,7 +3771,7 @@ export class BrewBrewingGraphComponent implements OnInit { if (!isMeticulous) { // We have found a written weight which is above 5 grams at least this.__setScaleWeight(weight, false, false); - this.brewComponent.timer.pauseTimer(); + this.brewComponent.timer.pauseTimer('shot_ended'); this.changeDetectorRef.markForCheck(); this.brewComponent.timer.checkChanges(); this.checkChanges(); diff --git a/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.html b/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.html index 3e04427aa..5f07d67ac 100644 --- a/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.html +++ b/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.html @@ -73,6 +73,49 @@ +
+ + + {{"IMPORT_SHOT_FROM_METICULOUS" | translate}} + +
+ + + {{"PREPARATION_DEVICE.TYPE_SANREMO_YOU.TITLE" | translate }} + + + + + {{"PREPARATION_DEVICE.TYPE_SANREMO_YOU.MODE_LISTENING" | translate}} + + + {{"PREPARATION_DEVICE.TYPE_SANREMO_YOU.MANUAL_CONTROLLING" | translate}} + + + {{"PREPARATION_DEVICE.TYPE_SANREMO_YOU.PROFILE_P1_CONTROLLING" | translate}} + + + {{"PREPARATION_DEVICE.TYPE_SANREMO_YOU.PROFILE_P2_CONTROLLING" | translate}} + + + {{"PREPARATION_DEVICE.TYPE_SANREMO_YOU.PROFILE_P3_CONTROLLING" | translate}} + + + + + + + + + + + {{"PREPARATION_DEVICE.TYPE_SANREMO_YOU.NO_PROFILE_TARGET_WEIGHT_INFORMATION" | translate}} + {{"PREPARATION_DEVICE.TYPE_SANREMO_YOU.NO_MANUAL_TARGET_WEIGHT_INFORMATION" | translate}} + + + diff --git a/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.ts b/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.ts index 34998a4d2..9e8f8d087 100644 --- a/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.ts +++ b/src/components/brews/brew-brewing-preparation-device/brew-brewing-preparation-device.component.ts @@ -29,6 +29,23 @@ import { UISettingsStorage } from '../../../services/uiSettingsStorage'; import { Settings } from '../../../classes/settings/settings'; import { Preparation } from '../../../classes/preparation/preparation'; import { UIPreparationStorage } from '../../../services/uiPreparationStorage'; +import moment from 'moment'; +import { + BrewFlow, + IBrewPressureFlow, + IBrewRealtimeWaterFlow, + IBrewTemperatureFlow, + IBrewWeightFlow, +} from '../../../classes/brew/brewFlow'; +import { BrewModalImportShotMeticulousComponent } from '../../../app/brew/brew-modal-import-shot-meticulous/brew-modal-import-shot-meticulous.component'; +import { ModalController } from '@ionic/angular'; +import { HistoryListingEntry } from '@meticulous-home/espresso-api/dist/types'; +import { + SanremoYOUDevice, + SanremoYOUParams, +} from '../../../classes/preparationDevice/sanremo/sanremoYOUDevice'; +import { SanremoYOUMode } from '../../../enums/preparationDevice/sanremo/sanremoYOUMode'; + @Component({ selector: 'brew-brewing-preparation-device', templateUrl: './brew-brewing-preparation-device.component.html', @@ -39,7 +56,8 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { @Input() public isEdit: boolean = false; @Output() public dataChange = new EventEmitter(); @Input() public brewComponent: BrewBrewingComponent; - public preparationDevice: XeniaDevice | MeticulousDevice = undefined; + public preparationDevice: XeniaDevice | MeticulousDevice | SanremoYOUDevice = + undefined; public preparation: Preparation = undefined; public settings: Settings = undefined; @@ -55,10 +73,11 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { private readonly uiBrewStorage: UIBrewStorage, private readonly uiHelper: UIHelper, private readonly uiToast: UIToast, - private readonly uiBrewHelper: UIBrewHelper, private readonly uiSettingsStorage: UISettingsStorage, private readonly uiPreparationStorage: UIPreparationStorage, - private readonly changeDetectorRef: ChangeDetectorRef + private readonly changeDetectorRef: ChangeDetectorRef, + private readonly modalController: ModalController, + public readonly uiBrewHelper: UIBrewHelper ) {} public ngOnInit() { @@ -75,6 +94,36 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { } } + public hasTargetWeightActive() { + return this.getTargetWeight() > 0; + } + + public getTargetWeight(): number { + const connectedType = this.getDataPreparationDeviceType(); + let targetWeight = 0; + if (connectedType === PreparationDeviceType.XENIA) { + targetWeight = + this.data.preparationDeviceBrew?.params.scriptAtWeightReachedNumber; + } else if (connectedType === PreparationDeviceType.SANREMO_YOU) { + targetWeight = this.data.preparationDeviceBrew?.params.stopAtWeight; + } + return targetWeight; + } + + public sanremoSelectedModeChange() { + const selectedMode: SanremoYOUMode = + this.data.preparationDeviceBrew?.params.selectedMode; + if (selectedMode === SanremoYOUMode.LISTENING) { + ( + this.data.preparationDeviceBrew?.params as SanremoYOUParams + ).stopAtWeight = 0; + this.drawTargetWeight(0); + } + } + + public drawTargetWeight(_weight: number) { + this.brewComponent.brewBrewingGraphEl?.drawTargetWeight(_weight); + } public hasAPreparationDeviceSet() { return ( this.preparation?.connectedPreparationDevice.type !== @@ -95,6 +144,8 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { await this.instanceXeniaPreparationDevice(connectedDevice, _brew); } else if (connectedDevice instanceof MeticulousDevice) { await this.instanceMeticulousPreparationDevice(connectedDevice, _brew); + } else if (connectedDevice instanceof SanremoYOUDevice) { + await this.instanceSanremoYOUPreparationDevice(connectedDevice, _brew); } this.checkChanges(); } else { @@ -270,7 +321,7 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { this.data.preparationDeviceBrew.params = new MeticulousParams(); await connectedDevice.connectToSocket().then( - (_connected) => { + async (_connected) => { if (_connected) { this.preparationDevice = connectedDevice as MeticulousDevice; this.preparationDevice.loadProfiles(); @@ -282,11 +333,116 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { ); } + private async instanceSanremoYOUPreparationDevice( + connectedDevice: SanremoYOUDevice, + _brew: Brew = null + ) { + this.data.preparationDeviceBrew.type = PreparationDeviceType.SANREMO_YOU; + this.data.preparationDeviceBrew.params = new SanremoYOUParams(); + await connectedDevice.deviceConnected().then( + () => { + this.preparationDevice = connectedDevice as SanremoYOUDevice; + }, + () => { + //Not connected + } + ); + } + + public async importShotFromMeticulous() { + const modal = await this.modalController.create({ + component: BrewModalImportShotMeticulousComponent, + id: BrewModalImportShotMeticulousComponent.COMPONENT_ID, + componentProps: { + meticulousDevice: this.preparationDevice as MeticulousDevice, + }, + }); + + await modal.present(); + const rData = await modal.onWillDismiss(); + + if (rData && rData.data && rData.data.choosenHistory) { + const chosenEntry = rData.data.choosenHistory as HistoryListingEntry; + this.generateShotFlowProfileFromMeticulousData(chosenEntry); + } + } + + private generateShotFlowProfileFromMeticulousData(_historyData) { + const newMoment = moment(new Date()).startOf('day'); + + let firstDripTimeSet: boolean = false; + const newBrewFlow = new BrewFlow(); + + let seconds: number = 0; + let milliseconds: number = 0; + for (const entry of _historyData.data as any) { + const shotEntry: any = entry.shot; + const shotEntryTime = newMoment.clone().add('milliseconds', entry.time); + const timestamp = shotEntryTime.format('HH:mm:ss.SSS'); + + seconds = shotEntryTime.diff(newMoment, 'seconds'); + milliseconds = shotEntryTime.get('milliseconds'); + + const realtimeWaterFlow: IBrewRealtimeWaterFlow = + {} as IBrewRealtimeWaterFlow; + + realtimeWaterFlow.brew_time = ''; + realtimeWaterFlow.timestamp = timestamp; + realtimeWaterFlow.smoothed_weight = 0; + realtimeWaterFlow.flow_value = shotEntry.flow; + realtimeWaterFlow.timestampdelta = 0; + + newBrewFlow.realtimeFlow.push(realtimeWaterFlow); + + const brewFlow: IBrewWeightFlow = {} as IBrewWeightFlow; + brewFlow.timestamp = timestamp; + brewFlow.brew_time = ''; + brewFlow.actual_weight = shotEntry.weight; + brewFlow.old_weight = 0; + brewFlow.actual_smoothed_weight = 0; + brewFlow.old_smoothed_weight = 0; + brewFlow.not_mutated_weight = 0; + newBrewFlow.weight.push(brewFlow); + + if (shotEntry.weight > 0 && firstDripTimeSet === false) { + firstDripTimeSet = true; + + this.brewComponent.brewFirstDripTime?.setTime(seconds, milliseconds); + this.brewComponent.brewFirstDripTime?.changeEvent(); + } + + const pressureFlow: IBrewPressureFlow = {} as IBrewPressureFlow; + pressureFlow.timestamp = timestamp; + pressureFlow.brew_time = ''; + pressureFlow.actual_pressure = shotEntry.pressure; + pressureFlow.old_pressure = 0; + newBrewFlow.pressureFlow.push(pressureFlow); + + const temperatureFlow: IBrewTemperatureFlow = {} as IBrewTemperatureFlow; + temperatureFlow.timestamp = timestamp; + temperatureFlow.brew_time = ''; + temperatureFlow.actual_temperature = shotEntry.temperature; + temperatureFlow.old_temperature = 0; + newBrewFlow.temperatureFlow.push(temperatureFlow); + } + + const lastEntry = newBrewFlow.weight[newBrewFlow.weight.length - 1]; + this.brewComponent.data.brew_beverage_quantity = lastEntry.actual_weight; + + this.brewComponent.timer?.setTime(seconds, milliseconds); + this.brewComponent.timer?.changeEvent(); + + this.brewComponent.brewBrewingGraphEl.flow_profile_raw = newBrewFlow; + this.brewComponent.brewBrewingGraphEl.initializeFlowChart(true); + } + public getPreparationDeviceType() { if (this.preparationDevice instanceof XeniaDevice) { return PreparationDeviceType.XENIA; } else if (this.preparationDevice instanceof MeticulousDevice) { return PreparationDeviceType.METICULOUS; + } else if (this.preparationDevice instanceof SanremoYOUDevice) { + return PreparationDeviceType.SANREMO_YOU; } return PreparationDeviceType.NONE; } @@ -309,4 +465,6 @@ export class BrewBrewingPreparationDeviceComponent implements OnInit { } }, 50); } + + protected readonly SanremoYOUMode = SanremoYOUMode; } diff --git a/src/components/brews/brew-brewing/brew-brewing.component.spec.ts b/src/components/brews/brew-brewing/brew-brewing.component.spec.ts index a94842149..8bea215ca 100644 --- a/src/components/brews/brew-brewing/brew-brewing.component.spec.ts +++ b/src/components/brews/brew-brewing/brew-brewing.component.spec.ts @@ -13,8 +13,8 @@ import { ScreenOrientation } from '@awesome-cordova-plugins/screen-orientation/n import { HttpClientTestingModule } from '@angular/common/http/testing'; import { BrewBrewingGraphComponent } from '../brew-brewing-graph/brew-brewing-graph.component'; import { FormsModule } from '@angular/forms'; -import { KeysPipe } from 'src/pipes/keys'; import { BrewBrewingPreparationDeviceComponent } from '../brew-brewing-preparation-device/brew-brewing-preparation-device.component'; +import { PipesModule } from 'src/pipes/pipes.module'; describe('BrewBrewingComponent', () => { let component: BrewBrewingComponent; @@ -26,13 +26,13 @@ describe('BrewBrewingComponent', () => { BrewBrewingComponent, BrewBrewingGraphComponent, BrewBrewingPreparationDeviceComponent, - KeysPipe, ], imports: [ IonicModule.forRoot(), TranslateModule.forRoot(), HttpClientTestingModule, FormsModule, + PipesModule, ], providers: [ { provide: Storage }, diff --git a/src/components/graph-display-card/graph-display-card.component.ts b/src/components/graph-display-card/graph-display-card.component.ts index 64c389208..023557a70 100644 --- a/src/components/graph-display-card/graph-display-card.component.ts +++ b/src/components/graph-display-card/graph-display-card.component.ts @@ -15,6 +15,8 @@ import { UIHelper } from '../../services/uiHelper'; import { UIFileHelper } from '../../services/uiFileHelper'; import { Platform } from '@ionic/angular'; import { UISettingsStorage } from '../../services/uiSettingsStorage'; +import { HistoryListingEntry } from '@meticulous-home/espresso-api/dist/types'; +import { MeticulousDevice } from '../../classes/preparationDevice/meticulous/meticulousDevice'; declare var Plotly; @Component({ @@ -26,7 +28,10 @@ export class GraphDisplayCardComponent implements OnInit { @Input() public flowProfileData: any; @Input() public flowProfilePath: any; + @Input() public meticulousHistoryData: HistoryListingEntry; + @Input() public chartWidth: number; + @Input() public chartHeight: number; public flow_profile_raw: BrewFlow = new BrewFlow(); @@ -56,8 +61,12 @@ export class GraphDisplayCardComponent implements OnInit { this.settings = this.uiSettingsStorage.getSettings(); if (this.flowProfilePath) { await this.readFlowProfile(); - } else { + } else if (this.flowProfileData) { this.flow_profile_raw = this.uiHelper.cloneData(this.flowProfileData); + } else if (this.meticulousHistoryData) { + this.flow_profile_raw = MeticulousDevice.returnBrewFlowForShotData( + this.meticulousHistoryData.data + ); } setTimeout(() => { this.initializeFlowChart(); @@ -89,7 +98,10 @@ export class GraphDisplayCardComponent implements OnInit { chartWidth = this.chartWidth; } - const chartHeight: number = 150; + let chartHeight: number = 150; + if (this.chartHeight) { + chartHeight = this.chartHeight; + } let tickFormat = '%S'; @@ -151,6 +163,16 @@ export class GraphDisplayCardComponent implements OnInit { }, }; + const graph_pressure_settings = this.settings.graph_pressure; + const suggestedMinPressure: number = graph_pressure_settings.lower; + let suggestedMaxPressure = graph_pressure_settings.upper; + try { + if (this.pressureTrace?.y.length > 0) { + suggestedMaxPressure = Math.max(...this.pressureTrace.y); + suggestedMaxPressure = Math.ceil(suggestedMaxPressure + 1); + } + } catch (ex) {} + layout['yaxis4'] = { title: '', titlefont: { color: '#05C793' }, @@ -160,8 +182,8 @@ export class GraphDisplayCardComponent implements OnInit { side: 'right', fixedrange: true, showgrid: false, + range: [suggestedMinPressure, suggestedMaxPressure], position: 0.93, - range: [0, 10], visible: true, }; @@ -205,7 +227,6 @@ export class GraphDisplayCardComponent implements OnInit { try { Plotly.purge(this.profileDiv.nativeElement); } catch (ex) {} - const graphSettings = this.settings.graph.FILTER; this.weightTrace = { x: [], diff --git a/src/components/photo-add/photo-add.component.html b/src/components/photo-add/photo-add.component.html index c2975a83e..099304c2e 100644 --- a/src/components/photo-add/photo-add.component.html +++ b/src/components/photo-add/photo-add.component.html @@ -2,7 +2,7 @@  {{"ADD_PHOTO" | translate}}
- + diff --git a/src/components/photo-add/photo-add.component.ts b/src/components/photo-add/photo-add.component.ts index 273ebc951..ff93088a8 100644 --- a/src/components/photo-add/photo-add.component.ts +++ b/src/components/photo-add/photo-add.component.ts @@ -3,6 +3,7 @@ import { ElementRef, EventEmitter, Input, + OnDestroy, OnInit, Output, ViewChild, @@ -24,7 +25,7 @@ import { TranslateService } from '@ngx-translate/core'; templateUrl: './photo-add.component.html', styleUrls: ['./photo-add.component.scss'], }) -export class PhotoAddComponent implements OnInit { +export class PhotoAddComponent implements OnInit, OnDestroy { @Input() public data: Brew | Bean | GreenBean | Mill | Preparation; @Output() public dataChange = new EventEmitter< Brew | Bean | GreenBean | Mill | Preparation @@ -47,7 +48,7 @@ export class PhotoAddComponent implements OnInit { this.updateSlider(); }, 250); } - + public ngOnDestroy() {} public addImage(): void { this.uiImage.showOptionChooser().then((_option) => { if (_option === 'CHOOSE') { diff --git a/src/components/photo-view/photo-view.component.html b/src/components/photo-view/photo-view.component.html index eec878722..9b25041d7 100644 --- a/src/components/photo-view/photo-view.component.html +++ b/src/components/photo-view/photo-view.component.html @@ -1,5 +1,5 @@
- + diff --git a/src/components/preparation-information-card/preparation-information-card.component.ts b/src/components/preparation-information-card/preparation-information-card.component.ts index 578bbdcd2..b07e55ba3 100644 --- a/src/components/preparation-information-card/preparation-information-card.component.ts +++ b/src/components/preparation-information-card/preparation-information-card.component.ts @@ -217,17 +217,7 @@ export class PreparationInformationCardComponent implements OnInit { } public async connectDevice() { - this.uiAnalytics.trackEvent( - PREPARATION_TRACKING.TITLE, - PREPARATION_TRACKING.ACTIONS.CONNECT_DEVICE - ); - const modal = await this.modalController.create({ - component: PreparationConnectedDeviceComponent, - componentProps: { preparation: this.preparation }, - id: PreparationConnectedDeviceComponent.COMPONENT_ID, - }); - await modal.present(); - await modal.onWillDismiss(); + await this.uiPreparationHelper.connectDevice(this.preparation); } public async longPressEditPreparation(event) { diff --git a/src/components/timer/timer.component.spec.ts b/src/components/timer/timer.component.spec.ts index 3657bdc77..2febeb6aa 100644 --- a/src/components/timer/timer.component.spec.ts +++ b/src/components/timer/timer.component.spec.ts @@ -6,6 +6,7 @@ import { ModalController } from '@ionic/angular'; import { CoffeeBluetoothDevicesService } from '../../services/coffeeBluetoothDevices/coffee-bluetooth-devices.service'; import { UISettingsStorage } from '../../services/uiSettingsStorage'; import { Settings } from '../../classes/settings/settings'; +import { Device } from '@awesome-cordova-plugins/device/ngx'; describe('TimerComponent', () => { let component: TimerComponent; @@ -32,6 +33,9 @@ describe('TimerComponent', () => { }, }, }, + { + provide: Device, + }, ], }).compileComponents(); })); diff --git a/src/enums/beans/beanSortAfter.ts b/src/enums/beans/beanSortAfter.ts index 9c324f15f..5b0427365 100755 --- a/src/enums/beans/beanSortAfter.ts +++ b/src/enums/beans/beanSortAfter.ts @@ -5,6 +5,6 @@ export enum BEAN_SORT_AFTER { ROASTING_DATE = 'ROASTING DATE', CREATION_DATE = 'CREATION DATE', PURCHASE_DATE = 'PURCHASE DATE', - RATING ='RATING' - + RATING = 'RATING', + BEAN_AGE = 'BEAN AGE', } diff --git a/src/enums/brews/brewAction.ts b/src/enums/brews/brewAction.ts index 96dc6f74a..3cc746fa9 100755 --- a/src/enums/brews/brewAction.ts +++ b/src/enums/brews/brewAction.ts @@ -14,4 +14,5 @@ export enum BREW_ACTION { RATING = 'RATING', TOGGLE_BEST_BREW = 'TOGGLE_BEST_BREW', SHOW_VISUALIZER = 'SHOW_VISUALIZER', + SHOW_GRAPH = 'SHOW_GRAPH', } diff --git a/src/enums/preparationDevice/sanremo/sanremoYOUMode.ts b/src/enums/preparationDevice/sanremo/sanremoYOUMode.ts new file mode 100755 index 000000000..1eaf2fd5a --- /dev/null +++ b/src/enums/preparationDevice/sanremo/sanremoYOUMode.ts @@ -0,0 +1,7 @@ +export enum SanremoYOUMode { + LISTENING = 'LISTENING', + MANUAL_CONTROLLING = 'MANUAL_CONTROLLING', + PROFILE_P1_CONTROLLING = 'PROFILE_P1_CONTROLLING', + PROFILE_P2_CONTROLLING = 'PROFILE_P2_CONTROLLING', + PROFILE_P3_CONTROLLING = 'PROFILE_P3_CONTROLLING', +} diff --git a/src/enums/preparations/preparationTypes.ts b/src/enums/preparations/preparationTypes.ts index 57f5e0413..140e7d282 100755 --- a/src/enums/preparations/preparationTypes.ts +++ b/src/enums/preparations/preparationTypes.ts @@ -1,5 +1,8 @@ export enum PREPARATION_TYPES { CUSTOM_PREPARATION = 'CUSTOM_PREPARATION', + METICULOUS = 'METICULOUS', + SANREMO_YOU = 'SANREMO_YOU', + XENIA = 'XENIA', AEROPRESS = 'AEROPRESS', V60 = 'V60', CHEMEX = 'CHEMEX', @@ -40,5 +43,4 @@ export enum PREPARATION_TYPES { ROK = 'ROK', TORNADO_DUO = 'TORNADO_DUO', TRICOLATE = 'TRICOLATE', - METICULOUS = 'METICULOUS', } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 2a39fae03..073bb2299 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,4 +1,7 @@ export const environment = { production: true, - API_URL: 'https://backend.beanconqueror.com/' + API_URL: 'https://backend.beanconqueror.com/', + FEATURES_ACTIVE: { + SANREMO_YOU: false, + }, }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index faa76bbe0..0c4481274 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -5,6 +5,9 @@ export const environment = { production: false, API_URL: 'https://backend.beanconqueror.com/', + FEATURES_ACTIVE: { + SANREMO_YOU: false, + }, }; /* diff --git a/src/interfaces/preparationDevices/sanremoYOU/iSanremoYOUParams.ts b/src/interfaces/preparationDevices/sanremoYOU/iSanremoYOUParams.ts new file mode 100644 index 000000000..f753a6389 --- /dev/null +++ b/src/interfaces/preparationDevices/sanremoYOU/iSanremoYOUParams.ts @@ -0,0 +1,7 @@ +import { SanremoYOUMode } from '../../../enums/preparationDevice/sanremo/sanremoYOUMode'; + +export interface ISanremoYOUParams { + stopAtWeight: number; + residualLagTime: number; + selectedMode: SanremoYOUMode; +} diff --git a/src/interfaces/settings/iSettings.ts b/src/interfaces/settings/iSettings.ts index 60396ee0d..35d8d7c12 100755 --- a/src/interfaces/settings/iSettings.ts +++ b/src/interfaces/settings/iSettings.ts @@ -84,6 +84,16 @@ export interface ISettings { ARCHIVED: IBeanPageSort; }; + bean_collapsed: { + OPEN: boolean; + ARCHIVED: boolean; + FROZEN: boolean; + }; + brew_collapsed: { + OPEN: boolean; + ARCHIVED: boolean; + }; + green_bean_sort: { OPEN: IBeanPageSort; ARCHIVED: IBeanPageSort; @@ -125,6 +135,10 @@ export interface ISettings { lower: number; }; }; + graph_pressure: { + upper: number; + lower: number; + }; welcome_page_showed: boolean; diff --git a/src/pipes/pipes.module.ts b/src/pipes/pipes.module.ts new file mode 100644 index 000000000..e8246313b --- /dev/null +++ b/src/pipes/pipes.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { KeysPipe } from './keys'; +import { EnumToArrayPipe } from './enumToArray'; +import { FormatDatePipe } from './formatDate'; + +@NgModule({ + declarations: [EnumToArrayPipe, FormatDatePipe, KeysPipe], + exports: [EnumToArrayPipe, FormatDatePipe, KeysPipe], +}) +export class PipesModule {} diff --git a/src/popover/data-corruption-found/data-corruption-found.component.html b/src/popover/data-corruption-found/data-corruption-found.component.html new file mode 100644 index 000000000..8f17dd60e --- /dev/null +++ b/src/popover/data-corruption-found/data-corruption-found.component.html @@ -0,0 +1,69 @@ + + Data corruption found + + + + + + It somehow looks that your current data are corrupted, we've checked the backup file, in the following list you'll see how much data you've stored actually and how many are stored in your backup.
+ You can then choose to restore this backup or not.
+ If you don't feel save right now please make a screenshot of this screen, contact info@beanconqueror.com and close this app completely - any issues will be prevent with this +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Key + Actual Storage + + Backup +
Beans{{actualUIStorageDataObj?.BEANS}}{{backupDataObj?.BEANS?.length}}
Brews{{actualUIStorageDataObj?.BREWS}}{{backupDataObj?.BREWS?.length}}
Preparation{{actualUIStorageDataObj?.PREPARATION}}{{backupDataObj?.PREPARATION?.length}}
Grinder{{actualUIStorageDataObj?.MILL}}{{backupDataObj?.MILL?.length}}
+
+
+
+ + + + + Dont import backup + + + Import backup + + + + + diff --git a/src/popover/data-corruption-found/data-corruption-found.component.scss b/src/popover/data-corruption-found/data-corruption-found.component.scss new file mode 100644 index 000000000..25eeb2a52 --- /dev/null +++ b/src/popover/data-corruption-found/data-corruption-found.component.scss @@ -0,0 +1,6 @@ +:host { + table { + width:100%; + text-align:left; + } +} diff --git a/src/popover/data-corruption-found/data-corruption-found.component.spec.ts b/src/popover/data-corruption-found/data-corruption-found.component.spec.ts new file mode 100644 index 000000000..6962cefa5 --- /dev/null +++ b/src/popover/data-corruption-found/data-corruption-found.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { DataCorruptionFoundComponent } from './data-corruption-found.component'; + +describe('DataCorruptionFoundComponent', () => { + let component: DataCorruptionFoundComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [DataCorruptionFoundComponent], + imports: [IonicModule.forRoot()], + }).compileComponents(); + + fixture = TestBed.createComponent(DataCorruptionFoundComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/popover/data-corruption-found/data-corruption-found.component.ts b/src/popover/data-corruption-found/data-corruption-found.component.ts new file mode 100644 index 000000000..30fc8e56e --- /dev/null +++ b/src/popover/data-corruption-found/data-corruption-found.component.ts @@ -0,0 +1,61 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ModalController, Platform } from '@ionic/angular'; + +@Component({ + selector: 'app-data-corruption-found', + templateUrl: './data-corruption-found.component.html', + styleUrls: ['./data-corruption-found.component.scss'], +}) +export class DataCorruptionFoundComponent implements OnInit { + public static POPOVER_ID: string = 'data-corruption-found-popover'; + + @Input() public actualUIStorageDataObj: any = undefined; + @Input() public backupDataObj: any = undefined; + + private disableHardwareBack; + + constructor( + private readonly modalController: ModalController, + private readonly platform: Platform + ) {} + + public ngOnInit() { + try { + this.disableHardwareBack = this.platform.backButton.subscribeWithPriority( + 9999, + (processNextHandler) => { + // Don't do anything. + } + ); + } catch (ex) {} + } + + public async dismiss() { + try { + this.disableHardwareBack.unsubscribe(); + } catch (ex) {} + + this.modalController.dismiss( + { + dismissed: true, + }, + undefined, + DataCorruptionFoundComponent.POPOVER_ID + ); + } + + public async import() { + try { + this.disableHardwareBack.unsubscribe(); + } catch (ex) {} + + this.modalController.dismiss( + { + dismissed: true, + import: true, + }, + undefined, + DataCorruptionFoundComponent.POPOVER_ID + ); + } +} diff --git a/src/popover/meticulous-help-popover/meticulous-help-popover.component.spec.ts b/src/popover/meticulous-help-popover/meticulous-help-popover.component.spec.ts index f4cd90453..dd6f7c190 100644 --- a/src/popover/meticulous-help-popover/meticulous-help-popover.component.spec.ts +++ b/src/popover/meticulous-help-popover/meticulous-help-popover.component.spec.ts @@ -2,21 +2,33 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { IonicModule } from '@ionic/angular'; import { MeticulousHelpPopoverComponent } from './meticulous-help-popover.component'; +import { IonicStorageModule } from '@ionic/storage-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { UIHelper } from 'src/services/uiHelper'; +import { UIHelperMock } from 'src/classes/mock'; -describe('MeticulousHelpPopoverComponent', () => { +describe('', () => { let component: MeticulousHelpPopoverComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [MeticulousHelpPopoverComponent], - imports: [IonicModule.forRoot()], + imports: [ + IonicModule.forRoot(), + IonicStorageModule.forRoot(), + // TranslateModule.forChild(), + TranslateModule.forRoot(), + ], + providers: [{ provide: UIHelper, useClass: UIHelperMock }], }).compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(MeticulousHelpPopoverComponent); component = fixture.componentInstance; fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/popover/photo-popover/photo-popover.component.html b/src/popover/photo-popover/photo-popover.component.html index ecb0bc64c..feb076c2d 100644 --- a/src/popover/photo-popover/photo-popover.component.html +++ b/src/popover/photo-popover/photo-popover.component.html @@ -10,7 +10,7 @@
- + diff --git a/src/popover/update-popover/update-popover.component.html b/src/popover/update-popover/update-popover.component.html index 5cb174de2..376a1bb6f 100644 --- a/src/popover/update-popover/update-popover.component.html +++ b/src/popover/update-popover/update-popover.component.html @@ -1,6 +1,6 @@ - +
diff --git a/src/popover/welcome-popover/welcome-popover.component.html b/src/popover/welcome-popover/welcome-popover.component.html index 97da5ca43..4098a24a4 100644 --- a/src/popover/welcome-popover/welcome-popover.component.html +++ b/src/popover/welcome-popover/welcome-popover.component.html @@ -30,7 +30,7 @@ - +
diff --git a/src/popover/welcome-popover/welcome-popover.component.ts b/src/popover/welcome-popover/welcome-popover.component.ts index 38c67d8ad..25145648a 100644 --- a/src/popover/welcome-popover/welcome-popover.component.ts +++ b/src/popover/welcome-popover/welcome-popover.component.ts @@ -12,12 +12,6 @@ import { UIPreparationHelper } from '../../services/uiPreparationHelper'; styleUrls: ['./welcome-popover.component.scss'], }) export class WelcomePopoverComponent implements OnInit { - public slideOpts = { - allowTouchMove: false, - speed: 400, - slide: 4, - }; - public slide: number = 1; @ViewChild('slider', { static: false }) public welcomeSlider: | ElementRef diff --git a/src/services/coffeeBluetoothDevices/coffee-bluetooth-devices.service.ts b/src/services/coffeeBluetoothDevices/coffee-bluetooth-devices.service.ts index 39f104380..a23dd3d47 100644 --- a/src/services/coffeeBluetoothDevices/coffee-bluetooth-devices.service.ts +++ b/src/services/coffeeBluetoothDevices/coffee-bluetooth-devices.service.ts @@ -41,6 +41,7 @@ import { BookooPressure } from 'src/classes/devices/bookooPressure'; import { BasicGrillThermometer } from 'src/classes/devices/basicGrillThermometer'; import { MeaterThermometer } from 'src/classes/devices/meaterThermometer'; import { CombustionThermometer } from '../../classes/devices/combustionThermometer'; +import { ArgosThermometer } from '../../classes/devices/argosThermometer'; declare var device: any; declare var ble: any; @@ -546,7 +547,8 @@ export class CoffeeBluetoothDevicesService { ETITemperature.test(scanDevice) || BasicGrillThermometer.test(scanDevice) || MeaterThermometer.test(scanDevice) || - CombustionThermometer.test(scanDevice) + CombustionThermometer.test(scanDevice) || + ArgosThermometer.test(scanDevice) ) { // We found all needed devices. promiseResolved = true; @@ -1046,6 +1048,15 @@ export class CoffeeBluetoothDevicesService { id: deviceTemperature.id, type: TemperatureType.COMBUSTION, }); + } else if (ArgosThermometer.test(deviceTemperature)) { + this.logger.log( + 'BleManager - We found a Argos Thermometer device ' + + JSON.stringify(deviceTemperature) + ); + supportedDevices.push({ + id: deviceTemperature.id, + type: TemperatureType.ARGOS, + }); } } resolve(supportedDevices); @@ -1091,6 +1102,15 @@ export class CoffeeBluetoothDevicesService { type: TemperatureType.COMBUSTION, }); return; + } else if (ArgosThermometer.test(deviceTemperature)) { + this.logger.log( + 'BleManager - We found a Argos Thermometer device ' + ); + resolve({ + id: deviceTemperature.id, + type: TemperatureType.ARGOS, + }); + return; } } resolve(undefined); diff --git a/src/services/uiBeanHelper.ts b/src/services/uiBeanHelper.ts index 6c3980d54..be5600be8 100644 --- a/src/services/uiBeanHelper.ts +++ b/src/services/uiBeanHelper.ts @@ -29,6 +29,8 @@ import { BEAN_ROASTING_TYPE_ENUM } from '../enums/beans/beanRoastingType'; import { AssociatedBrewsComponent } from '../app/brew/associated-brews/associated-brews.component'; import { BrewCuppingComponent } from '../app/brew/brew-cupping/brew-cupping.component'; import { BeanPopoverFreezeComponent } from '../app/beans/bean-popover-freeze/bean-popover-freeze.component'; +import { BeanPopoverFrozenListComponent } from '../app/beans/bean-popover-frozen-list/bean-popover-frozen-list.component'; +import { BeanPopoverListComponent } from '../app/beans/bean-popover-list/bean-popover-list.component'; /** * Handles every helping functionalities @@ -319,6 +321,16 @@ export class UIBeanHelper { await modal.onWillDismiss(); } + public async showBeans(_beanList: Array) { + const modal = await this.modalController.create({ + component: BeanPopoverListComponent, + id: BeanPopoverListComponent.COMPONENT_ID, + componentProps: { beansList: _beanList }, + }); + await modal.present(); + await modal.onWillDismiss(); + } + public async editBean(_bean: Bean) { const modal = await this.modalController.create({ component: BeansEditComponent, diff --git a/src/services/uiExcel.ts b/src/services/uiExcel.ts index d52ebf160..d8f3f7f32 100755 --- a/src/services/uiExcel.ts +++ b/src/services/uiExcel.ts @@ -15,7 +15,6 @@ import { BEAN_ROASTING_TYPE_ENUM } from '../enums/beans/beanRoastingType'; import { BEAN_MIX_ENUM } from '../enums/beans/mix'; import { UIPreparationStorage } from './uiPreparationStorage'; import { UIAlert } from './uiAlert'; -import { SocialSharing } from '@awesome-cordova-plugins/social-sharing/ngx'; import { UIFileHelper } from './uiFileHelper'; import { UIMillStorage } from './uiMillStorage'; import { BrewFlow, IBrewWaterFlow } from '../classes/brew/brewFlow'; @@ -23,6 +22,11 @@ import moment from 'moment'; import { UISettingsStorage } from './uiSettingsStorage'; import { Settings } from '../classes/settings/settings'; import { Brew } from '../classes/brew/brew'; +import { Bean } from '../classes/bean/bean'; +import { IBeanInformation } from '../interfaces/bean/iBeanInformation'; +import { UIBeanHelper } from './uiBeanHelper'; +import { GreenBean } from '../classes/green-bean/green-bean'; +import { UIGreenBeanStorage } from './uiGreenBeanStorage'; @Injectable({ providedIn: 'root', @@ -37,14 +41,14 @@ export class UIExcel { private readonly platform: Platform, private readonly uiBrewStorage: UIBrewStorage, private readonly uiBeanStorage: UIBeanStorage, + private readonly uiGreenBeanStorage: UIGreenBeanStorage, private readonly uiPreparationStoraage: UIPreparationStorage, private readonly translate: TranslateService, private readonly uiAlert: UIAlert, - private readonly socialsharing: SocialSharing, private readonly uiFileHelper: UIFileHelper, private readonly uiMillStorage: UIMillStorage, - private readonly alertCtrl: AlertController, - private readonly uiSettingsStorage: UISettingsStorage + private readonly uiSettingsStorage: UISettingsStorage, + private readonly uiBeanHelper: UIBeanHelper ) { this.settings = this.uiSettingsStorage.getSettings(); } @@ -80,8 +84,6 @@ export class UIExcel { const wsData: any[][] = [header]; for (const entry of _flow.weight) { - const notMutatedWeight: number = 0; - const wbEntry: Array = [ entry.timestamp, entry.brew_time, @@ -719,18 +721,530 @@ export class UIExcel { } } + public async importGreenBeansByExcel(_arrayBuffer) { + try { + /* data is an ArrayBuffer */ + const wb = XLSX.read(_arrayBuffer); + const data = XLSX.utils.sheet_to_json(wb.Sheets['Green Beans']); + + let varietyInformationWhereAdded: boolean = false; + const addedBeans: Array = []; + const toAddBeans: Array = []; + for (const entry of data) { + /** + * 1. Bean certification: 14 + * 1. Country: 5 + * 1. Elevation: 9 + * 1. Farm: 7 + * 1. Farmer: 8 + * 1. Harvested: 12 + * 1. Percentage: 13 + * 1. Processing: 11 + * 1. Region: 6 + * 1. Variety: 10 + * 1. Fob Price + * 1. Purchasing Price + * Archived: false - + * Cost: 13 - + * Cupping points: 92 - + * Decaffeinated: false - + * EAN / Articlenumber: 2 - + * Flavour profile: "Red, whine, apple" - + * Name: 123 - + * Notes: 3 + * Rating: 4 + * Website: 1 - + * Weight: 250 - + * Buy Date + */ + + const bean: GreenBean = new GreenBean(); + + const nameEntry = entry['Name']; + if (nameEntry) { + bean.name = nameEntry.toString(); + } else { + continue; + } + + const weightEntry = entry['Weight']; + if (weightEntry && Number(weightEntry) > 0) { + bean.weight = Number(weightEntry); + } + + const buyDateEntry = entry['Buy Date']; + if (buyDateEntry && Number(buyDateEntry) > 0) { + bean.date = this.getJsDateFromExcel(Number(buyDateEntry)); + } + + const websiteEntry = entry['Website']; + if (websiteEntry) { + bean.url = websiteEntry.toString(); + } + + const flavourProfileEntry = entry['Flavour profile']; + if (flavourProfileEntry) { + bean.aromatics = flavourProfileEntry.toString(); + } + + const eanArticleNumberEntry = entry['EAN / Articlenumber']; + if (eanArticleNumberEntry) { + bean.ean_article_number = eanArticleNumberEntry.toString(); + } + + const decaffeinatedEntry = entry['Decaffeinated']; + if (decaffeinatedEntry) { + bean.decaffeinated = decaffeinatedEntry; + } + + const cuppingPointsEntry = entry['Cupping points']; + if (cuppingPointsEntry) { + bean.cupping_points = cuppingPointsEntry.toString(); + } + + const costEntry = entry['Cost']; + if (costEntry) { + bean.cost = Number(costEntry); + } + + const archivedEntry = entry['Archived']; + if (archivedEntry) { + bean.finished = archivedEntry; + } + + const beanInformation: IBeanInformation = {} as IBeanInformation; + let hasOneBeanInformation: boolean = false; + + const informationCertificationEntry = entry['1. Bean certification']; + if (informationCertificationEntry) { + beanInformation.certification = + informationCertificationEntry.toString(); + hasOneBeanInformation = true; + } + + const informationCountryEntry = entry['1. Country']; + if (informationCountryEntry) { + beanInformation.country = informationCountryEntry.toString(); + hasOneBeanInformation = true; + } + + const informationElevationEntry = entry['1. Elevation']; + if (informationElevationEntry) { + beanInformation.elevation = informationElevationEntry.toString(); + hasOneBeanInformation = true; + } + + const informationFarmEntry = entry['1. Farm']; + if (informationFarmEntry) { + beanInformation.farm = informationFarmEntry.toString(); + hasOneBeanInformation = true; + } + + const informationFarmerEntry = entry['1. Farmer']; + if (informationFarmerEntry) { + beanInformation.farmer = informationFarmerEntry.toString(); + hasOneBeanInformation = true; + } + + const informationHarvestedEntry = entry['1. Harvested']; + if (informationHarvestedEntry) { + beanInformation.harvest_time = informationHarvestedEntry.toString(); + hasOneBeanInformation = true; + } + + const informationPercentageEntry = entry['1. Percentage']; + if ( + informationPercentageEntry && + Number(informationPercentageEntry) > 0 + ) { + beanInformation.percentage = Number(informationPercentageEntry); + hasOneBeanInformation = true; + } + + const informationProcessingEntry = entry['1. Processing']; + if (informationProcessingEntry) { + beanInformation.processing = informationProcessingEntry.toString(); + hasOneBeanInformation = true; + } + + const informationRegionEntry = entry['1. Region']; + if (informationRegionEntry) { + beanInformation.region = informationRegionEntry.toString(); + hasOneBeanInformation = true; + } + + const informationVarietyEntry = entry['1. Variety']; + if (informationVarietyEntry) { + beanInformation.variety = informationVarietyEntry.toString(); + hasOneBeanInformation = true; + } + + const informationFobPriceEntry = entry['1. Fob Price']; + if (informationFobPriceEntry && Number(informationFobPriceEntry) > 0) { + beanInformation.fob_price = Number(informationFobPriceEntry); + hasOneBeanInformation = true; + } + + const informationPurchasingPriceEntry = entry['1. Purchasing Price']; + if ( + informationPurchasingPriceEntry && + Number(informationPurchasingPriceEntry) > 0 + ) { + beanInformation.purchasing_price = Number( + informationPurchasingPriceEntry + ); + hasOneBeanInformation = true; + } + + if (hasOneBeanInformation) { + varietyInformationWhereAdded = true; + bean.bean_information.push(beanInformation); + } else { + /** Add atleast one empty bean information**/ + const emptyBeanInformation: IBeanInformation = {} as IBeanInformation; + bean.bean_information.push(emptyBeanInformation); + } + + toAddBeans.push(bean); + } + + if (varietyInformationWhereAdded) { + if (this.settings.bean_manage_parameters.bean_information === false) { + this.settings.bean_manage_parameters.bean_information = true; + await this.uiSettingsStorage.saveSettings(this.settings); + } + } + + /** + * Add all beans afterwards to not have some added and then going into an exception + */ + for await (const addBean of toAddBeans) { + try { + const newBean: GreenBean = await this.uiGreenBeanStorage.add(addBean); + addedBeans.push(newBean); + } catch (ex) {} + } + + if (addedBeans.length > 0) { + try { + await this.uiAlert.showMessage( + 'GREEN_BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION', + 'IMPORT_SUCCESSFULLY', + undefined, + true + ); + } catch (ex) {} + } else { + this.uiAlert.showMessage( + 'BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE', + 'IMPORT_UNSUCCESSFULLY', + 'OK', + false + ); + } + } catch (ex) { + this.uiAlert.showMessage( + ex.message, + this.translate.instant('IMPORT_UNSUCCESSFULLY'), + this.translate.instant('OK'), + false + ); + } + } + public async importBeansByExcel(_arrayBuffer) { try { /* data is an ArrayBuffer */ const wb = XLSX.read(_arrayBuffer); const data = XLSX.utils.sheet_to_json(wb.Sheets['Beans']); + + let isOneEntryFrozen: boolean = false; + let varietyInformationWhereAdded: boolean = false; + const addedBeans: Array = []; + const toAddBeans: Array = []; for (const entry of data) { - //const bean: Bean = new Bean(); - //bean.bean_information + /** + * 1. Bean certification: 14 + * 1. Country: 5 + * 1. Elevation: 9 + * 1. Farm: 7 + * 1. Farmer: 8 + * 1. Harvested: 12 + * 1. Percentage: 13 + * 1. Processing: 11 + * 1. Region: 6 + * 1. Variety: 10 + * 1. Fob Price + * 1. Purchasing Price + * Archived: false - + * Blend: "UNKNOWN" - + * Cost: 13 - + * Cupping points: 92 - + * Decaffeinated: false - + * Degree of Roast: "UNKNOWN" - + * EAN / Articlenumber: 2 - + * Flavour profile: "Red, whine, apple" - + * Name: 123 - + * Notes: 3 + * Rating: 4 + * Roast date: 44593 + * Roast type: "FILTER" - + * Roaster: 123 - + * Website: 1 - + * Weight: 250 - + * Frozen Date + * Unfrozen Date + * Freezing Storage Type + * Frozen Note + */ + + const bean: Bean = new Bean(); + + const nameEntry = entry['Name']; + if (nameEntry) { + bean.name = nameEntry.toString(); + } else { + continue; + } + + const weightEntry = entry['Weight']; + if (weightEntry && Number(weightEntry) > 0) { + bean.weight = Number(weightEntry); + } + + const roastDateEntry = entry['Roast date']; + if (roastDateEntry && Number(roastDateEntry) > 0) { + bean.roastingDate = this.getJsDateFromExcel(Number(roastDateEntry)); + } + + const websiteEntry = entry['Website']; + if (websiteEntry) { + bean.url = websiteEntry.toString(); + } + + const roasterEntry = entry['Roaster']; + if (roasterEntry) { + bean.roaster = roasterEntry.toString(); + } + + const roastTypeEntry = entry['Roast type']; + if (roastTypeEntry) { + bean.bean_roasting_type = roastTypeEntry; + } + + const flavourProfileEntry = entry['Flavour profile']; + if (flavourProfileEntry) { + bean.aromatics = flavourProfileEntry.toString(); + } + + const eanArticleNumberEntry = entry['EAN / Articlenumber']; + if (eanArticleNumberEntry) { + bean.ean_article_number = eanArticleNumberEntry.toString(); + } + + const degreeOfRoastEntry = entry['Degree of Roast']; + if (degreeOfRoastEntry) { + bean.roast = degreeOfRoastEntry; + } + const decaffeinatedEntry = entry['Decaffeinated']; + if (decaffeinatedEntry) { + bean.decaffeinated = decaffeinatedEntry; + } + + const cuppingPointsEntry = entry['Cupping points']; + if (cuppingPointsEntry) { + bean.cupping_points = cuppingPointsEntry.toString(); + } + + const costEntry = entry['Cost']; + if (costEntry) { + bean.cost = Number(costEntry); + } + + const blendEntry = entry['Blend']; + if (blendEntry) { + bean.beanMix = blendEntry; + } + + const archivedEntry = entry['Archived']; + if (archivedEntry) { + bean.finished = archivedEntry; + } + + const beanInformation: IBeanInformation = {} as IBeanInformation; + let hasOneBeanInformation: boolean = false; + + const informationCertificationEntry = entry['1. Bean certification']; + if (informationCertificationEntry) { + beanInformation.certification = + informationCertificationEntry.toString(); + hasOneBeanInformation = true; + } + + const informationCountryEntry = entry['1. Country']; + if (informationCountryEntry) { + beanInformation.country = informationCountryEntry.toString(); + hasOneBeanInformation = true; + } + + const informationElevationEntry = entry['1. Elevation']; + if (informationElevationEntry) { + beanInformation.elevation = informationElevationEntry.toString(); + hasOneBeanInformation = true; + } + + const informationFarmEntry = entry['1. Farm']; + if (informationFarmEntry) { + beanInformation.farm = informationFarmEntry.toString(); + hasOneBeanInformation = true; + } + + const informationFarmerEntry = entry['1. Farmer']; + if (informationFarmerEntry) { + beanInformation.farmer = informationFarmerEntry.toString(); + hasOneBeanInformation = true; + } + + const informationHarvestedEntry = entry['1. Harvested']; + if (informationHarvestedEntry) { + beanInformation.harvest_time = informationHarvestedEntry.toString(); + hasOneBeanInformation = true; + } + + const informationPercentageEntry = entry['1. Percentage']; + if ( + informationPercentageEntry && + Number(informationPercentageEntry) > 0 + ) { + beanInformation.percentage = Number(informationPercentageEntry); + hasOneBeanInformation = true; + } + + const informationProcessingEntry = entry['1. Processing']; + if (informationProcessingEntry) { + beanInformation.processing = informationProcessingEntry.toString(); + hasOneBeanInformation = true; + } + + const informationRegionEntry = entry['1. Region']; + if (informationRegionEntry) { + beanInformation.region = informationRegionEntry.toString(); + hasOneBeanInformation = true; + } + + const informationVarietyEntry = entry['1. Variety']; + if (informationVarietyEntry) { + beanInformation.variety = informationVarietyEntry.toString(); + hasOneBeanInformation = true; + } + + const informationFobPriceEntry = entry['1. Fob Price']; + if (informationFobPriceEntry && Number(informationFobPriceEntry) > 0) { + beanInformation.fob_price = Number(informationFobPriceEntry); + hasOneBeanInformation = true; + } + + const informationPurchasingPriceEntry = entry['1. Purchasing Price']; + if ( + informationPurchasingPriceEntry && + Number(informationPurchasingPriceEntry) > 0 + ) { + beanInformation.purchasing_price = Number( + informationPurchasingPriceEntry + ); + hasOneBeanInformation = true; + } + + if (hasOneBeanInformation) { + varietyInformationWhereAdded = true; + bean.bean_information.push(beanInformation); + } else { + /** Add atleast one empty bean information**/ + const emptyBeanInformation: IBeanInformation = {} as IBeanInformation; + bean.bean_information.push(emptyBeanInformation); + } + + const freezingStorageTypeEntry = entry['Freezing Storage Type']; + if (freezingStorageTypeEntry) { + bean.frozenStorageType = freezingStorageTypeEntry; + } + + const frozenNoteEntry = entry['Frozen Note']; + if (frozenNoteEntry) { + bean.frozenNote = frozenNoteEntry; + } + + const frozenDateEntry = entry['Frozen Date']; + if (frozenDateEntry && Number(frozenDateEntry) > 0) { + isOneEntryFrozen = true; + bean.frozenDate = this.getJsDateFromExcel(Number(frozenDateEntry)); + bean.frozenId = this.uiBeanHelper.generateFrozenId(); + } + + const unfrozenDateEntry = entry['Unfrozen Date']; + if (unfrozenDateEntry && Number(unfrozenDateEntry) > 0) { + isOneEntryFrozen = true; + bean.unfrozenDate = this.getJsDateFromExcel( + Number(unfrozenDateEntry) + ); + } + toAddBeans.push(bean); + } + + if (isOneEntryFrozen) { + // Activate the frozen feature if not active + if (this.settings.freeze_coffee_beans === false) { + this.settings.freeze_coffee_beans = true; + await this.uiSettingsStorage.saveSettings(this.settings); + } + } + if (varietyInformationWhereAdded) { + if (this.settings.bean_manage_parameters.bean_information === false) { + this.settings.bean_manage_parameters.bean_information = true; + await this.uiSettingsStorage.saveSettings(this.settings); + } + } + + /** + * Add all beans afterwards to not have some added and then going into an exception + */ + for await (const addBean of toAddBeans) { + try { + const newBean: Bean = await this.uiBeanStorage.add(addBean); + addedBeans.push(newBean); + } catch (ex) {} + } + + if (addedBeans.length > 0) { + try { + await this.uiAlert.showMessage( + 'BEANS_IMPORTED_SUCCESSFULLY_DESCRIPTION', + 'IMPORT_SUCCESSFULLY', + undefined, + true + ); + } catch (ex) {} + this.uiBeanHelper.showBeans(addedBeans); + } else { + this.uiAlert.showMessage( + 'BEANS_IMPORTED_UNSUCCESSFULLY_WRONG_EXCELFILE', + 'IMPORT_UNSUCCESSFULLY', + 'OK', + false + ); } - console.log(data); - console.log(wb); - } catch (ex) {} + } catch (ex) { + this.uiAlert.showMessage( + ex.message, + this.translate.instant('IMPORT_UNSUCCESSFULLY'), + this.translate.instant('OK'), + false + ); + } + } + + private getJsDateFromExcel(_dateNumber) { + return new Date((_dateNumber - (25567 + 2)) * 86400 * 1000).toISOString(); } /* Export button */ public async export() { diff --git a/src/services/uiExportImportHelper.ts b/src/services/uiExportImportHelper.ts index 07a6a274d..19c1d8468 100644 --- a/src/services/uiExportImportHelper.ts +++ b/src/services/uiExportImportHelper.ts @@ -20,11 +20,13 @@ import { import * as zip from '@zip.js/zip.js'; import { FileEntry } from '@awesome-cordova-plugins/file'; import { UILog } from './uiLog'; -import { Platform } from '@ionic/angular'; +import { ModalController, Platform } from '@ionic/angular'; import { UIFileHelper } from './uiFileHelper'; import { UIAlert } from './uiAlert'; import moment from 'moment'; import { UIBrewStorage } from './uiBrewStorage'; + +import { DataCorruptionFoundComponent } from '../popover/data-corruption-found/data-corruption-found.component'; @Injectable({ providedIn: 'root', }) @@ -43,7 +45,8 @@ export class UIExportImportHelper { private readonly uiFileHelper: UIFileHelper, private readonly uiAlert: UIAlert, private readonly uiSettingsStorage: UISettingsStorage, - private readonly uiBrewStorage: UIBrewStorage + private readonly uiBrewStorage: UIBrewStorage, + private readonly modalController: ModalController ) {} public async buildExportZIP(): Promise { @@ -239,6 +242,78 @@ export class UIExportImportHelper { }); }); } + private async checkBackupAndSeeIfDataAreCorrupted(_actualUIStorageDataObj) { + try { + this.uiLog.log( + 'checkBackupAndSeeIfDataAreCorrupted - Check if we got a deep corruption' + ); + const dataObj = _actualUIStorageDataObj.DATA; + const parsedJSON: any = await this.readBackupZIPFile(); + if (parsedJSON) { + let somethingCorrupted = false; + if (parsedJSON.BEANS?.length > dataObj.BEANS) { + somethingCorrupted = true; + } else if (parsedJSON.BREWS?.length > dataObj.BREWS) { + somethingCorrupted = true; + } else if (parsedJSON.PREPARATION?.length > dataObj.PREPARATION) { + somethingCorrupted = true; + } else if (parsedJSON.MILL?.length > dataObj.MILL) { + somethingCorrupted = true; + } + + this.uiLog.log( + 'checkBackupAndSeeIfDataAreCorrupted- Check over - if we got a deep corruption - Result: ' + + somethingCorrupted + ); + if (somethingCorrupted) { + const importBackup = await this.showDataCorruptionPopover( + dataObj, + parsedJSON + ); + if (importBackup) { + await this.importBackupJSON(parsedJSON); + } + } else { + this.uiLog.log( + "checkBackupAndSeeIfDataAreCorrupted - Check over - we didn't find any corrupted data" + ); + } + } else { + this.uiLog.log( + "checkBackupAndSeeIfDataAreCorrupted - We didn't found any json backup data so we can't do any checks" + ); + } + } catch (ex) { + this.uiLog.log( + 'Check over - if we got a deep corruption - Result exception: ' + + JSON.stringify(ex) + ); + } + } + + public async showDataCorruptionPopover( + _actualUIStorageDataObj, + _backupDataObj + ) { + const modal = await this.modalController.create({ + component: DataCorruptionFoundComponent, + id: DataCorruptionFoundComponent.POPOVER_ID, + componentProps: { + actualUIStorageDataObj: _actualUIStorageDataObj, + backupDataObj: _backupDataObj, + }, + }); + await modal.present(); + const returnData = await modal.onWillDismiss(); + this.uiLog.log( + 'Data corruption, choose to import: ' + returnData?.data?.import + ); + if (returnData?.data?.import) { + //User choose to import backup, go + return true; + } + return false; + } public async checkBackup() { try { @@ -248,90 +323,32 @@ export class UIExportImportHelper { this.uiLog.log('Check Backup'); const hasData = await this.uiStorage.hasData(); - let hasCorruptedData: boolean = false; + let actualUIStorageDataObj: any; if (hasData) { - hasCorruptedData = await this.uiStorage.hasCorruptedData(); + actualUIStorageDataObj = await this.uiStorage.hasCorruptedData(); } + this.uiLog.log('Check Backup - Has data ' + hasData); - if (!hasData || hasCorruptedData) { - this.uiLog.log( - 'Check Backup - No data are stored yet inside the app, so we try to find a backup file' - ); - // If we don't got any data, we check now if there is a Beanconqueror.zip saved. - this.uiFileHelper.getZIPFile('Beanconqueror.zip').then( - async (_arrayBuffer) => { - await this.uiAlert.showLoadingSpinner(); - try { - this.uiLog.log(' We found a backup, try to import'); - const parsedJSON = - await this.getJSONFromZIPArrayBufferContent(_arrayBuffer); - this.uiStorage.import(parsedJSON).then( - async () => { - this.uiLog.log('Sucessfully imported Backup'); - setTimeout(() => { - this.uiAlert.hideLoadingSpinner(); - }, 150); - resolve(null); - }, - () => { - this.uiLog.error('Could not import Backup'); - setTimeout(() => { - this.uiAlert.hideLoadingSpinner(); - }, 150); - resolve(null); - } - ); - } catch (ex) { - setTimeout(() => { - this.uiAlert.hideLoadingSpinner(); - }, 150); - } - }, - () => { - this.uiLog.log( - 'Check Backup - We couldnt retrieve any zip file - try the old JSON Way.' - ); - - this.uiFileHelper.getJSONFile('Beanconqueror.json').then( - async (_json) => { - await this.uiAlert.showLoadingSpinner(); - try { - this.uiLog.log('We found an backup, try to import'); - this.uiStorage.import(_json).then( - async () => { - this.uiLog.log('Sucessfully imported Backup'); - setTimeout(() => { - this.uiAlert.hideLoadingSpinner(); - }, 150); - resolve(null); - }, - () => { - this.uiLog.error('Could not import Backup'); - setTimeout(() => { - this.uiAlert.hideLoadingSpinner(); - }, 150); - resolve(null); - } - ); - } catch (ex) { - setTimeout(() => { - this.uiAlert.hideLoadingSpinner(); - }, 150); - } - }, - () => { - setTimeout(() => { - this.uiAlert.hideLoadingSpinner(); - }, 150); - this.uiLog.log( - 'Check Backup - We couldnt retrieve any JSON file' - ); - resolve(null); - } - ); - } - ); + if (!hasData || actualUIStorageDataObj.CORRUPTED) { + if (!hasData) { + this.uiLog.log( + 'Check Backup - We didnt found any data inside the app, so try to find a backup and import it' + ); + } else { + this.uiLog.log( + 'Check Backup - We found data but they where corrupted, so try to import a backup' + ); + } + + const parsedJSON = await this.readBackupZIPFile(); + if (parsedJSON) { + await this.importBackupJSON(parsedJSON); + } + resolve(null); } else { + await this.checkBackupAndSeeIfDataAreCorrupted( + actualUIStorageDataObj + ); resolve(null); } } else { @@ -345,6 +362,67 @@ export class UIExportImportHelper { } catch (ex) {} } + private importBackupJSON(_parsedJSON) { + const promise = new Promise(async (resolve, reject) => { + await this.uiAlert.showLoadingSpinner(); + + this.uiStorage.import(_parsedJSON).then( + async () => { + this.uiLog.log('Sucessfully imported Backup'); + setTimeout(() => { + this.uiAlert.hideLoadingSpinner(); + }, 150); + resolve(null); + }, + () => { + this.uiLog.error('Could not import Backup'); + setTimeout(() => { + this.uiAlert.hideLoadingSpinner(); + }, 150); + resolve(null); + } + ); + }); + return promise; + } + + private readBackupZIPFile() { + // If we don't got any data, we check now if there is a Beanconqueror.zip saved. + const promise = new Promise(async (resolve, reject) => { + this.uiFileHelper.getZIPFile('Beanconqueror.zip').then( + async (_arrayBuffer) => { + try { + this.uiLog.log('Read ZIP-File, we found an zip-file'); + const parsedJSON = await this.getJSONFromZIPArrayBufferContent( + _arrayBuffer + ); + resolve(parsedJSON); + } catch (ex) { + resolve(null); + } + }, + () => { + this.uiLog.log( + 'Read ZIP-FILE failed, try to read an old Beanconqueror.json' + ); + this.uiFileHelper.getJSONFile('Beanconqueror.json').then( + async (_json) => { + this.uiLog.log('Read ZIP-File, we found an json-file'); + resolve(_json); + }, + () => { + this.uiLog.log( + 'Check Backup - We couldnt retrieve any JSON file' + ); + resolve(null); + } + ); + } + ); + }); + return promise; + } + private getAutomatedBackupFilename(): string { return moment().format('DD_MM_YYYY').toString(); } diff --git a/src/services/uiFileHelper.ts b/src/services/uiFileHelper.ts index fbc075825..1cfe8c19d 100644 --- a/src/services/uiFileHelper.ts +++ b/src/services/uiFileHelper.ts @@ -847,10 +847,10 @@ export class UIFileHelper extends InstanceClass { ): Promise { return new Promise(async (resolve, reject) => { if (this.platform.is('cordova')) { - if (this.cachedInternalUrls[_filePath]) { + /** if (this.cachedInternalUrls[_filePath]) { //resolve(this.cachedInternalUrls[_filePath]); // return; - } + }**/ // let filePath: string; // filePath = _filePath; // filePath.slice(0, filePath.lastIndexOf('/')); @@ -884,8 +884,9 @@ export class UIFileHelper extends InstanceClass { this.domSanitizer.bypassSecurityTrustResourceUrl( convertedURL ); - this.cachedInternalUrls[_filePath] = convertedURL; - resolve(convertedURL); + const returningURL = convertedURL; + // this.cachedInternalUrls[_filePath] = convertedURL; + resolve(returningURL); }, () => { resolve(''); diff --git a/src/services/uiGraphHelper.ts b/src/services/uiGraphHelper.ts index 75fe25823..aa464a23f 100644 --- a/src/services/uiGraphHelper.ts +++ b/src/services/uiGraphHelper.ts @@ -16,6 +16,7 @@ import { FileEntry } from '@awesome-cordova-plugins/file'; import { UILog } from './uiLog'; import { UIFileHelper } from './uiFileHelper'; import BeanconquerorFlowTestDataDummy from '../assets/BeanconquerorFlowTestDataFourth.json'; +import { Brew } from '../classes/brew/brew'; /** * Handles every helping functionalities @@ -70,6 +71,15 @@ export class UIGraphHelper { await modal.present(); await modal.onWillDismiss(); } + public async detailBrewGraph(_brew: Brew) { + const modal = await this.modalController.create({ + component: GraphDetailComponent, + id: GraphDetailComponent.COMPONENT_ID, + componentProps: { brew: _brew }, + }); + await modal.present(); + await modal.onWillDismiss(); + } public async detailGraphRawData(_flowData: any) { const modal = await this.modalController.create({ diff --git a/src/services/uiHelper.ts b/src/services/uiHelper.ts index 51ec29d84..8026bcb1a 100755 --- a/src/services/uiHelper.ts +++ b/src/services/uiHelper.ts @@ -147,6 +147,16 @@ export class UIHelper { return moment(_unix).format(format); } + public formatTimeNumber(_time: number | string, _format?: string): string { + let format: string = + this.getSettingsStorageInstance().getSettings().date_format + + ', HH:mm:ss'; + if (_format) { + format = _format; + } + return moment(_time).format(format); + } + public toFixedIfNecessary(value, dp) { const parsedFloat = parseFloat(value); if (isNaN(parsedFloat)) { diff --git a/src/services/uiPreparationHelper.ts b/src/services/uiPreparationHelper.ts index d00acf5e8..3fd8ca616 100644 --- a/src/services/uiPreparationHelper.ts +++ b/src/services/uiPreparationHelper.ts @@ -20,9 +20,11 @@ import { PreparationDeviceType, } from '../classes/preparationDevice'; import { HttpClient } from '@angular/common/http'; -import { XeniaDevice } from '../classes/preparationDevice/xenia/xeniaDevice'; import { PreparationDevice } from '../classes/preparationDevice/preparationDevice'; import { PreparationSortToolsComponent } from '../app/preparation/preparation-sort-tools/preparation-sort-tools.component'; +import PREPARATION_TRACKING from '../data/tracking/preparationTracking'; +import { PreparationConnectedDeviceComponent } from '../app/preparation/preparation-connected-device/preparation-connected-device.component'; +import { UIAnalytics } from './uiAnalytics'; /** * Handles every helping functionalities @@ -39,7 +41,8 @@ export class UIPreparationHelper { private readonly uiHelper: UIHelper, private readonly translate: TranslateService, private readonly uiPreparationStorage: UIPreparationStorage, - private readonly httpClient: HttpClient + private readonly httpClient: HttpClient, + private readonly uiAnalytics: UIAnalytics ) { this.uiBrewStorage.attachOnEvent().subscribe((_val) => { // If an brew is deleted, we need to reset our array for the next call. @@ -84,6 +87,19 @@ export class UIPreparationHelper { await modal.present(); await modal.onWillDismiss(); } + public async connectDevice(_preparation: Preparation) { + this.uiAnalytics.trackEvent( + PREPARATION_TRACKING.TITLE, + PREPARATION_TRACKING.ACTIONS.CONNECT_DEVICE + ); + const modal = await this.modalController.create({ + component: PreparationConnectedDeviceComponent, + componentProps: { preparation: _preparation }, + id: PreparationConnectedDeviceComponent.COMPONENT_ID, + }); + await modal.present(); + await modal.onWillDismiss(); + } public async editPreparationTool( _preparation: Preparation, diff --git a/src/services/uiStorage.ts b/src/services/uiStorage.ts index 92eb75fdd..9bea4e0b1 100755 --- a/src/services/uiStorage.ts +++ b/src/services/uiStorage.ts @@ -240,13 +240,29 @@ export class UIStorage { return promise; } - public async hasCorruptedData(): Promise { - const promise: Promise = new Promise((resolve, reject) => { + public async hasCorruptedData(): Promise<{ + CORRUPTED: boolean; + DATA: { + BREWS: number; + MILL: number; + PREPARATION: number; + BEANS: number; + }; + }> { + const promise: Promise<{ + CORRUPTED: boolean; + DATA: { + BREWS: number; + MILL: number; + PREPARATION: number; + BEANS: number; + }; + }> = new Promise((resolve, reject) => { const hasDataObj = { - BREWS: false, - MILL: false, - PREPARATION: false, - BEANS: false, + BREWS: 0, + MILL: 0, + PREPARATION: 0, + BEANS: 0, }; this._storage .forEach((_value, _key, _index) => { @@ -258,7 +274,9 @@ export class UIStorage { ) { try { if (_value?.length > 0) { - hasDataObj[_key] = true; + hasDataObj[_key] = _value?.length; + } else { + hasDataObj[_key] = 0; } } catch (ex) {} } @@ -266,22 +284,22 @@ export class UIStorage { .then( () => { if ( - hasDataObj.BREWS === true && - (hasDataObj.MILL === false || - hasDataObj.PREPARATION === false || - hasDataObj.BEANS === false) + hasDataObj.BREWS > 0 && + (hasDataObj.MILL <= 0 || + hasDataObj.PREPARATION <= 0 || + hasDataObj.BEANS <= 0) ) { /** * If we got brews but not a mill / preparation / or bean something broke hard. * We saw this issue on android that a user got brews but no beans anymore, they where lost */ - resolve(true); + resolve({ CORRUPTED: true, DATA: hasDataObj }); } else { - resolve(false); + resolve({ CORRUPTED: false, DATA: hasDataObj }); } }, () => { - resolve(false); + resolve({ CORRUPTED: false, DATA: hasDataObj }); } ); }); diff --git a/src/services/uiUpdate.ts b/src/services/uiUpdate.ts index dd672e8de..c91619a27 100755 --- a/src/services/uiUpdate.ts +++ b/src/services/uiUpdate.ts @@ -66,6 +66,7 @@ export class UIUpdate { 'UPDATE_8', 'UPDATE_9', 'UPDATE_10', + 'UPDATE_11', ]; const version: Version = this.uiVersionStorage.getVersion(); const _silentUpdate = hasData; @@ -89,6 +90,7 @@ export class UIUpdate { await this.__checkUpdateForDataVersion('UPDATE_8', !hasData); await this.__checkUpdateForDataVersion('UPDATE_9', !hasData); await this.__checkUpdateForDataVersion('UPDATE_10', !hasData); + await this.__checkUpdateForDataVersion('UPDATE_11', !hasData); } catch (ex) { if (this.uiAlert.isLoadingSpinnerShown()) { await this.uiAlert.hideLoadingSpinner(); @@ -549,8 +551,15 @@ export class UIUpdate { case 'UPDATE_10': const settings_v10: Settings = this.uiSettingsStorage.getSettings(); settings_v10.resetFilter(); + settings_v10.resetBeanSort(); await this.uiSettingsStorage.saveSettings(settings_v10); break; + case 'UPDATE_11': + //#776 - We added a new beansort, thats why we reset the bean sort here. + const settings_v11: Settings = this.uiSettingsStorage.getSettings(); + settings_v11.resetBeanSort(); + await this.uiSettingsStorage.saveSettings(settings_v11); + break; default: break; } @@ -605,7 +614,7 @@ export class UIUpdate { versionCode = await this.appVersion.getVersionNumber(); } else { // Hardcored for testing - versionCode = '7.4.0'; + versionCode = '7.5.0'; } const version: Version = this.uiVersionStorage.getVersion(); const displayingVersions =