TSK-451-457 Fix pagination bugs

This commit is contained in:
Martin Rojas Miguel Angel 2018-04-25 14:59:54 +02:00 committed by Holger Hagen
parent 4794ec97fc
commit 74b61ccaa9
15 changed files with 129 additions and 68 deletions

37
web/package-lock.json generated
View File

@ -1694,6 +1694,13 @@
"requires": { "requires": {
"chartjs-color": "2.2.0", "chartjs-color": "2.2.0",
"moment": "2.18.1" "moment": "2.18.1"
},
"dependencies": {
"moment": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
"integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
}
} }
}, },
"chartjs-color": { "chartjs-color": {
@ -7366,9 +7373,10 @@
} }
}, },
"moment": { "moment": {
"version": "2.18.1", "version": "2.22.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.1.tgz",
"integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=" "integrity": "sha512-shJkRTSebXvsVqk56I+lkb2latjBs8I+pc2TzWc545y2iFnSjm7Wg0QMh+ZWcdSLQyGEau5jI8ocnmkyTgr9YQ==",
"dev": true
}, },
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
@ -7492,6 +7500,12 @@
"chart.js": "2.7.1" "chart.js": "2.7.1"
} }
}, },
"ng2-mock-component": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ng2-mock-component/-/ng2-mock-component-0.1.1.tgz",
"integrity": "sha512-HPcn61Wfmcmg2swJ4chqwGueTVkqz0PpnsTriLqMFblaW1/p1tthL9yKpa8TQpcwGSih2NI2Hb7IcQiMlC+E0w==",
"dev": true
},
"ngx-bootstrap": { "ngx-bootstrap": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-2.0.1.tgz", "resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-2.0.1.tgz",
@ -10798,6 +10812,23 @@
} }
} }
}, },
"ts-mockito": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.3.0.tgz",
"integrity": "sha512-IB37YP8DppTHUf/aJeayUa7fZp7mn7HK+NYalzdyoy0z0vOpZwO+HiUKLUkK+c9ERcRlOdwm8Do99ndYsvm+Bw==",
"dev": true,
"requires": {
"lodash": "4.17.10"
},
"dependencies": {
"lodash": {
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
"dev": true
}
}
},
"ts-node": { "ts-node": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-4.1.0.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-4.1.0.tgz",

View File

@ -55,7 +55,10 @@
"karma-jasmine": "1.1.1", "karma-jasmine": "1.1.1",
"karma-jasmine-html-reporter": "0.2.2", "karma-jasmine-html-reporter": "0.2.2",
"karma-phantomjs-launcher": "1.0.4", "karma-phantomjs-launcher": "1.0.4",
"moment": "2.22.1",
"ng2-mock-component": "^0.1.1",
"protractor": "5.2.2", "protractor": "5.2.2",
"ts-mockito": "^2.3.0",
"ts-node": "4.1.0", "ts-node": "4.1.0",
"tslint": "5.9.1", "tslint": "5.9.1",
"typescript": "2.5.3" "typescript": "2.5.3"

View File

@ -1,5 +1,5 @@
.classification-list-full-height { .classification-list-full-height {
height: calc(100vh - 55px); height: calc(100vh - 57px);
} }
.list-group-item { .list-group-item {

View File

@ -6,8 +6,9 @@
<li *ngFor="let pageNumber of workbasketsResource?.page?.totalPages | <li *ngFor="let pageNumber of workbasketsResource?.page?.totalPages |
spreadNumber: workbasketsResource?.page?.number: maxPagesAvailable: workbasketsResource?.page?.totalPages"> spreadNumber: workbasketsResource?.page?.number: maxPagesAvailable: workbasketsResource?.page?.totalPages">
<a *ngIf="pageNumber + 1 !== workbasketsResource?.page?.number" (click)="changeToPage(pageNumber+1)">{{pageNumber + 1}}</a> <a *ngIf="pageNumber + 1 !== workbasketsResource?.page?.number" (click)="changeToPage(pageNumber+1)">{{pageNumber + 1}}</a>
<input *ngIf="pageNumber + 1 === workbasketsResource?.page?.number" [(ngModel)]="pageSelected" (keyup.enter)="changeToPage(pageSelected)" <a *ngIf="pageNumber + 1 === workbasketsResource?.page?.number" class="pagination">
type="text" class="pagination" (blur)="changeToPage(pageSelected)"> <input [(ngModel)]="pageSelected" (keyup.enter)="changeToPage(pageSelected)" type="text" (blur)="changeToPage(pageSelected)">
</a>
</li> </li>
<li> <li>
<a (click)="changeToPage(workbasketsResource?.page.totalPages)" aria-label="Last">Last</a> <a (click)="changeToPage(workbasketsResource?.page.totalPages)" aria-label="Last">Last</a>

View File

@ -1,9 +1,24 @@
input.pagination{ a.pagination{
width: 35px; width: 35px;
height: 35px; height: 35px;
margin: 0 10px; margin: 0 10px;
text-align: center; padding: 0;
background-color: aliceblue; background-color: aliceblue;
> input{
margin: 1px;
width: 30px;
height: 30px;
text-align: center;
background-color: aliceblue;
border: none;
outline: none;
&:focus{
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
}
}
} }
ul.pagination{ ul.pagination{

View File

@ -1,19 +1,22 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleChange } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { SharedModule } from 'app/shared/shared.module';
import { PaginationComponent } from './pagination.component'; import { PaginationComponent } from './pagination.component';
import { SpreadNumberPipe } from 'app/shared/pipes/spreadNumber/spread-number'; import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
import { WorkbasketSummaryResource } from '../../../../../models/workbasket-summary-resource'; import { Page } from 'app/models/page';
import { Page } from '../../../../../models/page';
describe('PaginationComponent', () => { describe('PaginationComponent', () => {
let component: PaginationComponent; let component: PaginationComponent;
let fixture: ComponentFixture<PaginationComponent>; let fixture: ComponentFixture<PaginationComponent>;
let debugElement; let debugElement;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ return TestBed.configureTestingModule({
declarations: [PaginationComponent, SpreadNumberPipe], declarations: [
imports: [FormsModule] PaginationComponent
],
imports: [FormsModule, SharedModule]
}) })
.compileComponents(); .compileComponents();
})); }));
@ -81,4 +84,14 @@ describe('PaginationComponent', () => {
component.changeToPage(0); component.changeToPage(0);
}); });
it('should change pageSelected onChanges', () => {
expect(component.pageSelected).toBe(1);
component.ngOnChanges({
workbasketsResource: new SimpleChange(null, new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 2)), true)
});
fixture.detectChanges();
expect(component.pageSelected).toBe(2);
});
}); });

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource'; import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
@Component({ @Component({
@ -6,7 +6,7 @@ import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resourc
templateUrl: './pagination.component.html', templateUrl: './pagination.component.html',
styleUrls: ['./pagination.component.scss'] styleUrls: ['./pagination.component.scss']
}) })
export class PaginationComponent implements OnInit { export class PaginationComponent implements OnInit, OnChanges {
@Input() @Input()
workbasketsResource: WorkbasketSummaryResource; workbasketsResource: WorkbasketSummaryResource;
@ -19,6 +19,11 @@ export class PaginationComponent implements OnInit {
constructor() { } constructor() { }
ngOnChanges(changes: SimpleChanges): void {
if (changes.workbasketsResource.currentValue && changes.workbasketsResource.currentValue.page) {
this.pageSelected = changes.workbasketsResource.currentValue.page.number;
}
}
ngOnInit() { ngOnInit() {
} }
@ -30,7 +35,6 @@ export class PaginationComponent implements OnInit {
if (this.previousPageSelected !== page) { if (this.previousPageSelected !== page) {
this.changePage.emit(page); this.changePage.emit(page);
this.previousPageSelected = page; this.previousPageSelected = page;
this.pageSelected = page;
} }
} }
} }

