update borrow + fix invite

This commit is contained in:
_Bastler 2021-10-27 17:07:44 +02:00
parent 59ac345933
commit 925a03fd46
45 changed files with 2792 additions and 398 deletions

View File

@ -24,6 +24,7 @@
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"assets": [ "assets": [
"src/.htaccess", "src/.htaccess",
"src/qr-scanner-worker.min.js",
"src/favicon.ico", "src/favicon.ico",
"src/assets" "src/assets"
], ],

826
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,34 +12,36 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular-material-components/datetime-picker": "^6.0.3", "@angular-material-components/datetime-picker": "^6.0.3",
"@angular/animations": "~12.2.8", "@angular/animations": "^12.2.10",
"@angular/cdk": "^12.2.8", "@angular/cdk": "^12.2.10",
"@angular/common": "~12.2.8", "@angular/common": "^12.2.10",
"@angular/compiler": "~12.2.8", "@angular/compiler": "^12.2.10",
"@angular/core": "~12.2.8", "@angular/core": "^12.2.10",
"@angular/flex-layout": "^12.0.0-beta.35", "@angular/flex-layout": "^12.0.0-beta.35",
"@angular/forms": "~12.2.8", "@angular/forms": "^12.2.10",
"@angular/material": "^12.2.8", "@angular/material": "^12.2.10",
"@angular/material-moment-adapter": "^12.2.8", "@angular/material-moment-adapter": "^12.2.10",
"@angular/platform-browser": "~12.2.8", "@angular/platform-browser": "^12.2.10",
"@angular/platform-browser-dynamic": "~12.2.8", "@angular/platform-browser-dynamic": "^12.2.10",
"@angular/router": "~12.2.8", "@angular/router": "^12.2.10",
"moment": "^2.29.1", "moment": "^2.29.1",
"ng-qrcode": "^5.1.1", "ng-qrcode": "^5.1.1",
"ngx-mat-timepicker": "^12.1.0",
"openpgp": "^4.10.10", "openpgp": "^4.10.10",
"qr-scanner": "^1.3.0",
"rxjs": "~6.6.7", "rxjs": "~6.6.7",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"unique-names-generator": "^4.6.0", "unique-names-generator": "^4.6.0",
"zone.js": "~0.11.4" "zone.js": "~0.11.4"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^12.2.8", "@angular-devkit/build-angular": "^12.2.10",
"@angular/cli": "~12.2.8", "@angular/cli": "^12.2.10",
"@angular/compiler-cli": "~12.2.8", "@angular/compiler-cli": "^12.2.10",
"@angular/localize": "^12.2.8", "@angular/localize": "^12.2.10",
"@types/jasmine": "^3.6.11", "@types/jasmine": "^3.10.0",
"@types/jasminewd2": "^2.0.10", "@types/jasminewd2": "^2.0.10",
"@types/node": "^12.20.27", "@types/node": "^12.20.33",
"@types/openpgp": "^4.4.18", "@types/openpgp": "^4.4.18",
"codelyzer": "^6.0.2", "codelyzer": "^6.0.2",
"jasmine-core": "~3.6.0", "jasmine-core": "~3.6.0",

View File

@ -27,6 +27,11 @@ import { InvitesComponent } from './pages/invites/invites.component';
import { UrlShortenerComponent, UrlShortenerPasswordComponent } from './pages/urlshortener/urlshortener.component'; import { UrlShortenerComponent, UrlShortenerPasswordComponent } from './pages/urlshortener/urlshortener.component';
import { MinetestAccountsComponent } from './pages/minetest/accounts/accounts.component'; import { MinetestAccountsComponent } from './pages/minetest/accounts/accounts.component';
import { DividertestComponent } from './pages/dividertest/dividertest.component' import { DividertestComponent } from './pages/dividertest/dividertest.component'
import { BorrowProvingComponent } from './pages/borrow/proving/proving.component';
import { BorrowItemEditComponent, BorrowItemsComponent } from './pages/borrow/items/items.component';
import { BorrowRequestsComponent } from './pages/borrow/requests/requests.component';
import { BorrowComponent } from './pages/borrow/borrow.component';
import { InviteCodeComponent } from './pages/invites/code/code.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'profile/:username', component: UserComponent, canActivate: [ AuthUpdateGuard ] }, { path: 'profile/:username', component: UserComponent, canActivate: [ AuthUpdateGuard ] },
@ -52,6 +57,14 @@ const routes: Routes = [
{ path: 'domains', component: DomainsComponent, canActivate: [ AuthenticatedGuard ] } { path: 'domains', component: DomainsComponent, canActivate: [ AuthenticatedGuard ] }
] ]
}, },
{
path: 'borrow', component: BorrowComponent, canActivate: [ AuthenticatedGuard ], children: [
{ path: '', redirectTo: "/borrow/items", pathMatch: 'full' },
{ path: 'items', component: BorrowItemsComponent, canActivate: [ AuthenticatedGuard ] },
{ path: 'requests', component: BorrowRequestsComponent, canActivate: [ AuthenticatedGuard ] },
{ path: 'proving', component: BorrowProvingComponent, canActivate: [ AuthenticatedGuard ] }
]
},
{ path: 'register', component: RegisterComponent, canActivate: [ AnonymousGuard ] }, { path: 'register', component: RegisterComponent, canActivate: [ AnonymousGuard ] },
{ path: 'tokens', component: TokensComponent, canActivate: [ AuthGuard ] }, { path: 'tokens', component: TokensComponent, canActivate: [ AuthGuard ] },
{ path: 'jitsi', component: JitsiComponent, canActivate: [ AuthenticatedGuard ] }, { path: 'jitsi', component: JitsiComponent, canActivate: [ AuthenticatedGuard ] },
@ -62,6 +75,7 @@ const routes: Routes = [
{ path: 'urlshortener', component: UrlShortenerComponent, canActivate: [ AuthenticatedGuard ] }, { path: 'urlshortener', component: UrlShortenerComponent, canActivate: [ AuthenticatedGuard ] },
{ path: 'urlshortener/:code', component: UrlShortenerPasswordComponent, canActivate: [ AuthUpdateGuard ] }, { path: 'urlshortener/:code', component: UrlShortenerPasswordComponent, canActivate: [ AuthUpdateGuard ] },
{ path: 'invites/:quota', component: InvitesComponent, canActivate: [ AuthenticatedGuard ] }, { path: 'invites/:quota', component: InvitesComponent, canActivate: [ AuthenticatedGuard ] },
{ path: 'invite/:code', component: InviteCodeComponent, canActivate: [ AuthGuard ] },
{ path: 'unavailable', component: UnavailableComponent }, { path: 'unavailable', component: UnavailableComponent },
{ path: 'p/:username', component: UserComponent, canActivate: [ AuthUpdateGuard ] }, { path: 'p/:username', component: UserComponent, canActivate: [ AuthUpdateGuard ] },
{ path: '**', component: NotfoundComponent, pathMatch: 'full', canActivate: [ AuthUpdateGuard ] }, ] { path: '**', component: NotfoundComponent, pathMatch: 'full', canActivate: [ AuthUpdateGuard ] }, ]

View File