View File

@ -8,6 +8,8 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from 'app/shared/shared.module';
import { AppModule } from 'app/app.module';
import { WorkbasketSummary } from 'app/models/workbasket-summary'; import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { Links } from 'app/models/links'; import { Links } from 'app/models/links';
@ -20,16 +22,9 @@ import { IconTypeComponent } from 'app/administration/components/type-icon/icon-
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar.component'; import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar.component';
import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component'; import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component';
import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { AlertService } from 'app/services/alert/alert.service';
import { ClassificationDefinitionService } from 'app/administration/services/classification-definition/classification-definition.service'; import { ClassificationDefinitionService } from 'app/administration/services/classification-definition/classification-definition.service';
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service'; import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service';
import { DomainService } from 'app/services/domain/domain.service';
import { DomainServiceMock } from 'app/services/domain/domain.service.mock';
@Component({ @Component({
selector: 'taskana-dummy-detail', selector: 'taskana-dummy-detail',
@ -42,7 +37,7 @@ export class DummyDetailComponent {
describe('WorkbasketListToolbarComponent', () => { describe('WorkbasketListToolbarComponent', () => {
let component: WorkbasketListToolbarComponent; let component: WorkbasketListToolbarComponent;
let fixture: ComponentFixture<WorkbasketListToolbarComponent>; let fixture: ComponentFixture<WorkbasketListToolbarComponent>;
let debugElement, workbasketService, requestInProgressService, router; let debugElement, workbasketService, router;
const routes: Routes = [ const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' } { path: ':id', component: DummyDetailComponent, outlet: 'detail' }
@ -51,14 +46,14 @@ describe('WorkbasketListToolbarComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, AngularSvgIconModule, HttpModule, imports: [FormsModule, ReactiveFormsModule, AngularSvgIconModule, HttpModule,
HttpClientModule, RouterTestingModule.withRoutes(routes)], HttpClientModule, RouterTestingModule.withRoutes(routes), SharedModule, AppModule],
declarations: [WorkbasketListToolbarComponent, SortComponent, declarations: [WorkbasketListToolbarComponent, SortComponent,
FilterComponent, IconTypeComponent, DummyDetailComponent, MapValuesPipe, ImportExportComponent], FilterComponent, IconTypeComponent, DummyDetailComponent, ImportExportComponent],
providers: [ErrorModalService, WorkbasketService, RequestInProgressService, AlertService, providers: [
ClassificationDefinitionService, WorkbasketDefinitionService, { WorkbasketService,
provide: DomainService, ClassificationDefinitionService,
useClass: DomainServiceMock WorkbasketDefinitionService,
}] ]
}) })
.compileComponents(); .compileComponents();
})); }));
@ -66,11 +61,9 @@ describe('WorkbasketListToolbarComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketListToolbarComponent); fixture = TestBed.createComponent(WorkbasketListToolbarComponent);
workbasketService = TestBed.get(WorkbasketService); workbasketService = TestBed.get(WorkbasketService);
requestInProgressService = TestBed.get(RequestInProgressService);
router = TestBed.get(Router); router = TestBed.get(Router);
spyOn(workbasketService, 'deleteWorkbasket').and.returnValue(Observable.of('')); spyOn(workbasketService, 'deleteWorkbasket').and.returnValue(Observable.of(''));
spyOn(workbasketService, 'triggerWorkBasketSaved'); spyOn(workbasketService, 'triggerWorkBasketSaved');
spyOn(requestInProgressService, 'setRequestInProgress');
debugElement = fixture.debugElement.nativeElement; debugElement = fixture.debugElement.nativeElement;
component = fixture.componentInstance; component = fixture.componentInstance;

View File

@ -1,5 +1,5 @@
.workbasket-list-full-height{ .workbasket-list-full-height{
height: calc(100vh - 55px); height: calc(100vh - 57px);
} }
.row.list-group { .row.list-group {

View File

@ -7,31 +7,24 @@ import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from 'app/shared/shared.module';
import { AppModule } from 'app/app.module';
import { WorkbasketSummary } from 'app/models/workbasket-summary'; import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource'; import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
import { FilterModel } from 'app/models/filter'; import { FilterModel } from 'app/models/filter';
import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary'; import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { AlertService } from 'app/services/alert/alert.service';
import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service';
import { OrientationService } from 'app/services/orientation/orientation.service';
import { WorkbasketListComponent } from './workbasket-list.component'; import { WorkbasketListComponent } from './workbasket-list.component';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component'; import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component'; import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { SortComponent } from 'app/administration/components/sort/sort.component'; import { SortComponent } from 'app/administration/components/sort/sort.component';
import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component'; import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component';
import { RemoveNoneTypePipe } from 'app/shared/pipes/removeNoneType/remove-none-type.pipe';
import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe';
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service'; import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service';
import { ClassificationDefinitionService } from 'app/administration/services/classification-definition/classification-definition.service'; import { ClassificationDefinitionService } from 'app/administration/services/classification-definition/classification-definition.service';
import { DomainService } from 'app/services/domain/domain.service'; import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service';
import { DomainServiceMock } from 'app/services/domain/domain.service.mock';
@Component({ @Component({
selector: 'taskana-dummy-detail', selector: 'taskana-dummy-detail',
@ -83,19 +76,21 @@ describe('WorkbasketListComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent, WorkbasketListToolbarComponent, declarations: [WorkbasketListComponent, DummyDetailComponent, FilterComponent, WorkbasketListToolbarComponent,
RemoveNoneTypePipe, IconTypeComponent, SortComponent, PaginationComponent, ImportExportComponent, MapValuesPipe], IconTypeComponent, SortComponent, PaginationComponent, ImportExportComponent],
imports: [ imports: [
AngularSvgIconModule, AngularSvgIconModule,
HttpModule, HttpModule,
HttpClientModule, HttpClientModule,
RouterTestingModule.withRoutes(routes) RouterTestingModule.withRoutes(routes),
SharedModule,
AppModule
], ],
providers: [WorkbasketService, ErrorModalService, RequestInProgressService, AlertService, providers: [
WorkbasketDefinitionService, OrientationService, { WorkbasketService,
provide: DomainService, WorkbasketDefinitionService,
useClass: DomainServiceMock ClassificationDefinitionService
}, ClassificationDefinitionService] ]
}) })
.compileComponents(); .compileComponents();
@ -143,8 +138,10 @@ describe('WorkbasketListComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3); expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3);
expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class')).toBe('list-group-item'); expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class'))
expect(debugElement.querySelectorAll('#wb-list-container > li')[2].getAttribute('class')).toBe('list-group-item active'); .toBe('list-group-item ng-star-inserted');
expect(debugElement.querySelectorAll('#wb-list-container > li')[2].getAttribute('class'))
.toBe('list-group-item ng-star-inserted active');
}) })
})); }));