@ -13,6 +13,7 @@ import { DatePipe } from '@angular/common';
import { AutofocusDirective } from './material/autofocus'; import { AutofocusDirective } from './material/autofocus';
import { I18nPipe } from './utils/i18n.pipe'; import { I18nPipe } from './utils/i18n.pipe';
import { MomentPipe } from './utils/moment.pipe';
import { MainComponent } from './ui/main/main.component'; import { MainComponent } from './ui/main/main.component';
import { AccountComponent } from './pages/account/account.component'; import { AccountComponent } from './pages/account/account.component';
import { ServicesComponent } from './pages/services/services.component'; import { ServicesComponent } from './pages/services/services.component';
@ -42,18 +43,25 @@ import { UsernameDialog } from './pages/register/username-dialog/username.dialog
import { UnavailableComponent } from './pages/unavailable/unavailable.component'; import { UnavailableComponent } from './pages/unavailable/unavailable.component';
import { NotfoundComponent } from './pages/notfound/notfound.component'; import { NotfoundComponent } from './pages/notfound/notfound.component';
import { HtmlComponent } from './utils/html/html.component'; import { HtmlComponent } from './utils/html/html.component';
import { ConfirmDialog } from './ui/confirm/confirm.component' import { ConfirmDialog } from './ui/confirm/confirm.component';
import { UserComponent } from './pages/user/user.component' import { UserComponent } from './pages/user/user.component';
import { JitsiComponent, JitsiEditDialog, JitsiShareDialog } from './pages/jitsi/jitsi.component' import { JitsiComponent, JitsiEditDialog, JitsiShareDialog } from './pages/jitsi/jitsi.component';
import { ParteyComponent } from './pages/partey/partey.component' import { ParteyComponent } from './pages/partey/partey.component';
import { ParteyTimeslotsComponent, ParteyTimeslotDialog } from './pages/partey/timeslots/timeslots.compontent' import { ParteyTimeslotsComponent, ParteyTimeslotDialog } from './pages/partey/timeslots/timeslots.compontent';
import { UrlShortenerComponent, UrlShortenerPasswordComponent, UrlShortenerShareDialog, UrlShortenerEditDialog } from './pages/urlshortener/urlshortener.component' import { UrlShortenerComponent, UrlShortenerPasswordComponent, UrlShortenerShareDialog, UrlShortenerEditDialog } from './pages/urlshortener/urlshortener.component';
import { DividerComponent } from './ui/divider/divider.component' import { BorrowItemEditComponent, BorrowItemsComponent } from './pages/borrow/items/items.component';
import { DividertestComponent } from './pages/dividertest/dividertest.component' import { BorrowRequestEditComponent, BorrowRequestsComponent } from './pages/borrow/requests/requests.component';
import { BorrowProvingComponent, BorrowProvingResultDialog } from './pages/borrow/proving/proving.component';
import { DividerComponent } from './ui/divider/divider.component';
import { DividertestComponent } from './pages/dividertest/dividertest.component';
import { I18nService } from './services/i18n.service'; import { I18nService } from './services/i18n.service';
import { MinetestAccountsComponent } from './pages/minetest/accounts/accounts.component'; import { MinetestAccountsComponent } from './pages/minetest/accounts/accounts.component';
import { BorrowComponent } from './pages/borrow/borrow.component';
import { DurationpickerComponent } from './ui/durationpicker/durationpicker.component';
import { InviteCodeComponent } from './pages/invites/code/code.component';
import { InviteEditComponent } from './pages/invites/edit/invite.edit';
export function init_app(i18n: I18nService) { export function init_app(i18n: I18nService) {
@ -75,6 +83,7 @@ export class XhrInterceptor implements HttpInterceptor {
declarations: [ declarations: [
AutofocusDirective, AutofocusDirective,
I18nPipe, I18nPipe,
MomentPipe,
MainComponent, MainComponent,
AppComponent, AppComponent,
AccountComponent, AccountComponent,
@ -82,7 +91,7 @@ export class XhrInterceptor implements HttpInterceptor {
FormLoginComponent, FormLoginComponent,
FormLogin2FAComponent, FormLogin2FAComponent,
TokensComponent, TokensComponent,
InvitesComponent, InvitesComponent, InviteCodeComponent, InviteEditComponent,
ServicesComponent, ServicesComponent,
PermissionsComponent, PermissionsComponent,
ProfileFieldsComponent, ProfileFieldDialog, ProfileFieldBlob, ProfileFieldPgpBlob, ProfileFieldsComponent, ProfileFieldDialog, ProfileFieldBlob, ProfileFieldPgpBlob,
@ -110,7 +119,9 @@ export class XhrInterceptor implements HttpInterceptor {
ParteyComponent, ParteyTimeslotsComponent, ParteyTimeslotDialog, ParteyComponent, ParteyTimeslotsComponent, ParteyTimeslotDialog,
MinetestAccountsComponent, MinetestAccountsComponent,
UrlShortenerComponent, UrlShortenerShareDialog, UrlShortenerEditDialog, UrlShortenerPasswordComponent, UrlShortenerComponent, UrlShortenerShareDialog, UrlShortenerEditDialog, UrlShortenerPasswordComponent,
DividerComponent, DividertestComponent BorrowComponent, BorrowItemsComponent, BorrowItemEditComponent, BorrowRequestsComponent, BorrowRequestEditComponent, BorrowProvingComponent, BorrowProvingResultDialog,
DividerComponent, DividertestComponent,
DurationpickerComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View File

@ -47,10 +47,11 @@ import {FlexLayoutModule} from '@angular/flex-layout';
import { import {
NgxMatDatetimePickerModule, NgxMatDatetimePickerModule,
NgxMatNativeDateModule, NgxMatNativeDateModule
NgxMatTimepickerModule
} from '@angular-material-components/datetime-picker'; } from '@angular-material-components/datetime-picker';
import { NgxMatTimepickerModule } from 'ngx-mat-timepicker';
@NgModule({ @NgModule({
declarations: [], declarations: [],
imports: [ imports: [

View File

@ -0,0 +1,15 @@
<h2>{{'borrow' | i18n}}</h2>
<nav mat-tab-nav-bar>
<a mat-tab-link routerLink="items" routerLinkActive="active-tab" #rlaitems="routerLinkActive"
[active]="rlaitems.isActive">{{'borrow.items'
| i18n}}</a>
<a mat-tab-link routerLink="requests" routerLinkActive="active-tab" #rlarequests="routerLinkActive"
[active]="rlarequests.isActive">{{'borrow.requests'
| i18n}}</a>
<a mat-tab-link routerLink="proving" routerLinkActive="active-tab" #rlaproving="routerLinkActive"
[active]="rlaproving.isActive">{{'borrow.proving'
| i18n}}</a>
</nav>
<router-outlet></router-outlet>

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-borrow',
templateUrl: './borrow.component.html',
styleUrls: [ './borrow.component.scss' ]
})
export class BorrowComponent {
}

View File

@ -0,0 +1,204 @@
<h2 mat-dialog-title>{{(create ? 'borrow.items.create' : 'borrow.items.edit') | i18n}}</h2>
<mat-dialog-content>
<form [formGroup]="form">
<mat-form-field>
<input matInput placeholder="{{'borrow.items.name' | i18n}}" formControlName="name">
<mat-error>
{{'borrow.items.error.name' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'borrow.items.description' | i18n}}" formControlName="description"></textarea>
<mat-error>
{{'borrow.items.error.description' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="{{'borrow.items.url' | i18n}}" formControlName="url">
<mat-error>
{{'borrow.items.error.url' | i18n}}
</mat-error>
</mat-form-field>
<div fxLayout="row wrap" fxLayoutGap="24px grid">
<mat-form-field floatLabel="always" appearance="none" fxFlex="50%" fxFlex.sm="100%" fxFlex.xs="100%">
<input matInput hidden formControlName="minDuration" />
<label>{{'borrow.items.minDuration' | i18n}}</label>
<app-durationpicker formControlName="minDuration"></app-durationpicker>
<mat-error>
{{'borrow.items.error.minDuration' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none" fxFlex="50%" fxFlex.sm="100%" fxFlex.xs="100%">
<input matInput hidden formControlName="maxDuration" />
<label>{{'borrow.items.maxDuration' | i18n}}</label>
<app-durationpicker formControlName="maxDuration"></app-durationpicker>
<mat-error>
{{'borrow.items.error.maxDuration' | i18n}}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row wrap" fxLayoutAlign="start stretch" fxLayoutGap="24px grid">
<mat-form-field>
<mat-select formControlName="availability" placeholder="{{'borrow.items.availability' | i18n}}">
<mat-option value="ALWAYS">
{{'borrow.items.availability.ALWAYS' | i18n}}
</mat-option>
<mat-option value="PERIOD">
{{'borrow.items.availability.PERIOD' | i18n}}
</mat-option>
<mat-option value="MANUAL">
{{'borrow.items.availability.MANUAL' | i18n}}
</mat-option>
</mat-select>
<mat-error>
{{'borrow.items.error.availability' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle formControlName="autoAccept">{{'borrow.items.autoAccept' | i18n}}
</mat-slide-toggle>
<input matInput hidden />
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle formControlName="emailNotification">
{{'borrow.items.emailNotification' | i18n}}
</mat-slide-toggle>
<input matInput hidden />
</mat-form-field>
<mat-form-field *ngIf="form.get('emailNotification').value" fxFlex="1 1 0%">
<input matInput type="email" placeholder="{{'borrow.items.email' | i18n}}" formControlName="email">
<mat-error>
{{'borrow.items.error.email' | i18n}}
</mat-error>
</mat-form-field>
</div>
<ng-container *ngIf="form.get('availability').value == 'MANUAL'">
<mat-divider></mat-divider><br>
<label>{{'borrow.items.slot.MANUAL' | i18n}}</label>
<button mat-icon-button (click)="addManualSlot('','','')" title="{{'borrow.items.slot.add' | i18n}}">
<mat-icon>control_point_duplicate</mat-icon>
</button>
<ng-container *ngFor="let slotForm of slots.controls; let i = index">
<div [formGroup]="slotForm" fxLayout="row wrap" fxLayoutAlign="start stretch" fxLayoutGap="24px grid">
<mat-form-field>
<input matInput [ngxMatDatetimePicker]="slotStartPicker" formControlName="start"
placeholder="{{'borrow.items.slot.start' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="slotStartPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #slotStartPicker></ngx-mat-datetime-picker>
<mat-error>
{{'borrow.items.error.slot.start' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput [ngxMatDatetimePicker]="slotEndPicker" formControlName="end"
placeholder="{{'borrow.items.slot.end' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="slotEndPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #slotEndPicker></ngx-mat-datetime-picker>
<mat-error>
{{'borrow.items.error.slot.end' | i18n}}
</mat-error>
</mat-form-field>
<div>
<button mat-icon-button (click)="dublicateSlot(i)" title="{{'borrow.items.slot.dublicate' | i18n}}">
<mat-icon>control_point_duplicate</mat-icon>
</button>
<button mat-icon-button (click)="deleteSlot(i)" title="{{'borrow.items.slot.delete' | i18n}}">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</ng-container>
</ng-container>
<ng-container *ngIf="form.get('availability').value == 'PERIOD'">
<mat-divider></mat-divider><br>
<label>{{'borrow.items.slot.PERIOD' | i18n}}</label>
<button mat-icon-button (click)="addPeriodSlot('','','','','')" title="{{'borrow.items.slot.add' | i18n}}">
<mat-icon>control_point_duplicate</mat-icon>
</button>
<ng-container *ngFor="let slotForm of slots.controls; let i = index">
<div [formGroup]="slotForm" fxLayout="row wrap" fxLayoutAlign="start stretch" fxLayoutGap="12px grid">
<mat-form-field>
<mat-select formControlName="startDay" placeholder="{{'borrow.items.slot.startDay' | i18n}}">
<mat-option *ngFor="let day of weekdays" [value]="day">
{{'borrow.items.slot.day.' + day | i18n}}
</mat-option>
</mat-select>
<mat-error>
{{'borrow.items.error.slot.startDay' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput format="24" [ngxMatTimepicker]="startTimePicker" formControlName="startTime"
placeholder="{{'borrow.items.slot.startTime' | i18n}}">
<mat-icon matSuffix (click)="startTimePicker.open()">
watch_later
</mat-icon>
<mat-error>
{{'borrow.items.error.slot.startTime' | i18n}}
</mat-error>
</mat-form-field>
<ngx-mat-timepicker #startTimePicker></ngx-mat-timepicker>
<mat-form-field>
<mat-select formControlName="endDay" placeholder="{{'borrow.items.slot.endDay' | i18n}}">
<mat-option *ngFor="let day of weekdays" [value]="day">
{{'borrow.items.slot.day.' + day | i18n}}
</mat-option>
</mat-select>
<mat-error>
{{'borrow.items.error.slot.endDay' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput format="24" [ngxMatTimepicker]="endTimePicker" formControlName="endTime"
placeholder="{{'borrow.items.slot.endTime' | i18n}}">
<mat-icon matSuffix (click)="endTimePicker.open()">
watch_later
</mat-icon>
<mat-error>
{{'borrow.items.error.slot.endTime' | i18n}}
</mat-error>
</mat-form-field>
<ngx-mat-timepicker #endTimePicker></ngx-mat-timepicker>
<div>
<button mat-icon-button (click)="dublicateSlot(i)" title="{{'borrow.items.slot.dublicate' | i18n}}">
<mat-icon>control_point_duplicate</mat-icon>
</button>
<button mat-icon-button (click)="deleteSlot(i)" title="{{'borrow.items.slot.delete' | i18n}}">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</ng-container>
</ng-container>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<div fxLayout="row" fxLayoutAlign="space-between start">
<div>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
<button [disabled]="form.invalid" mat-raised-button (click)="save()" color="accent">
<mat-icon>save</mat-icon>{{(create ? 'borrow.items.create' : 'borrow.items.save') | i18n}}
</button>
</div>
<button *ngIf="borrowItemId" mat-button color="warn" (click)="confirmDelete()">
<mat-icon>delete</mat-icon> {{ 'borrow.items.delete' |
i18n}}
</button>
</div>
</mat-dialog-actions>

View File

@ -0,0 +1,7 @@
mat-form-field {
display: block;
}
mat-dialog-actions>div {
width: 100%;
}

View File

@ -0,0 +1,65 @@
<h3>{{'borrow.items' | i18n}}</h3>
<div *ngIf="borrowItems">
<div fxLayout="row" fxLayoutAlign="space-between start">
<form>
<mat-form-field>
<input matInput [formControl]="searchFormControl" placeholder="{{'borrow.items.search' | i18n}}">
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle [formControl]="ownerFormControl">{{'borrow.items.mine' | i18n}}</mat-slide-toggle>
<input matInput hidden />
</mat-form-field>
</form>
<button mat-raised-button (click)="create()" color="accent">{{'borrow.items.create' | i18n}}</button>
</div>
<table mat-table matSort [dataSource]="borrowItems.content" (matSortChange)="updateSort($event)">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'borrow.items.name' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem"> {{ borrowItem.name}} </td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef> {{'borrow.items.description' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem"> {{ borrowItem.description}} </td>
</ng-container>
<ng-container matColumnDef="availability">
<th mat-header-cell *matHeaderCellDef> {{'borrow.items.availability' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem"> {{ 'borrow.items.availability.' + borrowItem.availability | i18n}}
</td>
</ng-container>
<ng-container matColumnDef="url">
<th mat-header-cell *matHeaderCellDef> {{'borrow.items.url' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem">
<a *ngIf="borrowItem.url" mat-button color="accent" href="{{ borrowItem.url }}" target="_blank">
{{ borrowItem.url }}
<mat-icon style="font-size: 1em;">open_in_new</mat-icon>
</a>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="align-right"> {{'borrow.items.actions' | i18n}} </th>
<td mat-cell *matCellDef="let borrowItem" class="text-right">
<a mat-icon-button *ngIf="borrowItem.owner == userId">
<mat-icon (click)="request(borrowItem)">pending_actions</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowItem.owner == userId">
<mat-icon (click)="edit(borrowItem)">edit</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowItem.owner == userId">
<mat-icon (click)="confirmDelete(borrowItem)">delete</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowItem.owner != userId">
<mat-icon (click)="request(borrowItem)">pending_actions</mat-icon>
</a>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="borrowItemColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: borrowItemColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="borrowItems.totalElements" [pageSize]="borrowItems.size"
(page)="updatePages($event)" showFirstLastButtons></mat-paginator>
</div>

View File

@ -0,0 +1,21 @@
.mat-form-field+.mat-form-field, .mat-form-field+.mat-slide-toggle {
margin-left: 8px;
}
.mat-header-cell,
.mat-cell {
&.text-right {
text-align: right;
}
}
.mat-cell .mat-button {
padding-left: 0px;
padding-right: 0px;
}
.align-right{
display: flex;
padding: 21px 0;
justify-content: flex-end;
}

View File

@ -0,0 +1,410 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { PageEvent } from '@angular/material/paginator';
import { Sort } from '@angular/material/sort';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import * as moment from 'moment';
import { ConfirmDialog } from '../../../ui/confirm/confirm.component';
import { BorrowItemsService } from './../../../services/borrow.service';
import { AuthService } from './../../../services/auth.service';
import { BorrowRequestEditComponent } from '../requests/requests.component';
@Component({
selector: 'app-borrow-items',
templateUrl: './items.component.html',
styleUrls: [ './items.component.scss' ]
})
export class BorrowItemsComponent implements OnInit {
borrowItems: any[];
page: any = { page: 0, size: 10, sort: "id", desc: false };
pageSizeOptions: number[] = [ 5, 10, 25, 50 ];
searchFormControl = new FormControl();
ownerFormControl = new FormControl();
userId: any;
borrowItemColumns = [ "name", "description", "availability", "url", "actions" ];
constructor(private borrowItemsService: BorrowItemsService,
private authService: AuthService,
public dialog: MatDialog) {
}
ngOnInit() {
this.authService.auth.subscribe((auth: any) => {
if (auth.principal && auth.principal.userId) {
this.userId = auth.principal.userId;
}
});
this.searchFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => {
this.borrowItemsService.getItems(0, this.page.size, this.page.sort, this.page.desc, value, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowItems = data;
}, (error) => { })
})
this.ownerFormControl.valueChanges.subscribe(value => {
this.borrowItemsService.getItems(0, this.page.size, this.page.sort, this.page.desc, this.searchFormControl.value, value).subscribe((data: any) => {
this.borrowItems = data;
}, (error) => { })
})
this.refresh();
}
refresh(): void {
this.borrowItemsService.getItems(this.page.page, this.page.size, this.page.sort, this.page.desc, this.searchFormControl.value, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowItems = data;
})
}
updatePages(event: PageEvent) {
this.page.page = event.pageIndex;
this.page.size = event.pageSize;
this.borrowItemsService.getItems(this.page.page, this.page.size, this.page.sort, this.page.desc, this.searchFormControl.value, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowItems = data;
}, (error) => { })
}
updateSort(sort: Sort) {
if (sort.direction == "") {
this.page.sort = "id";
this.page.desc = false;
} else {
this.page.sort = sort.active;
this.page.desc = sort.direction == "desc";
}
this.borrowItemsService.getItems(this.page.page, this.page.size, this.page.sort, this.page.desc, this.searchFormControl.value, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowItems = data;
}, (error) => { })
}
edit(borrowItem) {
const dialogRef = this.dialog.open(BorrowItemEditComponent, {
data: borrowItem,
minWidth: '80%'
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refresh();
}
});
}
create() {
const dialogRef = this.dialog.open(BorrowItemEditComponent, {
data: {},
minWidth: '80%'
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refresh();
}
});
}
request(borrowItem) {
const dialogRef = this.dialog.open(BorrowRequestEditComponent, {
data: {
"user": this.userId,
"borrowItem": borrowItem
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refresh();
}
});
}
confirmDelete(borrowItem) {
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'borrow.items.confirmDelete',
'args': [ borrowItem.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.borrowItemsService.deleteItem(borrowItem.id).subscribe((result: any) => {
this.refresh();
})
}
});
}
}
@Component({
selector: 'app-borrow-item-edit',
templateUrl: './item.edit.html',
styleUrls: [ './item.edit.scss' ]
})
export class BorrowItemEditComponent {
borrowItemId: number;
create: boolean = false;
form: FormGroup;
weekdays: string[] = [ 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY' ];
constructor(private borrowItemsService: BorrowItemsService,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<BorrowItemEditComponent>,
public dialog: MatDialog) {
this.form = this.formBuilder.group({
name: [ '', Validators.required ],
description: [ '', Validators.nullValidator ],
url: [ '', Validators.nullValidator ],
minDuration: [ '', Validators.nullValidator ],
maxDuration: [ '', Validators.nullValidator ],
availability: [ '', Validators.required ],
autoAccept: [ '', Validators.nullValidator ],
emailNotification: [ '', Validators.nullValidator ],
email: [ '', Validators.nullValidator ],
slots: this.formBuilder.array([])
}, { validators: [ durationValidator ] })
this.form.get('availability').valueChanges.subscribe((value) => {
this.slots.clear();
if (value == 'MANUAL') {
this.addManualSlot('', '', '');
} else if (value == 'PERIOD') {
this.addPeriodSlot('', '', '', '', '');
}
})
if (data.id) {
this.borrowItemId = + data.id;
} else {
this.create = true;
this.borrowItemId = undefined;
}
this.form.get('name').setValue(data.name, { emitEvent: false });
this.form.get('description').setValue(data.description, { emitEvent: false });
this.form.get('url').setValue(data.url, { emitEvent: false });
this.form.get('minDuration').setValue(data.minDuration, { emitEvent: false });
this.form.get('maxDuration').setValue(data.maxDuration, { emitEvent: false });
this.form.get('availability').setValue(data.availability || 'ALWAYS', { emitEvent: false });
this.form.get('autoAccept').setValue(data.autoAccept, { emitEvent: false });
this.form.get('emailNotification').setValue(data.emailNotification, { emitEvent: false });
this.form.get('email').setValue(data.email), { emitEvent: false };
if (data.slots) {
for (let slot of data.slots) {
if (data.availability == 'MANUAL') {
this.addManualSlot(slot.id, slot.start, slot.end);
} else if (data.availability == 'PERIOD') {
this.addPeriodSlot(slot.id, slot.startDay, slot.startTime, slot.endDay, slot.endTime);
}
}
}
}
formToBorrowItem(): any {
const borrowItem: any = {};
borrowItem.id = this.borrowItemId;
borrowItem.name = this.form.get('name').value;
borrowItem.description = this.form.get('description').value;
borrowItem.url = this.form.get('url').value;
borrowItem.minDuration = this.form.get('minDuration').value;
borrowItem.maxDuration = this.form.get('maxDuration').value;
borrowItem.availability = this.form.get('availability').value;
borrowItem.autoAccept = this.form.get('autoAccept').value;
borrowItem.emailNotification = this.form.get('emailNotification').value;
borrowItem.email = this.form.get('email').value;
const slots: any[] = [];
for (let slotForm of this.slots.controls) {
if (borrowItem.availability == 'MANUAL') {
slots.push({
id: slotForm.get('id').value,
start: slotForm.get('start').value,
end: slotForm.get('end').value,
type: 'MANUAL'
})
} else if (borrowItem.availability == 'PERIOD') {
slots.push({
id: slotForm.get('id').value,
startDay: slotForm.get('startDay').value,
startTime: slotForm.get('startTime').value,
endDay: slotForm.get('endDay').value,
endTime: slotForm.get('endTime').value,
type: 'PERIOD'
})
}
}
borrowItem.slots = slots;
return borrowItem;
}
get slots() {
return this.form.controls[ "slots" ] as FormArray;
}
deleteSlot(index: number) {
this.slots.removeAt(index);
}
dublicateSlot(index: number) {
this.slots.push(this.slots.at(index));
}
addManualSlot(id, start, end) {
const manualSlotForm = this.formBuilder.group({
id: [ id, Validators.required ],
start: [ start, Validators.required ],
end: [ end, Validators.required ]
}, { validators: [ manualSlotValidator ] });
this.slots.push(manualSlotForm);
}
addPeriodSlot(id, startDay: string, startTime: string, endDay: string, endTime: string) {
if (startTime.length == 8) {
startTime = startTime.substr(0, 5);
}
if (endTime.length == 8) {
endTime = endTime.substr(0, 5);
}
const perdiodSlotForm = this.formBuilder.group({
id: [ id, Validators.required ],
startDay: [ startDay, Validators.required ],
startTime: [ startTime, Validators.required ],
endDay: [ endDay, Validators.required ],
endTime: [ endTime, Validators.required ]
}, { validators: [ periodSlotValidator ] });
this.slots.push(perdiodSlotForm);
}
save() {
this.borrowItemsService.createOrUpdateItem(this.formToBorrowItem()).subscribe((data: any) => {
this.dialogRef.close(data);
}, (error) => {
if (error.status == 409) {
let errors = {};
for (let code of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
for (let code in errors) {
this.form.get(code).setErrors(errors[ code ]);
}
}
});
}
confirmDelete() {
const borrowItem = this.formToBorrowItem();
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'borrow.items.confirmDelete',
'args': [ borrowItem.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.borrowItemsService.deleteItem(borrowItem.id).subscribe((result: any) => {
this.dialogRef.close(true);
})
}
});
}
}
const durationValidator: ValidatorFn = (fg: FormGroup) => {
const minDuration = fg.get('minDuration').value;
const maxDuration = fg.get('maxDuration').value;
fg.get('minDuration').setErrors(null);
fg.get('maxDuration').setErrors(null);
if (minDuration && maxDuration && (moment.duration(minDuration).asMinutes() >= moment.duration(maxDuration).asMinutes())) {
fg.get('minDuration').setErrors([ 'INVALID' ]);
fg.get('maxDuration').setErrors([ 'INVALID' ]);
return { 'INVALID': true };
}
return null;
};
const manualSlotValidator: ValidatorFn = (fg: FormGroup) => {
const start = fg.get('start').value;
const end = fg.get('end').value;
fg.get('id').setErrors(null);
fg.get('start').setErrors(null);
fg.get('end').setErrors(null);
if (!start) {
fg.get('start').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (!end) {
fg.get('end').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (start >= end) {
fg.get('start').setErrors([ 'INVALID_DATES' ]);
fg.get('end').setErrors([ 'INVALID_DATES' ]);
return { 'INVALID_DATES': true };
}
return null;
};
const periodSlotValidator: ValidatorFn = (fg: FormGroup) => {
const weekdays: string[] = [ 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY' ];
const startDay = fg.get('startDay').value;
const startTime = fg.get('startTime').value;
const endDay = fg.get('endDay').value;
const endTime = fg.get('endTime').value;
fg.get('id').setErrors(null);
fg.get('startDay').setErrors(null);
fg.get('startTime').setErrors(null);
fg.get('endDay').setErrors(null);
fg.get('endTime').setErrors(null);
if (!startDay) {
fg.get('startDay').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (!startTime) {
fg.get('startTime').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (!endDay) {
fg.get('endDay').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (!endTime) {
fg.get('endTime').setErrors([ 'MISSING_DATES' ]);
return { 'MISSING_DATES': true };
}
if (weekdays.indexOf(startDay) > weekdays.indexOf(endDay)) {
fg.get('startDay').setErrors([ 'INVALID_DAY' ]);
fg.get('endDay').setErrors([ 'INVALID_DAY' ]);
return { 'INVALID_DAY': true };
}
if (weekdays.indexOf(startDay) == weekdays.indexOf(endDay) && startTime >= endTime) {
fg.get('startTime').setErrors([ 'INVALID_TIME' ]);
fg.get('endTime').setErrors([ 'INVALID_TIME' ]);
return { 'INVALID_TIME': true };
}
return null;
};

View File

@ -0,0 +1,30 @@
<video #qrCam></video>
<form fxLayout="row wrap" fxLayoutGap="24px grid">
<mat-form-field>
<mat-select [formControl]="camera" name="camera" placeholder="{{'borrow.proving.camera' | i18n}}">
<mat-option *ngFor="let camera of cameras" [value]="camera.id">
{{camera.label || camera.id}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle [formControl]="toggleFlash" disabled>{{'borrow.proving.flash' | i18n}}</mat-slide-toggle>
<input matInput hidden name="toggleFlash" />
</mat-form-field>
</form>
<mat-card class="warn" *ngIf="initialized && noCamera">
<mat-card-header>
<mat-card-title>
<mat-icon>no_photography</mat-icon>
</mat-card-title>
<mat-card-subtitle>{{'borrow.proving.noCamera' | i18n}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{'borrow.proving.noCamera.text' | i18n}}
</p>
</mat-card-content>
</mat-card>

View File

@ -0,0 +1,4 @@
video {
width: 100%;
height: auto;
}

View File

@ -0,0 +1,105 @@
import { AfterViewInit, Component, ElementRef, ViewChild, Inject } from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { BorrowRequestsService } from 'src/app/services/borrow.service';
import QrScanner from 'qr-scanner';
import { HttpResponse } from '@angular/common/http';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-borrow-proving',
templateUrl: './proving.component.html',
styleUrls: [ './proving.component.scss' ]
})
export class BorrowProvingComponent implements AfterViewInit {
@ViewChild('qrCam') private qrCamVideo: ElementRef;
toggleFlash = new FormControl();
camera = new FormControl();
qrScanner: QrScanner;
cameras: QrScanner.Camera[];
noCamera: boolean = true;
initialized: boolean = false;
constructor(private borrowRequestService: BorrowRequestsService, private dialog: MatDialog) {
}
ngAfterViewInit() {
this.qrScanner = new QrScanner(this.qrCamVideo.nativeElement, (result) => {
this.onResult(result);
});
this.qrScanner.start().then(() => {
this.initialized = true;
QrScanner.listCameras(true).then((cameras: QrScanner.Camera[]) => {
this.cameras = cameras;
if (this.cameras.length) {
this.noCamera = false;
this.camera.setValue(this.cameras[ 0 ].id, { emitEvent: false });
this.camera.valueChanges.subscribe((value) => {
this.qrScanner.setCamera(value);
})
}
})
this.qrScanner.hasFlash().then(hasFlash => {
if (hasFlash) {
this.toggleFlash.enable();
this.toggleFlash.valueChanges.subscribe(value => {
if (value) {
this.qrScanner.turnFlashOn();
} else {
this.qrScanner.turnFlashOff();
}
});
} else {
this.toggleFlash.disable();
}
});
});
}
onResult(result: string) {
this.qrScanner.pause();
this.borrowRequestService.verifyRequest(result).subscribe((response) => {
this.openResultDialog(response, true);
}, (error) => {
this.openResultDialog(error, false);
});
}
openResultDialog(response, success: boolean) {
const dialogRef = this.dialog.open(BorrowProvingResultDialog, {
data: { "success": success, "response": response },
minWidth: '400px',
panelClass: "mat-card-dialog",
closeOnNavigation: false,
disableClose: true,
});
dialogRef.afterClosed().subscribe(result => {
this.qrScanner.start();
});
}
}
@Component({
selector: 'app-borrow-prooving-result',
templateUrl: 'proving.dialog.html',
styleUrls: [ './proving.dialog.scss' ]
})
export class BorrowProvingResultDialog {
result: any;
constructor(
@Inject(MAT_DIALOG_DATA) private data: any) {
this.result = JSON.parse(JSON.stringify(data));
console.log(this.result);
}
}

View File

@ -0,0 +1,55 @@
<mat-dialog-content>
<mat-card [ngClass]="result.success ? 'success' : (result.response.status == 412 ? 'accent' : 'warn')">
<mat-card-header>
<mat-card-title>
<mat-icon *ngIf="!result.success && result.response.status != 412">block</mat-icon>
<mat-icon *ngIf="!result.success && result.response.status == 412">help_outline</mat-icon>
<mat-icon *ngIf="result.success">check</mat-icon>
</mat-card-title>
<mat-card-subtitle>{{'borrow.proving.' + (result.success ? 'valid' : 'invalid') | i18n}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{'borrow.proving.' + (result.success ? 'valid' : 'invalid') + '.text' | i18n}}
</p>
<p *ngFor="let error of result.response.error">
<ng-container [ngSwitch]="error.field">
<span *ngSwitchCase="'nbf'">
{{'borrow.proving.error.' + error.code | i18n:(error.defaultMessage | datef)}}
</span>
<span *ngSwitchCase="'exp'">
{{'borrow.proving.error.' + error.code | i18n:(error.defaultMessage | datef)}}
</span>
<span *ngSwitchDefault>
{{'borrow.proving.error.default.' + error.code | i18n:error.defaultMessage}}
</span>
</ng-container>
</p>
<table *ngIf="result.success">
<tr>
<th>{{'borrow.request.user' | i18n}}</th>
<td>{{result.response.user}}</td>
</tr>
<tr>
<th>{{'borrow.request.item' | i18n}}</th>
<td>{{result.response.name}}</td>
</tr>
<tr>
<th>{{'borrow.request.owner' | i18n}}</th>
<td>{{result.response.owner}}</td>
</tr>
<tr>
<th>{{'borrow.request.started' | i18n}}</th>
<td>{{result.response.nbf | datef}}</td>
</tr>
<tr>
<th>{{'borrow.request.ends' | i18n}}</th>
<td>{{result.response.exp | datef}}</td>
</tr>
</table>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button [mat-dialog-close]="false">{{'ok' | i18n}}</button>
</mat-card-actions>
</mat-card>
</mat-dialog-content>

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,66 @@
<h2 mat-dialog-title>{{(create ? 'borrow.request.create' : 'borrow.request.edit') | i18n}}</h2>
<mat-dialog-content>
<h3>{{borrowRequest.borrowItem.name}}</h3>
<p>{{borrowRequest.borrowItem.description}}</p>
<a *ngIf="borrowRequest.borrowItem.url" mat-button color="accent" href="{{ borrowRequest.borrowItem.url }}"
target="_blank">
{{ borrowRequest.borrowItem.url }}
<mat-icon style="font-size: 1em;">open_in_new</mat-icon>
</a>
<br>
<br>
<mat-divider></mat-divider>
<ng-container *ngIf="borrowRequest.id">
<mat-chip-list *ngSwitch="borrowRequest.status">
<mat-chip *ngSwtichCase="'PENDING'" color="primary" selected></mat-chip>
<mat-chip color="accent" selected>Accent fish</mat-chip>
</mat-chip-list>
</ng-container>
<form [formGroup]="form">
<mat-form-field>
<input matInput [ngxMatDatetimePicker]="startPicker" formControlName="start"
placeholder="{{'borrow.request.start' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="startPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #startPicker></ngx-mat-datetime-picker>
<mat-error>
{{'borrow.request.error.start' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput [ngxMatDatetimePicker]="endPicker" formControlName="end"
placeholder="{{'borrow.request.end' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="endPicker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #endPicker></ngx-mat-datetime-picker>
<mat-error>
{{'borrow.request.error.end' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'borrow.request.comment' | i18n}}" formControlName="comment"></textarea>
<mat-error>
{{'borrow.request.error.comment' | i18n}}
</mat-error>
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<div fxLayout="row" fxLayoutAlign="space-between start">
<div>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
<button [disabled]="form.invalid" mat-raised-button (click)="save()" color="accent">
<mat-icon>save</mat-icon>{{(create ? 'borrow.request.create' : 'borrow.request.save') | i18n}}
</button>
</div>
<button *ngIf="borrowRequest.id" mat-button color="warn" (click)="confirmDelete()">
<mat-icon>delete</mat-icon> {{ 'borrow.request.delete' |
i18n}}
</button>
</div>
</mat-dialog-actions>

View File

@ -0,0 +1,7 @@
mat-form-field {
display: block;
}
mat-dialog-actions>div {
width: 100%;
}

View File

@ -0,0 +1,55 @@
<h3>{{'borrow.requests' | i18n}}</h3>
<div *ngIf="borrowRequests">
<div fxLayout="row" fxLayoutAlign="space-between start">
<form>
<mat-form-field floatLabel="always" appearance="none">
<mat-slide-toggle [formControl]="ownerFormControl">{{'borrow.requests.mine' | i18n}}</mat-slide-toggle>
<input matInput hidden />
</mat-form-field>
</form>
</div>
<table mat-table matSort [dataSource]="borrowRequests.content" (matSortChange)="updateSort($event)">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'borrow.item.name' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest"> {{ borrowRequest.borrowItem.name}} </td>
</ng-container>
<ng-container matColumnDef="starts">
<th mat-header-cell *matHeaderCellDef> {{'borrow.requests.starts' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest"> {{ borrowRequest.starts | datef}}
</td>
</ng-container>
<ng-container matColumnDef="ends">
<th mat-header-cell *matHeaderCellDef> {{'borrow.requests.ends' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest"> {{ borrowRequest.ends | datef}}
</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef> {{'borrow.requests.status' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest"> {{ 'borrow.requests.status.' + borrowRequest.status | i18n}}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="align-right"> {{'borrow.requests.actions' | i18n}} </th>
<td mat-cell *matCellDef="let borrowRequest" class="text-right">
<a mat-icon-button *ngIf="borrowRequest.user == userId">
<mat-icon (click)="edit(borrowRequest)">edit</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowRequest.user == userId">
<mat-icon (click)="confirmDelete(borrowRequest)">delete</mat-icon>
</a>
<a mat-icon-button *ngIf="borrowRequest.item.owner != userId">
<mat-icon (click)="updateStatus(borrowRequest)">pending_actions</mat-icon>
</a>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="borrowRequestColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: borrowRequestColumns"></tr>
</table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="borrowRequests.totalElements"
[pageSize]="borrowRequests.size" (page)="updatePages($event)" showFirstLastButtons></mat-paginator>
</div>

View File

@ -0,0 +1,21 @@
.mat-form-field+.mat-form-field, .mat-form-field+.mat-slide-toggle {
margin-left: 8px;
}
.mat-header-cell,
.mat-cell {
&.text-right {
text-align: right;
}
}
.mat-cell .mat-button {
padding-left: 0px;
padding-right: 0px;
}
.align-right{
display: flex;
padding: 21px 0;
justify-content: flex-end;
}

View File

@ -0,0 +1,181 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { PageEvent } from '@angular/material/paginator';
import { Sort } from '@angular/material/sort';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import * as moment from 'moment';
import { ConfirmDialog } from '../../../ui/confirm/confirm.component';
import { BorrowRequestsService } from './../../../services/borrow.service';
import { I18nService } from './../../../services/i18n.service';
import { AuthService } from './../../../services/auth.service';
@Component({
selector: 'app-borrow-requests',
templateUrl: './requests.component.html',
styleUrls: [ './requests.component.scss' ]
})
export class BorrowRequestsComponent implements OnInit {
borrowRequests: any[];
page: any = { page: 0, size: 10, sort: "id", desc: false };
pageSizeOptions: number[] = [ 5, 10, 25, 50 ];
ownerFormControl = new FormControl();
userId: any;
borrowRequestColumns = [ "name", "status", "starts", "ends", "actions" ];
constructor(private borrowRequestsService: BorrowRequestsService,
private i18n: I18nService,
private authService: AuthService,
public dialog: MatDialog) {
}
ngOnInit() {
this.authService.auth.subscribe((auth: any) => {
if (auth.principal && auth.principal.userId) {
this.userId = auth.principal.userId;
}
});
this.ownerFormControl.valueChanges.subscribe(value => {
this.borrowRequestsService.getRequests(0, this.page.size, this.page.sort, this.page.desc, value).subscribe((data: any) => {
this.borrowRequests = data;
}, (error) => { })
})
this.refresh();
}
refresh(): void {
this.borrowRequestsService.getRequests(this.page.page, this.page.size, this.page.sort, this.page.desc, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowRequests = data;
})
}
updatePages(event: PageEvent) {
this.page.page = event.pageIndex;
this.page.size = event.pageSize;
this.borrowRequestsService.getRequests(this.page.page, this.page.size, this.page.sort, this.page.desc, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowRequests = data;
}, (error) => { })
}
updateSort(sort: Sort) {
if (sort.direction == "") {
this.page.sort = "id";
this.page.desc = false;
} else {
this.page.sort = sort.active;
this.page.desc = sort.direction == "desc";
}
this.borrowRequestsService.getRequests(this.page.page, this.page.size, this.page.sort, this.page.desc, this.ownerFormControl.value).subscribe((data: any) => {
this.borrowRequests = data;
}, (error) => { })
}
edit(borrowRequest) {
const dialogRef = this.dialog.open(BorrowRequestEditComponent, {
data: borrowRequest
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.refresh();
}
});
}
confirmDelete(borrowRequest) {
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'borrow.items.confirmDelete',
'args': [ borrowRequest.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.borrowRequestsService.deleteRequest(borrowRequest.id).subscribe((result: any) => {
this.refresh();
})
}
});
}
}
@Component({
selector: 'app-borrow-request-edit',
templateUrl: './request.edit.html',
styleUrls: [ './request.edit.scss' ]
})
export class BorrowRequestEditComponent {
borrowRequest: any;
create: boolean = false;
form: FormGroup;
constructor(private borrowRequestsService: BorrowRequestsService,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<BorrowRequestEditComponent>,
public dialog: MatDialog) {
this.form = this.formBuilder.group({
})
this.borrowRequest = data;
if (!this.borrowRequest.id) {
this.create = true;
}
}
formToBorrowRequest(): any {
const borrowRequest: any = {};
return borrowRequest;
}
save() {
this.borrowRequestsService.createOrUpdateRequest(this.formToBorrowRequest()).subscribe((data: any) => {
this.dialogRef.close(data);
}, (error) => {
if (error.status == 409) {
let errors = {};
for (let code of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
for (let code in errors) {
this.form.get(code).setErrors(errors[ code ]);
}
}
});
}
confirmDelete() {
const borrowRequest = this.formToBorrowRequest();
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'borrow.requests.confirmDelete',
'args': [ borrowRequest.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.borrowRequestsService.deleteRequest(borrowRequest.id).subscribe((result: any) => {
this.dialogRef.close(true);
})
}
});
}
}

View File

@ -0,0 +1,117 @@
<mat-card>
<mat-card-header>
<mat-card-title>{{'invites.register' | i18n}}</mat-card-title>
</mat-card-header>
<mat-card-content>
<ng-container *ngIf="invite">
<h3>
<mat-icon inline=true>{{'invites.quota.' + invite.quota + ".icon" | i18n}}</mat-icon> {{'invites.quota.'
+
invite.quota
| i18n}}
</h3>
<p>{{'invites.quota.' + invite.quota +
".text" | i18n}}</p>
<p *ngIf="invite.url">
<a mat-button href="{{invite.url}}" target="_blank" color="accent">{{'invites.register.url' |
i18n}}</a>
</p>
<ng-container *ngIf="!success">
<blockquote *ngIf="invite.message && auth.principal.userId != invite.owner" class="message">
{{invite.message}}</blockquote>
<blockquote *ngIf="invite.note && auth.principal.userId != invite.owner" class="note">{{invite.note}}
</blockquote>
<form [formGroup]="form" *ngIf="!error">
<ng-container *ngIf="!auth.authenticated">
<mat-form-field>
<input matInput placeholder="{{'username' | i18n}}" formControlName="username" matAutofocus
tabindex="1">
<mat-error>
{{'username.error' | i18n}}
</mat-error>
<a mat-button matSuffix mat-icon-button (click)="genUsername()" tabindex="5">
<mat-icon>autorenew</mat-icon>
</a>
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="{{'password' | i18n}}"
formControlName="password" tabindex="2">
<mat-error>
<div *ngFor="let error of form.get('password').errors | keyvalue">
{{'password.error.' + error.key | i18n}}<br>
</div>
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="{{'password.confirm' | i18n}}"
formControlName="password2" tabindex="3">
<mat-error>
{{'password.not-match' | i18n}}
</mat-error>
</mat-form-field>
</ng-container>
<ng-container *ngIf="auth.principal.userId == invite.owner">
<mat-form-field>
<textarea matInput placeholder="{{'invite.message' | i18n}}"
formControlName="message"></textarea>
<mat-error>
{{'invite.errors.message' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'invite.note' | i18n}}" formControlName="note"></textarea>
<mat-error>
{{'invites.error.note' | i18n}}
</mat-error>
</mat-form-field>
</ng-container>
</form>
</ng-container>
<ng-container *ngIf="success">
<h2>{{'invites.register.success' | i18n}}</h2>
<p>{{'invites.register.success.text' | i18n}}</p>
</ng-container>
</ng-container>
<ng-container *ngIf="error">
<mat-error>
{{'invites.register.error.' + error | i18n}}
</mat-error>
</ng-container>
</mat-card-content>
<mat-card-actions>
<ng-container *ngIf="!success && invite">
<button *ngIf="!working && !auth.authenticated && !error" mat-raised-button color="primary"
[disabled]="form.invalid" (click)="register()" tabindex="4">
{{'invites.register' | i18n}}
</button>
<button *ngIf="auth.principal.userId == invite.owner && !error" mat-raised-button color="primary"
(click)="save()" tabindex="4">
{{'invites.edit.save' | i18n}}
</button>
</ng-container>
<ng-container *ngIf="success">
<a routerLink="/login" mat-raised-button color="primary">
{{'invites.register.login' | i18n}}
</a>
</ng-container>
<mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar>
</mat-card-actions>
<mat-card-footer>
<a href="https://wiki.bstly.de/services/webstly#invite" class="help-button"
matTooltip="{{'help-button' | i18n}}" matTooltipPosition="above" target="_blank" mat-fab color="accent">
<mat-icon>contact_support</mat-icon>
</a>
</mat-card-footer>
</mat-card>
<div *ngIf="!success && permissions && permissions[0] && !error">
<h3>{{'permissions' | i18n}}</h3>
<app-permissions [permissions]="permissions"></app-permissions>
</div>
<div *ngIf="!success && quotas && quotas[0] && !error">
<h3>{{'quotas' | i18n}}</h3>
<app-quotas [quotas]="quotas"></app-quotas>
</div>

View File

@ -0,0 +1,23 @@
@import '../../../../variables.scss';
mat-form-field {
display: block;
}
mat-hint {
white-space: normal;
}
blockquote {
margin-left: 5px;
padding: 5px;
&.message {
border-left: 2px solid $accent;
}
&.note {
border-left: 2px solid $primary;
}
}

View File

@ -0,0 +1,166 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { MatchingValidator } from './../../../utils/matching.validator';
import { InviteService } from './../../../services/invites.service';
import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator';
import { I18nService } from 'src/app/services/i18n.service';
import { MatDialog } from '@angular/material/dialog';
import { InviteEditComponent } from '../edit/invite.edit';
import { AuthService } from 'src/app/services/auth.service';
@Component({
selector: 'app-invite-code',
templateUrl: './code.component.html',
styleUrls: [ './code.component.scss' ]
})
export class InviteCodeComponent implements OnInit {
form: FormGroup;
error: string;
working: boolean = true;
success: boolean = false;
invite: any;
datetimeformat: string;
auth: any;
permissions = [];
quotas = [];
constructor(
private authService: AuthService,
private inviteService: InviteService,
private formBuilder: FormBuilder,
private i18n: I18nService,
public dialog: MatDialog,
private route: ActivatedRoute) {
this.authService.auth.subscribe(data => {
this.auth = data;
})
}
async ngOnInit() {
this.datetimeformat = this.i18n.get('format.datetime', []);
this.form = this.formBuilder.group({
username: [ '', Validators.required ],
password: [ '', Validators.nullValidator ],
password2: [ '', Validators.required ]
}, {
validators: MatchingValidator('password', 'password2')
});
const code = this.route.snapshot.paramMap.get('code');
this.inviteService.code(code).subscribe((data) => {
this.invite = data;
if (this.invite.redeemed) {
this.error = "ALREADY_REDEEMED";
}
if (this.invite.owner == this.auth.principal.userId) {
this.form = this.formBuilder.group({
message: [ '', Validators.nullValidator ],
note: [ '', Validators.nullValidator ],
});
this.form.get("message").setValue(this.invite.message);
this.form.get("note").setValue(this.invite.note);
}
this.working = false;
}, (error) => {
this.working = false;
if (error.status == 406) {
this.error = "INVALID_CODE";
}
})
this.inviteService.permissions(code).subscribe((data: any) => {
this.permissions = data;
})
this.inviteService.quotas(code).subscribe((data: any) => {
this.quotas = data;
})
}
genUsername() {
const config: Config = {
dictionaries: [ adjectives, colors, animals ],
separator: "",
style: "capital",
length: 3
};
this.form.get("username").setValue(uniqueNamesGenerator(config));
}
register() {
if (this.form.valid && !this.working) {
this.working = true;
const model: any = {};
model.token = this.invite.code;
model.username = this.form.get("username").value;
model.password = this.form.get("password").value;
model.password2 = this.form.get("password2").value;
this.inviteService.register(model).subscribe((result: any) => {
this.working = false;
this.success = true;
}, (error) => {
this.working = false;
if (error.status == 401) {
this.error = "NO_CODE";
} else if (error.status == 406) {
this.error = "INVALID_CODE";
} else if (error.status == 410) {
this.error = "ALREADY_REDEEMED";
} else if (error.status == 409) {
let errors = {};
for (let code of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
for (let code in errors) {
this.form.get(code).setErrors(errors[ code ]);
}
}
})
}
}
save() {
if (this.form.valid && !this.working) {
this.working = true;
this.invite.message = this.form.get("message").value;
this.invite.note = this.form.get("note").value;
this.inviteService.update(this.invite).subscribe((result: any) => {
this.invite = result;
this.working = false;
}, (error) => {
this.working = false;
if (error.status == 406) {
this.error = "INVALID_CODE";
} if (error.status == 410) {
this.error = "ALREADY_REDEEMED";
} else if (error.status == 409) {
let errors = {};
for (let code of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
for (let code in errors) {
this.form.get(code).setErrors(errors[ code ]);
}
}
})
}
}
}

View File

@ -0,0 +1,26 @@
<h2 mat-dialog-title>{{'invites.edit' | i18n}}</h2>
<mat-dialog-content>
<form [formGroup]="form">
<mat-error *ngIf="error">
{{'invites.register.error.' + error | i18n}}
</mat-error>
<mat-form-field>
<textarea matInput placeholder="{{'invite.message' | i18n}}" formControlName="message"></textarea>
<mat-error>
{{'invite.errors.message' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'invite.note' | i18n}}" formControlName="note" ></textarea>
<mat-error>
{{'invites.error.note' | i18n}}
</mat-error>
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
<button mat-raised-button color="primary" [disabled]="form.invalid || working" (click)="save()">
{{'invites.edit.save' | i18n}}
</button>
</mat-dialog-actions>

View File

@ -0,0 +1,7 @@
mat-form-field {
display: block;
}
mat-hint {
white-space: normal;
}

View File

@ -0,0 +1,71 @@
import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { MatchingValidator } from '../../../utils/matching.validator';
import { InviteService } from '../../../services/invites.service';
import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-invite-edit',
templateUrl: './invite.edit.html',
styleUrls: [ './invite.edit.scss' ]
})
export class InviteEditComponent {
form: FormGroup;
error: string;
working: boolean = false;
success: boolean = false;
invite: any;
constructor(
private inviteService: InviteService,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<InviteEditComponent>,
public dialog: MatDialog) {
this.form = this.formBuilder.group({
message: [ '', Validators.nullValidator ],
note: [ '', Validators.nullValidator ],
});
this.invite = data;
this.form.get("message").setValue(this.invite.message);
this.form.get("note").setValue(this.invite.note);
}
save() {
if (this.form.valid && !this.working) {
this.working = true;
this.invite.message = this.form.get("message").value;
this.invite.note = this.form.get("note").value;
this.inviteService.update(this.invite).subscribe((result: any) => {
this.working = false;
this.dialogRef.close(result);
}, (error) => {
this.working = false;
if (error.status == 406) {
this.error = "INVALID_CODE";
} if (error.status == 410) {
this.error = "ALREADY_REDEEMED";
} else if (error.status == 409) {
let errors = {};
for (let code of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
for (let code in errors) {
this.form.get(code).setErrors(errors[ code ]);
}
}
})
}
}
}

View File

@ -37,17 +37,17 @@
<td mat-cell *matCellDef="let invite"> {{ invite.expires | date:datetimeformat}} </td> <td mat-cell *matCellDef="let invite"> {{ invite.expires | date:datetimeformat}} </td>
</ng-container> </ng-container>
<ng-container matColumnDef="link"> <ng-container matColumnDef="link">
<th mat-header-cell *matHeaderCellDef> {{'invite.link' | i18n}} </th> <th mat-header-cell *matHeaderCellDef> {{'invite.link' | i18n}} </th>
<td mat-cell *matCellDef="let invite"> <a href="{{ invite.codeLink}}" target="_blank" mat-button color="accent"> <td mat-cell *matCellDef="let invite">
{{ <a href="{{ invite.codeLink }}" target="_blank" mat-button color="accent">{{invite.code}}</a>
invite.code}}</a> </td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="note"> <ng-container matColumnDef="note">
<th mat-header-cell *matHeaderCellDef> {{'invite.note' | i18n}} </th> <th mat-header-cell *matHeaderCellDef> {{'invite.note' | i18n}} </th>
<td mat-cell *matCellDef="let invite"> <span *ngIf="invite.note">{{ invite.note}}</span> <i *ngIf="!invite.note">{{ 'invite.noNote' | i18n}}</i> <td mat-cell *matCellDef="let invite"> <span *ngIf="invite.note">{{ invite.note}}</span> <i
*ngIf="!invite.note">{{ 'invite.noNote' | i18n}}</i>
</td> </td>
</ng-container> </ng-container>
@ -65,10 +65,20 @@
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> {{'invite.actions' | i18n}} </th>
<td mat-cell *matCellDef="let invite">
<a mat-icon-button>
<mat-icon (click)="edit(invite)">edit</mat-icon>
</a>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="inviteColumns"></tr> <tr mat-header-row *matHeaderRowDef="inviteColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: inviteColumns"></tr> <tr mat-row *matRowDef="let myRowData; columns: inviteColumns"></tr>
</table> </table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="invites.totalElements" [pageSize]="invites.size" (page)="updatePages($event)" showFirstLastButtons></mat-paginator> <mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="invites.totalElements" [pageSize]="invites.size"
(page)="updatePages($event)" showFirstLastButtons></mat-paginator>
</div> </div>
<mat-card> <mat-card>
@ -117,7 +127,8 @@
<table mat-table matSort [dataSource]="others.content"> <table mat-table matSort [dataSource]="others.content">
<ng-container matColumnDef="note"> <ng-container matColumnDef="note">
<th mat-header-cell *matHeaderCellDef> {{'invite.note' | i18n}} </th> <th mat-header-cell *matHeaderCellDef> {{'invite.note' | i18n}} </th>
<td mat-cell *matCellDef="let invite"> <span *ngIf="invite.note">{{ invite.note}}</span> <i *ngIf="!invite.note">{{ 'invite.noNote' | i18n}}</i> </td> <td mat-cell *matCellDef="let invite"> <span *ngIf="invite.note">{{ invite.note}}</span> <i
*ngIf="!invite.note">{{ 'invite.noNote' | i18n}}</i> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="redeemed"> <ng-container matColumnDef="redeemed">
@ -130,5 +141,6 @@
<tr mat-header-row *matHeaderRowDef="otherColumns"></tr> <tr mat-header-row *matHeaderRowDef="otherColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: otherColumns"></tr> <tr mat-row *matRowDef="let myRowData; columns: otherColumns"></tr>
</table> </table>
<mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="others.totalElements" [pageSize]="others.size" (page)="updateOthers($event)" showFirstLastButtons></mat-paginator> <mat-paginator [pageSizeOptions]="pageSizeOptions" [length]="others.totalElements" [pageSize]="others.size"
(page)="updateOthers($event)" showFirstLastButtons></mat-paginator>
</div> </div>

View File

@ -1,19 +1,21 @@
import {Component, OnInit, ViewChild} from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import {PageEvent} from '@angular/material/paginator'; import { PageEvent } from '@angular/material/paginator';
import {AuthService} from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import {I18nService} from '../../services/i18n.service'; import { I18nService } from '../../services/i18n.service';
import {QuotaService} from '../../services/quota.service'; import { QuotaService } from '../../services/quota.service';
import {InviteService} from '../../services/invites.service'; import { InviteService } from '../../services/invites.service';
import {FormControl} from '@angular/forms'; import { FormControl } from '@angular/forms';
import {debounceTime} from 'rxjs/operators'; import { debounceTime } from 'rxjs/operators';
import { InviteEditComponent } from './edit/invite.edit';
import { MatDialog } from '@angular/material/dialog';
@Component({ @Component({
selector: 'app-invites', selector: 'app-invites',
templateUrl: './invites.component.html', templateUrl: './invites.component.html',
styleUrls: ['./invites.component.scss'] styleUrls: [ './invites.component.scss' ]
}) })
export class InvitesComponent implements OnInit { export class InvitesComponent implements OnInit {
@ -24,21 +26,20 @@ export class InvitesComponent implements OnInit {
success: boolean; success: boolean;
working: boolean; working: boolean;
datetimeformat: string; datetimeformat: string;
pageSizeOptions: number[] = [5, 10, 25, 50]; pageSizeOptions: number[] = [ 5, 10, 25, 50 ];
searchFormControl = new FormControl(); searchFormControl = new FormControl();
redeemedFormControl = new FormControl(); redeemedFormControl = new FormControl();
searchOthersFormControl = new FormControl(); searchOthersFormControl = new FormControl();
redeemedOthersFormControl = new FormControl(); redeemedOthersFormControl = new FormControl();
inviteColumns = ["starts", "expires", "link", "note", "message", "redeemed"]; inviteColumns = [ "starts", "expires", "link", "note", "message", "redeemed", "actions" ];
otherColumns = ["note", "redeemed"]; otherColumns = [ "note", "redeemed" ];
constructor( constructor(
private authService: AuthService,
private inviteService: InviteService, private inviteService: InviteService,
private i18n: I18nService, private i18n: I18nService,
private quotaService: QuotaService, private quotaService: QuotaService,
private router: Router, public dialog: MatDialog,
private route: ActivatedRoute) { private route: ActivatedRoute) {
} }
@ -49,23 +50,23 @@ export class InvitesComponent implements OnInit {
this.searchFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => { this.searchFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => {
this.inviteService.getPages(this.quota, 0, this.invites.size, value, this.redeemedFormControl.value).subscribe((data: any) => { this.inviteService.getPages(this.quota, 0, this.invites.size, value, this.redeemedFormControl.value).subscribe((data: any) => {
this.invites = data; this.invites = data;
}, (error) => {}) }, (error) => { })
}) })
this.redeemedFormControl.valueChanges.subscribe(value => { this.redeemedFormControl.valueChanges.subscribe(value => {
this.inviteService.getPages(this.quota, 0, this.invites.size, this.searchFormControl.value ? this.searchFormControl.value : "", value).subscribe((data: any) => { this.inviteService.getPages(this.quota, 0, this.invites.size, this.searchFormControl.value ? this.searchFormControl.value : "", value).subscribe((data: any) => {
this.invites = data; this.invites = data;
}, (error) => {}) }, (error) => { })
}) })
this.searchOthersFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => { this.searchOthersFormControl.valueChanges.pipe(debounceTime(500)).subscribe(value => {
this.inviteService.getOthersPages(this.quota, 0, this.others.size, value, this.redeemedOthersFormControl.value).subscribe((data: any) => { this.inviteService.getOthersPages(this.quota, 0, this.others.size, value, this.redeemedOthersFormControl.value).subscribe((data: any) => {
this.others = data; this.others = data;
}, (error) => {}) }, (error) => { })
}) })
this.redeemedOthersFormControl.valueChanges.subscribe(value => { this.redeemedOthersFormControl.valueChanges.subscribe(value => {
this.inviteService.getOthersPages(this.quota, 0, this.others.size, this.searchOthersFormControl.value ? this.searchOthersFormControl.value : "", value).subscribe((data: any) => { this.inviteService.getOthersPages(this.quota, 0, this.others.size, this.searchOthersFormControl.value ? this.searchOthersFormControl.value : "", value).subscribe((data: any) => {
this.others = data; this.others = data;
}, (error) => {}) }, (error) => { })
}) })
this.update(); this.update();
@ -75,56 +76,67 @@ export class InvitesComponent implements OnInit {
update(): void { update(): void {
this.inviteQuota = 0; this.inviteQuota = 0;
this.quotaService.quotas().subscribe((data: any) => { this.quotaService.quotas().subscribe((data: any) => {
for(let quota of data) { for (let quota of data) {
if(quota.name == "invite_" + this.quota) { if (quota.name == "invite_" + this.quota) {
this.inviteQuota = quota.value; this.inviteQuota = quota.value;
} }
} }
}) })
if(!this.invites) { if (!this.invites) {
this.inviteService.get(this.quota).subscribe((data: any) => { this.inviteService.get(this.quota).subscribe((data: any) => {
this.invites = data; this.invites = data;
}) })
} else { } else {
this.inviteService.getPages(this.quota, this.invites.number || 0, this.invites.size || 10, this.searchFormControl.value ? this.searchFormControl.value : "", this.redeemedFormControl.value).subscribe((data: any) => { this.inviteService.getPages(this.quota, this.invites.number || 0, this.invites.size || 10, this.searchFormControl.value ? this.searchFormControl.value : "", this.redeemedFormControl.value).subscribe((data: any) => {
this.invites = data; this.invites = data;
}, (error) => {}) }, (error) => { })
} }
this.inviteService.getOthers(this.quota).subscribe((data: any) => { this.inviteService.getOthers(this.quota).subscribe((data: any) => {
this.others = data; this.others = data;
}, (error) => {}) }, (error) => { })
} }
updatePages(event: PageEvent) { updatePages(event: PageEvent) {
this.inviteService.getPages(this.quota, event.pageIndex, event.pageSize, this.searchFormControl.value ? this.searchFormControl.value : "", this.redeemedFormControl.value).subscribe((data: any) => { this.inviteService.getPages(this.quota, event.pageIndex, event.pageSize, this.searchFormControl.value ? this.searchFormControl.value : "", this.redeemedFormControl.value).subscribe((data: any) => {
this.invites = data; this.invites = data;
}, (error) => {}) }, (error) => { })
} }
create(): void { create(): void {
this.working = true; this.working = true;
this.inviteService.create(this.quota, {}).subscribe(response => { this.inviteService.create(this.quota, {}).subscribe(response => {
this.update(); this.update();
this.working = false; this.working = false;
}, (error) => { }, (error) => {
this.working = false; this.working = false;
if(error.status == 409) { if (error.status == 409) {
let errors = {}; let errors = {};
for(let code of error.error) { for (let code of error.error) {
errors[code.field] = errors[code.field] || {}; errors[ code.field ] = errors[ code.field ] || {};
errors[code.field][code.code] = true; errors[ code.field ][ code.code ] = true;
} }
} }
}) })
} }
edit(invite) {
const dialogRef = this.dialog.open(InviteEditComponent, {
data: invite,
minWidth: "400px"
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.update();
}
});
}
updateOthers(event: PageEvent) { updateOthers(event: PageEvent) {
this.inviteService.getOthersPages(this.quota, event.pageIndex, event.pageSize, this.searchOthersFormControl.value ? this.searchOthersFormControl.value : "", this.redeemedOthersFormControl.value).subscribe((data: any) => { this.inviteService.getOthersPages(this.quota, event.pageIndex, event.pageSize, this.searchOthersFormControl.value ? this.searchOthersFormControl.value : "", this.redeemedOthersFormControl.value).subscribe((data: any) => {
this.others = data; this.others = data;
}, (error) => {}) }, (error) => { })
} }
} }

View File

@ -3,7 +3,11 @@
<p *ngIf="!tags || tags.length == 0">{{'partey.tags.none' | i18n}}</p> <p *ngIf="!tags || tags.length == 0">{{'partey.tags.none' | i18n}}</p>
<mat-chip-list> <mat-chip-list>
<mat-chip *ngFor="let tag of tags">{{tag.tag}}</mat-chip> <ng-container *ngFor="let tag of tags">
<mat-chip *ngIf="activeTag(tag)" color="accent" selected>{{tag.tag}}</mat-chip>
<mat-chip *ngIf="upcomingTag(tag)" disabled matTooltip="{{'partey.tags.upcoming' | i18n:(tag.starts | datef)}}" selected>{{tag.tag}}</mat-chip>
<mat-chip *ngIf="expiringTag(tag)" color="primary" matTooltip="{{'partey.tags.expires' | i18n:(tag.expires | datef)}}" selected>{{tag.tag}}</mat-chip>
</ng-container>
</mat-chip-list> </mat-chip-list>

View File

@ -21,4 +21,20 @@ export class ParteyComponent implements OnInit {
}) })
} }
toDate(value : string) {
return new Date(value);
}
activeTag(tag: any) {
return !tag.starts && !tag.expires;
}
upcomingTag(tag: any) {
return tag.starts && (new Date() < this.toDate(tag.starts));
}
expiringTag(tag: any) {
return !(this.upcomingTag(tag)) && tag.expires;
}
} }

View File

@ -9,17 +9,17 @@
</mat-error> </mat-error>
<mat-form-field> <mat-form-field>
<input matInput placeholder="{{'username' | i18n}}" formControlName="username" <input matInput placeholder="{{'username' | i18n}}" formControlName="username"
[(ngModel)]="model.username" required matAutofocus> [(ngModel)]="model.username" required matAutofocus tabindex="1">
<mat-error> <mat-error>
{{'username.error' | i18n}} {{'username.error' | i18n}}
</mat-error> </mat-error>
<a mat-button matSuffix mat-icon-button (click)="genUsername()"> <a mat-button matSuffix mat-icon-button (click)="genUsername()" tabindex="8">
<mat-icon>autorenew</mat-icon> <mat-icon>autorenew</mat-icon>
</a> </a>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<input matInput type="password" placeholder="{{'password' | i18n}}" formControlName="password" <input matInput type="password" placeholder="{{'password' | i18n}}" formControlName="password"
[(ngModel)]="model.password" required> [(ngModel)]="model.password" required tabindex="2">
<mat-error> <mat-error>
<div *ngFor="let error of form.get('password').errors | keyvalue"> <div *ngFor="let error of form.get('password').errors | keyvalue">
{{'password.error.' + error.key | i18n}}<br> {{'password.error.' + error.key | i18n}}<br>
@ -28,14 +28,14 @@
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<input matInput type="password" placeholder="{{'password.confirm' | i18n}}" formControlName="password2" <input matInput type="password" placeholder="{{'password.confirm' | i18n}}" formControlName="password2"
[(ngModel)]="model.password2" required> [(ngModel)]="model.password2" required tabindex="3">
<mat-error> <mat-error>
{{'password.not-match' | i18n}} {{'password.not-match' | i18n}}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<mat-slide-toggle formControlName="primaryEmail" [(ngModel)]="model.primaryEmail" <mat-slide-toggle formControlName="primaryEmail" [(ngModel)]="model.primaryEmail"
(change)="onPrimaryChange()"> (change)="onPrimaryChange()" tabindex="4">
{{'email.primary' | i18n}} {{'email.primary' | i18n}}
</mat-slide-toggle> </mat-slide-toggle>
<mat-icon #primaryHint="matTooltip" (click)="primaryHint.toggle()" inline="true" <mat-icon #primaryHint="matTooltip" (click)="primaryHint.toggle()" inline="true"
@ -43,7 +43,7 @@
<mat-form-field *ngIf="model.primaryEmail"> <mat-form-field *ngIf="model.primaryEmail">
<input matInput type="email" placeholder="{{'email' | i18n}}" formControlName="email" <input matInput type="email" placeholder="{{'email' | i18n}}" formControlName="email"
[(ngModel)]="model.email" required> [(ngModel)]="model.email" required tabindex="5">
<mat-error> <mat-error>
{{'email.invalid' | i18n}} {{'email.invalid' | i18n}}
</mat-error> </mat-error>
@ -59,14 +59,16 @@
<mat-divider></mat-divider> <mat-divider></mat-divider>
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid"> <button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid" tabindex="6">
{{'register' | i18n}} {{'register' | i18n}}
</button> </button>
<mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="working" mode="indeterminate"></mat-progress-bar>
</mat-card-actions> </mat-card-actions>
<mat-card-footer> <mat-card-footer>
<a href="https://wiki.bstly.de/services/webstly#registration" class="help-button" matTooltip="{{'help-button' | i18n}}" matTooltipPosition="above" target="_blank" mat-fab color="accent"> <a href="https://wiki.bstly.de/services/webstly#registration" class="help-button"
matTooltip="{{'help-button' | i18n}}" matTooltipPosition="above" target="_blank" mat-fab color="accent"
tabindex="7">
<mat-icon>contact_support</mat-icon> <mat-icon>contact_support</mat-icon>
</a> </a>
</mat-card-footer> </mat-card-footer>

View File

@ -0,0 +1,106 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class BorrowItemsService {
constructor(private http: HttpClient) {
}
getItems(page: number, size: number, sort: string, desc: boolean, search: string, owner: boolean) {
let httpParams = new HttpParams();
if (page != undefined) {
httpParams = httpParams.set("page", "" + page);
}
if (size != undefined) {
httpParams = httpParams.set("size", "" + size);
}
if (sort != undefined) {
httpParams = httpParams.set("sort", sort);
}
if (desc != undefined) {
httpParams = httpParams.set("desc", "" + desc);
}
if (search != undefined) {
httpParams = httpParams.set("search", "" + search);
}
if (owner != undefined && owner) {
httpParams = httpParams.set("owner", "" + owner);
}
return this.http.get(environment.apiUrl + "/borrow/items", { params: httpParams });
}
getItem(id) {
return this.http.get(environment.apiUrl + "/borrow/items/" + id);
}
createOrUpdateItem(borrowItem) {
return this.http.post(environment.apiUrl + "/borrow/items", borrowItem);
}
deleteItem(id) {
return this.http.delete(environment.apiUrl + "/borrow/items/" + id);
}
}
@Injectable({
providedIn: 'root',
})
export class BorrowRequestsService {
constructor(private http: HttpClient) {
}
getRequests(page: number, size: number, sort: string, desc: boolean, owner: boolean) {
let httpParams = new HttpParams();
if (page != undefined) {
httpParams = httpParams.set("page", "" + page);
}
if (size != undefined) {
httpParams = httpParams.set("size", "" + size);
}
if (sort != undefined) {
httpParams = httpParams.set("sort", sort);
}
if (desc != undefined) {
httpParams = httpParams.set("desc", "" + desc);
}
if (owner != undefined && owner) {
httpParams = httpParams.set("owner", "" + owner);
}
return this.http.get(environment.apiUrl + "/borrow/requests", { params: httpParams });
}
createOrUpdateRequest(borrowRequest) {
return this.http.post(environment.apiUrl + "/borrow/requests", borrowRequest);
}
deleteRequest(id) {
return this.http.delete(environment.apiUrl + "/borrow/requests/" + id);
}
setRequestStatus(borrowRequest) {
return this.http.put(environment.apiUrl + "/borrow/requests", borrowRequest);
}
getRequestCode(id) {
return this.http.get(environment.apiUrl + "/borrow/requests/code/" + id);
}
verifyRequest(serialized) {
return this.http.post(environment.apiUrl + "/borrow/requests/verify", serialized);
}
}

View File

@ -1,6 +1,6 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {environment} from '../../environments/environment'; import { environment } from '../../environments/environment';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -8,7 +8,7 @@ import {environment} from '../../environments/environment';
export class I18nService { export class I18nService {
locale: string = "de-informal"; locale: string = "de-informal";
locales: any[] = ["de-informal"]; locales: any[] = [ "de-informal" ];
i18n: any; i18n: any;
constructor(private http: HttpClient) { constructor(private http: HttpClient) {
@ -31,13 +31,13 @@ export class I18nService {
let browserLocale = navigator.language; let browserLocale = navigator.language;
if(browserLocale.indexOf("-") != -1) { if (browserLocale.indexOf("-") != -1) {
browserLocale = browserLocale.split("-")[0]; browserLocale = browserLocale.split("-")[ 0 ];
} }
let locale = localStorage.getItem("bstly.locale") || browserLocale || this.locales[0]; let locale = localStorage.getItem("bstly.locale") || browserLocale || this.locales[ 0 ];
if(locale == 'de') { if (locale == 'de') {
locale = 'de-informal'; locale = 'de-informal';
} }
@ -45,54 +45,58 @@ export class I18nService {
await this.http.get(environment.apiUrl + "/i18n").toPromise().then((response: any) => { await this.http.get(environment.apiUrl + "/i18n").toPromise().then((response: any) => {
this.locales = response; this.locales = response;
}); });
} catch(e) { } catch (e) {
console.debug("fallback to default locales"); console.debug("fallback to default locales");
} }
if(this.locales.indexOf(locale) == -1) { if (this.locales.indexOf(locale) == -1) {
locale = this.locales[0]; locale = this.locales[ 0 ];
} }
this.setLocale(locale); this.setLocale(locale);
try { try {
this.i18n = await this.http.get(environment.apiUrl + "/i18n/" + locale).toPromise(); this.i18n = await this.http.get(environment.apiUrl + "/i18n/" + locale).toPromise();
} catch(e) { } catch (e) {
this.i18n = await this.http.get("/assets/i18n/" + locale + ".json").toPromise(); this.i18n = await this.http.get("/assets/i18n/" + locale + ".json").toPromise();
console.debug("fallback to default locale"); console.debug("fallback to default locale");
} }
} }
get(key, args: string[]): string { get(key, args: string[]): string {
return this.getInternal(key, args, this.i18n); return this.getInternal(key, args, this.i18n, "");
} }
getInternal(key, args: string[], from): string { getInternal(key, args: string[], from, path): string {
key += ''; key += '';
if(!from) { if (!from) {
return key; return this.empty(key, args, path);
} else if(from[key]) { } else if (from[ key ]) {
if(typeof from[key] === 'object') { if (typeof from[ key ] === 'object') {
if(from[key]["."]) { if (from[ key ][ "." ]) {
return this.insertArguments(from[key]["."], args); return this.insertArguments(from[ key ][ "." ], args);
} }
return key; return this.empty(key, args, path);
} }
return this.insertArguments(from[key], args); return this.insertArguments(from[ key ], args);
} else { } else {
let keys = key.split("."); let keys = key.split(".");
if(from[keys[0]]) { if (from[ keys[ 0 ] ]) {
key = keys.slice(1, keys.length).join("."); key = keys.slice(1, keys.length).join(".");
return this.getInternal(key, args, from[keys[0]]) return this.getInternal(key, args, from[ keys[ 0 ] ], path + keys[ 0 ] + ".")
} }
} }
return key; return this.empty(key, args, path);
}
empty(key, args: string[], path: string): string {
return (path ? path + (path.endsWith(".") ? "" : ".") : "") + key + (args && args.length > 0 ? (" [" + args + "]") : "");
} }
insertArguments(label: string, args: string[]) { insertArguments(label: string, args: string[]) {
if(args) { if (args) {
for(let index in args) { for (let index in args) {
label = label.replace(`{${index}}`, this.get(args[index], [])); label = label.replace(`{${index}}`, this.get(args[ index ], []));
} }
} }
return label; return label;

View File

@ -1,7 +1,7 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {environment} from '../../environments/environment'; import { environment } from '../../environments/environment';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -19,6 +19,23 @@ export class InviteService {
return this.http.get(environment.apiUrl + "/invites" + (quota ? "?quota=" + quota + "&" : "?") + "page=" + page + "&size=" + size + "&search=" + search + (redeemed ? "&redeemed=" + redeemed : "")); return this.http.get(environment.apiUrl + "/invites" + (quota ? "?quota=" + quota + "&" : "?") + "page=" + page + "&size=" + size + "&search=" + search + (redeemed ? "&redeemed=" + redeemed : ""));
} }
code(code: string) {
return this.http.get(environment.apiUrl + "/invites/" + code);
}
permissions(code: string) {
return this.http.get(environment.apiUrl + "/invites/" + code + "/permissions");
}
quotas(code: string) {
return this.http.get(environment.apiUrl + "/invites/" + code + "/quotas");
}
register(model: any) {
return this.http.post(environment.apiUrl + "/invites", model);
}
getOthers(quota: string) { getOthers(quota: string) {
return this.http.get(environment.apiUrl + "/invites/" + quota + "/others"); return this.http.get(environment.apiUrl + "/invites/" + quota + "/others");
} }
@ -32,7 +49,7 @@ export class InviteService {
} }
update(invite: any) { update(invite: any) {
return this.http.post(environment.apiUrl + "/invites", invite); return this.http.patch(environment.apiUrl + "/invites", invite);
} }
} }

View File

@ -0,0 +1,20 @@
<div fxLayout="row wrap" fxLayoutAlign="start stretch">
<div fxFlex="33%">
<mat-form-field>
<input matInput type="number" min="0" placeholder="{{'durationpicker.days' | i18n}}" [formControl]="days">
</mat-form-field>
</div>
<div fxFlex="33%">
<mat-form-field>
<input matInput type="number" min="0" max="23" placeholder="{{'durationpicker.hours' | i18n}}"
[formControl]="hours">
</mat-form-field>
</div>
<div fxFlex="33%">
<mat-form-field>
<input matInput type="number" min="0" max="59" placeholder="{{'durationpicker.minutes' | i18n}}"
[formControl]="minutes">
<mat-hint align="end">{{duration}}</mat-hint>
</mat-form-field>
</div>
</div>

View File

@ -0,0 +1,4 @@
mat-form-field {
display: block;
padding-right: 8px;
}

View File

@ -0,0 +1,98 @@
import { Component, OnInit } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import * as moment from 'moment';
@Component({
selector: 'app-durationpicker',
templateUrl: './durationpicker.component.html',
styleUrls: [ './durationpicker.component.scss' ],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: DurationpickerComponent
}
]
})
export class DurationpickerComponent implements OnInit, ControlValueAccessor {
days = new FormControl();
hours = new FormControl();
minutes = new FormControl();
duration: string;
model: moment.Duration;
onChange = (duration) => { };
onTouched = () => { };
isDisabled = false;
constructor() { }
ngOnInit(): void {
this.days.valueChanges.subscribe(value => {
if (value < this.model.days()) {
this.model.subtract((this.model.days() - value), "days");
} else {
this.model.add((value - this.model.days()), "days");
}
this.checkValue();
})
this.hours.valueChanges.subscribe(value => {
if (value < this.model.hours()) {
this.model.subtract((this.model.hours() - value), "hours");
} else {
this.model.add((value - this.model.hours()), "hours");
}
this.checkValue();
})
this.minutes.valueChanges.subscribe(value => {
if (value < this.model.minutes()) {
this.model.subtract((this.model.minutes() - value), "minutes");
} else {
this.model.add((value - this.model.minutes()), "minutes");
}
this.checkValue();
})
}
checkValue() {
if (this.model.asMinutes() <= 0) {
this.days.setValue(0, { emitEvent: false });
this.hours.setValue(0, { emitEvent: false });
this.minutes.setValue(0, { emitEvent: false });
this.duration = null;
} else {
this.duration = this.model.toISOString();
}
this.onChange(this.duration);
this.onTouched();
}
writeValue(duration: string): void {
this.duration = duration;
this.model = moment.duration(this.duration);
this.days.setValue(this.model.days(), { emitEvent: false });
this.hours.setValue(this.model.hours()), { emitEvent: false };
this.minutes.setValue(this.model.minutes(), { emitEvent: false });
this.onTouched();
}
registerOnChange(onChange: any): void {
this.onChange = onChange;
}
registerOnTouched(onTouched: any): void {
this.onTouched = onTouched;
}
setDisabledState?(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}
}

View File

@ -0,0 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';
@Pipe({ name: 'datef' })
export class MomentPipe implements PipeTransform {
transform(value: Date | moment.Moment, dateFormat: string): any {
if (!dateFormat) {
return moment(value).fromNow();
}
return moment(value).format(dateFormat);
}
}

View File

@ -4,8 +4,8 @@
export const environment = { export const environment = {
production: false, production: false,
apiUrl : 'http://localhost:9000', // apiUrl : 'http://localhost:9000',
//apiUrl : 'https://api.bstly.lh8.de', apiUrl : 'https://api.bstly.lh8.de',
}; };
/* /*

87
src/qr-scanner-worker.min.js vendored Normal file
View File

@ -0,0 +1,87 @@
'use strict';(function(){function T(a,b){let c=[],d="";b=a.readBits([8,16,16][b]);for(let d=0;d<b;d++){let b=a.readBits(8);c.push(b)}try{d+=decodeURIComponent(c.map(a=>`%${("0"+a.toString(16)).substr(-2)}`).join(""))}catch(e){}return{bytes:c,text:d}}function U(a,b){a=new V(a);let c=9>=b?0:26>=b?1:2;for(b={text:"",bytes:[],chunks:[],version:b};4<=a.available();){var d=a.readBits(4);if(d===t.Terminator)return b;if(d===t.ECI)0===a.readBits(1)?b.chunks.push({type:r.ECI,assignmentNumber:a.readBits(7)}):
0===a.readBits(1)?b.chunks.push({type:r.ECI,assignmentNumber:a.readBits(14)}):0===a.readBits(1)?b.chunks.push({type:r.ECI,assignmentNumber:a.readBits(21)}):b.chunks.push({type:r.ECI,assignmentNumber:-1});else if(d===t.Numeric){var e=a;d=[];for(var f="",g=e.readBits([10,12,14][c]);3<=g;){var h=e.readBits(10);if(1E3<=h)throw Error("Invalid numeric value above 999");var k=Math.floor(h/100),n=Math.floor(h/10)%10;h%=10;d.push(48+k,48+n,48+h);f+=k.toString()+n.toString()+h.toString();g-=3}if(2===g){g=e.readBits(7);
if(100<=g)throw Error("Invalid numeric value above 99");e=Math.floor(g/10);g%=10;d.push(48+e,48+g);f+=e.toString()+g.toString()}else if(1===g){e=e.readBits(4);if(10<=e)throw Error("Invalid numeric value above 9");d.push(48+e);f+=e.toString()}d={bytes:d,text:f};b.text+=d.text;b.bytes.push(...d.bytes);b.chunks.push({type:r.Numeric,text:d.text})}else if(d===t.Alphanumeric){e=a;d=[];f="";for(g=e.readBits([9,11,13][c]);2<=g;)n=e.readBits(11),k=Math.floor(n/45),n%=45,d.push(B[k].charCodeAt(0),B[n].charCodeAt(0)),
f+=B[k]+B[n],g-=2;1===g&&(e=e.readBits(6),d.push(B[e].charCodeAt(0)),f+=B[e]);d={bytes:d,text:f};b.text+=d.text;b.bytes.push(...d.bytes);b.chunks.push({type:r.Alphanumeric,text:d.text})}else if(d===t.Byte)d=T(a,c),b.text+=d.text,b.bytes.push(...d.bytes),b.chunks.push({type:r.Byte,bytes:d.bytes,text:d.text});else if(d===t.Kanji){f=a;d=[];e=f.readBits([8,10,12][c]);for(g=0;g<e;g++)k=f.readBits(13),k=Math.floor(k/192)<<8|k%192,k=7936>k?k+33088:k+49472,d.push(k>>8,k&255);f=(new TextDecoder("shift-jis")).decode(Uint8Array.from(d));
d={bytes:d,text:f};b.text+=d.text;b.bytes.push(...d.bytes);b.chunks.push({type:r.Kanji,bytes:d.bytes,text:d.text})}else d===t.StructuredAppend&&b.chunks.push({type:r.StructuredAppend,currentSequence:a.readBits(4),totalSequence:a.readBits(4),parity:a.readBits(8)})}if(0===a.available()||0===a.readBits(a.available()))return b}function J(a,b){return a^b}function W(a,b,c,d){b.degree()<c.degree()&&([b,c]=[c,b]);let e=a.zero;for(var f=a.one;c.degree()>=d/2;){var g=b;let d=e;b=c;e=f;if(b.isZero())return null;
c=g;f=a.zero;g=b.getCoefficient(b.degree());for(g=a.inverse(g);c.degree()>=b.degree()&&!c.isZero();){let d=c.degree()-b.degree(),e=a.multiply(c.getCoefficient(c.degree()),g);f=f.addOrSubtract(a.buildMonomial(d,e));c=c.addOrSubtract(b.multiplyByMonomial(d,e))}f=f.multiplyPoly(e).addOrSubtract(d);if(c.degree()>=b.degree())return null}d=f.getCoefficient(0);if(0===d)return null;a=a.inverse(d);return[f.multiply(a),c.multiply(a)]}function X(a,b){let c=new Uint8ClampedArray(a.length);c.set(a);a=new Y(285,
256,0);var d=new w(a,c),e=new Uint8ClampedArray(b),f=!1;for(var g=0;g<b;g++){var h=d.evaluateAt(a.exp(g+a.generatorBase));e[e.length-1-g]=h;0!==h&&(f=!0)}if(!f)return c;d=new w(a,e);d=W(a,a.buildMonomial(b,1),d,b);if(null===d)return null;b=d[0];g=b.degree();if(1===g)b=[b.getCoefficient(1)];else{e=Array(g);f=0;for(h=1;h<a.size&&f<g;h++)0===b.evaluateAt(h)&&(e[f]=a.inverse(h),f++);b=f!==g?null:e}if(null==b)return null;d=d[1];e=b.length;f=Array(e);for(g=0;g<e;g++){h=a.inverse(b[g]);let c=1;for(let d=
0;d<e;d++)g!==d&&(c=a.multiply(c,J(1,a.multiply(b[d],h))));f[g]=a.multiply(d.evaluateAt(h),a.inverse(c));0!==a.generatorBase&&(f[g]=a.multiply(f[g],h))}d=f;for(e=0;e<b.length;e++){f=c.length-1-a.log(b[e]);if(0>f)return null;c[f]^=d[e]}return c}function E(a,b){a^=b;for(b=0;a;)b++,a&=a-1;return b}function C(a,b){return b<<1|a}function Z(a,b,c){c=aa[c.dataMask];let d=a.height;var e=17+4*b.versionNumber,f=A.createEmpty(e,e);f.setRegion(0,0,9,9,!0);f.setRegion(e-8,0,8,9,!0);f.setRegion(0,e-8,9,8,!0);for(var g of b.alignmentPatternCenters)for(var h of b.alignmentPatternCenters)6===
g&&6===h||6===g&&h===e-7||g===e-7&&6===h||f.setRegion(g-2,h-2,5,5,!0);f.setRegion(6,9,1,e-17,!0);f.setRegion(9,6,e-17,1,!0);6<b.versionNumber&&(f.setRegion(e-11,0,3,6,!0),f.setRegion(0,e-11,6,3,!0));b=f;g=[];e=h=0;f=!0;for(let k=d-1;0<k;k-=2){6===k&&k--;for(let n=0;n<d;n++){let m=f?d-1-n:n;for(let d=0;2>d;d++){let f=k-d;if(!b.get(f,m)){e++;let b=a.get(f,m);c({y:m,x:f})&&(b=!b);h=h<<1|b;8===e&&(g.push(h),h=e=0)}}}f=!f}return g}function ba(a){var b=a.height,c=Math.floor((b-17)/4);if(6>=c)return K[c-
1];c=0;for(var d=5;0<=d;d--)for(var e=b-9;e>=b-11;e--)c=C(a.get(e,d),c);d=0;for(e=5;0<=e;e--)for(let c=b-9;c>=b-11;c--)d=C(a.get(e,c),d);a=Infinity;let f;for(let e of K){if(e.infoBits===c||e.infoBits===d)return e;b=E(c,e.infoBits);b<a&&(f=e,a=b);b=E(d,e.infoBits);b<a&&(f=e,a=b)}if(3>=a)return f}function ca(a){let b=0;for(var c=0;8>=c;c++)6!==c&&(b=C(a.get(c,8),b));for(c=7;0<=c;c--)6!==c&&(b=C(a.get(8,c),b));var d=a.height;c=0;for(var e=d-1;e>=d-7;e--)c=C(a.get(8,e),c);for(e=d-8;e<d;e++)c=C(a.get(e,
8),c);a=Infinity;d=null;for(let {bits:f,formatInfo:g}of da){if(f===b||f===c)return g;e=E(b,f);e<a&&(d=g,a=e);b!==c&&(e=E(c,f),e<a&&(d=g,a=e))}return 3>=a?d:null}function ea(a,b,c){let d=b.errorCorrectionLevels[c],e=[],f=0;d.ecBlocks.forEach(a=>{for(let b=0;b<a.numBlocks;b++)e.push({numDataCodewords:a.dataCodewordsPerBlock,codewords:[]}),f+=a.dataCodewordsPerBlock+d.ecCodewordsPerBlock});if(a.length<f)return null;a=a.slice(0,f);b=d.ecBlocks[0].dataCodewordsPerBlock;for(c=0;c<b;c++)for(var g of e)g.codewords.push(a.shift());
if(1<d.ecBlocks.length)for(g=d.ecBlocks[0].numBlocks,b=d.ecBlocks[1].numBlocks,c=0;c<b;c++)e[g+c].codewords.push(a.shift());for(;0<a.length;)for(let b of e)b.codewords.push(a.shift());return e}function L(a){let b=ba(a);if(!b)return null;var c=ca(a);if(!c)return null;a=Z(a,b,c);var d=ea(a,b,c.errorCorrectionLevel);if(!d)return null;c=d.reduce((a,b)=>a+b.numDataCodewords,0);c=new Uint8ClampedArray(c);a=0;for(let b of d){d=X(b.codewords,b.codewords.length-b.numDataCodewords);if(!d)return null;for(let e=
0;e<b.numDataCodewords;e++)c[a++]=d[e]}try{return U(c,b.versionNumber)}catch(e){return null}}function M(a,b,c,d){var e=a.x-b.x+c.x-d.x;let f=a.y-b.y+c.y-d.y;if(0===e&&0===f)return{a11:b.x-a.x,a12:b.y-a.y,a13:0,a21:c.x-b.x,a22:c.y-b.y,a23:0,a31:a.x,a32:a.y,a33:1};{let h=b.x-c.x;var g=d.x-c.x;let k=b.y-c.y,n=d.y-c.y;c=h*n-g*k;g=(e*n-g*f)/c;e=(h*f-e*k)/c;return{a11:b.x-a.x+g*b.x,a12:b.y-a.y+g*b.y,a13:g,a21:d.x-a.x+e*d.x,a22:d.y-a.y+e*d.y,a23:e,a31:a.x,a32:a.y,a33:1}}}function fa(a,b,c,d){a=M(a,b,c,d);
return{a11:a.a22*a.a33-a.a23*a.a32,a12:a.a13*a.a32-a.a12*a.a33,a13:a.a12*a.a23-a.a13*a.a22,a21:a.a23*a.a31-a.a21*a.a33,a22:a.a11*a.a33-a.a13*a.a31,a23:a.a13*a.a21-a.a11*a.a23,a31:a.a21*a.a32-a.a22*a.a31,a32:a.a12*a.a31-a.a11*a.a32,a33:a.a11*a.a22-a.a12*a.a21}}function ha(a,b){var c=fa({x:3.5,y:3.5},{x:b.dimension-3.5,y:3.5},{x:b.dimension-6.5,y:b.dimension-6.5},{x:3.5,y:b.dimension-3.5}),d=M(b.topLeft,b.topRight,b.alignmentPattern,b.bottomLeft),e=d.a11*c.a11+d.a21*c.a12+d.a31*c.a13,f=d.a12*c.a11+
d.a22*c.a12+d.a32*c.a13,g=d.a13*c.a11+d.a23*c.a12+d.a33*c.a13,h=d.a11*c.a21+d.a21*c.a22+d.a31*c.a23,k=d.a12*c.a21+d.a22*c.a22+d.a32*c.a23,n=d.a13*c.a21+d.a23*c.a22+d.a33*c.a23,m=d.a11*c.a31+d.a21*c.a32+d.a31*c.a33,l=d.a12*c.a31+d.a22*c.a32+d.a32*c.a33,p=d.a13*c.a31+d.a23*c.a32+d.a33*c.a33;c=A.createEmpty(b.dimension,b.dimension);d=(a,b)=>{const c=g*a+n*b+p;return{x:(e*a+h*b+m)/c,y:(f*a+k*b+l)/c}};for(let e=0;e<b.dimension;e++)for(let f=0;f<b.dimension;f++){let b=d(f+.5,e+.5);c.set(f,e,a.get(Math.floor(b.x),
Math.floor(b.y)))}return{matrix:c,mappingFunction:d}}function x(a){return a.reduce((a,c)=>a+c)}function ia(a,b,c){let d=y(a,b),e=y(b,c),f=y(a,c),g,h,k;e>=d&&e>=f?[g,h,k]=[b,a,c]:f>=e&&f>=d?[g,h,k]=[a,b,c]:[g,h,k]=[a,c,b];0>(k.x-h.x)*(g.y-h.y)-(k.y-h.y)*(g.x-h.x)&&([g,k]=[k,g]);return{bottomLeft:g,topLeft:h,topRight:k}}function ja(a,b,c,d){d=(x(z(a,c,d,5))/7+x(z(a,b,d,5))/7+x(z(c,a,d,5))/7+x(z(b,a,d,5))/7)/4;if(1>d)throw Error("Invalid module size");b=Math.round(y(a,b)/d);a=Math.round(y(a,c)/d);a=
Math.floor((b+a)/2)+7;switch(a%4){case 0:a++;break;case 2:a--}return{dimension:a,moduleSize:d}}function N(a,b,c,d){let e=[{x:Math.floor(a.x),y:Math.floor(a.y)}];var f=Math.abs(b.y-a.y)>Math.abs(b.x-a.x);if(f){var g=Math.floor(a.y);var h=Math.floor(a.x);a=Math.floor(b.y);b=Math.floor(b.x)}else g=Math.floor(a.x),h=Math.floor(a.y),a=Math.floor(b.x),b=Math.floor(b.y);let k=Math.abs(a-g),n=Math.abs(b-h),m=Math.floor(-k/2),l=g<a?1:-1,p=h<b?1:-1,q=!0;for(let v=g,u=h;v!==a+l;v+=l){g=f?u:v;h=f?v:u;if(c.get(g,
h)!==q&&(q=!q,e.push({x:g,y:h}),e.length===d+1))break;m+=n;if(0<m){if(u===b)break;u+=p;m-=k}}c=[];for(f=0;f<d;f++)e[f]&&e[f+1]?c.push(y(e[f],e[f+1])):c.push(0);return c}function z(a,b,c,d){let e=b.y-a.y,f=b.x-a.x;b=N(a,b,c,Math.ceil(d/2));a=N(a,{x:a.x-f,y:a.y-e},c,Math.ceil(d/2));c=b.shift()+a.shift()-1;return a.concat(c).concat(...b)}function F(a,b){let c=x(a)/x(b),d=0;b.forEach((b,f)=>{d+=Math.pow(a[f]-b*c,2)});return{averageSize:c,error:d}}function O(a,b,c){try{let d=z(a,{x:-1,y:a.y},c,b.length),
e=z(a,{x:a.x,y:-1},c,b.length),f=z(a,{x:Math.max(0,a.x-a.y)-1,y:Math.max(0,a.y-a.x)-1},c,b.length),g=z(a,{x:Math.min(c.width,a.x+a.y)+1,y:Math.min(c.height,a.y+a.x)+1},c,b.length),h=F(d,b),k=F(e,b),n=F(f,b),m=F(g,b),l=(h.averageSize+k.averageSize+n.averageSize+m.averageSize)/4;return Math.sqrt(h.error*h.error+k.error*k.error+n.error*n.error+m.error*m.error)+(Math.pow(h.averageSize-l,2)+Math.pow(k.averageSize-l,2)+Math.pow(n.averageSize-l,2)+Math.pow(m.averageSize-l,2))/l}catch(d){return Infinity}}
function H(a,b){for(var c=Math.round(b.x);a.get(c,Math.round(b.y));)c--;for(var d=Math.round(b.x);a.get(d,Math.round(b.y));)d++;c=(c+d)/2;for(d=Math.round(b.y);a.get(Math.round(c),d);)d--;for(b=Math.round(b.y);a.get(Math.round(c),b);)b++;return{x:c,y:(d+b)/2}}function ka(a){var b=[],c=[];let d=[];var e=[];for(let m=0;m<=a.height;m++){var f=0,g=!1;let l=[0,0,0,0,0];for(let b=-1;b<=a.width;b++){var h=a.get(b,m);if(h===g)f++;else{l=[l[1],l[2],l[3],l[4],f];f=1;g=h;var k=x(l)/7;k=Math.abs(l[0]-k)<k&&Math.abs(l[1]-
k)<k&&Math.abs(l[2]-3*k)<3*k&&Math.abs(l[3]-k)<k&&Math.abs(l[4]-k)<k&&!h;var n=x(l.slice(-3))/3;h=Math.abs(l[2]-n)<n&&Math.abs(l[3]-n)<n&&Math.abs(l[4]-n)<n&&h;if(k){let a=b-l[3]-l[4],d=a-l[2];k={startX:d,endX:a,y:m};n=c.filter(b=>d>=b.bottom.startX&&d<=b.bottom.endX||a>=b.bottom.startX&&d<=b.bottom.endX||d<=b.bottom.startX&&a>=b.bottom.endX&&1.5>l[2]/(b.bottom.endX-b.bottom.startX)&&.5<l[2]/(b.bottom.endX-b.bottom.startX));0<n.length?n[0].bottom=k:c.push({top:k,bottom:k})}if(h){let a=b-l[4],c=a-
l[3];h={startX:c,y:m,endX:a};k=e.filter(b=>c>=b.bottom.startX&&c<=b.bottom.endX||a>=b.bottom.startX&&c<=b.bottom.endX||c<=b.bottom.startX&&a>=b.bottom.endX&&1.5>l[2]/(b.bottom.endX-b.bottom.startX)&&.5<l[2]/(b.bottom.endX-b.bottom.startX));0<k.length?k[0].bottom=h:e.push({top:h,bottom:h})}}}b.push(...c.filter(a=>a.bottom.y!==m&&2<=a.bottom.y-a.top.y));c=c.filter(a=>a.bottom.y===m);d.push(...e.filter(a=>a.bottom.y!==m));e=e.filter(a=>a.bottom.y===m)}b.push(...c.filter(a=>2<=a.bottom.y-a.top.y));d.push(...e);
c=[];for(var m of b)2>m.bottom.y-m.top.y||(b=(m.top.startX+m.top.endX+m.bottom.startX+m.bottom.endX)/4,e=(m.top.y+m.bottom.y+1)/2,a.get(Math.round(b),Math.round(e))&&(f=[m.top.endX-m.top.startX,m.bottom.endX-m.bottom.startX,m.bottom.y-m.top.y+1],f=x(f)/f.length,g=O({x:Math.round(b),y:Math.round(e)},[1,1,3,1,1],a),c.push({score:g,x:b,y:e,size:f})));if(3>c.length)return null;c.sort((a,b)=>a.score-b.score);m=[];for(b=0;b<Math.min(c.length,5);++b){e=c[b];f=[];for(var l of c)l!==e&&f.push(Object.assign(Object.assign({},
l),{score:l.score+Math.pow(l.size-e.size,2)/e.size}));f.sort((a,b)=>a.score-b.score);m.push({points:[e,f[0],f[1]],score:e.score+f[0].score+f[1].score})}m.sort((a,b)=>a.score-b.score);let {topRight:p,topLeft:q,bottomLeft:v}=ia(...m[0].points);m=P(a,d,p,q,v);l=[];m&&l.push({alignmentPattern:{x:m.alignmentPattern.x,y:m.alignmentPattern.y},bottomLeft:{x:v.x,y:v.y},dimension:m.dimension,topLeft:{x:q.x,y:q.y},topRight:{x:p.x,y:p.y}});m=H(a,p);b=H(a,q);c=H(a,v);(a=P(a,d,m,b,c))&&l.push({alignmentPattern:{x:a.alignmentPattern.x,
y:a.alignmentPattern.y},bottomLeft:{x:c.x,y:c.y},topLeft:{x:b.x,y:b.y},topRight:{x:m.x,y:m.y},dimension:a.dimension});return 0===l.length?null:l}function P(a,b,c,d,e){let f,g;try{({dimension:f,moduleSize:g}=ja(d,c,e,a))}catch(m){return null}var h=c.x-d.x+e.x,k=c.y-d.y+e.y;c=(y(d,e)+y(d,c))/2/g;e=1-3/c;let n={x:d.x+e*(h-d.x),y:d.y+e*(k-d.y)};b=b.map(b=>{const c=(b.top.startX+b.top.endX+b.bottom.startX+b.bottom.endX)/4;b=(b.top.y+b.bottom.y+1)/2;if(a.get(Math.floor(c),Math.floor(b))){var d=O({x:Math.floor(c),
y:Math.floor(b)},[1,1,1],a)+y({x:c,y:b},n);return{x:c,y:b,score:d}}}).filter(a=>!!a).sort((a,b)=>a.score-b.score);return{alignmentPattern:15<=c&&b.length?b[0]:n,dimension:f}}function Q(a){var b=ka(a);if(!b)return null;for(let e of b){b=ha(a,e);var c=b.matrix;if(null==c)c=null;else{var d=L(c);if(d)c=d;else{for(d=0;d<c.width;d++)for(let a=d+1;a<c.height;a++)c.get(d,a)!==c.get(a,d)&&(c.set(d,a,!c.get(d,a)),c.set(a,d,!c.get(a,d)));c=L(c)}}if(c)return{binaryData:c.bytes,data:c.text,chunks:c.chunks,version:c.version,
location:{topRightCorner:b.mappingFunction(e.dimension,0),topLeftCorner:b.mappingFunction(0,0),bottomRightCorner:b.mappingFunction(e.dimension,e.dimension),bottomLeftCorner:b.mappingFunction(0,e.dimension),topRightFinderPattern:e.topRight,topLeftFinderPattern:e.topLeft,bottomLeftFinderPattern:e.bottomLeft,bottomRightAlignmentPattern:e.alignmentPattern},matrix:b.matrix}}return null}function R(a,b){Object.keys(b).forEach(c=>{a[c]=b[c]})}function I(a,b,c,d={}){let e=Object.create(null);R(e,la);R(e,d);
d="onlyInvert"===e.inversionAttempts||"invertFirst"===e.inversionAttempts;var f="attemptBoth"===e.inversionAttempts||d;var g=e.greyScaleWeights,h=e.canOverwriteImage,k=b*c;if(a.length!==4*k)throw Error("Malformed data passed to binarizer.");var n=0;if(h){var m=new Uint8ClampedArray(a.buffer,n,k);n+=k}m=new S(b,c,m);if(g.useIntegerApproximation)for(var l=0;l<c;l++)for(var p=0;p<b;p++){var q=4*(l*b+p);m.set(p,l,g.red*a[q]+g.green*a[q+1]+g.blue*a[q+2]+128>>8)}else for(l=0;l<c;l++)for(p=0;p<b;p++)q=4*
(l*b+p),m.set(p,l,g.red*a[q]+g.green*a[q+1]+g.blue*a[q+2]);g=Math.ceil(b/8);l=Math.ceil(c/8);p=g*l;if(h){var v=new Uint8ClampedArray(a.buffer,n,p);n+=p}v=new S(g,l,v);for(p=0;p<l;p++)for(q=0;q<g;q++){var u=Infinity,r=0;for(var t=0;8>t;t++)for(let a=0;8>a;a++){let b=m.get(8*q+a,8*p+t);u=Math.min(u,b);r=Math.max(r,b)}t=(u+r)/2;t=Math.min(255,1.11*t);24>=r-u&&(t=u/2,0<p&&0<q&&(r=(v.get(q,p-1)+2*v.get(q-1,p)+v.get(q-1,p-1))/4,u<r&&(t=r)));v.set(q,p,t)}h?(p=new Uint8ClampedArray(a.buffer,n,k),n+=k,p=new A(p,
b)):p=A.createEmpty(b,c);q=null;f&&(h?(a=new Uint8ClampedArray(a.buffer,n,k),q=new A(a,b)):q=A.createEmpty(b,c));for(b=0;b<l;b++)for(a=0;a<g;a++){c=g-3;c=2>a?2:a>c?c:a;h=l-3;h=2>b?2:b>h?h:b;k=0;for(n=-2;2>=n;n++)for(u=-2;2>=u;u++)k+=v.get(c+n,h+u);c=k/25;for(h=0;8>h;h++)for(k=0;8>k;k++)n=8*a+h,u=8*b+k,r=m.get(n,u),p.set(n,u,r<=c),f&&q.set(n,u,!(r<=c))}f=f?{binarized:p,inverted:q}:{binarized:p};let {binarized:w,inverted:x}=f;(f=Q(d?x:w))||"attemptBoth"!==e.inversionAttempts&&"invertFirst"!==e.inversionAttempts||
(f=Q(d?w:x));return f}class A{constructor(a,b){this.width=b;this.height=a.length/b;this.data=a}static createEmpty(a,b){return new A(new Uint8ClampedArray(a*b),a)}get(a,b){return 0>a||a>=this.width||0>b||b>=this.height?!1:!!this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c?1:0}setRegion(a,b,c,d,e){for(let f=b;f<b+d;f++)for(let b=a;b<a+c;b++)this.set(b,f,!!e)}}class S{constructor(a,b,c){this.width=a;a*=b;if(c&&c.length!==a)throw Error("Wrong buffer size");this.data=c||new Uint8ClampedArray(a)}get(a,
b){return this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c}}class V{constructor(a){this.bitOffset=this.byteOffset=0;this.bytes=a}readBits(a){if(1>a||32<a||a>this.available())throw Error("Cannot read "+a.toString()+" bits");var b=0;if(0<this.bitOffset){b=8-this.bitOffset;var c=a<b?a:b;b-=c;b=(this.bytes[this.byteOffset]&255>>8-c<<b)>>b;a-=c;this.bitOffset+=c;8===this.bitOffset&&(this.bitOffset=0,this.byteOffset++)}if(0<a){for(;8<=a;)b=b<<8|this.bytes[this.byteOffset]&255,this.byteOffset++,
a-=8;0<a&&(c=8-a,b=b<<a|(this.bytes[this.byteOffset]&255>>c<<c)>>c,this.bitOffset+=a)}return b}available(){return 8*(this.bytes.length-this.byteOffset)-this.bitOffset}}var r;(function(a){a.Numeric="numeric";a.Alphanumeric="alphanumeric";a.Byte="byte";a.Kanji="kanji";a.ECI="eci";a.StructuredAppend="structuredappend"})(r||(r={}));var t;(function(a){a[a.Terminator=0]="Terminator";a[a.Numeric=1]="Numeric";a[a.Alphanumeric=2]="Alphanumeric";a[a.Byte=4]="Byte";a[a.Kanji=8]="Kanji";a[a.ECI=7]="ECI";a[a.StructuredAppend=
3]="StructuredAppend"})(t||(t={}));let B="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");class w{constructor(a,b){if(0===b.length)throw Error("No coefficients.");this.field=a;let c=b.length;if(1<c&&0===b[0]){let d=1;for(;d<c&&0===b[d];)d++;if(d===c)this.coefficients=a.zero.coefficients;else for(this.coefficients=new Uint8ClampedArray(c-d),a=0;a<this.coefficients.length;a++)this.coefficients[a]=b[d+a]}else this.coefficients=b}degree(){return this.coefficients.length-1}isZero(){return 0===
this.coefficients[0]}getCoefficient(a){return this.coefficients[this.coefficients.length-1-a]}addOrSubtract(a){if(this.isZero())return a;if(a.isZero())return this;let b=this.coefficients;a=a.coefficients;b.length>a.length&&([b,a]=[a,b]);let c=new Uint8ClampedArray(a.length),d=a.length-b.length;for(var e=0;e<d;e++)c[e]=a[e];for(e=d;e<a.length;e++)c[e]=b[e-d]^a[e];return new w(this.field,c)}multiply(a){if(0===a)return this.field.zero;if(1===a)return this;let b=this.coefficients.length,c=new Uint8ClampedArray(b);
for(let d=0;d<b;d++)c[d]=this.field.multiply(this.coefficients[d],a);return new w(this.field,c)}multiplyPoly(a){if(this.isZero()||a.isZero())return this.field.zero;let b=this.coefficients,c=b.length;a=a.coefficients;let d=a.length,e=new Uint8ClampedArray(c+d-1);for(let h=0;h<c;h++){let c=b[h];for(let b=0;b<d;b++){var f=h+b,g=this.field.multiply(c,a[b]);e[f]=e[h+b]^g}}return new w(this.field,e)}multiplyByMonomial(a,b){if(0>a)throw Error("Invalid degree less than 0");if(0===b)return this.field.zero;
let c=this.coefficients.length;a=new Uint8ClampedArray(c+a);for(let d=0;d<c;d++)a[d]=this.field.multiply(this.coefficients[d],b);return new w(this.field,a)}evaluateAt(a){let b=0;if(0===a)return this.getCoefficient(0);let c=this.coefficients.length;if(1===a)return this.coefficients.forEach(a=>{b^=a}),b;b=this.coefficients[0];for(let d=1;d<c;d++)b=J(this.field.multiply(a,b),this.coefficients[d]);return b}}class Y{constructor(a,b,c){this.primitive=a;this.size=b;this.generatorBase=c;this.expTable=Array(this.size);
this.logTable=Array(this.size);a=1;for(b=0;b<this.size;b++)this.expTable[b]=a,a*=2,a>=this.size&&(a=(a^this.primitive)&this.size-1);for(a=0;a<this.size-1;a++)this.logTable[this.expTable[a]]=a;this.zero=new w(this,Uint8ClampedArray.from([0]));this.one=new w(this,Uint8ClampedArray.from([1]))}multiply(a,b){return 0===a||0===b?0:this.expTable[(this.logTable[a]+this.logTable[b])%(this.size-1)]}inverse(a){if(0===a)throw Error("Can't invert 0");return this.expTable[this.size-this.logTable[a]-1]}buildMonomial(a,
b){if(0>a)throw Error("Invalid monomial degree less than 0");if(0===b)return this.zero;a=new Uint8ClampedArray(a+1);a[0]=b;return new w(this,a)}log(a){if(0===a)throw Error("Can't take log(0)");return this.logTable[a]}exp(a){return this.expTable[a]}}let K=[{infoBits:null,versionNumber:1,alignmentPatternCenters:[],errorCorrectionLevels:[{ecCodewordsPerBlock:7,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:13,
ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:13}]},{ecCodewordsPerBlock:17,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:2,alignmentPatternCenters:[6,18],errorCorrectionLevels:[{ecCodewordsPerBlock:10,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:34}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:28}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:16}]}]},
{infoBits:null,versionNumber:3,alignmentPatternCenters:[6,22],errorCorrectionLevels:[{ecCodewordsPerBlock:15,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:55}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:13}]}]},{infoBits:null,versionNumber:4,alignmentPatternCenters:[6,26],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:1,
dataCodewordsPerBlock:80}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:32}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:9}]}]},{infoBits:null,versionNumber:5,alignmentPatternCenters:[6,30],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:43}]},{ecCodewordsPerBlock:18,
ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:11},{numBlocks:2,dataCodewordsPerBlock:12}]}]},{infoBits:null,versionNumber:6,alignmentPatternCenters:[6,34],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68}]},{ecCodewordsPerBlock:16,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:27}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:19}]},
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:31892,versionNumber:7,alignmentPatternCenters:[6,22,38],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:78}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:31}]},{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:13},
{numBlocks:1,dataCodewordsPerBlock:14}]}]},{infoBits:34236,versionNumber:8,alignmentPatternCenters:[6,24,42],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:97}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:38},{numBlocks:2,dataCodewordsPerBlock:39}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:18},{numBlocks:2,dataCodewordsPerBlock:19}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:14},
{numBlocks:2,dataCodewordsPerBlock:15}]}]},{infoBits:39577,versionNumber:9,alignmentPatternCenters:[6,26,46],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:16},{numBlocks:4,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:12},
{numBlocks:4,dataCodewordsPerBlock:13}]}]},{infoBits:42195,versionNumber:10,alignmentPatternCenters:[6,28,50],errorCorrectionLevels:[{ecCodewordsPerBlock:18,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:68},{numBlocks:2,dataCodewordsPerBlock:69}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:43},{numBlocks:1,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,
dataCodewordsPerBlock:15},{numBlocks:2,dataCodewordsPerBlock:16}]}]},{infoBits:48118,versionNumber:11,alignmentPatternCenters:[6,30,54],errorCorrectionLevels:[{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:81}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:50},{numBlocks:4,dataCodewordsPerBlock:51}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:22},{numBlocks:4,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:3,
dataCodewordsPerBlock:12},{numBlocks:8,dataCodewordsPerBlock:13}]}]},{infoBits:51042,versionNumber:12,alignmentPatternCenters:[6,32,58],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:92},{numBlocks:2,dataCodewordsPerBlock:93}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:36},{numBlocks:2,dataCodewordsPerBlock:37}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:20},{numBlocks:6,dataCodewordsPerBlock:21}]},
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:14},{numBlocks:4,dataCodewordsPerBlock:15}]}]},{infoBits:55367,versionNumber:13,alignmentPatternCenters:[6,34,62],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:37},{numBlocks:1,dataCodewordsPerBlock:38}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:20},{numBlocks:4,dataCodewordsPerBlock:21}]},
{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:11},{numBlocks:4,dataCodewordsPerBlock:12}]}]},{infoBits:58893,versionNumber:14,alignmentPatternCenters:[6,26,46,66],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:115},{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:40},{numBlocks:5,dataCodewordsPerBlock:41}]},{ecCodewordsPerBlock:20,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:16},
{numBlocks:5,dataCodewordsPerBlock:17}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:5,dataCodewordsPerBlock:13}]}]},{infoBits:63784,versionNumber:15,alignmentPatternCenters:[6,26,48,70],errorCorrectionLevels:[{ecCodewordsPerBlock:22,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:87},{numBlocks:1,dataCodewordsPerBlock:88}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:41},{numBlocks:5,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:12},{numBlocks:7,dataCodewordsPerBlock:13}]}]},{infoBits:68472,versionNumber:16,alignmentPatternCenters:[6,26,50,74],errorCorrectionLevels:[{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:98},{numBlocks:1,dataCodewordsPerBlock:99}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},
{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:19},{numBlocks:2,dataCodewordsPerBlock:20}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},{infoBits:70749,versionNumber:17,alignmentPatternCenters:[6,30,54,78],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},
{numBlocks:1,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:22},{numBlocks:15,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:17,dataCodewordsPerBlock:15}]}]},{infoBits:76311,versionNumber:18,alignmentPatternCenters:[6,30,56,82],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:120},{numBlocks:1,dataCodewordsPerBlock:121}]},{ecCodewordsPerBlock:26,
ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:43},{numBlocks:4,dataCodewordsPerBlock:44}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:1,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:14},{numBlocks:19,dataCodewordsPerBlock:15}]}]},{infoBits:79154,versionNumber:19,alignmentPatternCenters:[6,30,58,86],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:113},{numBlocks:4,
dataCodewordsPerBlock:114}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:44},{numBlocks:11,dataCodewordsPerBlock:45}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:21},{numBlocks:4,dataCodewordsPerBlock:22}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:9,dataCodewordsPerBlock:13},{numBlocks:16,dataCodewordsPerBlock:14}]}]},{infoBits:84390,versionNumber:20,alignmentPatternCenters:[6,34,62,90],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,
dataCodewordsPerBlock:107},{numBlocks:5,dataCodewordsPerBlock:108}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:41},{numBlocks:13,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:5,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:15},{numBlocks:10,dataCodewordsPerBlock:16}]}]},{infoBits:87683,versionNumber:21,alignmentPatternCenters:[6,28,50,72,94],errorCorrectionLevels:[{ecCodewordsPerBlock:28,
ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:116},{numBlocks:4,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:42}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:16},{numBlocks:6,dataCodewordsPerBlock:17}]}]},{infoBits:92361,versionNumber:22,alignmentPatternCenters:[6,26,50,74,98],errorCorrectionLevels:[{ecCodewordsPerBlock:28,
ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:111},{numBlocks:7,dataCodewordsPerBlock:112}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:24,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:13}]}]},{infoBits:96236,versionNumber:23,alignmentPatternCenters:[6,30,54,74,102],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,
dataCodewordsPerBlock:121},{numBlocks:5,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:47},{numBlocks:14,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:16,dataCodewordsPerBlock:15},{numBlocks:14,dataCodewordsPerBlock:16}]}]},{infoBits:102084,versionNumber:24,alignmentPatternCenters:[6,28,54,80,106],errorCorrectionLevels:[{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:45},{numBlocks:14,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:24},{numBlocks:16,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:30,dataCodewordsPerBlock:16},{numBlocks:2,dataCodewordsPerBlock:17}]}]},{infoBits:102881,versionNumber:25,alignmentPatternCenters:[6,
32,58,84,110],errorCorrectionLevels:[{ecCodewordsPerBlock:26,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:106},{numBlocks:4,dataCodewordsPerBlock:107}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:47},{numBlocks:13,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:13,dataCodewordsPerBlock:16}]}]},
{infoBits:110507,versionNumber:26,alignmentPatternCenters:[6,30,58,86,114],errorCorrectionLevels:[{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:114},{numBlocks:2,dataCodewordsPerBlock:115}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:46},{numBlocks:4,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:28,dataCodewordsPerBlock:22},{numBlocks:6,dataCodewordsPerBlock:23}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:33,dataCodewordsPerBlock:16},
{numBlocks:4,dataCodewordsPerBlock:17}]}]},{infoBits:110734,versionNumber:27,alignmentPatternCenters:[6,34,62,90,118],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:45},{numBlocks:3,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:8,dataCodewordsPerBlock:23},{numBlocks:26,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:117786,versionNumber:28,alignmentPatternCenters:[6,26,50,74,98,122],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:117},{numBlocks:10,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:3,dataCodewordsPerBlock:45},{numBlocks:23,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:24},{numBlocks:31,
dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:31,dataCodewordsPerBlock:16}]}]},{infoBits:119615,versionNumber:29,alignmentPatternCenters:[6,30,54,78,102,126],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:7,dataCodewordsPerBlock:116},{numBlocks:7,dataCodewordsPerBlock:117}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:21,dataCodewordsPerBlock:45},{numBlocks:7,dataCodewordsPerBlock:46}]},{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:1,dataCodewordsPerBlock:23},{numBlocks:37,dataCodewordsPerBlock:24}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:26,dataCodewordsPerBlock:16}]}]},{infoBits:126325,versionNumber:30,alignmentPatternCenters:[6,26,52,78,104,130],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:5,dataCodewordsPerBlock:115},{numBlocks:10,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:47},
{numBlocks:10,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:15,dataCodewordsPerBlock:24},{numBlocks:25,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:25,dataCodewordsPerBlock:16}]}]},{infoBits:127568,versionNumber:31,alignmentPatternCenters:[6,30,56,82,108,134],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:3,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,
ecBlocks:[{numBlocks:2,dataCodewordsPerBlock:46},{numBlocks:29,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:24},{numBlocks:1,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:23,dataCodewordsPerBlock:15},{numBlocks:28,dataCodewordsPerBlock:16}]}]},{infoBits:133589,versionNumber:32,alignmentPatternCenters:[6,34,60,86,112,138],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115}]},
{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:24},{numBlocks:35,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:15},{numBlocks:35,dataCodewordsPerBlock:16}]}]},{infoBits:136944,versionNumber:33,alignmentPatternCenters:[6,30,58,86,114,142],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:115},
{numBlocks:1,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:21,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:24},{numBlocks:19,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:11,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:141498,versionNumber:34,alignmentPatternCenters:[6,34,62,90,118,146],errorCorrectionLevels:[{ecCodewordsPerBlock:30,
ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:115},{numBlocks:6,dataCodewordsPerBlock:116}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:14,dataCodewordsPerBlock:46},{numBlocks:23,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:44,dataCodewordsPerBlock:24},{numBlocks:7,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:59,dataCodewordsPerBlock:16},{numBlocks:1,dataCodewordsPerBlock:17}]}]},{infoBits:145311,versionNumber:35,alignmentPatternCenters:[6,
30,54,78,102,126,150],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:121},{numBlocks:7,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:12,dataCodewordsPerBlock:47},{numBlocks:26,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:39,dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:22,dataCodewordsPerBlock:15},{numBlocks:41,dataCodewordsPerBlock:16}]}]},
{infoBits:150283,versionNumber:36,alignmentPatternCenters:[6,24,50,76,102,128,154],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:121},{numBlocks:14,dataCodewordsPerBlock:122}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:6,dataCodewordsPerBlock:47},{numBlocks:34,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:46,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:2,
dataCodewordsPerBlock:15},{numBlocks:64,dataCodewordsPerBlock:16}]}]},{infoBits:152622,versionNumber:37,alignmentPatternCenters:[6,28,54,80,106,132,158],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:17,dataCodewordsPerBlock:122},{numBlocks:4,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:29,dataCodewordsPerBlock:46},{numBlocks:14,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:49,dataCodewordsPerBlock:24},{numBlocks:10,dataCodewordsPerBlock:25}]},
{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:24,dataCodewordsPerBlock:15},{numBlocks:46,dataCodewordsPerBlock:16}]}]},{infoBits:158308,versionNumber:38,alignmentPatternCenters:[6,32,58,84,110,136,162],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:4,dataCodewordsPerBlock:122},{numBlocks:18,dataCodewordsPerBlock:123}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:13,dataCodewordsPerBlock:46},{numBlocks:32,dataCodewordsPerBlock:47}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:48,
dataCodewordsPerBlock:24},{numBlocks:14,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:42,dataCodewordsPerBlock:15},{numBlocks:32,dataCodewordsPerBlock:16}]}]},{infoBits:161089,versionNumber:39,alignmentPatternCenters:[6,26,54,82,110,138,166],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:117},{numBlocks:4,dataCodewordsPerBlock:118}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:40,dataCodewordsPerBlock:47},{numBlocks:7,dataCodewordsPerBlock:48}]},
{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:43,dataCodewordsPerBlock:24},{numBlocks:22,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:10,dataCodewordsPerBlock:15},{numBlocks:67,dataCodewordsPerBlock:16}]}]},{infoBits:167017,versionNumber:40,alignmentPatternCenters:[6,30,58,86,114,142,170],errorCorrectionLevels:[{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:19,dataCodewordsPerBlock:118},{numBlocks:6,dataCodewordsPerBlock:119}]},{ecCodewordsPerBlock:28,ecBlocks:[{numBlocks:18,
dataCodewordsPerBlock:47},{numBlocks:31,dataCodewordsPerBlock:48}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:34,dataCodewordsPerBlock:24},{numBlocks:34,dataCodewordsPerBlock:25}]},{ecCodewordsPerBlock:30,ecBlocks:[{numBlocks:20,dataCodewordsPerBlock:15},{numBlocks:61,dataCodewordsPerBlock:16}]}]}],da=[{bits:21522,formatInfo:{errorCorrectionLevel:1,dataMask:0}},{bits:20773,formatInfo:{errorCorrectionLevel:1,dataMask:1}},{bits:24188,formatInfo:{errorCorrectionLevel:1,dataMask:2}},{bits:23371,formatInfo:{errorCorrectionLevel:1,
dataMask:3}},{bits:17913,formatInfo:{errorCorrectionLevel:1,dataMask:4}},{bits:16590,formatInfo:{errorCorrectionLevel:1,dataMask:5}},{bits:20375,formatInfo:{errorCorrectionLevel:1,dataMask:6}},{bits:19104,formatInfo:{errorCorrectionLevel:1,dataMask:7}},{bits:30660,formatInfo:{errorCorrectionLevel:0,dataMask:0}},{bits:29427,formatInfo:{errorCorrectionLevel:0,dataMask:1}},{bits:32170,formatInfo:{errorCorrectionLevel:0,dataMask:2}},{bits:30877,formatInfo:{errorCorrectionLevel:0,dataMask:3}},{bits:26159,
formatInfo:{errorCorrectionLevel:0,dataMask:4}},{bits:25368,formatInfo:{errorCorrectionLevel:0,dataMask:5}},{bits:27713,formatInfo:{errorCorrectionLevel:0,dataMask:6}},{bits:26998,formatInfo:{errorCorrectionLevel:0,dataMask:7}},{bits:5769,formatInfo:{errorCorrectionLevel:3,dataMask:0}},{bits:5054,formatInfo:{errorCorrectionLevel:3,dataMask:1}},{bits:7399,formatInfo:{errorCorrectionLevel:3,dataMask:2}},{bits:6608,formatInfo:{errorCorrectionLevel:3,dataMask:3}},{bits:1890,formatInfo:{errorCorrectionLevel:3,
dataMask:4}},{bits:597,formatInfo:{errorCorrectionLevel:3,dataMask:5}},{bits:3340,formatInfo:{errorCorrectionLevel:3,dataMask:6}},{bits:2107,formatInfo:{errorCorrectionLevel:3,dataMask:7}},{bits:13663,formatInfo:{errorCorrectionLevel:2,dataMask:0}},{bits:12392,formatInfo:{errorCorrectionLevel:2,dataMask:1}},{bits:16177,formatInfo:{errorCorrectionLevel:2,dataMask:2}},{bits:14854,formatInfo:{errorCorrectionLevel:2,dataMask:3}},{bits:9396,formatInfo:{errorCorrectionLevel:2,dataMask:4}},{bits:8579,formatInfo:{errorCorrectionLevel:2,
dataMask:5}},{bits:11994,formatInfo:{errorCorrectionLevel:2,dataMask:6}},{bits:11245,formatInfo:{errorCorrectionLevel:2,dataMask:7}}],aa=[a=>0===(a.y+a.x)%2,a=>0===a.y%2,a=>0===a.x%3,a=>0===(a.y+a.x)%3,a=>0===(Math.floor(a.y/2)+Math.floor(a.x/3))%2,a=>0===a.x*a.y%2+a.x*a.y%3,a=>0===(a.y*a.x%2+a.y*a.x%3)%2,a=>0===((a.y+a.x)%2+a.y*a.x%3)%2],y=(a,b)=>Math.sqrt(Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2)),la={inversionAttempts:"attemptBoth",greyScaleWeights:{red:.2126,green:.7152,blue:.0722,useIntegerApproximation:!1},
canOverwriteImage:!0};I.default=I;let G="dontInvert",D={red:77,green:150,blue:29,useIntegerApproximation:!0};self.onmessage=a=>{let b=a.data.data;switch(a.data.type){case "decode":a=I(b.data,b.width,b.height,{inversionAttempts:G,greyScaleWeights:D});self.postMessage({type:"qrResult",data:a?a.data:null});break;case "grayscaleWeights":D.red=b.red;D.green=b.green;D.blue=b.blue;D.useIntegerApproximation=b.useIntegerApproximation;break;case "inversionMode":switch(b){case "original":G="dontInvert";break;
case "invert":G="onlyInvert";break;case "both":G="attemptBoth";break;default:throw Error("Invalid inversion mode");}break;case "close":self.close()}}})()
//# sourceMappingURL=qr-scanner-worker.min.js.map

View File

@ -1,5 +1,5 @@
// Custom Theming for Angular Material // Custom Theming for Angular Material
@use '~@angular/material' as mat; @use '~@angular/material'as mat;
// For more information: https://material.angular.io/guide/theming // For more information: https://material.angular.io/guide/theming
// Plus imports for other components in your app. // Plus imports for other components in your app.
@ -83,7 +83,8 @@ body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
} }
app-root, app-main { app-root,
app-main {
height: 100%; height: 100%;
max-height: 100%; max-height: 100%;
display: flex; display: flex;
@ -327,3 +328,45 @@ table {
top: -40px; top: -40px;
right: 15px; right: 15px;
} }
mat-card.success {
padding: 0;
mat-card-header {
padding: 16px;
padding-bottom: 16px;
padding-bottom: 0;
background-color: #43A047 !important;
}
mat-card-content {
padding: 16px;
}
mat-card-actions {
padding: 16px !important;
padding-top: 16px;
padding-top: 0 !important;
}
}
.mat-card-dialog {
.mat-dialog-container {
margin: 0;
padding: 0;
.mat-dialog-content {
margin: 0;
padding: 0;
.mat-card {
margin: 0;
.mat-card-actions {
margin: 0;
}
}
}
}
}