View File

@ -17,7 +17,7 @@ import { DomainServiceMock } from 'app/services/domain/domain.service.mock';
import { DomainService } from 'app/services/domain/domain.service'; import { DomainService } from 'app/services/domain/domain.service';
describe('AppComponent', () => { fdescribe('AppComponent', () => {
let app, fixture, debugElement; let app, fixture, debugElement;

View File

@ -43,6 +43,7 @@ import { NavBarComponent } from 'app/components/nav-bar/nav-bar.component';
* Guards * Guards
*/ */
import { DomainGuard } from './guards/domain-guard'; import { DomainGuard } from './guards/domain-guard';
import { APP_BASE_HREF } from '@angular/common';
const MODULES = [ const MODULES = [
@ -72,6 +73,7 @@ export function startupServiceFactory(startupService: StartupService): Function
declarations: DECLARATIONS, declarations: DECLARATIONS,
imports: MODULES, imports: MODULES,
providers: [ providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
DomainService, DomainService,
{ {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,

View File

@ -12,7 +12,7 @@ import { DomainServiceMock } from 'app/services/domain/domain.service.mock';
describe('NavBarComponent', () => { describe('NavBarComponent', () => {
let component: NavBarComponent; let component: NavBarComponent;
let fixture: ComponentFixture<NavBarComponent>; let fixture: ComponentFixture<NavBarComponent>;
let navBar; let debugElement, navBar;
const routes: Routes = [ const routes: Routes = [
{ path: 'classifications', component: NavBarComponent } { path: 'classifications', component: NavBarComponent }
@ -37,10 +37,16 @@ describe('NavBarComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(NavBarComponent); fixture = TestBed.createComponent(NavBarComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
navBar = fixture.debugElement.componentInstance; navBar = fixture.debugElement.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
afterEach(() => {
fixture.detectChanges()
document.body.removeChild(debugElement);
});
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });

View File

@ -21,18 +21,18 @@
/* /*
* Base structure * Base structure
*/ */
/* Move down content because we have a fixed navbar that is 55px tall */ /* Move down content because we have a fixed navbar that is 57px tall */
.container-main { .container-main {
margin-top: 55px; margin-top: 57px;
overflow-y: hidden; overflow-y: hidden;
/*Min mobile view size*/ /*Min mobile view size*/
min-width: 420px; min-width: 420px;
} }
.container-scrollable { .container-scrollable {
max-height: calc(100vh - 55px); max-height: calc(100vh - 57px);
height: calc(100vh - 55px); height: calc(100vh - 57px);
overflow: hidden; overflow: hidden;
} }
@ -226,7 +226,7 @@ svg-icon.fa-fw > svg {
transition: opacity 300ms ease, visibility 300ms ease; transition: opacity 300ms ease, visibility 300ms ease;
} }
.vertical-align{ .vertical-align{
vertical-align: middle; vertical-align: sub;
} }
.panel-heading{ .panel-heading{

View File

@ -5,14 +5,10 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Administration</title> <title>Administration</title>
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="./taskana.ico"> <link rel="icon" type="image/x-icon" href="./taskana.ico">
</head> </head>
<body> <body>
<taskana-root></taskana-root> <taskana-root></taskana-root>
</body> </body>
</html>
</html>