diff --git a/.circleci/config.yml b/.circleci/config.yml index 854fadfc..d271bbd8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,17 +7,17 @@ aliases: # https://github.com/CircleCI-Public/circleci-dockerfiles/tree/master/node/images - &use_docker_node docker: - - image: circleci/node:13.4.0-stretch + - image: circleci/node:13.5.0-stretch - &use_docker_node_browsers docker: - - image: circleci/node:13.4.0-stretch-browsers + - image: circleci/node:13.5.0-stretch-browsers # https://github.com/cypress-io/cypress-docker-images/tree/master/included - &use_docker_cypress_included docker: # 3.8.0 fails with `Error: write EPIPE` when running `benchmark`!? - - image: cypress/included:3.7.0 + - image: cypress/included:3.8.2 - &workspace ~/talus diff --git a/.nxignore b/.nxignore new file mode 100644 index 00000000..34902dd4 --- /dev/null +++ b/.nxignore @@ -0,0 +1,2 @@ +# Hack for getting benchmark app (generated with ng g @nrwl/node:application) through linting +/apps/benchmark diff --git a/apps/benchmark/src/app/vdb/grid.benchmark.ts b/apps/benchmark/src/app/vdb/grid.benchmark.ts index 94e46c7c..6cde5004 100644 --- a/apps/benchmark/src/app/vdb/grid.benchmark.ts +++ b/apps/benchmark/src/app/vdb/grid.benchmark.ts @@ -46,7 +46,7 @@ suite('[Grid] setValue()', () => { for (let x = 0; x < i; x++) { for (let y = 0; y < i; y++) { for (let z = 0; z < i; z++) { - accessor.setValue([x, y, z], i); + accessor.setValueOn([x, y, z], i); } } } diff --git a/apps/benchmark/src/app/vdb/node-to-mesh.benchmark.ts b/apps/benchmark/src/app/vdb/node-to-mesh.benchmark.ts new file mode 100644 index 00000000..b06dcd42 --- /dev/null +++ b/apps/benchmark/src/app/vdb/node-to-mesh.benchmark.ts @@ -0,0 +1,34 @@ +import { benchmark, suite } from '../../main'; + +suite('[NodeToMesh] array.push()', () => { + benchmark('multiple small push', () => { + const positions: number[] = []; + + positions.push(1); + positions.push(2); + positions.push(3); + positions.push(4); + positions.push(5); + positions.push(6); + positions.push(7); + positions.push(8); + positions.push(9); + positions.push(10); + positions.push(11); + positions.push(12); + positions.push(13); + positions.push(14); + positions.push(15); + positions.push(16); + positions.push(17); + positions.push(18); + positions.push(19); + positions.push(20); + }); + + benchmark('single big push', () => { + const positions: number[] = []; + + positions.push(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); + }); +}); diff --git a/apps/benchmark/src/main.ts b/apps/benchmark/src/main.ts index 10319c92..d6bb2c68 100644 --- a/apps/benchmark/src/main.ts +++ b/apps/benchmark/src/main.ts @@ -86,7 +86,9 @@ function convertToTestSuite(suiteName: string, currentSuite: Benchmark.Suite): T } function sumUpTotalTime(): void { - report.time = report.suites.map(s => s.time).reduce((previous, current) => previous + current, 0); + report.time = report.suites + .map(s => s.time) + .reduce((previous, current) => (previous && current ? previous + current : 0), 0); } function everySuiteFinished(): boolean { diff --git a/apps/benchmark/tslint.json b/apps/benchmark/tslint.json index 04809f83..8e2f21b3 100644 --- a/apps/benchmark/tslint.json +++ b/apps/benchmark/tslint.json @@ -1 +1,4 @@ -{ "extends": "../../tslint.json", "rules": [] } +{ + "extends": "../../tslint.json", + "rules": {} +} diff --git a/apps/frontend/src/app/app.actions.ts b/apps/frontend/src/app/app.actions.ts index ab592e7e..d2d34e5c 100644 --- a/apps/frontend/src/app/app.actions.ts +++ b/apps/frontend/src/app/app.actions.ts @@ -1,6 +1,6 @@ import { createAction } from '@ngrx/store'; -const actionTypePrefix = '[App]'; +const actionTypePrefix = '[app]'; export const wentOnline = createAction(`${actionTypePrefix} Went online`); export const wentOffline = createAction(`${actionTypePrefix} Went offline`); diff --git a/apps/frontend/src/app/app.component.scss b/apps/frontend/src/app/app.component.scss new file mode 100644 index 00000000..f2cbf979 --- /dev/null +++ b/apps/frontend/src/app/app.component.scss @@ -0,0 +1,11 @@ +:host { + display: flex; + flex-flow: column; + height: 100%; +} + +main { + flex-grow: 1; + // https://stackoverflow.com/a/38383437 + min-height: 0; +} diff --git a/apps/frontend/src/app/app.component.spec.ts b/apps/frontend/src/app/app.component.spec.ts index 4605e2a5..49deb6a1 100644 --- a/apps/frontend/src/app/app.component.spec.ts +++ b/apps/frontend/src/app/app.component.spec.ts @@ -2,9 +2,16 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { async, TestBed } from '@angular/core/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; -import { SceneViewerTestModule, SidenavShellModule } from '@talus/ui'; +import { UiSceneViewerTestModule, UiSidenavShellModule } from '@talus/ui'; import { AppComponent } from './app.component'; +@Component({ + selector: 'fe-menu-bar-container', + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class MenuBarContainerStubComponent {} + @Component({ selector: 'fe-scene-viewer-container', template: '', @@ -22,12 +29,17 @@ class ToolsPanelStubComponent {} describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [AppComponent, SceneViewerContainerStubComponent, ToolsPanelStubComponent], + declarations: [ + AppComponent, + MenuBarContainerStubComponent, + SceneViewerContainerStubComponent, + ToolsPanelStubComponent, + ], imports: [ BrowserAnimationsModule, RouterTestingModule, - SidenavShellModule, - SceneViewerTestModule, + UiSceneViewerTestModule, + UiSidenavShellModule, ], }).compileComponents(); })); diff --git a/apps/frontend/src/app/app.component.ts b/apps/frontend/src/app/app.component.ts index a5ef2d16..26ac5b17 100644 --- a/apps/frontend/src/app/app.component.ts +++ b/apps/frontend/src/app/app.component.ts @@ -3,21 +3,30 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'fe-root', template: ` - - - - +
+ +
- - Right - +
+ + + + - - - - + + Right + + + + + + +
+ + `, changeDetection: ChangeDetectionStrategy.OnPush, + styleUrls: ['./app.component.scss'], }) export class AppComponent { title = 'frontend'; diff --git a/apps/frontend/src/app/app.module.ts b/apps/frontend/src/app/app.module.ts index 58db50bd..72df9030 100644 --- a/apps/frontend/src/app/app.module.ts +++ b/apps/frontend/src/app/app.module.ts @@ -4,12 +4,14 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterModule } from '@angular/router'; import { EffectsModule } from '@ngrx/effects'; import { StoreModule } from '@ngrx/store'; -import { SidenavShellModule } from '@talus/ui'; +import { UiSidenavShellModule } from '@talus/ui'; import { AppComponent } from './app.component'; import { AppEffects } from './app.effects'; import { metaReducers, ROOT_REDUCERS } from './app.reducer'; +import { MenuBarContainerModule } from './menu-bar-container/menu-bar-container.module'; import { SceneViewerContainerModule } from './scene-viewer-container'; import { ToolsPanelModule } from './tools-panel/tools-panel.module'; +import { UndoRedoModule } from './undo-redo/undo-redo.module'; @NgModule({ declarations: [AppComponent], @@ -35,9 +37,11 @@ import { ToolsPanelModule } from './tools-panel/tools-panel.module'; }, }), + MenuBarContainerModule, SceneViewerContainerModule, - SidenavShellModule, + UiSidenavShellModule, ToolsPanelModule, + UndoRedoModule, ], providers: [], bootstrap: [AppComponent], diff --git a/apps/frontend/src/app/app.reducer.ts b/apps/frontend/src/app/app.reducer.ts index 82ec8af7..4c401a18 100644 --- a/apps/frontend/src/app/app.reducer.ts +++ b/apps/frontend/src/app/app.reducer.ts @@ -10,8 +10,10 @@ import { import { environment } from '../environments/environment'; import * as fromSceneViewerContainer from './scene-viewer-container/scene-viewer-container.reducer'; import * as fromToolsPanel from './tools-panel/tools-panel.reducer'; +import * as fromUndoRedo from './undo-redo/undo-redo.reducer'; export interface State { + [fromUndoRedo.featureKey]: fromUndoRedo.State; [fromToolsPanel.featureKey]: fromToolsPanel.State; [fromSceneViewerContainer.featureKey]: fromSceneViewerContainer.State; } @@ -27,8 +29,9 @@ export const ROOT_REDUCERS = new InjectionToken> 'Root reducers token', { factory: () => ({ - [fromToolsPanel.featureKey]: fromToolsPanel.reducer, [fromSceneViewerContainer.featureKey]: fromSceneViewerContainer.reducer, + [fromToolsPanel.featureKey]: fromToolsPanel.reducer, + [fromUndoRedo.featureKey]: fromUndoRedo.reducer, }), }, ); @@ -78,3 +81,30 @@ export const selectSelectedToolId = createSelector( selectToolsPanelState, fromToolsPanel.selectSelectedToolId, ); + +/** + * UndoRedo reducers + */ +export const selectUndoRedoState = createFeatureSelector( + fromUndoRedo.featureKey, +); + +export const selectCurrentUndoStartAction = createSelector( + selectUndoRedoState, + fromUndoRedo.selectCurrentUndoStartAction, +); + +export const selectCurrentRedoStartAction = createSelector( + selectUndoRedoState, + fromUndoRedo.selectCurrentRedoStartAction, +); + +export const selectCurrentUndoEndAction = createSelector( + selectUndoRedoState, + fromUndoRedo.selectCurrentUndoEndAction, +); + +export const selectCurrentRedoEndAction = createSelector( + selectUndoRedoState, + fromUndoRedo.selectCurrentRedoEndAction, +); diff --git a/apps/frontend/src/app/menu-bar-container/menu-bar-container.actions.ts b/apps/frontend/src/app/menu-bar-container/menu-bar-container.actions.ts new file mode 100644 index 00000000..04d964a2 --- /dev/null +++ b/apps/frontend/src/app/menu-bar-container/menu-bar-container.actions.ts @@ -0,0 +1,6 @@ +import { createAction } from '@ngrx/store'; + +const actionTypePrefix = `[menuBarContainer]`; + +export const undo = createAction(`${actionTypePrefix} Undo`); +export const redo = createAction(`${actionTypePrefix} Redo`); diff --git a/apps/frontend/src/app/menu-bar-container/menu-bar-container.component.spec.ts b/apps/frontend/src/app/menu-bar-container/menu-bar-container.component.spec.ts new file mode 100644 index 00000000..b8bc23ba --- /dev/null +++ b/apps/frontend/src/app/menu-bar-container/menu-bar-container.component.spec.ts @@ -0,0 +1,53 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Store, StoreModule } from '@ngrx/store'; +import { MockStore } from '@ngrx/store/testing'; +import { ROOT_REDUCERS, State } from '../app.reducer'; +import { undo } from './menu-bar-container.actions'; +import { MenuBarContainerComponent } from './menu-bar-container.component'; +import { MenuBarContainerModule } from './menu-bar-container.module'; + +describe('MenuBarComponent', () => { + let component: MenuBarContainerComponent; + let fixture: ComponentFixture; + + let store: MockStore; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [], + imports: [ + MenuBarContainerModule, + StoreModule.forRoot(ROOT_REDUCERS, { + runtimeChecks: { + strictStateImmutability: true, + strictActionImmutability: true, + strictStateSerializability: true, + strictActionSerializability: true, + }, + }), + ], + }).compileComponents(); + + store = TestBed.get>(Store); + })); + + beforeEach(() => { + spyOn(store, 'dispatch'); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MenuBarContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should dispatch action', () => { + component.onMenuItemClick(undo()); + + expect(store.dispatch).toHaveBeenCalledWith(undo()); + }); +}); diff --git a/apps/frontend/src/app/menu-bar-container/menu-bar-container.component.ts b/apps/frontend/src/app/menu-bar-container/menu-bar-container.component.ts new file mode 100644 index 00000000..a2e76188 --- /dev/null +++ b/apps/frontend/src/app/menu-bar-container/menu-bar-container.component.ts @@ -0,0 +1,40 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Action, Store } from '@ngrx/store'; +import { UiMenuBarConfig } from '@talus/ui'; +import * as fromApp from '../app.reducer'; +import { redo, undo } from './menu-bar-container.actions'; + +@Component({ + selector: 'fe-menu-bar-container', + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MenuBarContainerComponent { + menuConfig: UiMenuBarConfig = { + menus: [ + { + label: 'Edit', + menuItems: [ + { + icon: 'undo', + label: 'Undo', + value: undo(), + }, + { + icon: 'redo', + label: 'Redo', + value: redo(), + }, + ], + }, + ], + }; + + constructor(private store: Store) {} + + onMenuItemClick(action: Action): void { + this.store.dispatch(action); + } +} diff --git a/apps/frontend/src/app/menu-bar-container/menu-bar-container.module.ts b/apps/frontend/src/app/menu-bar-container/menu-bar-container.module.ts new file mode 100644 index 00000000..2347c696 --- /dev/null +++ b/apps/frontend/src/app/menu-bar-container/menu-bar-container.module.ts @@ -0,0 +1,10 @@ +import { NgModule } from '@angular/core'; +import { UiMenuBarModule } from '@talus/ui'; +import { MenuBarContainerComponent } from './menu-bar-container.component'; + +@NgModule({ + declarations: [MenuBarContainerComponent], + imports: [UiMenuBarModule], + exports: [MenuBarContainerComponent], +}) +export class MenuBarContainerModule {} diff --git a/apps/frontend/src/app/scene-viewer-container/grid.service.ts b/apps/frontend/src/app/scene-viewer-container/grid.service.ts index 4b2d47c9..4fda072e 100644 --- a/apps/frontend/src/app/scene-viewer-container/grid.service.ts +++ b/apps/frontend/src/app/scene-viewer-container/grid.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Coord, Grid, gridToMesh, MeshData } from '@talus/vdb'; +import { Coord, Grid, MeshData, nodeToMesh } from '@talus/vdb'; /** * Keeps the mutable state of the single grid. This state is not part of the store, due to @@ -12,15 +12,73 @@ export class GridService { grid = new Grid(0); accessor = this.grid.getAccessor(); - addVoxel(xyz: Coord, value: number): void { - this.accessor.setValue(xyz, value); + colors = { + 0: [0, 0, 0, 1], + 1: [0, 0, 1, 1], + 2: [0, 1, 0, 1], + 3: [0, 1, 1, 1], + 4: [1, 0, 0, 1], + 5: [1, 0, 1, 1], + 6: [1, 1, 0, 1], + 7: [1, 1, 1, 1], + }; + + /** + * Adds a new voxel via accessor to share access path. + * @returns origin of `InternalNode1` of affected node (node containing added voxel). + */ + addVoxel(xyz: Coord, value: number): VoxelChange { + this.accessor.setValueOn(xyz, value); + + return { + affectedNodeOrigin: this.accessor.internalNode1Origin, + value, + position: xyz, + }; } - removeVoxel(xyz: Coord): void { - this.accessor.setValueOff(xyz, this.grid.background); + addVoxels(coords: Coord[], values: number[]): VoxelChange[] { + const changes = new Map(); + + if (coords.length !== values.length) { + throw new Error(`Coordinates and values don't have the same length.`); + } + + coords.forEach((xyz, i) => { + const change = this.addVoxel(xyz, values[i]); + changes.set(change.affectedNodeOrigin.toString(), change); + }); + + return Array.from(changes.values()); } - computeMesh(): MeshData | undefined { - return gridToMesh(this.grid); + removeVoxel(xyz: Coord): VoxelChange { + this.accessor.setActiveState(xyz, false); + + return { + affectedNodeOrigin: this.accessor.internalNode1Origin, + value: this.accessor.getValue(xyz), + position: xyz, + }; } + + computeInternalNode1Mesh(origin: Coord): MeshData | undefined { + const internal1 = this.accessor.probeInternalNode1(origin); + + if (!internal1) { + return undefined; + } + + return nodeToMesh(internal1, this.valueToColor); + } + + private valueToColor = (value: number): [number, number, number, number] => { + return this.colors[value % 8]; + }; +} + +export interface VoxelChange { + affectedNodeOrigin: Coord; + value: number; + position: Coord; } diff --git a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.actions.ts b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.actions.ts index d8f32abc..5154e280 100644 --- a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.actions.ts +++ b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.actions.ts @@ -1,18 +1,29 @@ import { createAction, props } from '@ngrx/store'; import { Coord } from '@talus/vdb'; +import { VoxelChange } from './grid.service'; -const actionTypePrefix = '[SceneViewerContainer]'; +const actionTypePrefix = `[sceneViewerContainer]`; export const addVoxel = createAction( `${actionTypePrefix} Add voxel`, props<{ position: Coord; value: number }>(), ); export const addVoxelFailed = createAction(`${actionTypePrefix} Add voxel failed`); -export const voxelAdded = createAction(`${actionTypePrefix} Voxel added`); +export const voxelAdded = createAction(`${actionTypePrefix} Voxel added`, props()); + +export const addVoxels = createAction( + `${actionTypePrefix} Add voxels`, + props<{ positions: Coord[]; values: number[] }>(), +); +export const addVoxelsFailed = createAction(`${actionTypePrefix} Add voxels failed`); +export const voxelsAdded = createAction( + `${actionTypePrefix} Voxels added`, + props<{ voxelChanges: VoxelChange[] }>(), +); export const removeVoxel = createAction( `${actionTypePrefix} Remove voxel`, props<{ position: Coord }>(), ); export const removeVoxelFailed = createAction(`${actionTypePrefix} Remove voxel failed`); -export const voxelRemoved = createAction(`${actionTypePrefix} Voxel removed`); +export const voxelRemoved = createAction(`${actionTypePrefix} Voxel removed`, props()); diff --git a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.component.spec.ts b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.component.spec.ts index cc914e18..d3da561d 100644 --- a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.component.spec.ts +++ b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.component.spec.ts @@ -3,7 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { MemoizedSelector, Store } from '@ngrx/store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; -import { PointerButton, PointerPickInfo } from '@talus/ui'; +import { UiPointerButton, UiPointerPickInfo } from '@talus/ui'; import { Coord } from '@talus/vdb'; import { Subject } from 'rxjs'; import * as fromApp from '../app.reducer'; @@ -18,7 +18,7 @@ import { SceneViewerContainerComponent } from './scene-viewer-container.componen changeDetection: ChangeDetectionStrategy.OnPush, }) class SceneViewerStubComponent { - @Output() pointerPick = new Subject(); + @Output() pointerPick = new Subject(); } describe('SceneViewerContainerComponent', () => { @@ -36,7 +36,6 @@ describe('SceneViewerContainerComponent', () => { }).compileComponents(); mockStore = TestBed.get(Store); - spyOn(mockStore, 'dispatch'); mockSelectedToolIdSelector = mockStore.overrideSelector( fromApp.selectSelectedToolId, @@ -44,6 +43,10 @@ describe('SceneViewerContainerComponent', () => { ); })); + beforeEach(async(() => { + spyOn(mockStore, 'dispatch'); + })); + beforeEach(() => { fixture = TestBed.createComponent(SceneViewerContainerComponent); component = fixture.componentInstance; @@ -62,11 +65,12 @@ describe('SceneViewerContainerComponent', () => { it('should dispatch no action if not PointerButton.Main', () => { stubComponent.pointerPick.next({ pickedPoint: [0, 0, 0], - pointerButton: PointerButton.Secondary, + pointerButton: UiPointerButton.Secondary, normal: [0, 0, 0], }); - expect(mockStore.dispatch).not.toHaveBeenCalled(); + // Only once called due to first initial added voxel at [0, 0, 0] + expect(mockStore.dispatch).toHaveBeenCalledTimes(1); }); it.each([ @@ -113,14 +117,16 @@ describe('SceneViewerContainerComponent', () => { ])( 'should dispatch `addVoxel` action for %j', (pickedPoint: Coord, position: Coord, normal: Coord) => { - const action = addVoxel({ position, value: 42 }); + const initialAction = addVoxel({ position: [0, 0, 0], value: 42 }); + const action = addVoxel({ position, value: 1 }); stubComponent.pointerPick.next({ pickedPoint, - pointerButton: PointerButton.Main, + pointerButton: UiPointerButton.Main, normal, }); + expect(mockStore.dispatch).toHaveBeenCalledWith(initialAction); expect(mockStore.dispatch).toHaveBeenCalledWith(action); }, ); @@ -172,7 +178,7 @@ describe('SceneViewerContainerComponent', () => { stubComponent.pointerPick.next({ pickedPoint, - pointerButton: PointerButton.Main, + pointerButton: UiPointerButton.Main, normal, }); diff --git a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.component.ts b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.component.ts index d9efee4c..179cbf63 100644 --- a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.component.ts +++ b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.component.ts @@ -1,8 +1,8 @@ -import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, ViewChild } from '@angular/core'; // import '@babylonjs/core/Rendering/edgesRenderer'; // import '@babylonjs/core/Rendering/outlineRenderer'; import { select, Store } from '@ngrx/store'; -import { PointerButton, PointerPickInfo, SceneViewerComponent } from '@talus/ui'; +import { UiPointerButton, UiPointerPickInfo, UiSceneViewerComponent } from '@talus/ui'; import { Coord } from '@talus/vdb'; import { Observable } from 'rxjs'; import * as fromApp from '../app.reducer'; @@ -19,16 +19,20 @@ import { addVoxel, removeVoxel } from './scene-viewer-container.actions'; `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SceneViewerContainerComponent { - @ViewChild(SceneViewerComponent, { static: false }) - private sceneViewerComponent: SceneViewerComponent; +export class SceneViewerContainerComponent implements AfterViewInit { + @ViewChild(UiSceneViewerComponent, { static: false }) + private sceneViewerComponent: UiSceneViewerComponent; selectedToolId$: Observable = this.store.pipe(select(fromApp.selectSelectedToolId)); voxelCount$: Observable = this.store.pipe(select(fromApp.selectVoxelCount)); constructor(private store: Store) {} - onPointerPick(event: PointerPickInfo, selectedToolId: Tool): void { + ngAfterViewInit(): void { + this.store.dispatch(addVoxel({ position: [0, 0, 0], value: 42 })); + } + + onPointerPick(event: UiPointerPickInfo, selectedToolId: Tool): void { this.dispatchPickAction(event, selectedToolId); } @@ -37,15 +41,15 @@ export class SceneViewerContainerComponent { // mesh.renderOutline = !mesh.renderOutline; // } - private dispatchPickAction(pickInfo: PointerPickInfo, selectedToolId: Tool): void { - if (pickInfo.pointerButton !== PointerButton.Main) { + private dispatchPickAction(pickInfo: UiPointerPickInfo, selectedToolId: Tool): void { + if (pickInfo.pointerButton !== UiPointerButton.Main) { return; } switch (selectedToolId) { case Tool.AddVoxel: this.store.dispatch( - addVoxel({ position: this.calcVoxelToAddPosition(pickInfo), value: 42 }), + addVoxel({ position: this.calcVoxelToAddPosition(pickInfo), value: 1 }), ); break; case Tool.RemoveVoxel: @@ -54,7 +58,7 @@ export class SceneViewerContainerComponent { } } - private calcVoxelToAddPosition(pickInfo: PointerPickInfo): Coord { + private calcVoxelToAddPosition(pickInfo: UiPointerPickInfo): Coord { const pickedIntegerPoint = this.roundDimensionAlongNormal(pickInfo); // VDB removes fractional-part of the coordinate, i.e. 0.54 -> 0. @@ -76,7 +80,7 @@ export class SceneViewerContainerComponent { return newPoint; } - private calcVoxelToRemovePosition(pickInfo: PointerPickInfo): Coord { + private calcVoxelToRemovePosition(pickInfo: UiPointerPickInfo): Coord { const pickedIntegerPoint = this.roundDimensionAlongNormal(pickInfo); const newPoint: Coord = [ @@ -103,7 +107,7 @@ export class SceneViewerContainerComponent { * Since all the voxels are placed on integer positions the dimension of the picked point * needs to be rounded. */ - private roundDimensionAlongNormal(pickInfo: PointerPickInfo): Coord { + private roundDimensionAlongNormal(pickInfo: UiPointerPickInfo): Coord { return [ pickInfo.normal[0] !== 0 ? Math.round(pickInfo.pickedPoint[0]) : pickInfo.pickedPoint[0], pickInfo.normal[1] !== 0 ? Math.round(pickInfo.pickedPoint[1]) : pickInfo.pickedPoint[1], diff --git a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.effects.ts b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.effects.ts index 20571c6c..03c7fe79 100644 --- a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.effects.ts +++ b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.effects.ts @@ -1,16 +1,20 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { SceneViewerService } from '@talus/ui'; +import { UiSceneViewerService } from '@talus/ui'; +import { Coord } from '@talus/vdb'; import { of } from 'rxjs'; -import { catchError, map, tap } from 'rxjs/operators'; +import { catchError, map } from 'rxjs/operators'; import { GridService } from './grid.service'; import { addVoxel, addVoxelFailed, + addVoxels, + addVoxelsFailed, removeVoxel, removeVoxelFailed, voxelAdded, voxelRemoved, + voxelsAdded, } from './scene-viewer-container.actions'; @Injectable() @@ -18,34 +22,60 @@ export class SceneViewerContainerEffects { constructor( private actions$: Actions, private gridService: GridService, - private sceneViewerService: SceneViewerService, + private sceneViewerService: UiSceneViewerService, ) {} addVoxel$ = createEffect(() => this.actions$.pipe( ofType(addVoxel), - tap({ - next: ({ position, value }) => { - this.gridService.addVoxel(position, value); - this.sceneViewerService.updateGridMesh(this.gridService.computeMesh()); - }, - }), - map(() => voxelAdded()), + map(({ position, value }) => this.gridService.addVoxel(position, value)), + map(voxelAdded), catchError(() => of(addVoxelFailed())), ), ); + addVoxels$ = createEffect(() => + this.actions$.pipe( + ofType(addVoxels), + map(({ positions, values }) => this.gridService.addVoxels(positions, values)), + map(voxelChanges => voxelsAdded({ voxelChanges })), + catchError(() => of(addVoxelsFailed())), + ), + ); + removeVoxel$ = createEffect(() => this.actions$.pipe( ofType(removeVoxel), - tap({ - next: ({ position }) => { - this.gridService.removeVoxel(position); - this.sceneViewerService.updateGridMesh(this.gridService.computeMesh()); - }, - }), - map(() => voxelRemoved()), + map(({ position }) => this.gridService.removeVoxel(position)), + map(voxelRemoved), catchError(() => of(removeVoxelFailed())), ), ); + + updateGridMesh$ = createEffect( + () => + this.actions$.pipe( + ofType(voxelAdded, voxelRemoved), + map(({ affectedNodeOrigin }) => this.computeAndUpdateNodeMesh(affectedNodeOrigin)), + ), + { dispatch: false }, + ); + + updateGridMeshMultiple$ = createEffect( + () => + this.actions$.pipe( + ofType(voxelsAdded), + map(({ voxelChanges }) => + voxelChanges.map(change => { + this.computeAndUpdateNodeMesh(change.affectedNodeOrigin); + }), + ), + ), + { dispatch: false }, + ); + + private computeAndUpdateNodeMesh(affectedNodeOrigin: Coord): void { + const mesh = this.gridService.computeInternalNode1Mesh(affectedNodeOrigin); + this.sceneViewerService.updateNodeMesh(mesh, affectedNodeOrigin); + } } diff --git a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.module.ts b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.module.ts index ac65b3af..2c2622fe 100644 --- a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.module.ts +++ b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.module.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; -import { SceneViewerModule } from '@talus/ui'; +import { UiSceneViewerModule } from '@talus/ui'; import { GridService } from './grid.service'; import { SceneViewerContainerComponent } from './scene-viewer-container.component'; import { SceneViewerContainerEffects } from './scene-viewer-container.effects'; @@ -11,7 +11,7 @@ import { SceneViewerContainerEffects } from './scene-viewer-container.effects'; imports: [ CommonModule, EffectsModule.forFeature([SceneViewerContainerEffects]), - SceneViewerModule, + UiSceneViewerModule, ], exports: [SceneViewerContainerComponent], providers: [GridService], diff --git a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.reducer.spec.ts b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.reducer.spec.ts new file mode 100644 index 00000000..b8f51290 --- /dev/null +++ b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.reducer.spec.ts @@ -0,0 +1,30 @@ +import { VoxelChange } from './grid.service'; +import { voxelAdded, voxelRemoved } from './scene-viewer-container.actions'; +import { reducer, selectVoxelCount } from './scene-viewer-container.reducer'; + +describe('SceneViewerContainerReducer', () => { + const voxelChange: VoxelChange = { + position: [0, 0, 0], + affectedNodeOrigin: [0, 0, 0], + value: 42, + }; + + it('should increment counter', () => { + const stateWithOneVoxel = reducer(undefined, voxelAdded(voxelChange)); + + expect(stateWithOneVoxel.voxelCount).toEqual(1); + }); + + it('should decrement counter', () => { + const stateWithOneVoxel = reducer(undefined, voxelAdded(voxelChange)); + const stateWithNoVoxel = reducer(stateWithOneVoxel, voxelRemoved(voxelChange)); + + expect(stateWithNoVoxel.voxelCount).toEqual(0); + }); + + it('should select voxel count', () => { + const stateWithOneVoxel = reducer(undefined, voxelAdded(voxelChange)); + + expect(selectVoxelCount(stateWithOneVoxel)).toEqual(1); + }); +}); diff --git a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.reducer.ts b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.reducer.ts index f89ac931..1b8ea98b 100644 --- a/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.reducer.ts +++ b/apps/frontend/src/app/scene-viewer-container/scene-viewer-container.reducer.ts @@ -1,5 +1,5 @@ import { createReducer, on } from '@ngrx/store'; -import { voxelAdded } from './scene-viewer-container.actions'; +import { voxelAdded, voxelRemoved } from './scene-viewer-container.actions'; export const featureKey = 'sceneViewerContainer'; @@ -19,6 +19,12 @@ export const reducer = createReducer( voxelCount: state.voxelCount + 1, }; }), + on(voxelRemoved, state => { + return { + ...state, + voxelCount: state.voxelCount - 1, + }; + }), ); export const selectVoxelCount = (state: State) => state.voxelCount; diff --git a/apps/frontend/src/app/tools-panel/tools-panel.actions.ts b/apps/frontend/src/app/tools-panel/tools-panel.actions.ts index 308ce61b..1827a91b 100644 --- a/apps/frontend/src/app/tools-panel/tools-panel.actions.ts +++ b/apps/frontend/src/app/tools-panel/tools-panel.actions.ts @@ -1,4 +1,4 @@ import { createAction, props } from '@ngrx/store'; import { Tool } from './tool.model'; -export const selectTool = createAction('[ToolsPanel] Select tool', props<{ id: Tool }>()); +export const selectTool = createAction('[toolsPanel] Select tool', props<{ id: Tool }>()); diff --git a/apps/frontend/src/app/tools-panel/tools-panel.module.ts b/apps/frontend/src/app/tools-panel/tools-panel.module.ts index 55db8f76..fc96800f 100644 --- a/apps/frontend/src/app/tools-panel/tools-panel.module.ts +++ b/apps/frontend/src/app/tools-panel/tools-panel.module.ts @@ -1,11 +1,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { ToolbarModule } from '@talus/ui'; +import { UiToolbarModule } from '@talus/ui'; import { ToolsPanelComponent } from './tools-panel.component'; @NgModule({ declarations: [ToolsPanelComponent], - imports: [CommonModule, ToolbarModule], + imports: [CommonModule, UiToolbarModule], exports: [ToolsPanelComponent], }) export class ToolsPanelModule {} diff --git a/apps/frontend/src/app/tools-panel/tools-panel.reducer.spec.ts b/apps/frontend/src/app/tools-panel/tools-panel.reducer.spec.ts new file mode 100644 index 00000000..48409661 --- /dev/null +++ b/apps/frontend/src/app/tools-panel/tools-panel.reducer.spec.ts @@ -0,0 +1,11 @@ +import { Tool } from './tool.model'; +import { selectTool } from './tools-panel.actions'; +import { reducer } from './tools-panel.reducer'; + +describe('ToolsPanelReducer', () => { + it('should set selected tool', () => { + const newState = reducer(undefined, selectTool({ id: Tool.AddVoxel })); + + expect(newState.selectedToolId).toEqual(Tool.AddVoxel); + }); +}); diff --git a/apps/frontend/src/app/undo-redo/undo-redo.actions.ts b/apps/frontend/src/app/undo-redo/undo-redo.actions.ts new file mode 100644 index 00000000..a797592b --- /dev/null +++ b/apps/frontend/src/app/undo-redo/undo-redo.actions.ts @@ -0,0 +1,26 @@ +import { Action, createAction, props } from '@ngrx/store'; + +const actionTypePrefix = `[undoRedo]`; + +export const addUndo = createAction( + `${actionTypePrefix} Add undo`, + props<{ + redoStartAction: Action; + redoEndActionType: string; + undoStartAction: Action; + undoEndActionType: string; + }>(), +); + +export const undo = createAction(`${actionTypePrefix} Undo`); + +export const undone = createAction(`${actionTypePrefix} Undone`); + +export const addRedo = createAction( + `${actionTypePrefix} Add redo`, + props<{ redoAction: Action }>(), +); + +export const redo = createAction(`${actionTypePrefix} Redo`); + +export const redone = createAction(`${actionTypePrefix} Redone`); diff --git a/apps/frontend/src/app/undo-redo/undo-redo.effects.ts b/apps/frontend/src/app/undo-redo/undo-redo.effects.ts new file mode 100644 index 00000000..4bafbe85 --- /dev/null +++ b/apps/frontend/src/app/undo-redo/undo-redo.effects.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { select, Store } from '@ngrx/store'; +import { filter, map, switchMap, withLatestFrom } from 'rxjs/operators'; +import * as fromApp from '../app.reducer'; +import * as menuBarContainerActions from '../menu-bar-container/menu-bar-container.actions'; +import { + addVoxel, + removeVoxel, + voxelAdded, + voxelRemoved, +} from '../scene-viewer-container/scene-viewer-container.actions'; +import { addUndo, redo, redone, undo, undone } from './undo-redo.actions'; + +@Injectable() +export class UndoRedoEffects { + constructor(private actions$: Actions, private store: Store) {} + + undo$ = createEffect(() => + this.actions$.pipe( + ofType(undo, menuBarContainerActions.undo), + withLatestFrom(this.store.pipe(select(fromApp.selectCurrentUndoStartAction))), + switchMap(([action, currentUndoAction]) => + currentUndoAction ? [currentUndoAction] : [undone()], + ), + ), + ); + + redo$ = createEffect(() => + this.actions$.pipe( + ofType(redo, menuBarContainerActions.redo), + withLatestFrom(this.store.pipe(select(fromApp.selectCurrentRedoStartAction))), + switchMap(([action, currentRedoAction]) => + currentRedoAction ? [currentRedoAction] : [redone()], + ), + ), + ); + + undoTriggeredActions$ = this.actions$.pipe( + withLatestFrom(this.store.pipe(select(fromApp.selectUndoRedoState))), + filter(([action, state]) => state.isUndoing), + map(([action, state]) => action), + ); + + undone$ = createEffect(() => + this.undoTriggeredActions$.pipe( + withLatestFrom(this.store.pipe(select(fromApp.selectCurrentUndoEndAction))), + filter(([action, undoEndAction]) => action.type === undoEndAction), + map(() => undone()), + ), + ); + + redoTriggeredActions$ = this.actions$.pipe( + withLatestFrom(this.store.pipe(select(fromApp.selectUndoRedoState))), + filter(([action, state]) => state.isRedoing), + map(([action, state]) => action), + ); + + redone$ = createEffect(() => + this.redoTriggeredActions$.pipe( + withLatestFrom(this.store.pipe(select(fromApp.selectCurrentRedoEndAction))), + filter(([action, redoEndAction]) => action.type === redoEndAction), + map(() => redone()), + ), + ); + + userTriggeredActions$ = this.actions$.pipe( + withLatestFrom(this.store.pipe(select(fromApp.selectUndoRedoState))), + filter(([action, state]) => !state.isUndoing && !state.isRedoing), + map(([action, state]) => action), + ); + + addUndoActionForAddVoxel$ = createEffect(() => + this.userTriggeredActions$.pipe( + ofType(voxelAdded), + map(voxelChange => ({ + redoStartAction: addVoxel(voxelChange), + redoEndActionType: voxelAdded.type, + undoStartAction: removeVoxel({ position: voxelChange.position }), + undoEndActionType: voxelRemoved.type, + })), + map(addUndo), + ), + ); + + addUndoActionForRemoveVoxel$ = createEffect(() => + this.userTriggeredActions$.pipe( + ofType(voxelRemoved), + map(voxelChange => ({ + redoStartAction: removeVoxel({ position: voxelChange.position }), + redoEndActionType: voxelRemoved.type, + undoStartAction: addVoxel(voxelChange), + undoEndActionType: voxelAdded.type, + })), + map(addUndo), + ), + ); +} diff --git a/apps/frontend/src/app/undo-redo/undo-redo.module.ts b/apps/frontend/src/app/undo-redo/undo-redo.module.ts new file mode 100644 index 00000000..7dd160d4 --- /dev/null +++ b/apps/frontend/src/app/undo-redo/undo-redo.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from '@angular/core'; +import { EffectsModule } from '@ngrx/effects'; +import { UndoRedoEffects } from './undo-redo.effects'; + +@NgModule({ + imports: [EffectsModule.forFeature([UndoRedoEffects])], +}) +export class UndoRedoModule {} diff --git a/apps/frontend/src/app/undo-redo/undo-redo.reducer.spec.ts b/apps/frontend/src/app/undo-redo/undo-redo.reducer.spec.ts new file mode 100644 index 00000000..61afc316 --- /dev/null +++ b/apps/frontend/src/app/undo-redo/undo-redo.reducer.spec.ts @@ -0,0 +1,199 @@ +import { Action } from '@ngrx/store'; +import { Coord } from '@talus/vdb'; +import { VoxelChange } from '../scene-viewer-container/grid.service'; +import { + addVoxel, + removeVoxel, + voxelAdded, + voxelRemoved, +} from '../scene-viewer-container/scene-viewer-container.actions'; +import { addUndo, redo, redone, undo, undone } from './undo-redo.actions'; +import { + reducer, + selectCurrentRedoEndAction, + selectCurrentRedoStartAction, + selectCurrentUndoEndAction, + selectCurrentUndoStartAction, + State, +} from './undo-redo.reducer'; + +describe('UndoRedoReducer', () => { + const voxelChange0 = createVoxelChange([0, 0, 0]); + const step0 = createStep(voxelChange0); + const voxelChange1 = createVoxelChange([1, 1, 1]); + const step1 = createStep(voxelChange1); + const voxelChange2 = createVoxelChange([2, 2, 2]); + const step2 = createStep(voxelChange2); + + it('should be undoing', () => { + const newState = reducer(undefined, undo()); + + expect(newState.isUndoing).toEqual(true); + }); + + it('should be redoing', () => { + const newState = reducer(undefined, redo()); + + expect(newState.isRedoing).toEqual(true); + }); + + it('should add undo step', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + + expectActionLengthToEqual(newState, 3); + }); + + it('should update current index', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + + expect(newState.currentIndex).toEqual(2); + expectActionLengthToEqual(newState, 3); + }); + + it('should select current undo/redo', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + + expect(selectCurrentUndoStartAction(newState)).toEqual(step2.undoStartAction); + expect(selectCurrentUndoEndAction(newState)).toEqual(step2.undoEndActionType); + expect(selectCurrentRedoStartAction(newState)).toBeUndefined(); + expect(selectCurrentRedoEndAction(newState)).toBeUndefined(); + }); + + it('should select current undo/redo after one undone', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + newState = reducer(newState, undone()); + + expect(newState.currentIndex).toEqual(1); + expectActionLengthToEqual(newState, 3); + + expect(selectCurrentUndoStartAction(newState)).toEqual(step1.undoStartAction); + expect(selectCurrentUndoEndAction(newState)).toEqual(step1.undoEndActionType); + expect(selectCurrentRedoStartAction(newState)).toEqual(step2.redoStartAction); + expect(selectCurrentRedoEndAction(newState)).toEqual(step2.redoEndActionType); + }); + + it('should select current undo/redo after three undone & one redo', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + newState = reducer(newState, redone()); + + expect(newState.currentIndex).toEqual(0); + expectActionLengthToEqual(newState, 3); + + expect(selectCurrentUndoStartAction(newState)).toEqual(step0.undoStartAction); + expect(selectCurrentUndoEndAction(newState)).toEqual(step0.undoEndActionType); + expect(selectCurrentRedoStartAction(newState)).toEqual(step1.redoStartAction); + expect(selectCurrentRedoEndAction(newState)).toEqual(step1.redoEndActionType); + }); + + it('should consider max buffer size of 10', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + newState = reducer(newState, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + newState = reducer(newState, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + newState = reducer(newState, addUndo(step0)); + + newState = reducer(newState, addUndo(step1)); + + expect(newState.currentIndex).toEqual(9); + expectActionLengthToEqual(newState, 10); + }); + + it('should remove history', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step0)); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + newState = reducer(newState, addUndo(step2)); + + expect(newState.currentIndex).toEqual(4); + expectActionLengthToEqual(newState, 5); + }); + + it('should stop decrement index after too many undo', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + + newState = reducer(newState, undone()); + + expect(newState.currentIndex).toEqual(-1); + expectActionLengthToEqual(newState, 3); + }); + + it('should stop increment index after too many redo', () => { + let newState = reducer(undefined, addUndo(step0)); + newState = reducer(newState, addUndo(step1)); + newState = reducer(newState, addUndo(step2)); + newState = reducer(newState, undone()); + newState = reducer(newState, undone()); + newState = reducer(newState, redone()); + newState = reducer(newState, redone()); + + newState = reducer(newState, redone()); + + expect(newState.currentIndex).toEqual(2); + expectActionLengthToEqual(newState, 3); + }); +}); + +function createVoxelChange(xyz: Coord): VoxelChange { + return { + position: xyz, + affectedNodeOrigin: xyz, + value: 42, + }; +} + +function createStep( + voxelChange: VoxelChange, +): { + redoEndActionType: string; + redoStartAction: Action; + undoEndActionType: string; + undoStartAction: Action; +} { + return { + redoStartAction: addVoxel(voxelChange), + redoEndActionType: voxelAdded.type, + undoStartAction: removeVoxel({ position: voxelChange.position }), + undoEndActionType: voxelRemoved.type, + }; +} + +function expectActionLengthToEqual(state: State, length: number): void { + expect(state.redoStartActions.length).toEqual(length); + expect(state.redoEndActionTypes.length).toEqual(length); + expect(state.undoStartActions.length).toEqual(length); + expect(state.undoEndActionTypes.length).toEqual(length); +} diff --git a/apps/frontend/src/app/undo-redo/undo-redo.reducer.ts b/apps/frontend/src/app/undo-redo/undo-redo.reducer.ts new file mode 100644 index 00000000..565251e3 --- /dev/null +++ b/apps/frontend/src/app/undo-redo/undo-redo.reducer.ts @@ -0,0 +1,103 @@ +import { Action, createReducer, on } from '@ngrx/store'; +import * as menuBarContainerActions from '../menu-bar-container/menu-bar-container.actions'; +import { addUndo, redo, redone, undo, undone } from './undo-redo.actions'; + +/** + * Use normal reducer instead of meta-reducer, to have the state in the normal store + * and therefore accessible (e.g. for display in app the number of redo actions). + * Also use normal effects to run/replay/dispatch the action to be undone. + */ + +export const featureKey = 'undoRedo'; + +export interface State { + currentIndex: number; + isRedoing: boolean; + isUndoing: boolean; + redoEndActionTypes: string[]; + redoStartActions: Action[]; + undoEndActionTypes: string[]; + undoStartActions: Action[]; +} + +export const initialState: State = { + currentIndex: -1, + isRedoing: false, + isUndoing: false, + redoEndActionTypes: [], + redoStartActions: [], + undoEndActionTypes: [], + undoStartActions: [], +}; + +const maxBufferSize = 10; +const maxBufferIndex = maxBufferSize - 1; + +export const reducer = createReducer( + initialState, + + on( + addUndo, + (state, { redoStartAction, redoEndActionType, undoStartAction, undoEndActionType }) => { + const newIndex = state.currentIndex + 1; + + const redoStartActions = state.redoStartActions.slice(0, newIndex); + const redoEndActionTypes = state.redoEndActionTypes.slice(0, newIndex); + const undoStartActions = state.undoStartActions.slice(0, newIndex); + const undoEndActionTypes = state.undoEndActionTypes.slice(0, newIndex); + + return { + ...state, + currentIndex: newIndex > maxBufferIndex ? maxBufferIndex : newIndex, + redoStartActions: [...redoStartActions.slice(-maxBufferIndex), redoStartAction], + redoEndActionTypes: [...redoEndActionTypes.slice(-maxBufferIndex), redoEndActionType], + undoStartActions: [...undoStartActions.slice(-maxBufferIndex), undoStartAction], + undoEndActionTypes: [...undoEndActionTypes.slice(-maxBufferIndex), undoEndActionType], + }; + }, + ), + + on(undo, menuBarContainerActions.undo, state => { + return { + ...state, + isUndoing: true, + }; + }), + + on(redo, menuBarContainerActions.redo, state => { + return { + ...state, + isRedoing: true, + }; + }), + + on(undone, state => { + const newIndex = state.currentIndex - 1; + + return { + ...state, + isUndoing: false, + currentIndex: newIndex < 0 ? -1 : newIndex, + }; + }), + + on(redone, state => { + const newIndex = state.currentIndex + 1; + const maxRedoIndex = state.redoStartActions.length - 1; + + return { + ...state, + isRedoing: false, + currentIndex: newIndex > maxRedoIndex ? maxRedoIndex : newIndex, + }; + }), +); + +export const selectCurrentRedoStartAction = (state: State) => + state.redoStartActions[state.currentIndex + 1]; +export const selectCurrentRedoEndAction = (state: State) => + state.redoEndActionTypes[state.currentIndex + 1]; +export const selectCurrentUndoStartAction = (state: State) => + state.undoStartActions[state.currentIndex]; +export const selectCurrentUndoEndAction = (state: State) => + state.undoEndActionTypes[state.currentIndex]; diff --git a/libs/ui/jest.config.js b/libs/ui/jest.config.js index 35d35ffa..b2ed4000 100644 --- a/libs/ui/jest.config.js +++ b/libs/ui/jest.config.js @@ -21,11 +21,11 @@ module.exports = { ], // https://github.com/nrwl/nx/issues/1439#issuecomment-561268656 // When using `Run test` directly in WebStorm, change the used config to - // this file i.e. `./ui/jest.config.js` and not `/jest.config.js`. + // this file i.e. `./ui/jest.config.js` and not `/jest.config.js`. // Otherwise, following error might occur: // - Cannot find module '@talus/ui' // - Zone is needed for the async() test helper but could not be found. - setupFilesAfterEnv: ['./src/test-setup.ts'], + setupFilesAfterEnv: ['/src/test-setup.ts'], // https://github.com/thymikee/jest-preset-angular/issues/293#issuecomment-513544717 // When using `Run test` directly in WebStorm, the scss couldn't be loaded. diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts index 03119d04..5793e9fa 100644 --- a/libs/ui/src/index.ts +++ b/libs/ui/src/index.ts @@ -1,4 +1,5 @@ -export * from './lib/ui.module'; -export * from './lib/sidenav-shell'; +export * from './lib/menu-bar'; export * from './lib/scene-viewer'; +export * from './lib/sidenav-shell'; export * from './lib/toolbar'; +export * from './lib/ui.module'; diff --git a/libs/ui/src/lib/menu-bar/index.ts b/libs/ui/src/lib/menu-bar/index.ts new file mode 100644 index 00000000..3a658a8f --- /dev/null +++ b/libs/ui/src/lib/menu-bar/index.ts @@ -0,0 +1,2 @@ +export * from './menu-bar.component'; +export * from './menu-bar.module'; diff --git a/libs/ui/src/lib/menu-bar/menu-bar.component.scss b/libs/ui/src/lib/menu-bar/menu-bar.component.scss new file mode 100644 index 00000000..22da7177 --- /dev/null +++ b/libs/ui/src/lib/menu-bar/menu-bar.component.scss @@ -0,0 +1,5 @@ +$angular-button-height: 36px; + +mat-toolbar { + height: $angular-button-height; +} diff --git a/libs/ui/src/lib/menu-bar/menu-bar.component.spec.ts b/libs/ui/src/lib/menu-bar/menu-bar.component.spec.ts new file mode 100644 index 00000000..7f709fc2 --- /dev/null +++ b/libs/ui/src/lib/menu-bar/menu-bar.component.spec.ts @@ -0,0 +1,112 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { UiMenuBarModule } from '@talus/ui'; +import { UiMenuBarComponent } from './menu-bar.component'; + +describe('UiMenuBarComponent', () => { + let component: UiMenuBarComponent; + let fixture: ComponentFixture; + + const expectedMenus = [ + { + label: 'Menu 1', + menuItems: [ + { + label: 'Item 1.1', + icon: 'undo', + value: 'Value 1.1', + }, + ], + }, + { + label: 'Menu 2', + menuItems: [ + { + label: 'Item 2.1', + icon: 'undo', + value: 'Value 2.1', + }, + { + label: 'Item 2.2', + icon: 'undo', + value: 'Value 2.2', + }, + ], + }, + ]; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [UiMenuBarModule, BrowserAnimationsModule], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UiMenuBarComponent); + component = fixture.componentInstance; + // Don't call `detectChanges()`, because of `OnPush` + // See: https://github.com/angular/angular/issues/12313#issuecomment-301848232 + // fixture.detectChanges(); + }); + + beforeEach(() => { + spyOn(component.menuItemClick, 'emit'); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should add all menus', () => { + component.menuConfig = { + menus: expectedMenus, + }; + fixture.detectChanges(); + + const menus = fixture.debugElement.queryAll(By.css('mat-menu')); + const menuButtons = fixture.debugElement.queryAll(By.css('button')); + + expect(menus.length).toEqual(expectedMenus.length); + expect(menuButtons.length).toEqual(expectedMenus.length); + expect(menuButtons[0].nativeElement.textContent).toContain(expectedMenus[0].label); + expect(menuButtons[1].nativeElement.textContent).toContain(expectedMenus[1].label); + }); + + it('should add all menu-items for second menu', () => { + component.menuConfig = { + menus: expectedMenus, + }; + fixture.detectChanges(); + + const menuButtons = fixture.debugElement.queryAll(By.css('button')); + menuButtons[1].nativeElement.click(); + fixture.detectChanges(); + + const menuItemButtons = fixture.debugElement.queryAll(By.css('button.mat-menu-item > span')); + expect(menuItemButtons.length).toEqual(expectedMenus[1].menuItems.length); + + menuItemButtons.forEach((menuItemButton, j) => { + expect(menuItemButton.nativeElement.textContent).toEqual(expectedMenus[1].menuItems[j].label); + }); + }); + + it('should emit value of clicked menu items', () => { + component.menuConfig = { + menus: expectedMenus, + }; + fixture.detectChanges(); + + const menuButtons = fixture.debugElement.queryAll(By.css('button')); + menuButtons[1].nativeElement.click(); + fixture.detectChanges(); + + const menuItemButtons = fixture.debugElement.queryAll(By.css('button.mat-menu-item > span')); + + menuItemButtons[0].nativeElement.click(); + expect(component.menuItemClick.emit).toBeCalledWith('Value 2.1'); + + menuItemButtons[1].nativeElement.click(); + expect(component.menuItemClick.emit).toBeCalledWith('Value 2.2'); + }); +}); diff --git a/libs/ui/src/lib/menu-bar/menu-bar.component.ts b/libs/ui/src/lib/menu-bar/menu-bar.component.ts new file mode 100644 index 00000000..010b1e28 --- /dev/null +++ b/libs/ui/src/lib/menu-bar/menu-bar.component.ts @@ -0,0 +1,48 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; + +export interface UiMenuBarConfig { + menus: UiMenuBarMenu[]; +} + +interface UiMenuBarMenu { + label: string; + menuItems: UiMenuBarMenuItem[]; +} + +interface UiMenuBarMenuItem { + icon?: string; + label: string; + value: T; +} + +@Component({ + selector: 'ui-menu-bar', + template: ` + + + + + + + + + `, + styleUrls: ['./menu-bar.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UiMenuBarComponent { + @Input() menuConfig: UiMenuBarConfig = { menus: [] }; + + @Output() menuItemClick = new EventEmitter(); + + onMenuItemClick(value: any): void { + this.menuItemClick.emit(value); + } +} diff --git a/libs/ui/src/lib/menu-bar/menu-bar.module.ts b/libs/ui/src/lib/menu-bar/menu-bar.module.ts new file mode 100644 index 00000000..cff9cb92 --- /dev/null +++ b/libs/ui/src/lib/menu-bar/menu-bar.module.ts @@ -0,0 +1,11 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { MatButtonModule, MatIconModule, MatMenuModule, MatToolbarModule } from '@angular/material'; +import { UiMenuBarComponent } from './menu-bar.component'; + +@NgModule({ + declarations: [UiMenuBarComponent], + imports: [CommonModule, MatButtonModule, MatIconModule, MatMenuModule, MatToolbarModule], + exports: [UiMenuBarComponent], +}) +export class UiMenuBarModule {} diff --git a/libs/ui/src/lib/scene-viewer/index.ts b/libs/ui/src/lib/scene-viewer/index.ts index 14b12407..7dfea1ff 100644 --- a/libs/ui/src/lib/scene-viewer/index.ts +++ b/libs/ui/src/lib/scene-viewer/index.ts @@ -1,5 +1,5 @@ export * from './scene-viewer.component'; export * from './scene-viewer.module'; export * from './scene-viewer.module.testing'; -export { PointerButton } from './pointer-button'; -export { PointerPickInfo, SceneViewerService } from './scene-viewer.service'; +export { UiPointerButton } from './pointer-button'; +export { UiPointerPickInfo, UiSceneViewerService } from './scene-viewer.service'; diff --git a/libs/ui/src/lib/scene-viewer/pointer-button.ts b/libs/ui/src/lib/scene-viewer/pointer-button.ts index 39afd4c3..f5ca1d33 100644 --- a/libs/ui/src/lib/scene-viewer/pointer-button.ts +++ b/libs/ui/src/lib/scene-viewer/pointer-button.ts @@ -1,7 +1,7 @@ /** * https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button#Syntax */ -export enum PointerButton { +export enum UiPointerButton { Main = 0, // usually the left button or the un-initialized state Auxiliary = 1, // usually the wheel button or the middle button (if present) Secondary = 2, // usually the right button diff --git a/libs/ui/src/lib/scene-viewer/scene-viewer.component.scss b/libs/ui/src/lib/scene-viewer/scene-viewer.component.scss index 519ffd16..5a6bfab9 100644 --- a/libs/ui/src/lib/scene-viewer/scene-viewer.component.scss +++ b/libs/ui/src/lib/scene-viewer/scene-viewer.component.scss @@ -1,6 +1,4 @@ -:host, canvas { - display: block; - width: 100%; height: 100%; + width: 100%; } diff --git a/libs/ui/src/lib/scene-viewer/scene-viewer.component.spec.ts b/libs/ui/src/lib/scene-viewer/scene-viewer.component.spec.ts index b9ae1951..abc1db3f 100644 --- a/libs/ui/src/lib/scene-viewer/scene-viewer.component.spec.ts +++ b/libs/ui/src/lib/scene-viewer/scene-viewer.component.spec.ts @@ -1,20 +1,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { SceneViewerComponent } from './scene-viewer.component'; -import { SceneViewerTestModule } from './scene-viewer.module.testing'; +import { UiSceneViewerComponent } from './scene-viewer.component'; +import { UiSceneViewerTestModule } from './scene-viewer.module.testing'; -describe('SceneViewerComponent', () => { - let component: SceneViewerComponent; - let fixture: ComponentFixture; +describe('UiSceneViewerComponent', () => { + let component: UiSceneViewerComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [SceneViewerComponent], - imports: [SceneViewerTestModule], + declarations: [UiSceneViewerComponent], + imports: [UiSceneViewerTestModule], }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(SceneViewerComponent); + fixture = TestBed.createComponent(UiSceneViewerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/libs/ui/src/lib/scene-viewer/scene-viewer.component.ts b/libs/ui/src/lib/scene-viewer/scene-viewer.component.ts index 6c5538ae..58e40fcd 100644 --- a/libs/ui/src/lib/scene-viewer/scene-viewer.component.ts +++ b/libs/ui/src/lib/scene-viewer/scene-viewer.component.ts @@ -2,35 +2,94 @@ import { ChangeDetectionStrategy, Component, ElementRef, + EventEmitter, HostListener, OnInit, Output, ViewChild, } from '@angular/core'; -import { SceneViewerService } from './scene-viewer.service'; +import { UiSceneViewerService } from './scene-viewer.service'; @Component({ selector: 'ui-scene-viewer', template: ` - + `, styleUrls: ['./scene-viewer.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SceneViewerComponent implements OnInit { +export class UiSceneViewerComponent implements OnInit { + constructor(private sceneViewerService: UiSceneViewerService) {} + @ViewChild('canvas', { static: true }) canvas: ElementRef; @Output() pointerPick = this.sceneViewerService.pointerPick$; - constructor(private sceneViewerService: SceneViewerService) {} + @Output() dropFiles = new EventEmitter(); + + ngOnInit(): void { + this.sceneViewerService.initialize(this.canvas.nativeElement); + this.sceneViewerService.startRendering(); + } @HostListener('window:resize') onWindowsResize(): void { this.sceneViewerService.resizeView(); } - ngOnInit(): void { - this.sceneViewerService.initialize(this.canvas.nativeElement); - this.sceneViewerService.startRendering(); + onDragOver(event: DragEvent): void { + // Prevent file from being opened + event.preventDefault(); + } + + onDrop(event: DragEvent): void { + // Prevent file from being opened + event.preventDefault(); + + this.dropFiles.emit(this.getFiles(event)); + } + + // Source: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop + private getFiles(event: DragEvent): File[] { + return event.dataTransfer && event.dataTransfer.items + ? this.getFilesFromDataTransferItemList(event) + : this.getFilesFromDataTransfer(event); + } + + private getFilesFromDataTransferItemList(event: DragEvent): File[] { + const files: File[] = []; + + if (!event.dataTransfer) { + return files; + } + + // Use DataTransferItemList interface to access the file(s) + for (let i = 0; i < event.dataTransfer.items.length; i++) { + // If dropped items aren't files, reject them + if (event.dataTransfer.items[i].kind === 'file') { + const file = event.dataTransfer.items[i].getAsFile(); + + if (file) { + files.push(file); + } + } + } + + return files; + } + + private getFilesFromDataTransfer(event: DragEvent): File[] { + const files: File[] = []; + + if (!event.dataTransfer) { + return files; + } + + // Use DataTransfer interface to access the file(s) + for (let i = 0; i < event.dataTransfer.files.length; i++) { + files.push(event.dataTransfer.files[i]); + } + + return files; } } diff --git a/libs/ui/src/lib/scene-viewer/scene-viewer.module.testing.ts b/libs/ui/src/lib/scene-viewer/scene-viewer.module.testing.ts index e5b28056..10527d82 100644 --- a/libs/ui/src/lib/scene-viewer/scene-viewer.module.testing.ts +++ b/libs/ui/src/lib/scene-viewer/scene-viewer.module.testing.ts @@ -4,7 +4,7 @@ import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; import { NullEngine } from '@babylonjs/core/Engines/nullEngine'; import { Vector3 } from '@babylonjs/core/Maths/math.vector'; import { Scene } from '@babylonjs/core/scene'; -import { CameraFactory, EngineFactory, SceneViewerService } from './scene-viewer.service'; +import { CameraFactory, EngineFactory, UiSceneViewerService } from './scene-viewer.service'; function testCameraFactory(): any { return { @@ -47,16 +47,16 @@ function testEngineFactor(): any { template: ``, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SceneViewerStubComponent {} +export class UiSceneViewerStubComponent {} @NgModule({ - declarations: [SceneViewerStubComponent], - exports: [SceneViewerStubComponent], + declarations: [UiSceneViewerStubComponent], + exports: [UiSceneViewerStubComponent], imports: [CommonModule], providers: [ { provide: CameraFactory, useValue: testCameraFactory() }, { provide: EngineFactory, useValue: testEngineFactor() }, - SceneViewerService, + UiSceneViewerService, ], }) -export class SceneViewerTestModule {} +export class UiSceneViewerTestModule {} diff --git a/libs/ui/src/lib/scene-viewer/scene-viewer.module.ts b/libs/ui/src/lib/scene-viewer/scene-viewer.module.ts index c569b163..cf093c83 100644 --- a/libs/ui/src/lib/scene-viewer/scene-viewer.module.ts +++ b/libs/ui/src/lib/scene-viewer/scene-viewer.module.ts @@ -1,12 +1,12 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { SceneViewerComponent } from './scene-viewer.component'; -import { CameraFactory, EngineFactory, SceneViewerService } from './scene-viewer.service'; +import { UiSceneViewerComponent } from './scene-viewer.component'; +import { CameraFactory, EngineFactory, UiSceneViewerService } from './scene-viewer.service'; @NgModule({ - declarations: [SceneViewerComponent], + declarations: [UiSceneViewerComponent], imports: [CommonModule], - exports: [SceneViewerComponent], - providers: [CameraFactory, EngineFactory, SceneViewerService], + exports: [UiSceneViewerComponent], + providers: [CameraFactory, EngineFactory, UiSceneViewerService], }) -export class SceneViewerModule {} +export class UiSceneViewerModule {} diff --git a/libs/ui/src/lib/scene-viewer/scene-viewer.service.ts b/libs/ui/src/lib/scene-viewer/scene-viewer.service.ts index 1ffeb152..5dfeb749 100644 --- a/libs/ui/src/lib/scene-viewer/scene-viewer.service.ts +++ b/libs/ui/src/lib/scene-viewer/scene-viewer.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { AbstractMesh } from '@babylonjs/core'; // Babylon.js needs to target individual files to fully benefit from tree shaking. // See: https://doc.babylonjs.com/features/es6_support#tree-shaking import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; @@ -7,14 +8,15 @@ import { Engine } from '@babylonjs/core/Engines/engine'; import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight'; import '@babylonjs/core/Materials/standardMaterial'; import { Vector3 } from '@babylonjs/core/Maths/math.vector'; +import { VertexBuffer } from '@babylonjs/core/Meshes/buffer'; import { Mesh } from '@babylonjs/core/Meshes/mesh'; import { VertexData } from '@babylonjs/core/Meshes/mesh.vertexData'; -import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder'; +import { TransformNode } from '@babylonjs/core/Meshes/transformNode'; import '@babylonjs/core/Physics/physicsHelper'; // Needed for `onPointerPick` import { Scene } from '@babylonjs/core/scene'; import { Coord, MeshData } from '@talus/vdb'; import { Subject } from 'rxjs'; -import { PointerButton } from './pointer-button'; +import { UiPointerButton } from './pointer-button'; @Injectable() export class EngineFactory { @@ -51,13 +53,12 @@ export class CameraFactory { * provided on module level. Therefore, only one `SceneViewerComponent` at the time is supported. */ @Injectable() -export class SceneViewerService { - scene: Scene; - gridMesh: Mesh; - - pointerPick$ = new Subject(); +export class UiSceneViewerService { + pointerPick$ = new Subject(); private engine: Engine; + private scene: Scene; + private gridNode: TransformNode; // @ts-ignore: noUnusedLocals private light: HemisphericLight; @@ -65,17 +66,11 @@ export class SceneViewerService { initialize(canvas: HTMLCanvasElement): void { this.engine = this.engineFactory.create(canvas); - this.scene = new Scene(this.engine); - + this.createScene(); this.createCamera(); this.createLight(); this.registerPointerPick(); - - const box = MeshBuilder.CreateBox('box', {}, this.scene); - box.position.x = 0.5; - box.position.y = 0.5; - box.position.z = 0.5; } startRendering(): void { @@ -86,22 +81,43 @@ export class SceneViewerService { this.engine.resize(); } - updateGridMesh(mesh?: MeshData): void { - this.scene.removeMesh(this.gridMesh); + updateNodeMesh(mesh: MeshData | undefined, origin: Coord): void { + const meshName = `node1 [${origin}]`; + + this.deleteMesh(meshName); if (mesh) { - this.gridMesh = new Mesh('grid', this.scene); const data = new VertexData(); + const nodeMesh = new Mesh(meshName, this.scene, this.gridNode); + // https://www.html5gamedevs.com/topic/31617-mesh-without-indices/?tab=comments#comment-181659 + // https://doc.babylonjs.com/how_to/optimizing_your_scene#using-unindexed-meshes + nodeMesh._unIndexed = true; data.colors = mesh.colors; - data.indices = mesh.indices; + data.normals = mesh.normals; data.positions = mesh.positions; - data.applyToMesh(this.gridMesh); - this.gridMesh.convertToFlatShadedMesh(); + data.applyToMesh(nodeMesh); + + // https://doc.babylonjs.com/how_to/optimizing_your_scene + // https://www.html5gamedevs.com/topic/12504-performancedraw-calls/ + nodeMesh.freezeNormals(); + nodeMesh.freezeWorldMatrix(); } } + private createScene(): void { + // https://doc.babylonjs.com/how_to/optimizing_your_scene + this.scene = new Scene(this.engine, { + useGeometryUniqueIdsMap: true, + useClonedMeshMap: true, + }); + this.scene.freezeMaterials(); + + // Used only as parent to have all nodes grouped together + this.gridNode = new TransformNode('grid', this.scene); + } + private createCamera(): void { const camera: ArcRotateCamera = this.cameraFactory.create( 'camera', @@ -113,12 +129,16 @@ export class SceneViewerService { ); camera.inertia = 0; camera.panningInertia = 0; + camera.wheelPrecision = 1.0; - camera.panningSensibility = 20; + camera.panningSensibility = 10; camera.angularSensibilityX = 200; camera.angularSensibilityY = 100; - camera.attachControl(this.engine.getRenderingCanvas(), true, false, 2); + const renderingCanvas = this.engine.getRenderingCanvas(); + if (renderingCanvas) { + camera.attachControl(renderingCanvas, true, false, 2); + } camera.setPosition(new Vector3(20, 20, -20)); } @@ -128,23 +148,51 @@ export class SceneViewerService { private registerPointerPick(): void { this.scene.onPointerPick = (event: PointerEvent, pickInfo: PickingInfo): void => { - const info: PointerPickInfo = { - pickedPoint: vector3ToCoord(pickInfo.pickedPoint), - pointerButton: event.button, - normal: vector3ToCoord(pickInfo.getNormal()), - }; - - this.pointerPick$.next(info); + if (pickInfo.pickedMesh && pickInfo.pickedPoint) { + const info: UiPointerPickInfo = { + pickedPoint: vector3ToCoord(pickInfo.pickedPoint), + pointerButton: event.button, + normal: this.getNormal(pickInfo.pickedMesh, pickInfo.faceId), + }; + + this.pointerPick$.next(info); + } }; } + + /** + * PickingInfo.getNormal() requires to have indices which are not available + * for an un-indexed custom mesh. Therefore, read normals directly from picked mesh. + * + * https://github.com/BabylonJS/Babylon.js/blob/master/src/Collisions/pickingInfo.ts#L65 + */ + private getNormal(pickedMesh: AbstractMesh, faceId: number): Coord { + const normals = pickedMesh.getVerticesData(VertexBuffer.NormalKind); + + if (!normals) { + throw new Error('Could not get normals from picked mesh.'); + } + + const faceIndex = faceId * 9; + + return [normals[faceIndex], normals[faceIndex + 1], normals[faceIndex + 2]]; + } + + private deleteMesh(name: string): void { + const oldMesh = this.scene.getMeshByName(name); + + if (oldMesh) { + oldMesh.dispose(); + } + } } function vector3ToCoord(vector: Vector3): Coord { return [vector.x, vector.y, vector.z]; } -export interface PointerPickInfo { +export interface UiPointerPickInfo { pickedPoint: Coord; - pointerButton: PointerButton; + pointerButton: UiPointerButton; normal: Coord; } diff --git a/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.scss b/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.scss index dbc3fea8..62b77cef 100644 --- a/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.scss +++ b/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.scss @@ -1,6 +1,5 @@ :host, mat-sidenav-container { - display: block; height: 100%; } diff --git a/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.spec.ts b/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.spec.ts index 3d684bf3..464b0862 100644 --- a/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.spec.ts +++ b/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.spec.ts @@ -1,21 +1,23 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatSidenav } from '@angular/material'; +import { By } from '@angular/platform-browser'; -import { SidenavShellComponent } from './sidenav-shell.component'; -import { SidenavShellModule } from './sidenav-shell.module'; +import { UiSidenavShellComponent } from './sidenav-shell.component'; +import { UiSidenavShellModule } from './sidenav-shell.module'; describe('SidenavShellComponent', () => { - let component: SidenavShellComponent; - let fixture: ComponentFixture; + let component: UiSidenavShellComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [SidenavShellModule], + imports: [UiSidenavShellModule], declarations: [], }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(SidenavShellComponent); + fixture = TestBed.createComponent(UiSidenavShellComponent); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -23,4 +25,32 @@ describe('SidenavShellComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should have open left & right sidenav', () => { + const sidenavs = fixture.debugElement.queryAll(By.directive(MatSidenav)); + const leftSidenavIcon = fixture.debugElement.query(By.css('#left-sidenav-button mat-icon')); + const rightSidenavIcon = fixture.debugElement.query(By.css('#right-sidenav-button mat-icon')); + + expect(sidenavs.length).toEqual(2); + expect(leftSidenavIcon.nativeElement.textContent).toEqual('keyboard_arrow_left'); + expect(rightSidenavIcon.nativeElement.textContent).toEqual('keyboard_arrow_right'); + }); + + it('should close left & right sidenav', () => { + // Close left sidenav + const leftSidenavButton = fixture.debugElement.query(By.css('#left-sidenav-button')); + leftSidenavButton.nativeElement.click(); + fixture.detectChanges(); + + const leftIconClosed = fixture.debugElement.query(By.css('#left-sidenav-button mat-icon')); + expect(leftIconClosed.nativeElement.textContent).toEqual('keyboard_arrow_right'); + + // Close right sidenav + const rightSidenavButton = fixture.debugElement.query(By.css('#right-sidenav-button')); + rightSidenavButton.nativeElement.click(); + fixture.detectChanges(); + + const rightIconClosed = fixture.debugElement.query(By.css('#right-sidenav-button mat-icon')); + expect(rightIconClosed.nativeElement.textContent).toEqual('keyboard_arrow_left'); + }); }); diff --git a/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.ts b/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.ts index bc22de2f..d4087cfa 100644 --- a/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.ts +++ b/libs/ui/src/lib/sidenav-shell/sidenav-shell.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'ui-sidenav-shell-content', @@ -7,7 +7,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SidenavShellContentComponent {} +export class UiSidenavShellContentComponent {} @Component({ selector: 'ui-sidenav-shell-left', @@ -16,7 +16,7 @@ export class SidenavShellContentComponent {} `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SidenavShellLeftComponent {} +export class UiSidenavShellLeftComponent {} @Component({ selector: 'ui-sidenav-shell-right', @@ -25,7 +25,7 @@ export class SidenavShellLeftComponent {} `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SidenavShellRightComponent {} +export class UiSidenavShellRightComponent {} @Component({ selector: 'ui-sidenav-shell', @@ -57,8 +57,4 @@ export class SidenavShellRightComponent {} styleUrls: ['./sidenav-shell.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SidenavShellComponent implements OnInit { - constructor() {} - - public ngOnInit(): void {} -} +export class UiSidenavShellComponent {} diff --git a/libs/ui/src/lib/sidenav-shell/sidenav-shell.module.ts b/libs/ui/src/lib/sidenav-shell/sidenav-shell.module.ts index 0486da5c..50a11df6 100644 --- a/libs/ui/src/lib/sidenav-shell/sidenav-shell.module.ts +++ b/libs/ui/src/lib/sidenav-shell/sidenav-shell.module.ts @@ -3,18 +3,18 @@ import { NgModule } from '@angular/core'; import { MatButtonModule, MatIconModule, MatSidenavModule } from '@angular/material'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { - SidenavShellComponent, - SidenavShellContentComponent, - SidenavShellLeftComponent, - SidenavShellRightComponent, + UiSidenavShellComponent, + UiSidenavShellContentComponent, + UiSidenavShellLeftComponent, + UiSidenavShellRightComponent, } from './sidenav-shell.component'; @NgModule({ declarations: [ - SidenavShellComponent, - SidenavShellContentComponent, - SidenavShellLeftComponent, - SidenavShellRightComponent, + UiSidenavShellComponent, + UiSidenavShellContentComponent, + UiSidenavShellLeftComponent, + UiSidenavShellRightComponent, ], imports: [ BrowserAnimationsModule, @@ -24,10 +24,10 @@ import { MatSidenavModule, ], exports: [ - SidenavShellComponent, - SidenavShellContentComponent, - SidenavShellLeftComponent, - SidenavShellRightComponent, + UiSidenavShellComponent, + UiSidenavShellContentComponent, + UiSidenavShellLeftComponent, + UiSidenavShellRightComponent, ], }) -export class SidenavShellModule {} +export class UiSidenavShellModule {} diff --git a/libs/ui/src/lib/toolbar/toolbar.component.spec.ts b/libs/ui/src/lib/toolbar/toolbar.component.spec.ts index 3d0b3276..c605ef2a 100644 --- a/libs/ui/src/lib/toolbar/toolbar.component.spec.ts +++ b/libs/ui/src/lib/toolbar/toolbar.component.spec.ts @@ -1,24 +1,55 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ToolbarComponent } from './toolbar.component'; -import { ToolbarModule } from './toolbar.module'; +import { By } from '@angular/platform-browser'; +import { UiToolbarComponent, UiToolbarToolConfig } from './toolbar.component'; +import { UiToolbarModule } from './toolbar.module'; describe('ToolbarComponent', () => { - let component: ToolbarComponent; - let fixture: ComponentFixture; + let component: UiToolbarComponent; + let fixture: ComponentFixture; + + const tools: UiToolbarToolConfig[] = [ + { + icon: 'add_circle_outline', + tooltip: 'Add', + value: 'Add test value', + }, + { + icon: 'remove_circle_outline', + tooltip: 'Remove', + value: 'Remove test value', + }, + ]; beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [ToolbarModule], + imports: [UiToolbarModule], }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(ToolbarComponent); + fixture = TestBed.createComponent(UiToolbarComponent); component = fixture.componentInstance; - fixture.detectChanges(); + // fixture.detectChanges(); + }); + + beforeEach(() => { + spyOn(component.toolChange, 'emit'); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should emit tool change', () => { + component.tools = tools; + fixture.detectChanges(); + + const buttonToggles = fixture.debugElement.queryAll(By.css('button.mat-button-toggle-button')); + expect(buttonToggles.length).toEqual(tools.length); + + buttonToggles[0].nativeElement.click(); + fixture.detectChanges(); + + expect(component.toolChange.emit).toHaveBeenCalled(); + }); }); diff --git a/libs/ui/src/lib/toolbar/toolbar.component.ts b/libs/ui/src/lib/toolbar/toolbar.component.ts index 68e2f341..d18e85c9 100644 --- a/libs/ui/src/lib/toolbar/toolbar.component.ts +++ b/libs/ui/src/lib/toolbar/toolbar.component.ts @@ -28,7 +28,7 @@ export interface UiToolbarToolChange extends MatButtonToggleChange { `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ToolbarComponent { +export class UiToolbarComponent { @Input() tools: UiToolbarToolConfig[]; @Input() selectedToolId: any; diff --git a/libs/ui/src/lib/toolbar/toolbar.module.ts b/libs/ui/src/lib/toolbar/toolbar.module.ts index 6e77b28c..5e5bbd0f 100644 --- a/libs/ui/src/lib/toolbar/toolbar.module.ts +++ b/libs/ui/src/lib/toolbar/toolbar.module.ts @@ -1,11 +1,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { MatButtonToggleModule, MatIconModule, MatTooltipModule } from '@angular/material'; -import { ToolbarComponent } from './toolbar.component'; +import { UiToolbarComponent } from './toolbar.component'; @NgModule({ - declarations: [ToolbarComponent], + declarations: [UiToolbarComponent], imports: [CommonModule, MatButtonToggleModule, MatIconModule, MatTooltipModule], - exports: [ToolbarComponent], + exports: [UiToolbarComponent], }) -export class ToolbarModule {} +export class UiToolbarModule {} diff --git a/libs/ui/src/test-setup.ts b/libs/ui/src/test-setup.ts index 8d88704e..26ec1bb0 100644 --- a/libs/ui/src/test-setup.ts +++ b/libs/ui/src/test-setup.ts @@ -1 +1,2 @@ +import 'hammerjs'; import 'jest-preset-angular'; diff --git a/libs/ui/tsconfig.spec.json b/libs/ui/tsconfig.spec.json index 61c376d5..29121926 100644 --- a/libs/ui/tsconfig.spec.json +++ b/libs/ui/tsconfig.spec.json @@ -4,6 +4,6 @@ "outDir": "../../dist/out-tsc", "types": ["jest", "node"] }, - "files": ["src/test-setup.ts"], + "files": ["./src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } diff --git a/libs/vdb/README.md b/libs/vdb/README.md index bc063f73..76cc2814 100644 --- a/libs/vdb/README.md +++ b/libs/vdb/README.md @@ -17,6 +17,7 @@ When manipulating data in OpenVDB, the three essential objects are ### Sources +- [Repo](https://github.com/AcademySoftwareFoundation/openvdb) - [Overview](https://www.openvdb.org/documentation/doxygen/overview.html) - [Cookbook](https://www.openvdb.org/documentation/doxygen/codeExamples.html) diff --git a/libs/vdb/src/lib/grid.spec.ts b/libs/vdb/src/lib/grid.spec.ts new file mode 100644 index 00000000..262a0804 --- /dev/null +++ b/libs/vdb/src/lib/grid.spec.ts @@ -0,0 +1,26 @@ +import { Grid } from '@talus/vdb'; + +describe('Grid', () => { + it('should get background', () => { + const grid = new Grid(-1); + + expect(grid.background).toEqual(-1); + }); + + it('should iterate over each activated voxel', () => { + const grid = new Grid(-1); + const accessor = grid.getAccessor(); + + accessor.setValueOn([2, 1, 0], 42); + accessor.setValueOn([845, 64, 242], 42); + accessor.setValueOn([1000, 200000, 4000], 42); + + let counter = 0; + for (const voxel of grid.beginVoxelOn()) { + counter++; + expect(voxel.value).toEqual(42); + } + + expect(counter).toEqual(3); + }); +}); diff --git a/libs/vdb/src/lib/math/coord.spec.ts b/libs/vdb/src/lib/math/coord.spec.ts new file mode 100644 index 00000000..a1b365d2 --- /dev/null +++ b/libs/vdb/src/lib/math/coord.spec.ts @@ -0,0 +1,28 @@ +import { add, areEqual, clone, Coord, createMinCoord } from './coord'; + +describe('Coord', () => { + it('should add coordinates', () => { + const c1: Coord = [1, 2, 3]; + const c2: Coord = [3, 2, 1]; + + expect(add(c1, c2)).toEqual([4, 4, 4]); + }); + + it('should create minimal coordinate', () => { + expect(createMinCoord()).toEqual([5e-324, 5e-324, 5e-324]); + }); + + it('should consider coordinates equal', () => { + const c1: Coord = [1, 2, 3]; + const c2: Coord = [1, 2, 3]; + + expect(areEqual(c1, c2)).toBeTruthy(); + }); + + it('should create clone', () => { + const c1: Coord = [1, 2, 3]; + + expect(clone(c1)).toEqual(c1); + expect(clone(c1)).not.toBe(c1); + }); +}); diff --git a/libs/vdb/src/lib/math/coord.ts b/libs/vdb/src/lib/math/coord.ts index 0001351f..f0ec4069 100644 --- a/libs/vdb/src/lib/math/coord.ts +++ b/libs/vdb/src/lib/math/coord.ts @@ -24,3 +24,11 @@ export function createMaxCoord(): Coord { export function createMinCoord(): Coord { return [Number.MIN_VALUE, Number.MIN_VALUE, Number.MIN_VALUE]; } + +export function areEqual(c1: Coord, c2: Coord): boolean { + return c1[0] === c2[0] && c1[1] === c2[1] && c1[2] === c2[2]; +} + +export function clone(c: Coord): Coord { + return [c[0], c[1], c[2]]; +} diff --git a/libs/vdb/src/lib/tools/grid-to-mesh.spec.ts b/libs/vdb/src/lib/tools/grid-to-mesh.spec.ts deleted file mode 100644 index ab6a602f..00000000 --- a/libs/vdb/src/lib/tools/grid-to-mesh.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Grid } from '../grid'; -import { gridToMesh } from './grid-to-mesh'; - -describe('gridToMesh()', () => { - it('should generate the mesh', () => { - const grid = new Grid(0); - const accessor = grid.getAccessor(); - - accessor.setValue([0, 0, 0], 1); - accessor.setValue([0, 0, 1], 1); - - const meshData = gridToMesh(grid); - - const voxels = 2; - - const corners = 8; - const triangles = 12; - - const xyz = 3; - const rgba = 4; - - expect(meshData.positions.length).toEqual(voxels * corners * xyz); - expect(meshData.colors.length).toEqual(voxels * corners * rgba); - expect(meshData.indices.length).toEqual(voxels * triangles * xyz); - }); -}); diff --git a/libs/vdb/src/lib/tools/grid-to-mesh.ts b/libs/vdb/src/lib/tools/grid-to-mesh.ts deleted file mode 100644 index fe5218ec..00000000 --- a/libs/vdb/src/lib/tools/grid-to-mesh.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Babylon.js has a left-handed coordinate system with Y pointing up. - * Furthermore, the front face of a facet is considered the face where the facet’s vertices - * are positioned counterclockwise. - * - * Position of a facet is the location of the facet’s barycenter (a.k.a. centroid). - * - * 6-------7 - * /| /| - * / | / | - * Y 3--|----2 | - * | Z | 5----|--4 - * | / | / | / - * 0--- X 0-------1 - */ - -import { Grid } from '../grid'; - -export interface MeshData { - colors: number[]; - indices: number[]; - // normals: number[]; - positions: number[]; -} - -/** - * Returns a mesh if there are any active voxels saved in the grid. - * Otherwise, returns `undefined` i.e. if there are no active voxels. - */ -export function gridToMesh(grid: Grid): MeshData | undefined { - const mesh: MeshData = { - colors: [], - indices: [], - positions: [], - // normals: [], - }; - - let vertexCount = 0; - for (const voxel of grid.beginVoxelOn()) { - const [x, y, z] = voxel.globalCoord; - - mesh.indices.push(...[5, 0, 3, 3, 6, 5].map(i => i + vertexCount)); // Left - mesh.indices.push(...[1, 4, 7, 7, 2, 1].map(i => i + vertexCount)); // Right - - mesh.indices.push(...[5, 4, 1, 1, 0, 5].map(i => i + vertexCount)); // Bottom - mesh.indices.push(...[7, 6, 3, 3, 2, 7].map(i => i + vertexCount)); // Top - - mesh.indices.push(...[0, 1, 2, 2, 3, 0].map(i => i + vertexCount)); // Front - mesh.indices.push(...[4, 5, 6, 6, 7, 4].map(i => i + vertexCount)); // Back - - // mesh.normals.push(-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0); // Left - // mesh.normals.push(1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0); // Right - - // mesh.normals.push(0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0); // Bottom - // mesh.normals.push(0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0); // Top - - // mesh.normals.push(0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1); // Front - // mesh.normals.push(0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1); // Back - - mesh.positions.push(x, y, z); // 0 - mesh.positions.push(x + 1, y, z); // 1 - mesh.positions.push(x + 1, y + 1, z); // 2 - mesh.positions.push(x, y + 1, z); // 3 - - mesh.positions.push(x + 1, y, z + 1); // 4 - mesh.positions.push(x, y, z + 1); // 5 - mesh.positions.push(x, y + 1, z + 1); // 6 - mesh.positions.push(x + 1, y + 1, z + 1); // 7 - - vertexCount += 8; - - mesh.colors.push(0, 1, 0, 1); - mesh.colors.push(0, 1, 0, 1); - mesh.colors.push(0, 1, 0, 1); - mesh.colors.push(0, 1, 0, 1); - - mesh.colors.push(0, 1, 0, 1); - mesh.colors.push(0, 1, 0, 1); - mesh.colors.push(0, 1, 0, 1); - mesh.colors.push(0, 1, 0, 1); - } - - return vertexCount !== 0 ? mesh : undefined; -} diff --git a/libs/vdb/src/lib/tools/index.ts b/libs/vdb/src/lib/tools/index.ts index e2f1c696..479b8e5c 100644 --- a/libs/vdb/src/lib/tools/index.ts +++ b/libs/vdb/src/lib/tools/index.ts @@ -1 +1 @@ -export * from './grid-to-mesh'; +export * from './node-to-mesh'; diff --git a/libs/vdb/src/lib/tools/node-to-mesh.spec.ts b/libs/vdb/src/lib/tools/node-to-mesh.spec.ts new file mode 100644 index 00000000..00337913 --- /dev/null +++ b/libs/vdb/src/lib/tools/node-to-mesh.spec.ts @@ -0,0 +1,37 @@ +import { Grid } from '../grid'; +import { nodeToMesh } from './node-to-mesh'; + +describe('nodeToMesh()', () => { + it('should generate the mesh', () => { + const grid = new Grid(0); + const accessor = grid.getAccessor(); + + accessor.setValueOn([0, 0, 0], 1); + accessor.setValueOn([0, 0, 1], 1); + + const meshData = nodeToMesh(grid.tree.root, () => [0, 0, 0, 1]); + + const voxels = 2; + + const triangles = 12; + const triangleCorners = triangles * 3; + + const xyz = 3; + const rgba = 4; + + expect(meshData).toBeDefined(); + if (meshData) { + expect(meshData.positions.length).toEqual(voxels * triangleCorners * xyz); + expect(meshData.colors.length).toEqual(voxels * triangleCorners * rgba); + expect(meshData.normals.length).toEqual(meshData.positions.length); + } + }); + + it('should return undefined if there are no data', () => { + const grid = new Grid(0); + + const meshData = nodeToMesh(grid.tree.root, () => [0, 0, 0, 1]); + + expect(meshData).toBeUndefined(); + }); +}); diff --git a/libs/vdb/src/lib/tools/node-to-mesh.ts b/libs/vdb/src/lib/tools/node-to-mesh.ts new file mode 100644 index 00000000..d666417f --- /dev/null +++ b/libs/vdb/src/lib/tools/node-to-mesh.ts @@ -0,0 +1,449 @@ +/** + * Babylon.js has a left-handed coordinate system with Y pointing up. + * Furthermore, the front face of a facet is considered the face where the facet’s vertices + * are positioned counterclockwise. + * + * Position of a facet is the location of the facet’s barycenter (a.k.a. centroid). + * + * 6-------7 + * /| /| + * / | / | + * Y 3--|----2 | + * | Z | 5----|--4 + * | / | / | / + * 0--- X 0-------1 + */ + +import { HashableNode } from '../tree/node'; + +export interface MeshData { + colors: number[]; + normals: number[]; + positions: number[]; +} + +/** + * Returns a mesh if there are any active voxels saved in the grid. + * Otherwise, returns `undefined` i.e. if there are no active voxels. + * + * Doesn't use indices, since it is more efficient to send 32 positions + * instead of 24 positions and 32 indices for a cube. + * See: https://doc.babylonjs.com/how_to/optimizing_your_scene#using-unindexed-meshes + */ +export function nodeToMesh( + node: HashableNode, + valueToColor: (value: T) => [number, number, number, number], +): MeshData | undefined { + const mesh: MeshData = { + colors: [], + positions: [], + normals: [], + }; + + for (const voxel of node.beginVoxelOn()) { + const [x, y, z] = voxel.globalCoord; + const [r, g, b, a] = valueToColor(voxel.value); + + const v0 = [x, y, z]; + const v1 = [x + 1, y, z]; + const v2 = [x + 1, y + 1, z]; + const v3 = [x, y + 1, z]; + const v4 = [x + 1, y, z + 1]; + const v5 = [x, y, z + 1]; + const v6 = [x, y + 1, z + 1]; + const v7 = [x + 1, y + 1, z + 1]; + + mesh.positions.push( + // Front + v4[0], + v4[1], + v4[2], + v5[0], + v5[1], + v5[2], + v6[0], + v6[1], + v6[2], + v4[0], + v4[1], + v4[2], + v6[0], + v6[1], + v6[2], + v7[0], + v7[1], + v7[2], + + // Back + v0[0], + v0[1], + v0[2], + v1[0], + v1[1], + v1[2], + v2[0], + v2[1], + v2[2], + v0[0], + v0[1], + v0[2], + v2[0], + v2[1], + v2[2], + v3[0], + v3[1], + v3[2], + + // Left + v5[0], + v5[1], + v5[2], + v0[0], + v0[1], + v0[2], + v3[0], + v3[1], + v3[2], + v5[0], + v5[1], + v5[2], + v3[0], + v3[1], + v3[2], + v6[0], + v6[1], + v6[2], + + // Right + v1[0], + v1[1], + v1[2], + v4[0], + v4[1], + v4[2], + v7[0], + v7[1], + v7[2], + v1[0], + v1[1], + v1[2], + v7[0], + v7[1], + v7[2], + v2[0], + v2[1], + v2[2], + + // Top + v3[0], + v3[1], + v3[2], + v2[0], + v2[1], + v2[2], + v7[0], + v7[1], + v7[2], + v3[0], + v3[1], + v3[2], + v7[0], + v7[1], + v7[2], + v6[0], + v6[1], + v6[2], + + // Bottom + v5[0], + v5[1], + v5[2], + v4[0], + v4[1], + v4[2], + v1[0], + v1[1], + v1[2], + v5[0], + v5[1], + v5[2], + v1[0], + v1[1], + v1[2], + v0[0], + v0[1], + v0[2], + ); + + // Front + mesh.normals.push( + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + + // Back + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + + // Left + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + + // Right + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + + // Top + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + 0, + 1, + 0, + + // Bottom + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + 0, + ); + + mesh.colors.push( + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + r, + g, + b, + a, + ); + } + + return mesh.positions.length !== 0 ? mesh : undefined; +} diff --git a/libs/vdb/src/lib/tree/index.ts b/libs/vdb/src/lib/tree/index.ts index 54c0aae2..6ed61bee 100644 --- a/libs/vdb/src/lib/tree/index.ts +++ b/libs/vdb/src/lib/tree/index.ts @@ -1 +1,2 @@ +export * from './leaf-node'; export * from './internal-node'; diff --git a/libs/vdb/src/lib/tree/internal-node.spec.ts b/libs/vdb/src/lib/tree/internal-node.spec.ts index b8b51738..8307d50e 100644 --- a/libs/vdb/src/lib/tree/internal-node.spec.ts +++ b/libs/vdb/src/lib/tree/internal-node.spec.ts @@ -5,12 +5,12 @@ import { LeafNode } from './leaf-node'; describe('InternalNode', () => { describe('InternalNode1', () => { describe('static config values', () => { - expect(InternalNode1.LOG2DIM).toEqual(2); - expect(InternalNode1.TOTAL).toEqual(5); - expect(InternalNode1.DIM).toEqual(32); - expect(InternalNode1.NUM_VALUES).toEqual(64); + expect(InternalNode1.LOG2DIM).toEqual(3); + expect(InternalNode1.TOTAL).toEqual(3 + 3); + expect(InternalNode1.DIM).toEqual(64); + expect(InternalNode1.NUM_VALUES).toEqual(512); expect(InternalNode1.LEVEL).toEqual(1); - expect(InternalNode1.NUM_VOXELS).toEqual(32_768); + expect(InternalNode1.NUM_VOXELS).toEqual(262_144); }); describe('setValueOn()', () => { @@ -50,28 +50,28 @@ describe('InternalNode', () => { describe('coordToOffset()', () => { it.each([ [[0, 0, LeafNode.DIM], 1], - [[0, LeafNode.DIM, LeafNode.DIM], 4 + 1], - [[LeafNode.DIM, LeafNode.DIM, LeafNode.DIM], 16 + 4 + 1], + [[0, LeafNode.DIM, LeafNode.DIM], 8 + 1], + [[LeafNode.DIM, LeafNode.DIM, LeafNode.DIM], 64 + 8 + 1], [[0, 0, LeafNode.DIM * 0], 0], [[0, 0, LeafNode.DIM * 1], 1], [[0, 0, LeafNode.DIM * 2], 2], [[0, 0, LeafNode.DIM * 3], 3], - [[0, LeafNode.DIM * 0, 0], 0 * 4], - [[0, LeafNode.DIM * 1, 0], 1 * 4], - [[0, LeafNode.DIM * 2, 0], 2 * 4], - [[0, LeafNode.DIM * 3, 0], 3 * 4], + [[0, LeafNode.DIM * 0, 0], 0 * 8], + [[0, LeafNode.DIM * 1, 0], 1 * 8], + [[0, LeafNode.DIM * 2, 0], 2 * 8], + [[0, LeafNode.DIM * 3, 0], 3 * 8], - [[LeafNode.DIM * 0, 0, 0], 0 * 16], - [[LeafNode.DIM * 1, 0, 0], 1 * 16], - [[LeafNode.DIM * 2, 0, 0], 2 * 16], - [[LeafNode.DIM * 3, 0, 0], 3 * 16], + [[LeafNode.DIM * 0, 0, 0], 0 * 64], + [[LeafNode.DIM * 1, 0, 0], 1 * 64], + [[LeafNode.DIM * 2, 0, 0], 2 * 64], + [[LeafNode.DIM * 3, 0, 0], 3 * 64], - // 24 / 8 = 3 -> 3 x 16 = 48 ╮ - // 9 / 8 = 1 -> 1 x 4 = 4 ├─> 48 + 4 + 2 = 54 - // 17 / 8 = 2 -> 2 x 1 = 2 ╯ - [[24, 9, 17], 54], + // 24 / 8 = 3 -> 3 x 64 = 192 ╮ + // 9 / 8 = 1 -> 1 x 8 = 8 ├─> 192 + 8 + 2 = 202 + // 17 / 8 = 2 -> 2 x 1 = 2 ╯ + [[24, 9, 17], 202], ])('should return for coordinate %j the offset (%j)', (xyz: Coord, offset: number) => { const child = new InternalNode1([0, 0, 0]); @@ -82,11 +82,12 @@ describe('InternalNode', () => { describe('isValueOn()', () => { it('should set each voxel', () => { const node1 = new InternalNode1([0, 0, 0]); + const maxDim = InternalNode1.DIM / 4; // Shorten test running time let onCounter = 0; - for (let x = 0; x < InternalNode1.DIM; x++) { - for (let y = 0; y < InternalNode1.DIM; y++) { - for (let z = 0; z < InternalNode1.DIM; z++) { + for (let x = 0; x < maxDim; x++) { + for (let y = 0; y < maxDim; y++) { + for (let z = 0; z < maxDim; z++) { node1.setValueOn([x, y, z], onCounter); expect(node1.getValue([x, y, z])).toEqual(onCounter); @@ -95,18 +96,19 @@ describe('InternalNode', () => { } } - expect(onCounter).toEqual(Math.pow(InternalNode1.DIM, 3)); + expect(onCounter).toEqual(Math.pow(maxDim, 3)); }); }); describe('onVoxelCount()', () => { it('should count all activated voxels', () => { const node1 = new InternalNode1([0, 0, 0]); + const maxDim = InternalNode1.DIM / 4; // Shorten test running time let onCounter = 0; - for (let x = 0; x < InternalNode1.DIM; x++) { - for (let y = 0; y < InternalNode1.DIM; y++) { - for (let z = 0; z < InternalNode1.DIM; z++) { + for (let x = 0; x < maxDim; x++) { + for (let y = 0; y < maxDim; y++) { + for (let z = 0; z < maxDim; z++) { node1.setValueOn([x, y, z], 42); onCounter++; @@ -115,7 +117,7 @@ describe('InternalNode', () => { } } - expect(onCounter).toEqual(Math.pow(InternalNode1.DIM, 3)); + expect(onCounter).toEqual(Math.pow(maxDim, 3)); }); }); @@ -143,11 +145,11 @@ describe('InternalNode', () => { describe('InternalNode2', () => { describe('static config values', () => { expect(InternalNode2.LOG2DIM).toEqual(2); - expect(InternalNode2.TOTAL).toEqual(7); - expect(InternalNode2.DIM).toEqual(128); + expect(InternalNode2.TOTAL).toEqual(2 + 3 + 3); + expect(InternalNode2.DIM).toEqual(256); expect(InternalNode2.NUM_VALUES).toEqual(64); expect(InternalNode2.LEVEL).toEqual(2); - expect(InternalNode2.NUM_VOXELS).toEqual(2_097_152); + expect(InternalNode2.NUM_VOXELS).toEqual(16_777_216); }); describe('setValueOn()', () => { @@ -176,14 +178,14 @@ describe('InternalNode', () => { describe('coordToOffset()', () => { it.each([ [[0, 0, 0], 0], - [[0, 0, 128], 0], - [[0, 0, 32], 1], - [[0, 0, 64], 2], - [[0, 0, 96], 3], - - [[0, 0, 32], 1], - [[0, 32, 0], 4], - [[32, 0, 0], 16], + [[0, 0, 256], 0], + [[0, 0, 64], 1], + [[0, 0, 128], 2], + [[0, 0, 192], 3], + + [[0, 0, 64], 1], + [[0, 64, 0], 4], + [[64, 0, 0], 16], ])('should return for coordinate %j the offset (%j)', (xyz: Coord, offset: number) => { const child = new InternalNode2([0, 0, 0]); @@ -194,18 +196,19 @@ describe('InternalNode', () => { describe('onVoxelCount()', () => { it('should count all activated voxels', () => { const node2 = new InternalNode2([0, 0, 0]); + const maxDim = InternalNode2.DIM / 4; // Shorten test running time let onCounter = 0; - for (let x = 0; x < InternalNode2.DIM; x++) { - for (let y = 0; y < InternalNode2.DIM; y++) { - for (let z = 0; z < InternalNode2.DIM; z++) { + for (let x = 0; x < maxDim; x++) { + for (let y = 0; y < maxDim; y++) { + for (let z = 0; z < maxDim; z++) { node2.setValueOn([x, y, z], 42); onCounter++; } } } - expect(onCounter).toEqual(Math.pow(InternalNode2.DIM, 3)); + expect(onCounter).toEqual(Math.pow(maxDim, 3)); }); }); diff --git a/libs/vdb/src/lib/tree/internal-node.ts b/libs/vdb/src/lib/tree/internal-node.ts index 306bce01..072d7e9c 100644 --- a/libs/vdb/src/lib/tree/internal-node.ts +++ b/libs/vdb/src/lib/tree/internal-node.ts @@ -8,9 +8,9 @@ import { ValueAccessor3 } from './value-accessor'; import { Voxel } from './voxel'; abstract class InternalNode implements HashableNode { + origin: Coord; protected childMask: NodeMask; protected valueMask: NodeMask; - protected origin: Coord; protected nodes: NodeUnion>[]; @@ -78,6 +78,23 @@ abstract class InternalNode implements HashableNode { return node.getValue(); } + probeInternalNode1AndCache( + xyz: Coord, + accessor: ValueAccessor3, + ): InternalNode1 | undefined { + const i: Index = this.coordToOffset(xyz); + const node = this.nodes[i]; + + if (this.childMask.isOff(i)) { + return undefined; + } + + const child = node.getChild(); + accessor.insert(xyz, child); + + return child instanceof InternalNode1 ? child : child.probeInternalNode1AndCache(xyz, accessor); + } + setValueOn(xyz: Coord, value: T): void { const i: Index = this.coordToOffset(xyz); const node = this.nodes[i]; @@ -146,6 +163,27 @@ abstract class InternalNode implements HashableNode { } } + setActiveStateAndCache(xyz: Coord, on: boolean, accessor: ValueAccessor3): void { + const i: Index = this.coordToOffset(xyz); + const node = this.nodes[i]; + let hasChild = this.childMask.isOn(i); + + if (!hasChild) { + if (on !== this.valueMask.isOn(i)) { + // If the voxel belongs to a tile with the wrong active state, + // then a child subtree must be constructed. + // 'on' is the voxel's new state, therefore '!on' is the tile's current state + hasChild = true; + this.setChildNode(i, this.createChildNode(xyz, node.getValue(), !on)); + } + } + + if (hasChild) { + accessor.insert(xyz, node.getChild()); + node.getChild().setActiveStateAndCache(xyz, on, accessor); + } + } + isValueOn(xyz: Coord): boolean { const i: Index = this.coordToOffset(xyz); if (this.childMask.isOff(i)) { @@ -242,7 +280,7 @@ abstract class InternalNode implements HashableNode { export class InternalNode1 extends InternalNode { // tslint:disable:no-bitwise - static readonly LOG2DIM = 2; // log2 of tile count in one dimension + static readonly LOG2DIM = 3; // log2 of tile count in one dimension static readonly TOTAL = InternalNode1.LOG2DIM + LeafNode.TOTAL; // log2 of voxel count in one dimension static readonly DIM = 1 << InternalNode1.TOTAL; // total voxel count in one dimension static readonly DIM_MAX_INDEX_INVERTED: Index = ~(InternalNode1.DIM - 1); // Performance: max index diff --git a/libs/vdb/src/lib/tree/leaf-node.ts b/libs/vdb/src/lib/tree/leaf-node.ts index 4cada089..b7d9b10d 100644 --- a/libs/vdb/src/lib/tree/leaf-node.ts +++ b/libs/vdb/src/lib/tree/leaf-node.ts @@ -1,5 +1,6 @@ import { Coord } from '../math/coord'; import { NodeMask } from '../util/node-mask'; +import { InternalNode1 } from './internal-node'; import { LeafBuffer } from './leaf-buffer'; import { HashableNode } from './node'; import { ValueAccessor3 } from './value-accessor'; @@ -21,12 +22,13 @@ export class LeafNode implements HashableNode { static readonly LEVEL: Index = 0; // level 0 = leaf // tslint:enable:no-bitwise + // Global grid index coordinates (x,y,z) of the local origin of this node + readonly origin: Coord; + // Buffer containing the actual data values private buffer: LeafBuffer; // Bitmask that determines which voxels are active private valueMask: NodeMask; - // Global grid index coordinates (x,y,z) of the local origin of this node - private readonly origin: Coord; /** * Return the linear table offset of the given global or local coordinates. @@ -96,6 +98,13 @@ export class LeafNode implements HashableNode { this.valueMask.setOff(offset); } + /** + * Set the active state of the voxel at the given coordinates but don't change its value. + */ + setActiveState(xyz: Coord, on: boolean): void { + this.valueMask.set(LeafNode.coordToOffset(xyz), on); + } + /** * Return the value of the voxel at the given coordinates. */ @@ -119,6 +128,10 @@ export class LeafNode implements HashableNode { return this.getValue(xyz); } + probeInternalNode1AndCache(xyz: Coord, _: ValueAccessor3): InternalNode1 | undefined { + throw new Error(`Shouldn't be called on LeafNode`); + } + /** * @brief Change the value of the voxel at the given coordinates and mark it as active. * @note Used internally by ValueAccessor. @@ -143,6 +156,14 @@ export class LeafNode implements HashableNode { return this.isValueOn(xyz); } + /** + * @brief Set the active state of the voxel at the given coordinates without changing its value. + * @note Used internally by ValueAccessor. + */ + setActiveStateAndCache(xyz: Coord, on: boolean, _: ValueAccessor3): void { + return this.setActiveState(xyz, on); + } + /** * Return the global coordinates for a linear table offset. */ diff --git a/libs/vdb/src/lib/tree/node.ts b/libs/vdb/src/lib/tree/node.ts index e4c34174..01d4c8cc 100644 --- a/libs/vdb/src/lib/tree/node.ts +++ b/libs/vdb/src/lib/tree/node.ts @@ -1,3 +1,4 @@ +import { InternalNode1 } from '@talus/vdb'; import { Coord } from '../math/coord'; import { ValueAccessor3 } from './value-accessor'; import { Voxel } from './voxel'; @@ -33,11 +34,18 @@ export interface HashableNode extends Node { */ getValueAndCache(xyz: Coord, accessor: ValueAccessor3): T; + /** + * Same as probeNode() except, if necessary, update the accessor with pointers + * to the nodes along the path from the root node to the node containing (x, y, z). + */ + probeInternalNode1AndCache(xyz: Coord, accessor: ValueAccessor3): InternalNode1 | undefined; + /** * Change the value of the voxel at the given coordinates and mark it as active. * If necessary, update the accessor with pointers to the nodes along the path * from the root node to the node containing the voxel. * @note Used internally by ValueAccessor. + * @return The affected `LeafNode` in which a value was set. */ setValueAndCache(xyz: Coord, value: T, accessor: ValueAccessor3): void; @@ -56,6 +64,14 @@ export interface HashableNode extends Node { * @note Used internally by ValueAccessor. */ isValueOnAndCache(xyz: Coord, accessor: ValueAccessor3): boolean; + + /** + * Set the active state of the voxel at the given coordinates without changing its value. + * If necessary, update the accessor with pointers to the nodes along the path + * from the root node to the node containing the voxel. + * @note Used internally by ValueAccessor. + */ + setActiveStateAndCache(xyz: Coord, on: boolean, accessor: ValueAccessor3): void; } export interface IterableNode { diff --git a/libs/vdb/src/lib/tree/root-node.spec.ts b/libs/vdb/src/lib/tree/root-node.spec.ts index 6ee42d90..47b1d179 100644 --- a/libs/vdb/src/lib/tree/root-node.spec.ts +++ b/libs/vdb/src/lib/tree/root-node.spec.ts @@ -2,95 +2,93 @@ import { InternalNode2 } from './internal-node'; import { RootNode } from './root-node'; describe('RootNode', () => { - describe('setValueOn()', () => { - it('should set given value and activate it', () => { - const root = new RootNode(-1); + it('should set given value and activate it', () => { + const root = new RootNode(-1); - root.setValueOn([0, 0, 0], 24); - root.setValueOn([102, 15, 127], 42); + root.setValueOn([0, 0, 0], 24); + root.setValueOn([102, 15, 127], 42); - expect(root.getValue([0, 0, 0])).toEqual(24); - expect(root.getValue([102, 15, 127])).toEqual(42); + expect(root.getValue([0, 0, 0])).toEqual(24); + expect(root.getValue([102, 15, 127])).toEqual(42); - expect(root.isValueOn([0, 0, 0])).toEqual(true); - expect(root.isValueOn([102, 15, 127])).toEqual(true); + expect(root.isValueOn([0, 0, 0])).toEqual(true); + expect(root.isValueOn([102, 15, 127])).toEqual(true); - expect(root.getValue([1, 1, 1])).toEqual(-1); - }); + expect(root.getValue([1, 1, 1])).toEqual(-1); + }); + + it('should return background', () => { + const root = new RootNode(-1); + + expect(root.getValue([111, 222, 333])).toEqual(-1); + }); - it('should set value when given float coordinates', () => { - const root = new RootNode(-1); + it('should set value when given float coordinates', () => { + const root = new RootNode(-1); - root.setValueOn([0, 0, 0.1], 1); - root.setValueOn([0, 1.5, 0], 5); - root.setValueOn([2.9, 0, 0], 9); + root.setValueOn([0, 0, 0.1], 1); + root.setValueOn([0, 1.5, 0], 5); + root.setValueOn([2.9, 0, 0], 9); - expect(root.getValue([0, 0, 0])).toEqual(1); - expect(root.getValue([0, 0, 1])).toEqual(-1); - expect(root.getValue([0, 1, 0])).toEqual(5); - expect(root.getValue([0, 2, 0])).toEqual(-1); - expect(root.getValue([2, 0, 0])).toEqual(9); - expect(root.getValue([3, 0, 0])).toEqual(-1); + expect(root.getValue([0, 0, 0])).toEqual(1); + expect(root.getValue([0, 0, 1])).toEqual(-1); + expect(root.getValue([0, 1, 0])).toEqual(5); + expect(root.getValue([0, 2, 0])).toEqual(-1); + expect(root.getValue([2, 0, 0])).toEqual(9); + expect(root.getValue([3, 0, 0])).toEqual(-1); - root.setValueOn([0, 0, -1.2], -2); - root.setValueOn([0, -2.5, 0], -5); - root.setValueOn([-3.9, 0, 0], -9); + root.setValueOn([0, 0, -1.2], -2); + root.setValueOn([0, -2.5, 0], -5); + root.setValueOn([-3.9, 0, 0], -9); - expect(root.getValue([0, 0, -1])).toEqual(-2); - expect(root.getValue([0, -2, 0])).toEqual(-5); - expect(root.getValue([-3, 0, 0])).toEqual(-9); - }); + expect(root.getValue([0, 0, -1])).toEqual(-2); + expect(root.getValue([0, -2, 0])).toEqual(-5); + expect(root.getValue([-3, 0, 0])).toEqual(-9); }); - describe('onVoxelCount()', () => { - it('should count all activated voxels', () => { - const root = new RootNode(-1); + it('should count all activated voxels', () => { + const root = new RootNode(-1); - root.setValueOn([0, 0, 0], 24); - root.setValueOn([102, 15, 127], 42); + root.setValueOn([0, 0, 0], 24); + root.setValueOn([102, 15, 127], 42); - expect(root.onVoxelCount()).toEqual(2); - }); + expect(root.onVoxelCount()).toEqual(2); }); - describe('getTableSize()', () => { - it('should create new entries when inserting values', () => { - const root = new RootNode(-1); - - root.setValueOn([0, 0, 0], 24); - root.setValueOn([0, 0, 1], 24); - root.setValueOn([102, 15, 127], 42); - expect(root.getTableSize()).toEqual(1); - - root.setValueOn([0, 0, InternalNode2.DIM], 42); - root.setValueOn([0, 1, InternalNode2.DIM], 42); - root.setValueOn([1, 0, InternalNode2.DIM], 42); - expect(root.getTableSize()).toEqual(2); - - root.setValueOn([InternalNode2.DIM, 0, 0], 42); - root.setValueOn([InternalNode2.DIM, 0, 1], 42); - root.setValueOn([InternalNode2.DIM, 1, 0], 42); - expect(root.getTableSize()).toEqual(3); - }); + it('should create new entries when inserting values', () => { + const root = new RootNode(-1); + + root.setValueOn([0, 0, 0], 24); + root.setValueOn([0, 0, 1], 24); + root.setValueOn([102, 15, 127], 42); + expect(root.getTableSize()).toEqual(1); + + root.setValueOn([0, 0, InternalNode2.DIM], 42); + root.setValueOn([0, 1, InternalNode2.DIM], 42); + root.setValueOn([1, 0, InternalNode2.DIM], 42); + expect(root.getTableSize()).toEqual(2); + + root.setValueOn([InternalNode2.DIM, 0, 0], 42); + root.setValueOn([InternalNode2.DIM, 0, 1], 42); + root.setValueOn([InternalNode2.DIM, 1, 0], 42); + expect(root.getTableSize()).toEqual(3); }); - describe('beginVoxelOn()', () => { - it('should iterate over all activated voxels', () => { - const root = new RootNode(-1); - const expectedValues = [0, 1, 2, 3]; + it('should iterate over all activated voxels', () => { + const root = new RootNode(-1); + const expectedValues = [0, 1, 2, 3]; - root.setValueOn([0, 0, 0], expectedValues[0]); - root.setValueOn([0, 0, InternalNode2.DIM], expectedValues[1]); - root.setValueOn([0, InternalNode2.DIM, 0], expectedValues[2]); - root.setValueOn([InternalNode2.DIM, 0, 0], expectedValues[3]); + root.setValueOn([0, 0, 0], expectedValues[0]); + root.setValueOn([0, 0, InternalNode2.DIM], expectedValues[1]); + root.setValueOn([0, InternalNode2.DIM, 0], expectedValues[2]); + root.setValueOn([InternalNode2.DIM, 0, 0], expectedValues[3]); - let counter = 0; - for (const voxel of root.beginVoxelOn()) { - expect(voxel.value).toEqual(expectedValues[counter]); - counter++; - } + let counter = 0; + for (const voxel of root.beginVoxelOn()) { + expect(voxel.value).toEqual(expectedValues[counter]); + counter++; + } - expect(counter).toEqual(expectedValues.length); - }); + expect(counter).toEqual(expectedValues.length); }); }); diff --git a/libs/vdb/src/lib/tree/root-node.ts b/libs/vdb/src/lib/tree/root-node.ts index 20d3dc9e..8366e667 100644 --- a/libs/vdb/src/lib/tree/root-node.ts +++ b/libs/vdb/src/lib/tree/root-node.ts @@ -1,5 +1,5 @@ import { Coord } from '../math/coord'; -import { InternalNode2 } from './internal-node'; +import { InternalNode1, InternalNode2 } from './internal-node'; import { HashableNode } from './node'; import { ValueAccessor3 } from './value-accessor'; import { Voxel } from './voxel'; @@ -57,9 +57,25 @@ export class RootNode implements HashableNode { return struct.getTile().value; } + probeInternalNode1AndCache( + xyz: Coord, + accessor: ValueAccessor3, + ): InternalNode1 | undefined { + const struct = this.findCoord(xyz); + + if (!struct || struct.isTile()) { + return undefined; + } + + const child = struct.getChild(); + accessor.insert(xyz, child); + + return child.probeInternalNode1AndCache(xyz, accessor); + } + setValueOn(xyz: Coord, value: T): void { + let child: HashableNode | undefined; const struct = this.findCoord(xyz); - let child: HashableNode; if (!struct) { child = new InternalNode2(xyz, this._background); @@ -77,7 +93,7 @@ export class RootNode implements HashableNode { } setValueAndCache(xyz: Coord, value: T, accessor: ValueAccessor3): void { - let child: HashableNode; + let child: HashableNode | undefined; const struct = this.findCoord(xyz); if (!struct) { @@ -92,12 +108,12 @@ export class RootNode implements HashableNode { if (child) { accessor.insert(xyz, child); - child.setValueOn(xyz, value); + child.setValueAndCache(xyz, value, accessor); } } setValueOffAndCache(xyz: Coord, value: T, accessor: ValueAccessor3): void { - let child: HashableNode; + let child: HashableNode | undefined; const struct = this.findCoord(xyz); if (!struct) { @@ -118,6 +134,30 @@ export class RootNode implements HashableNode { } } + setActiveStateAndCache(xyz: Coord, on: boolean, accessor: ValueAccessor3): void { + let child: HashableNode | undefined; + const struct = this.findCoord(xyz); + + if (!struct) { + if (on) { + child = new InternalNode2(xyz, this._background); + this.table.set(RootNode.coordToKey(xyz), new NodeStruct(child)); + } /*else { + // Nothing to do; (x, y, z) is background and therefore already inactive. + }*/ + } else if (struct.isChild()) { + child = struct.getChild(); + } else if (on !== struct.getTile().active) { + child = new InternalNode2(xyz, struct.getTile().value, !on); + struct.setChild(child); + } + + if (child) { + accessor.insert(xyz, child); + child.setActiveStateAndCache(xyz, on, accessor); + } + } + isValueOn(xyz: Coord): boolean { const struct = this.findCoord(xyz); @@ -186,6 +226,10 @@ class NodeStruct { constructor(private child?: HashableNode) {} getChild(): HashableNode { + if (!this.child) { + throw new Error('Access undefined child.'); + } + return this.child; } diff --git a/libs/vdb/src/lib/tree/tree.spec.ts b/libs/vdb/src/lib/tree/tree.spec.ts new file mode 100644 index 00000000..6e86aa4f --- /dev/null +++ b/libs/vdb/src/lib/tree/tree.spec.ts @@ -0,0 +1,51 @@ +import { Tree } from './tree'; + +describe('Tree', () => { + it('should get background', () => { + const tree = new Tree(-1); + + expect(tree.background).toEqual(-1); + }); + + it('should get/set value', () => { + const tree = new Tree(-1); + + tree.setValueOn([0, 1, 2], 42); + + expect(tree.getValue([0, 1, 2])).toEqual(42); + }); + + it('should activated value', () => { + const tree = new Tree(-1); + + tree.setValueOn([0, 1, 2], 42); + + expect(tree.isValueOn([0, 1, 2])).toBeTruthy(); + }); + + it('should count voxel', () => { + const tree = new Tree(-1); + + tree.setValueOn([0, 1, 2], 42); + tree.setValueOn([845, 242, 64], 42); + tree.setValueOn([1000, 4000, 200000], 42); + + expect(tree.onVoxelCount()).toEqual(3); + }); + + it('should iterate over each activated voxel', () => { + const tree = new Tree(-1); + + tree.setValueOn([0, 1, 2], 42); + tree.setValueOn([845, 242, 64], 42); + tree.setValueOn([1000, 4000, 200000], 42); + + let counter = 0; + for (const voxel of tree.beginVoxelOn()) { + counter++; + expect(voxel.value).toEqual(42); + } + + expect(counter).toEqual(3); + }); +}); diff --git a/libs/vdb/src/lib/tree/value-accessor.spec.ts b/libs/vdb/src/lib/tree/value-accessor.spec.ts index 425daade..8398e31b 100644 --- a/libs/vdb/src/lib/tree/value-accessor.spec.ts +++ b/libs/vdb/src/lib/tree/value-accessor.spec.ts @@ -1,8 +1,16 @@ -import { InternalNode2 } from './internal-node'; +import { LeafNode } from '@talus/vdb'; +import { InternalNode1, InternalNode2 } from './internal-node'; import { Tree } from './tree'; import { ValueAccessor3 } from './value-accessor'; describe('ValueAccessor', () => { + it('should return background', () => { + const tree = new Tree(-1); + const accessor = new ValueAccessor3(tree); + + expect(accessor.getValue([111, 222, 333])).toEqual(-1); + }); + describe('getValue()', () => { it('should cache coordinate', () => { const tree = new Tree(0); @@ -22,12 +30,12 @@ describe('ValueAccessor', () => { }); }); - describe('setValue()', () => { + describe('setValueOn()', () => { it('should cache coordinate', () => { const tree = new Tree(0); const accessor = new ValueAccessor3(tree); - accessor.setValue([0, 0, 0], 1496); + accessor.setValueOn([0, 0, 0], 1496); const value = accessor.getValue([0, 0, 0]); expect(value).toEqual(1496); @@ -64,6 +72,27 @@ describe('ValueAccessor', () => { }); }); + describe('setActiveState()', () => { + it('should set state', () => { + const tree = new Tree(0); + const accessor = new ValueAccessor3(tree); + + accessor.setValueOn([0, 0, 0], 1496); + expect(accessor.getValue([0, 0, 0])).toEqual(1496); + expect(accessor.isValueOn([0, 0, 0])).toEqual(true); + + accessor.setActiveState([0, 0, 0], false); + expect(accessor.getValue([0, 0, 0])).toEqual(1496); + expect(accessor.isValueOn([0, 0, 0])).toBeFalsy(); + + accessor.setActiveState([0, 0, 0], true); + expect(accessor.getValue([0, 0, 0])).toEqual(1496); + expect(accessor.isValueOn([0, 0, 0])).toBeTruthy(); + + expect(accessor.isCached([0, 0, 0])).toBeTruthy(); + }); + }); + describe('isValueOn()', () => { it('should set value and (de)activate voxel', () => { const tree = new Tree(0); @@ -82,4 +111,42 @@ describe('ValueAccessor', () => { expect(accessor.isValueOn([0, 0, 1])).toBeFalsy(); }); }); + + describe('probeInternalNode1()', () => { + const tree = new Tree(-1); + + it('should return undefined if no internal node 1', () => { + const accessor = new ValueAccessor3(tree); + + expect(accessor.probeInternalNode1([111, 222, 333])).toBeUndefined(); + }); + + it('should hit no cache and return internal node 1', () => { + const accessor = new ValueAccessor3(tree); + + accessor.setValueOn([0, 0, 0], 42); + // Produce a cache miss (go over root) + accessor.setValueOn([InternalNode2.DIM, 0, 0], 42); + + expect(accessor.probeInternalNode1([32, 0, 0])).toBeInstanceOf(InternalNode1); + }); + + it('should hit cache L2 and return internal node 1', () => { + const accessor = new ValueAccessor3(tree); + + accessor.setValueOn([0, 0, 0], 42); + // Produce a cache miss (go over InternalNode2) + accessor.setValueOn([InternalNode1.DIM, 0, 0], 42); + + expect(accessor.probeInternalNode1([32, 0, 0])).toBeInstanceOf(InternalNode1); + }); + + it('should hit cache L1 and return internal node 1', () => { + const accessor = new ValueAccessor3(tree); + + accessor.setValueOn([0, 0, 0], 42); + + expect(accessor.probeInternalNode1([LeafNode.DIM, 0, 0])).toBeInstanceOf(InternalNode1); + }); + }); }); diff --git a/libs/vdb/src/lib/tree/value-accessor.ts b/libs/vdb/src/lib/tree/value-accessor.ts index 9ba01796..484a1ba6 100644 --- a/libs/vdb/src/lib/tree/value-accessor.ts +++ b/libs/vdb/src/lib/tree/value-accessor.ts @@ -1,4 +1,4 @@ -import { Coord, createMaxCoord } from '../math/coord'; +import { clone, Coord, createMaxCoord } from '../math/coord'; import { InternalNode1, InternalNode2 } from './internal-node'; import { LeafNode } from './leaf-node'; import { HashableNode } from './node'; @@ -28,7 +28,9 @@ import { Tree } from './tree'; * The configuration is hard-coded and has a depth of four. */ export class ValueAccessor3 { - constructor(private tree: Tree) {} + get internalNode1Origin(): Coord { + return clone(this.internalKey1); + } private leafKey: Coord = createMaxCoord(); private leafNode: LeafNode; @@ -39,6 +41,8 @@ export class ValueAccessor3 { private internalKey2: Coord = createMaxCoord(); private internalNode2: InternalNode2; + constructor(private tree: Tree) {} + /** * Return true if any of the nodes along the path to the given voxel have been cached. */ @@ -46,6 +50,20 @@ export class ValueAccessor3 { return this.isHashed2(xyz) || this.isHashed1(xyz) || this.isHashed0(xyz); } + /** + * @returns Returns the node that contains voxel (x, y, z) + * and if it doesn't exist, return undefined. + */ + probeInternalNode1(xyz: Coord): InternalNode1 | undefined { + if (this.isHashed1(xyz)) { + return this.internalNode1; + } else if (this.isHashed2(xyz)) { + return this.internalNode2.probeInternalNode1AndCache(xyz, this); + } else { + return this.tree.root.probeInternalNode1AndCache(xyz, this); + } + } + /** * Return the value of the voxel at the given coordinates. */ @@ -64,7 +82,7 @@ export class ValueAccessor3 { /** * Set the value of the voxel at the given coordinates and mark the voxel as active. */ - setValue(xyz: Coord, value: T): void { + setValueOn(xyz: Coord, value: T): void { if (this.isHashed0(xyz)) { this.leafNode.setValueAndCache(xyz, value, this); } else if (this.isHashed1(xyz)) { @@ -75,9 +93,6 @@ export class ValueAccessor3 { this.tree.root.setValueAndCache(xyz, value, this); } } - setValueOn(xyz: Coord, value: T): void { - this.setValue(xyz, value); - } /** * Set the value of the voxel at the given coordinates and mark the voxel as inactive. @@ -108,6 +123,20 @@ export class ValueAccessor3 { return this.tree.root.isValueOnAndCache(xyz, this); } + /** + * Set the active state of the voxel at the given coordinates without changing its value. + */ + setActiveState(xyz: Coord, on: boolean): void { + if (this.isHashed0(xyz)) { + this.leafNode.setActiveStateAndCache(xyz, on, this); + } else if (this.isHashed1(xyz)) { + this.internalNode1.setActiveStateAndCache(xyz, on, this); + } else if (this.isHashed2(xyz)) { + this.internalNode2.setActiveStateAndCache(xyz, on, this); + } + this.tree.root.setActiveStateAndCache(xyz, on, this); + } + // tslint:disable:no-bitwise insert(xyz: Coord, node: HashableNode): void { if (node instanceof LeafNode) { diff --git a/libs/vdb/src/lib/util/node-mask.ts b/libs/vdb/src/lib/util/node-mask.ts index a654a154..5c45e9a8 100644 --- a/libs/vdb/src/lib/util/node-mask.ts +++ b/libs/vdb/src/lib/util/node-mask.ts @@ -51,4 +51,8 @@ export class NodeMask { isOff(i: Index): boolean { return this.mask.test(i) === false; } + + set(i: Index, on: boolean): void { + on ? this.setOn(i) : this.setOff(i); + } } diff --git a/libs/vdb/tslint.json b/libs/vdb/tslint.json index 04809f83..8e2f21b3 100644 --- a/libs/vdb/tslint.json +++ b/libs/vdb/tslint.json @@ -1 +1,4 @@ -{ "extends": "../../tslint.json", "rules": [] } +{ + "extends": "../../tslint.json", + "rules": {} +} diff --git a/package.json b/package.json index dd0b33a6..512b2fa6 100644 --- a/package.json +++ b/package.json @@ -44,49 +44,49 @@ "@angular/platform-browser": "8.2.14", "@angular/platform-browser-dynamic": "8.2.14", "@angular/router": "8.2.14", - "@babylonjs/core": "4.1.0-beta.12", - "@babylonjs/materials": "4.1.0-beta.12", + "@babylonjs/core": "4.1.0-beta.20", + "@babylonjs/materials": "4.1.0-beta.20", "@ngrx/effects": "8.6.0", "@ngrx/store": "8.6.0", - "@nrwl/angular": "8.9.0", - "core-js": "3.6.0", + "@nrwl/angular": "8.11.0", "hammerjs": "2.0.8", "mnemonist": "0.32.0", - "rxjs": "6.5.3", + "rxjs": "6.5.4", "zone.js": "0.10.2" }, "devDependencies": { - "@angular-devkit/build-angular": "0.803.21", - "@angular/cli": "8.3.21", + "@angular-devkit/build-angular": "0.803.22", + "@angular/cli": "8.3.22", "@angular/compiler-cli": "8.2.14", "@angular/language-service": "8.2.14", - "@babylonjs/inspector": "4.1.0-beta.12", - "@nrwl/cypress": "8.9.0", - "@nrwl/jest": "8.9.0", - "@nrwl/node": "8.9.0", - "@nrwl/workspace": "8.9.0", + "@babylonjs/inspector": "4.1.0-beta.20", + "@nrwl/cypress": "8.11.0", + "@nrwl/jest": "8.11.0", + "@nrwl/node": "8.11.0", + "@nrwl/workspace": "8.11.0", "@types/benchmark": "1.0.31", - "@types/jest": "24.0.24", - "@types/node": "12.12.21", - "babylonjs": "4.1.0-beta.12", + "@types/jest": "24.9.0", + "@types/node": "13.1.7", + "babylonjs": "4.1.0-beta.20", "benchmark": "2.1.4", "codelyzer": "5.2.1", - "cypress": "3.7.0", + "cypress": "3.8.2", "dotenv": "8.2.0", - "gh-pages": "2.1.1", + "gh-pages": "2.2.0", + "handlebars": "4.7.2", "jasmine-marbles": "0.6.0", "jest": "24.9.0", "jest-junit": "10.0.0", "jest-preset-angular": "7.1.1", "junit-xml": "1.2.0", "prettier": "1.19.1", - "stylelint": "12.0.0", + "stylelint": "13.0.0", "stylelint-config-recommended": "3.0.0", "stylelint-config-recommended-scss": "4.1.0", "stylelint-config-standard": "19.0.0", "stylelint-scss": "3.13.0", - "ts-jest": "24.2.0", - "ts-node": "8.5.4", + "ts-jest": "24.3.0", + "ts-node": "8.6.2", "tslint": "5.20.1", "typescript": "3.5.3" } diff --git a/tsconfig.json b/tsconfig.json index 79137cd9..5139f576 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,27 @@ { "compileOnSave": false, "compilerOptions": { - "rootDir": ".", - "sourceMap": true, + "baseUrl": ".", "declaration": false, - "moduleResolution": "node", "emitDecoratorMetadata": true, "esModuleInterop": true, "experimentalDecorators": true, "importHelpers": true, - "target": "es2015", - "module": "esnext", - "typeRoots": ["node_modules/@types"], "lib": ["es2017", "dom"], - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "baseUrl": ".", + "module": "esnext", + "moduleResolution": "node", "paths": { "@talus/ui": ["libs/ui/src/index.ts"], "@talus/math": ["libs/math/src/index.ts"], "@talus/vdb": ["libs/vdb/src/index.ts"] - } + }, + "rootDir": ".", + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "strictNullChecks": true, + "target": "es2015", + "typeRoots": ["node_modules/@types"] }, "exclude": ["node_modules", "tmp"] } diff --git a/yarn.lock b/yarn.lock index 1d45ac82..b8e310aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,31 +10,31 @@ "@angular-devkit/core" "8.3.14" rxjs "6.4.0" -"@angular-devkit/architect@0.803.21": - version "0.803.21" - resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.803.21.tgz#0e76b6f646ebdbd9bb88d3972b2ca66fed950f7d" - integrity sha512-E2K/YexIWVyKM/xmyxvDjkJf+wX9u4c8YYpNaK4htsRzA06juc7N1MhlL/jURZiRl5b/K9sapYeq3tMX76saxA== +"@angular-devkit/architect@0.803.22": + version "0.803.22" + resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.803.22.tgz#33b54099298aaef5f93ffefb8b91a98ea9f85387" + integrity sha512-5Gr0LH+Hjd/NLdmi660VBoo3WbzQM7/yeG+ziktb7hbeVaYK4Mejtcg/DJnCoZ3hzlZuZokWVwvpdFo+A9xKbg== dependencies: - "@angular-devkit/core" "8.3.21" + "@angular-devkit/core" "8.3.22" rxjs "6.4.0" -"@angular-devkit/build-angular@0.803.21": - version "0.803.21" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.803.21.tgz#f3d12ea09748b05eb2d835a7de7997599fb6b752" - integrity sha512-flfgflvfpwdsm3x/U7QnfbtyZPEbsVipzQAoao1Zo58Beq1a+NsKsWbjrF/x4TSoI2czt0OVWXNytlfXM7LMhg== +"@angular-devkit/build-angular@0.803.22": + version "0.803.22" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.803.22.tgz#0fa668e84c237c6b1ab524e50b0ccaedc3a92929" + integrity sha512-2q9qLsD52D4GACUAuQhvkgQ7vLAhZzdU0jzfs74RTxqUZ3PS6Ltrrwpdg2kp9RlQ53+nSCYjWBDLk1CxoEt4pg== dependencies: - "@angular-devkit/architect" "0.803.21" - "@angular-devkit/build-optimizer" "0.803.21" - "@angular-devkit/build-webpack" "0.803.21" - "@angular-devkit/core" "8.3.21" + "@angular-devkit/architect" "0.803.22" + "@angular-devkit/build-optimizer" "0.803.22" + "@angular-devkit/build-webpack" "0.803.22" + "@angular-devkit/core" "8.3.22" "@babel/core" "7.7.5" "@babel/preset-env" "7.7.6" - "@ngtools/webpack" "8.3.21" + "@ngtools/webpack" "8.3.22" ajv "6.10.2" autoprefixer "9.6.1" - browserslist "4.6.6" + browserslist "4.8.3" cacache "12.0.2" - caniuse-lite "1.0.30000989" + caniuse-lite "1.0.30001019" circular-dependency-plugin "5.2.0" clean-css "4.2.1" copy-webpack-plugin "5.1.1" @@ -80,10 +80,10 @@ webpack-subresource-integrity "1.1.0-rc.6" worker-plugin "3.2.0" -"@angular-devkit/build-optimizer@0.803.21": - version "0.803.21" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.803.21.tgz#ecb3b6bba4b13ffbfbdbefb5997f690aa3635203" - integrity sha512-gNN6kPaF4phZco3TmsrNr9tIEKXYsoSeoaUiDUfgmCYwa7fAqM8Ojh7HX6IQuB2PpVmEwKGlCcSh6xDtB33NjA== +"@angular-devkit/build-optimizer@0.803.22": + version "0.803.22" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.803.22.tgz#6d09cbb5fd28ab7bd22ddc54e938f305dfff8b20" + integrity sha512-VIDeQcBn88PjHBTen3BRVA7DJiKEJdDwukx61mUvUDOcY7S5Ot5WqG0nrZifRjha17Z+fl3XuwS9TZNYmlF7WQ== dependencies: loader-utils "1.2.3" source-map "0.7.3" @@ -100,13 +100,13 @@ "@angular-devkit/core" "8.3.14" rxjs "6.4.0" -"@angular-devkit/build-webpack@0.803.21": - version "0.803.21" - resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.803.21.tgz#fd45754c0123f44fcde8fa6411ebea52d98054f0" - integrity sha512-zCFVla/Xdk8qGVybvnHtoKml2h0/ShasSjT55VNZO1XaTCMqYkQEwwqSGEiVajpauafWjKrKxxBhsmWoI4efAA== +"@angular-devkit/build-webpack@0.803.22": + version "0.803.22" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.803.22.tgz#079b2ded794308b2dc93769406bdb0a1e32015c0" + integrity sha512-RDLAhKHfTFzthzeawHEefYB1MxHiU2I32QzXI3XTCpR2XySw5JG9jIVIcsyDHQH1JtIfpHGq8vgfiTsE3r0YWA== dependencies: - "@angular-devkit/architect" "0.803.21" - "@angular-devkit/core" "8.3.21" + "@angular-devkit/architect" "0.803.22" + "@angular-devkit/core" "8.3.22" rxjs "6.4.0" "@angular-devkit/core@8.3.14": @@ -120,10 +120,10 @@ rxjs "6.4.0" source-map "0.7.3" -"@angular-devkit/core@8.3.21": - version "8.3.21" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.21.tgz#447022813e46333e930816c287722d06b9c4dd3a" - integrity sha512-BYyVbrbys535FplX0+GVOlYBg/cyk1U5SRhSxRRFZYi9epVlEBBPk8/6wV4cQPGb6EwXkVj7YtPWXjXcGfzWmA== +"@angular-devkit/core@8.3.22": + version "8.3.22" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.22.tgz#085cc1cf356cec00d0a1cea83ef1f21277f5ff55" + integrity sha512-lOEYcvK3MktjR9YZT/cUjiQE5dZxl8rZ/vgWgwDiL7RtzfXTt8lPapoJe7YKS53gLbUYiBNPCtTyTAqnslWgGA== dependencies: ajv "6.10.2" fast-json-stable-stringify "2.0.0" @@ -139,12 +139,12 @@ "@angular-devkit/core" "8.3.14" rxjs "6.4.0" -"@angular-devkit/schematics@8.3.21": - version "8.3.21" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.21.tgz#80d515f480180be18a4130ea691f90153bcab3ea" - integrity sha512-+wH0362CRr/SijVX4w2baY2ANZ4scQ1k2xO8lT+NMeZQkw3IJQPOfwk1IaqiAs2xuBJZcSDH1Gn80+Jh4Dit7w== +"@angular-devkit/schematics@8.3.22": + version "8.3.22" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.22.tgz#41461768cc6cf54708d55311e59b9defa7bff484" + integrity sha512-ETLdV1ftT+ZuuiHl6FjFQ4XLQznWMcxWognX+qgByn+DQOXsYRRvZK1L5eG/SG8CKJ8NL5oteTDloDnghARHFw== dependencies: - "@angular-devkit/core" "8.3.21" + "@angular-devkit/core" "8.3.22" rxjs "6.4.0" "@angular-eslint/builder@0.0.1-alpha.17": @@ -168,16 +168,16 @@ optionalDependencies: parse5 "^5.0.0" -"@angular/cli@8.3.21": - version "8.3.21" - resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-8.3.21.tgz#cbb05b86b7f34ecd81d52ccad922359e66a16a15" - integrity sha512-ZZpA7mMfIobFT06rBNxm8vucAh8W2s0huJZ4iL0BPujnhIr71PL+gDwssySWDEz2q6i4CkH9QRH76DHhtL6VSQ== +"@angular/cli@8.3.22": + version "8.3.22" + resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-8.3.22.tgz#2512e0aeaffdbb30ace35dc288eeba9a86f28b95" + integrity sha512-OT2rzwnxwI0ETP7rXCxjxsIAZEYo9wHP/5rRbu3m15GlQ3Bclq34ZDRwC/bRxXL5+1DfmhAs9AjtYNoFoDM4Tg== dependencies: - "@angular-devkit/architect" "0.803.21" - "@angular-devkit/core" "8.3.21" - "@angular-devkit/schematics" "8.3.21" - "@schematics/angular" "8.3.21" - "@schematics/update" "0.803.21" + "@angular-devkit/architect" "0.803.22" + "@angular-devkit/core" "8.3.22" + "@angular-devkit/schematics" "8.3.22" + "@schematics/angular" "8.3.22" + "@schematics/update" "0.803.22" "@yarnpkg/lockfile" "1.1.0" ansi-colors "4.1.1" debug "^4.1.1" @@ -1534,58 +1534,58 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@babylonjs/core@4.1.0-beta.12": - version "4.1.0-beta.12" - resolved "https://registry.yarnpkg.com/@babylonjs/core/-/core-4.1.0-beta.12.tgz#9aaaa51fa4fc72defb9e444d8376294f3bccbb0b" - integrity sha512-o5IUXJJWwZpON4YRytvYJkdhuKVf5Kf8bieotp4TVqo+ECB+mUGvxkDFoa/Mc1A0QLT7YcDSuibYQbu6asRrTQ== +"@babylonjs/core@4.1.0-beta.20": + version "4.1.0-beta.20" + resolved "https://registry.yarnpkg.com/@babylonjs/core/-/core-4.1.0-beta.20.tgz#fa554a10127e9409bec56b5c77256374b0524390" + integrity sha512-vKVaRNTGz/SgOljfhRhSJaAKajjkoXQQE5AgtdUz6zc4ps+9YPqV4sIFTS8jCSREnlnwpyLe7F8MjVYw4O51Aw== dependencies: tslib "^1.10.0" -"@babylonjs/gui@4.1.0-beta.12": - version "4.1.0-beta.12" - resolved "https://registry.yarnpkg.com/@babylonjs/gui/-/gui-4.1.0-beta.12.tgz#b467209fde28479e340dd0db107736b0334ae6a3" - integrity sha512-7WjS0liqvtSnJKqCR3ft2cwyOlqFiAUDQwzsSax4f1Iq9ql4M5J5+LC0FLXgkAGiltut8JjioSdEG39kD3T0TA== +"@babylonjs/gui@4.1.0-beta.20": + version "4.1.0-beta.20" + resolved "https://registry.yarnpkg.com/@babylonjs/gui/-/gui-4.1.0-beta.20.tgz#20933a191eb8ec6b1c49c809eea5af0f9da62856" + integrity sha512-dxLiAvfrgqaE+LMaulcpb40VYe2VIgWjf/SrHKPNnV7SY9dvaxBjqH7+1J7E8zO0e3n8i6nFuUWZ9ij2lKcGsQ== dependencies: - "@babylonjs/core" "4.1.0-beta.12" + "@babylonjs/core" "4.1.0-beta.20" tslib "^1.10.0" -"@babylonjs/inspector@4.1.0-beta.12": - version "4.1.0-beta.12" - resolved "https://registry.yarnpkg.com/@babylonjs/inspector/-/inspector-4.1.0-beta.12.tgz#c2e489b2a6a29d65523eec93a44346b1e65c97ec" - integrity sha512-Wga2Q5Gu5CBiR3zNMV30hCeTlDt5DxOGogOmMlkU9IfXg84VcpgjjHa4a1NV/nDaJtOP+sANKIvxVyAgtMhhhQ== - dependencies: - "@babylonjs/core" "4.1.0-beta.12" - "@babylonjs/gui" "4.1.0-beta.12" - "@babylonjs/loaders" "4.1.0-beta.12" - "@babylonjs/materials" "4.1.0-beta.12" - "@babylonjs/serializers" "4.1.0-beta.12" - babylonjs-gltf2interface "4.1.0-beta.12" +"@babylonjs/inspector@4.1.0-beta.20": + version "4.1.0-beta.20" + resolved "https://registry.yarnpkg.com/@babylonjs/inspector/-/inspector-4.1.0-beta.20.tgz#79224464a772bcf470c3ef352d117560a771356d" + integrity sha512-aonk4bArPhZ+HPj5J13BAyqD/QHBxcAr4oZZgim2DQXjuC40f6ClUYRQH/p0hPJYoy3uR0GiTOvWxx/MPhCOiQ== + dependencies: + "@babylonjs/core" "4.1.0-beta.20" + "@babylonjs/gui" "4.1.0-beta.20" + "@babylonjs/loaders" "4.1.0-beta.20" + "@babylonjs/materials" "4.1.0-beta.20" + "@babylonjs/serializers" "4.1.0-beta.20" + babylonjs-gltf2interface "4.1.0-beta.20" tslib "^1.10.0" -"@babylonjs/loaders@4.1.0-beta.12": - version "4.1.0-beta.12" - resolved "https://registry.yarnpkg.com/@babylonjs/loaders/-/loaders-4.1.0-beta.12.tgz#a0225a705507705ca9d4730eede87c95aa4b84c7" - integrity sha512-FIZxjltGLMP3DSCtos365HjPpL7zOfzkERe+X5/yy9zkMKPrzZGcKv90MZyEx+9lrjMTV3sueDMNTnp6d9pc/g== +"@babylonjs/loaders@4.1.0-beta.20": + version "4.1.0-beta.20" + resolved "https://registry.yarnpkg.com/@babylonjs/loaders/-/loaders-4.1.0-beta.20.tgz#31c910fb5f72a3049688181c7c0ffbdd519e8f4e" + integrity sha512-zNqCjb9vR4oArVaOPMqnKMQFzRe3gw0aSY56oBfMisfcwilWRD9Dcc9dy4gn7m5VgPUTIMtMrBfRnrDerNK1yA== dependencies: - "@babylonjs/core" "4.1.0-beta.12" - babylonjs-gltf2interface "4.1.0-beta.12" + "@babylonjs/core" "4.1.0-beta.20" + babylonjs-gltf2interface "4.1.0-beta.20" tslib "^1.10.0" -"@babylonjs/materials@4.1.0-beta.12": - version "4.1.0-beta.12" - resolved "https://registry.yarnpkg.com/@babylonjs/materials/-/materials-4.1.0-beta.12.tgz#71dfe537b54c243e8c7db0a46683986c6d284008" - integrity sha512-lwJ6SZySiQiT+dijkg3yddgU71sGzmiO2Zm8JzSsRJkuA2P6K8s1sZUDVAhI5h6Im8i7LiSFWLPQQjZsDaOAkA== +"@babylonjs/materials@4.1.0-beta.20": + version "4.1.0-beta.20" + resolved "https://registry.yarnpkg.com/@babylonjs/materials/-/materials-4.1.0-beta.20.tgz#67a3368a894ffa645999a068f995c0802497a7c1" + integrity sha512-uQSaFdQAKdZC/9CLEufvDyQ37SsPC2zuZt9awugB20tRCsYuj0q6FWmrGR+hleVsOoq/xm9e8y0vnf1Kx9F5bg== dependencies: - "@babylonjs/core" "4.1.0-beta.12" + "@babylonjs/core" "4.1.0-beta.20" tslib "^1.10.0" -"@babylonjs/serializers@4.1.0-beta.12": - version "4.1.0-beta.12" - resolved "https://registry.yarnpkg.com/@babylonjs/serializers/-/serializers-4.1.0-beta.12.tgz#0c5f6a0050af4a6b7384a6d3dcb34a8f496ea58a" - integrity sha512-T2YWPPgj6voKowx9YanPTaFqPatxfBR3oVm/uPtt2iRFvt8POy1wYWBBzjFHRJKicQflhA2LEfHWFr82xyzCCg== +"@babylonjs/serializers@4.1.0-beta.20": + version "4.1.0-beta.20" + resolved "https://registry.yarnpkg.com/@babylonjs/serializers/-/serializers-4.1.0-beta.20.tgz#9ce3f3661c9d4246c1215a97482df1d86c3a12ef" + integrity sha512-BWMXr0DxGC1hNOiDaQ2FKlueBP3bdFTs98uoo+sWarKhiaOwKkast8DF7QZnEyZVR6oPvUr06FEYmWZnVgidQg== dependencies: - "@babylonjs/core" "4.1.0-beta.12" - babylonjs-gltf2interface "4.1.0-beta.12" + "@babylonjs/core" "4.1.0-beta.20" + babylonjs-gltf2interface "4.1.0-beta.20" tslib "^1.10.0" "@cnakazawa/watch@^1.0.3": @@ -1774,14 +1774,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@mrmlnc/readdir-enhanced@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" - integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== - dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" - "@ngrx/effects@8.6.0": version "8.6.0" resolved "https://registry.yarnpkg.com/@ngrx/effects/-/effects-8.6.0.tgz#a0d7339597a5128c5cf896ddcf93f73406a45860" @@ -1792,48 +1784,64 @@ resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-8.6.0.tgz#8540c5bd40b33fc2f443e7e86f47c0d801b8f413" integrity sha512-K4cvCEa+5hw9qrETQWO+Cha3YbVCAT8yaIKJr/N35KntTL9mQMjoL+51JWLZfBwPV0e19CFgJIyrBnVUTxwr2A== -"@ngtools/webpack@8.3.21": - version "8.3.21" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-8.3.21.tgz#d28f2b66a8aeced5260c42ae722192ec5d5e4e56" - integrity sha512-DGqmFQ52sV4uB3y3spQTNLa69oU5cwd1yIqMB4GSM+Qp+hozdzrPA2gVH90N2DDhWe8icsSQHAtZQiR9+BDL8g== +"@ngtools/webpack@8.3.22": + version "8.3.22" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-8.3.22.tgz#f6792d66430fb48c890a8145dee5088e3519860b" + integrity sha512-MES7Q0k6GpQEY74cxElUVy7jIaDBSLvY+eOUN2GKL5CznvBSp3+U5px6X7ZjPGzCp7no1L1JkV9g2e0hPatlcw== dependencies: - "@angular-devkit/core" "8.3.21" + "@angular-devkit/core" "8.3.22" enhanced-resolve "4.1.0" rxjs "6.4.0" tree-kill "1.2.1" webpack-sources "1.4.3" -"@nodelib/fs.stat@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" - integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== -"@nrwl/angular@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@nrwl/angular/-/angular-8.9.0.tgz#54b5bf31d969a8cd8a49f9e1a112f3d0fa7ac437" - integrity sha512-FxMKD3g5JQ3LwInRv1Pa8X1efG/nSoeBOTMbtEJs7CnaTonMWMPbY8QNSFidM8J+dWxOMDs9DXm5v66eOELpnw== +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + +"@nrwl/angular@8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@nrwl/angular/-/angular-8.11.0.tgz#d65f8800acb852544fec4b11b0b49fe12bbb34b3" + integrity sha512-MIFwd2WdY8uvW1oySPzGxs2kYtprXIHYzXkkW3L5BMY0yVWTUqpUpmtMHQBPgA2E2OCIIO7jpccrXMRofMU57Q== dependencies: "@angular-devkit/schematics" "8.3.14" - "@nrwl/cypress" "8.9.0" - "@nrwl/jest" "8.9.0" + "@nrwl/cypress" "8.11.0" + "@nrwl/jest" "8.11.0" "@schematics/angular" "8.3.14" jasmine-marbles "~0.6.0" -"@nrwl/cli@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-8.9.0.tgz#5553d01909b75efc43e24ee3d5781243fbacbc9a" - integrity sha512-Z2ci9BQNsPeBfJTpXwMJ9NIfMLTEMsPoOXGxlCTjTZcpr6t2TlScVR9BIArgrv4BiMctyKUFRBxe6aX9eO98KA== +"@nrwl/cli@8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-8.11.0.tgz#bb45b31c45409af706b2636798ec695a2c6104aa" + integrity sha512-S8z9wgeWObmRuCXWVrQ9yIeWnyJJ4CEIzmuSbs3k+XBx/MTP2QsoluJSYHv4NYxFM2hTboZPNuicNsz16Ua5/Q== dependencies: - "@nrwl/tao" "8.9.0" + "@nrwl/tao" "8.11.0" chalk "2.4.2" tmp "0.0.33" yargs "^11.0.0" yargs-parser "10.0.0" -"@nrwl/cypress@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@nrwl/cypress/-/cypress-8.9.0.tgz#90016e1f9f4442436e5c3ce65d30923355d22210" - integrity sha512-xVTp5Lyy2HRYqJn3xwFJ85bzJiDygKuSm7ZXKJojCqg4ZwCBJDOoE3GzT/gYs1jRrKhV/jizkXkNtJA1UK2HZQ== +"@nrwl/cypress@8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@nrwl/cypress/-/cypress-8.11.0.tgz#d0771f9a5f4e1cb25c7230de6372d3da9d9adcf0" + integrity sha512-AB8Df+eX/qTR05EDswhk1hr9/RxNBe41mUzUq3KGvXEj7xMw+6Jt3JmBnoISk2olqDTB1DM3gLV0ytSC1FkktQ== dependencies: "@angular-devkit/architect" "0.803.14" "@angular-devkit/core" "8.3.14" @@ -1843,37 +1851,37 @@ tsconfig-paths-webpack-plugin "3.2.0" webpack-node-externals "1.7.2" -"@nrwl/jest@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@nrwl/jest/-/jest-8.9.0.tgz#be8d07c0c3b10d918e119eaa8b51a14013c3cb97" - integrity sha512-Vi10aiIAsNb5xqedyPT173nQLhblfFZG/GleO+SkM9qXjRLm6n1KgFdiLuOI5V+mtU0UcCSd+SbI2uJsk19i2w== +"@nrwl/jest@8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@nrwl/jest/-/jest-8.11.0.tgz#ed02e7facc90c91bff0f6974a3d329d1e83ca9f4" + integrity sha512-EyuuNAItQDSwJNtsDTs7zINdRG9GjU2rnTJsonMdnsdZIikM0HD8xooF/XzdIqfbc5c7ZZ46LqCChQ8PhWqBhQ== dependencies: "@angular-devkit/architect" "0.803.14" "@angular-devkit/core" "8.3.14" "@angular-devkit/schematics" "8.3.14" -"@nrwl/linter@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@nrwl/linter/-/linter-8.9.0.tgz#260a7ccaf9cc951042f5c23f480114a20794e0b8" - integrity sha512-e4mkAaR6BrgPWxw8YMhvuhIQUl7epm7rDnriZ3Y93DF/tfFNh5R5Hy57Nix52NDlnjP6kVcf2lxvrPSUi4iyrA== +"@nrwl/linter@8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@nrwl/linter/-/linter-8.11.0.tgz#9113d574dabfedda4059b8130e1299a95b4169c6" + integrity sha512-a66Hx84XJZTziiVNyqoan1lhcI10NFLfta0Sgk+9gGHuaT4KI3NjAhKSUNSchIlyN8enRb+Gl3Wenv2CHnqyJw== dependencies: "@angular-devkit/architect" "0.803.14" "@angular-eslint/builder" "0.0.1-alpha.17" -"@nrwl/node@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@nrwl/node/-/node-8.9.0.tgz#91f7770053f67672fa53d207b0fa57220c147da4" - integrity sha512-wCKLdeUnlohPJ7B4m3YUrn727i7simRzeByfHbslxnY85U0PmbVkOZXs6RlQxAh0dErNAbDfgN+vRPESVn9Eng== +"@nrwl/node@8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@nrwl/node/-/node-8.11.0.tgz#8e21f869ef4b9139cc9bed0ae6ee19802f11888f" + integrity sha512-H4LXQO1TYn5XjTo5bEySzuNSzu/gIffWbm4DHX9Ilie2iRr1fBDLD+/oo5qNLGhLuFWM8o9gKG6FDpNAhI7B6g== dependencies: "@angular-devkit/architect" "0.803.14" "@angular-devkit/build-webpack" "0.803.14" "@angular-devkit/core" "8.3.14" "@angular-devkit/schematics" "8.3.14" - "@nrwl/jest" "8.9.0" - "@nrwl/linter" "8.9.0" + "@nrwl/jest" "8.11.0" + "@nrwl/linter" "8.11.0" circular-dependency-plugin "5.2.0" - copy-webpack-plugin "5.0.3" - fork-ts-checker-webpack-plugin "0.4.15" + copy-webpack-plugin "5.1.1" + fork-ts-checker-webpack-plugin "^3.1.1" license-webpack-plugin "2.1.2" source-map-support "0.5.12" tree-kill "1.2.1" @@ -1881,12 +1889,13 @@ tsconfig-paths-webpack-plugin "3.2.0" webpack "4.41.2" webpack-dev-server "3.9.0" + webpack-merge "4.2.1" webpack-node-externals "1.7.2" -"@nrwl/tao@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-8.9.0.tgz#f28a8a62e2613d30805fd85f2e9234fb01d39133" - integrity sha512-hAIw7J94qv+i+JMs6aB0ZS/eBOlYJ0L50VeaFWk2n94FmDNW4WFKIQRuNBM4CGcdpT+h1z3tR7QeWPjCaXk3Ow== +"@nrwl/tao@8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-8.11.0.tgz#bf03d8825cccb06a7fd7d7c498b278deda882f78" + integrity sha512-9N+Vk/M8u1XVjMiLvtjEeFXYkb1TfSFvlKR0XyY7arCYmA/a6z594D4nUADjpDogNy2cyxBXcAjkKTOK1FSqZw== dependencies: "@angular-devkit/architect" "0.803.14" "@angular-devkit/core" "8.3.14" @@ -1896,21 +1905,21 @@ minimist "^1.2.0" strip-json-comments "2.0.1" -"@nrwl/workspace@8.9.0": - version "8.9.0" - resolved "https://registry.yarnpkg.com/@nrwl/workspace/-/workspace-8.9.0.tgz#4dc3d1ff9b34ce983158a5aa4bf3a0ef28c0df69" - integrity sha512-mwimmwx1V5kO9DXVygw++PoRhyIrut+LSQ3VGWu+ui9qwNQlcCLPwQQdHHp447qGdcb8Qne9ZPMHXUt8sWo5EQ== +"@nrwl/workspace@8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@nrwl/workspace/-/workspace-8.11.0.tgz#42b37098db2ad31047db1907169f6969907feee1" + integrity sha512-3cougMUedrOO8v1164i6I+mrGyDm1Uv2yFrYwMAnAcW+9s9Yy2KgEJ4HyBb4rJIhi7uKyQk9u9Q9QQtJsW+Kjg== dependencies: "@angular-devkit/core" "8.3.14" "@angular-devkit/schematics" "8.3.14" - "@nrwl/cli" "8.9.0" + "@nrwl/cli" "8.11.0" chalk "2.4.2" cosmiconfig "4.0.0" fs-extra "6.0.0" + hasha "5.1.0" ignore "5.0.4" npm-run-all "4.1.5" opn "^5.3.0" - prettier "1.18.2" rxjs "^6.4.0" semver "5.4.1" strip-json-comments "2.0.1" @@ -1926,21 +1935,21 @@ "@angular-devkit/core" "8.3.14" "@angular-devkit/schematics" "8.3.14" -"@schematics/angular@8.3.21": - version "8.3.21" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-8.3.21.tgz#4902e0b6e8be47006859009bf96a026e3d39dd27" - integrity sha512-KahQ+dHvTsGOZwY6IdzqJZLDEn0G89rrK3OY+7okZujoaLM+LXhxlPoznW1udnZJVTa3VNxYGx11fkgLtRJRqA== +"@schematics/angular@8.3.22": + version "8.3.22" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-8.3.22.tgz#fab009312bd3d50115332f2c41a92e15744ac09f" + integrity sha512-vD+UgPdbEoFPOH6xe2laFpHn/MC9R5C4A/+J9yQ6HBg5kt1YdyIBakvPOcXQCyWr5VZzDmTyMO76rd3zaef3DQ== dependencies: - "@angular-devkit/core" "8.3.21" - "@angular-devkit/schematics" "8.3.21" + "@angular-devkit/core" "8.3.22" + "@angular-devkit/schematics" "8.3.22" -"@schematics/update@0.803.21": - version "0.803.21" - resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.803.21.tgz#572c955bb132348bca03a128491ae264b0068a0a" - integrity sha512-D3BRvEBF2cJEgogvFaNOfqtTFHHv/ctSRfOeAYWjUxILtb+2DpuZ9h5QYDFhN9MPgz/vRaOqFORa3sEZCRkX4g== +"@schematics/update@0.803.22": + version "0.803.22" + resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.803.22.tgz#88b2fd3c5c2e3c5b2f453b912b281df14c94fd34" + integrity sha512-X+1sJ7YadcYxDqcLX7l7MEAIL3SHIXpCqToQdAZbAE06NdTFvg5eqiKreSdmm7ZdfL0dBe6oXi/yCDVMoL2zcw== dependencies: - "@angular-devkit/core" "8.3.21" - "@angular-devkit/schematics" "8.3.21" + "@angular-devkit/core" "8.3.22" + "@angular-devkit/schematics" "8.3.22" "@yarnpkg/lockfile" "1.1.0" ini "1.3.5" pacote "9.5.5" @@ -2025,10 +2034,10 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/jest@24.0.24": - version "24.0.24" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.24.tgz#0f2f523dc77cc1bc6bef34eaf287ede887a73f05" - integrity sha512-vgaG968EDPSJPMunEDdZvZgvxYSmeH8wKqBlHSkBt1pV2XlLEVDzsj1ZhLuI4iG4Pv841tES61txSBF0obh4CQ== +"@types/jest@24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.9.0.tgz#78c6991cd1734cf0d390be24875e310bb0a9fb74" + integrity sha512-dXvuABY9nM1xgsXlOtLQXJKdacxZJd7AtvLsKZ/0b57ruMXDKCOXAC/M75GbllQX6o1pcZ5hAG4JzYy7Z/wM2w== dependencies: jest-diff "^24.3.0" @@ -2042,15 +2051,25 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/minimist@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" + integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + "@types/node@*": version "12.7.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.2.tgz#c4e63af5e8823ce9cc3f0b34f7b998c2171f0c44" integrity sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg== -"@types/node@12.12.21": - version "12.12.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.21.tgz#aa44a6363291c7037111c47e4661ad210aded23f" - integrity sha512-8sRGhbpU+ck1n0PGAUgVrWrWdjSW2aqNeyC15W88GRsMpSwzv6RJGlLhE7s2RhVSOdyDmxbqlWSeThq4/7xqlA== +"@types/node@13.1.7": + version "13.1.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.7.tgz#db51d28b8dfacfe4fb2d0da88f5eb0a2eca00675" + integrity sha512-HU0q9GXazqiKwviVxg9SI/+t/nAsGkvLDkIdxz+ObejG2nX6Si00TeLqHMoS+a/1tjH7a8YpKVQwtgHuMQsldg== + +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== "@types/parse-json@^4.0.0": version "4.0.0" @@ -2328,11 +2347,16 @@ acorn@^5.5.3: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== -acorn@^6.0.1, acorn@^6.0.4, acorn@^6.2.1: +acorn@^6.0.1, acorn@^6.2.1: version "6.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== +acorn@^6.0.4: + version "6.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" + integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== + agent-base@4, agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -2477,6 +2501,14 @@ anymatch@^3.0.1: normalize-path "^3.0.0" picomatch "^2.0.4" +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + app-root-path@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" @@ -2540,11 +2572,6 @@ array-equal@^1.0.0: resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -2555,13 +2582,18 @@ array-flatten@^2.1.0: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-union@^1.0.1, array-union@^1.0.2: +array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -2673,17 +2705,17 @@ autoprefixer@9.6.1: postcss "^7.0.17" postcss-value-parser "^4.0.0" -autoprefixer@^9.7.1: - version "9.7.2" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.2.tgz#26cf729fbb709323b40171a874304884dcceffed" - integrity sha512-LCAfcdej1182uVvPOZnytbq61AhnOZ/4JelDaJGDeNwewyU1AMaNthcHsyz1NRjTmd2FkurMckLWfkHg3Z//KA== +autoprefixer@^9.7.3: + version "9.7.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" + integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== dependencies: - browserslist "^4.7.3" - caniuse-lite "^1.0.30001010" + browserslist "^4.8.3" + caniuse-lite "^1.0.30001020" chalk "^2.4.2" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.23" + postcss "^7.0.26" postcss-value-parser "^4.0.2" aws-sign2@~0.7.0: @@ -2837,15 +2869,15 @@ babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== -babylonjs-gltf2interface@4.1.0-beta.12: - version "4.1.0-beta.12" - resolved "https://registry.yarnpkg.com/babylonjs-gltf2interface/-/babylonjs-gltf2interface-4.1.0-beta.12.tgz#38dbc4d3b48e76d4b29d9508092d62eb17e4ae26" - integrity sha512-BOLX7C2elDVd9UtJXPnOF7qMCy7LebtpySCEnhlsZDvCIVWqBhQgzXA/d3Oeb4tGtpiZtaB87WG2bzuLWL0FzA== +babylonjs-gltf2interface@4.1.0-beta.20: + version "4.1.0-beta.20" + resolved "https://registry.yarnpkg.com/babylonjs-gltf2interface/-/babylonjs-gltf2interface-4.1.0-beta.20.tgz#90368bda475b3857b3043913d6358f8c4604d05b" + integrity sha512-Y3hGTh+5Yhywc7k8p8n49KKk3Od8u9pSgIp4OOjxliGSuRtc301chifpfVThgcs7UVoowfCXXxdB6mrUzRnFXw== -babylonjs@4.1.0-beta.12: - version "4.1.0-beta.12" - resolved "https://registry.yarnpkg.com/babylonjs/-/babylonjs-4.1.0-beta.12.tgz#f42d88d865c9ba984506afde3b054dec215af092" - integrity sha512-SyQVqarSuBDAmIkIzGctb35XSwuRAv+vI9CaE5rz4ls35pbvPdj0Nf3sZUePUDw6CZueRp3z2sL+zgPuvbg3Ow== +babylonjs@4.1.0-beta.20: + version "4.1.0-beta.20" + resolved "https://registry.yarnpkg.com/babylonjs/-/babylonjs-4.1.0-beta.20.tgz#46ce13e315f37e98d8930c561f49f81761b6c704" + integrity sha512-FU52D50wsS3Gr/VXWmLEKAqCOA4tuw+BIPJWnCaKmTN644k9IKyzOsZnMz+WulyC29Uce3duUKgv+PCpRMCKig== bail@^1.0.0: version "1.0.4" @@ -2977,7 +3009,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@^3.0.2: +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -3060,7 +3092,16 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@4.6.6, browserslist@^4.6.0, browserslist@^4.6.3, browserslist@^4.6.6: +browserslist@4.8.3, browserslist@^4.8.3: + version "4.8.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.3.tgz#65802fcd77177c878e015f0e3189f2c4f627ba44" + integrity sha512-iU43cMMknxG1ClEZ2MDKeonKE1CCrFVkQK2AqO2YWFmvIrx4JWrvQ4w4hQez6EpVI8rHTtqh/ruHHDHSOKxvUg== + dependencies: + caniuse-lite "^1.0.30001017" + electron-to-chromium "^1.3.322" + node-releases "^1.1.44" + +browserslist@^4.6.0, browserslist@^4.6.3, browserslist@^4.6.6: version "4.6.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453" integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA== @@ -3069,15 +3110,6 @@ browserslist@4.6.6, browserslist@^4.6.0, browserslist@^4.6.3, browserslist@^4.6. electron-to-chromium "^1.3.191" node-releases "^1.1.25" -browserslist@^4.7.3: - version "4.7.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.3.tgz#02341f162b6bcc1e1028e30624815d4924442dc3" - integrity sha512-jWvmhqYpx+9EZm/FxcZSbUZyDEvDTLDi3nSAKbzEkyWvtI0mNSmUosey+5awDW1RUlrgXbQb5A6qY1xQH9U6MQ== - dependencies: - caniuse-lite "^1.0.30001010" - electron-to-chromium "^1.3.306" - node-releases "^1.1.40" - browserslist@^4.8.2: version "4.8.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.2.tgz#b45720ad5fbc8713b7253c20766f701c9a694289" @@ -3176,26 +3208,6 @@ cacache@12.0.2, cacache@^12.0.2: unique-filename "^1.1.1" y18n "^4.0.0" -cacache@^11.3.2: - version "11.3.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc" - integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - cacache@^12.0.0, cacache@^12.0.3: version "12.0.3" resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" @@ -3239,11 +3251,6 @@ cachedir@1.3.0: dependencies: os-homedir "^1.0.1" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= - caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -3268,14 +3275,14 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase-keys@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" - integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c= +camelcase-keys@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.1.1.tgz#0d24dde78cea4c7d2da7f4ea40b7995083328c8d" + integrity sha512-kEPCddRFChEzO0d6w61yh0WbBiSv9gBnfZWGfXRYPlGqIdIGef6HMR6pgqVSEWCYkrp8B0AtEpEXNY+Jx0xk1A== dependencies: - camelcase "^4.1.0" - map-obj "^2.0.0" - quick-lru "^1.0.0" + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" camelcase@^4.1.0: version "4.1.0" @@ -3287,21 +3294,31 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@1.0.30000989, caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984: +caniuse-lite@1.0.30001019: + version "1.0.30001019" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001019.tgz#857e3fccaad2b2feb3f1f6d8a8f62d747ea648e1" + integrity sha512-6ljkLtF1KM5fQ+5ZN0wuyVvvebJxgJPTmScOMaFuQN2QuOzvRJnWSKfzQskQU5IOU4Gap3zasYPIinzwUjoj/g== + +caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984: version "1.0.30000989" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9" integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw== -caniuse-lite@^1.0.30001010: - version "1.0.30001012" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001012.tgz#653ec635e815b9e0fb801890923b0c2079eb34ec" - integrity sha512-7RR4Uh04t9K1uYRWzOJmzplgEOAXbfK72oVNokCdMzA67trrhPzy93ahKk1AWHiA0c58tD2P+NHqxrA8FZ+Trg== - caniuse-lite@^1.0.30001015: version "1.0.30001016" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001016.tgz#16ea48d7d6e8caf3cad3295c2d746fe38c4e7f66" integrity sha512-yYQ2QfotceRiH4U+h1Us86WJXtVHDmy3nEKIdYPsZCYnOV5/tMgGbmoIlrMzmh2VXlproqYtVaKeGDBkMZifFA== +caniuse-lite@^1.0.30001017: + version "1.0.30001020" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001020.tgz#3f04c1737500ffda78be9beb0b5c1e2070e15926" + integrity sha512-yWIvwA68wRHKanAVS1GjN8vajAv7MBFshullKCeq/eKpK7pJBVDgFFEqvgWTkcP2+wIDeQGYFRXECjKZnLkUjA== + +caniuse-lite@^1.0.30001020: + version "1.0.30001021" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001021.tgz#e75ed1ef6dbadd580ac7e7720bb16f07b083f254" + integrity sha512-wuMhT7/hwkgd8gldgp2jcrUjOU9RXJ4XxGumQeOsUr91l3WwmM68Cpa/ymCnWEDqakwFXhuDQbaKNHXBPgeE9g== + canonical-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d" @@ -3416,7 +3433,7 @@ chokidar@^2.0.2, chokidar@^2.1.1: optionalDependencies: fsevents "^1.2.7" -chokidar@^2.0.4, chokidar@^2.1.8: +chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -3435,6 +3452,21 @@ chokidar@^2.0.4, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" + integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.3.0" + optionalDependencies: + fsevents "~2.1.2" + chownr@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" @@ -3762,24 +3794,6 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -copy-webpack-plugin@5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.3.tgz#2179e3c8fd69f13afe74da338896f1f01a875b5c" - integrity sha512-PlZRs9CUMnAVylZq+vg2Juew662jWtwOXOqH4lbQD9ZFhRG9R7tVStOgHt21CBGVq7k5yIJaz8TXDLSjV+Lj8Q== - dependencies: - cacache "^11.3.2" - find-cache-dir "^2.1.0" - glob-parent "^3.1.0" - globby "^7.1.1" - is-glob "^4.0.1" - loader-utils "^1.2.3" - minimatch "^3.0.4" - normalize-path "^3.0.0" - p-limit "^2.2.0" - schema-utils "^1.0.0" - serialize-javascript "^1.7.0" - webpack-log "^2.0.0" - copy-webpack-plugin@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz#5481a03dea1123d88a988c6ff8b78247214f0b88" @@ -3819,11 +3833,6 @@ core-js@3.2.1: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.2.1.tgz#cd41f38534da6cc59f7db050fe67307de9868b09" integrity sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw== -core-js@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.0.tgz#2b854e451de1967d1e29896025cdc13a2518d9ea" - integrity sha512-AHPTNKzyB+YwgDWoSOCaid9PUSEF6781vsfiK8qUz62zRR448/XgK2NtCbpiUGizbep8Lrpt0Du19PpGGZvw3Q== - core-js@^2.4.0: version "2.6.9" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" @@ -3976,22 +3985,15 @@ cssstyle@^1.0.0, cssstyle@^1.1.1: dependencies: cssom "0.3.x" -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= -cypress@3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.7.0.tgz#e2cd71b87b6ce0d4c72c6ea25da1005d75c1f231" - integrity sha512-o+vfRxqAba8TduelzfZQ4WHmj2yNEjaoO2EuZ8dZ9pJpuW+WGtBGheKIp6zkoQsp8ZgFe8OoHh1i2mY8BDnMAw== +cypress@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.8.2.tgz#58fa96e1e7dae712403b0f4e8af1efe35442ff7a" + integrity sha512-aTs0u3+dfEuLe0Ct0FVO5jD1ULqxbuqWUZwzBm0rxdLgLxIAOI/A9f/WkgY5Cfy1TEXe8pKC6Wal0ZpnkdGRSw== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/xvfb" "1.2.4" @@ -4004,6 +4006,7 @@ cypress@3.7.0: commander "2.15.1" common-tags "1.8.0" debug "3.2.6" + eventemitter2 "4.1.2" execa "0.10.0" executable "4.1.1" extract-zip "1.6.7" @@ -4090,7 +4093,7 @@ debuglog@^1.0.1: resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -decamelize-keys@^1.0.0: +decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= @@ -4260,13 +4263,20 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@^2.0.0, dir-glob@^2.2.2: +dir-glob@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== dependencies: path-type "^3.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -4372,11 +4382,6 @@ electron-to-chromium@^1.3.191: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.230.tgz#2d0618cb6f724391d5fd0926dde84d6c67cbcda9" integrity sha512-r0RljY5DZi9RX4v8mjHxJkDWnQe+nsrkGlHtrDF2uvZcvAkw+iglvlQi1794gZhwRtJoDOomMJlDHL2LfXSCZA== -electron-to-chromium@^1.3.306: - version "1.3.314" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.314.tgz#c186a499ed2c9057bce9eb8dca294d6d5450facc" - integrity sha512-IKDR/xCxKFhPts7h+VaSXS02Z1mznP3fli1BbXWXeN89i2gCzKraU8qLpEid8YzKcmZdZD3Mly3cn5/lY9xsBQ== - electron-to-chromium@^1.3.322: version "1.3.322" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz#a6f7e1c79025c2b05838e8e344f6e89eb83213a8" @@ -4520,7 +4525,19 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.11.0, escodegen@^1.9.1: +escodegen@^1.11.0: + version "1.12.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.12.1.tgz#08770602a74ac34c7a90ca9229e7d51e379abc76" + integrity sha512-Q8t2YZ+0e0pc7NRVj3B4tSQ9rim1oi4Fh46k2xhJ2qOiEwhQfdjyEQddWdj7ZFaKmU+5104vn1qrcjEPWq+bgQ== + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +escodegen@^1.9.1: version "1.12.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.12.0.tgz#f763daf840af172bb3a2b6dd7219c0e17f7ff541" integrity sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg== @@ -4572,6 +4589,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +eventemitter2@4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15" + integrity sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU= + eventemitter3@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" @@ -4799,17 +4821,16 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= -fast-glob@^2.2.6: - version "2.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" - integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== +fast-glob@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.1.tgz#87ee30e9e9f3eb40d6f254a7997655da753d7c82" + integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - "@nodelib/fs.stat" "^1.1.2" - glob-parent "^3.1.0" - is-glob "^4.0.0" - merge2 "^1.2.3" - micromatch "^3.1.10" + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" fast-json-stable-stringify@2.0.0, fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.0.0" @@ -4826,6 +4847,13 @@ fastparse@^1.1.1: resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== +fastq@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" + integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA== + dependencies: + reusify "^1.0.0" + faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -4966,7 +4994,7 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-up@^2.0.0, find-up@^2.1.0: +find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= @@ -4980,7 +5008,7 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -5027,19 +5055,19 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -fork-ts-checker-webpack-plugin@0.4.15: - version "0.4.15" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-0.4.15.tgz#7cd9f94f3dd58cd1fe8f953f876e72090eda3f6d" - integrity sha512-qNYuygh2GxXehBvQZ5rI5YlQFn+7ZV6kmkyD9Sgs33dWl73NZdUOB5aCp8v0EXJn176AhPrZP8YCMT3h01fs+g== +fork-ts-checker-webpack-plugin@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz#a1642c0d3e65f50c2cc1742e9c0a80f441f86b19" + integrity sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ== dependencies: babel-code-frame "^6.22.0" chalk "^2.4.1" - chokidar "^2.0.4" - lodash "^4.17.11" + chokidar "^3.3.0" micromatch "^3.1.10" minimatch "^3.0.4" - resolve "^1.5.0" + semver "^5.6.0" tapable "^1.0.0" + worker-rpc "^0.1.0" form-data@~2.3.2: version "2.3.3" @@ -5093,12 +5121,12 @@ fs-extra@6.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== dependencies: - graceful-fs "^4.1.2" + graceful-fs "^4.2.0" jsonfile "^4.0.0" universalify "^0.1.0" @@ -5137,6 +5165,11 @@ fsevents@^2.0.6: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== +fsevents@~2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" + integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + function-bind@^1.0.2, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -5207,19 +5240,17 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -gh-pages@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-2.1.1.tgz#5be70a92f9cb70404bafabd8bb149c0e9a8c264b" - integrity sha512-yNW2SFp9xGRP/8Sk2WXuLI/Gn92oOL4HBgudn6PsqAnuWT90Y1tozJoTfX1WdrDSW5Rb90kLVOf5mm9KJ/2fDw== +gh-pages@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-2.2.0.tgz#74ebeaca8d2b9a11279dcbd4a39ddfff3e6caa24" + integrity sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA== dependencies: async "^2.6.1" commander "^2.18.0" email-addresses "^3.0.1" filenamify-url "^1.0.0" - fs-extra "^7.0.0" + fs-extra "^8.1.0" globby "^6.1.0" - graceful-fs "^4.1.11" - rimraf "^2.6.2" glob-parent@^3.1.0: version "3.1.0" @@ -5236,10 +5267,12 @@ glob-parent@^5.0.0: dependencies: is-glob "^4.0.1" -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= +glob-parent@^5.1.0, glob-parent@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" + integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== + dependencies: + is-glob "^4.0.1" glob@7.0.x: version "7.0.6" @@ -5298,6 +5331,18 @@ globals@^9.18.0: resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== +globby@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.0.tgz#56fd0e9f0d4f8fb0c456f1ab0dee96e1380bc154" + integrity sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -5321,20 +5366,6 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" -globby@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" - integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^1.0.2" - dir-glob "^2.2.2" - fast-glob "^2.2.6" - glob "^7.1.3" - ignore "^4.0.3" - pify "^4.0.1" - slash "^2.0.0" - globjoin@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" @@ -5352,6 +5383,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== +graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -5367,6 +5403,17 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== +handlebars@4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.2.tgz#01127b3840156a0927058779482031afe0e730d7" + integrity sha512-4PwqDL2laXtTWZghzzCtunQUTLbo31pcCJrd/B/9JP8XbhVzpS5ZXuKqlOzsd1rtcaLo4KqAn8nl8mkknS4MHw== + dependencies: + neo-async "^2.6.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + handlebars@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" @@ -5391,6 +5438,11 @@ har-validator@~5.1.0: ajv "^6.5.5" har-schema "^2.0.0" +hard-rejection@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -5472,6 +5524,14 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasha@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.1.0.tgz#dd05ccdfcfe7dab626247ce2a58efe461922f4ca" + integrity sha512-OFPDWmzPN1l7atOV1TgBVmNtBxaIysToK6Ve9DK+vT6pYuklw/nPNT+HJbZi0KDcI6vWB+9tgvZ5YD7fA3CXcA== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -5670,11 +5730,6 @@ ignore@^3.3.5: resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== -ignore@^4.0.3: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" @@ -5745,6 +5800,11 @@ indent-string@^3.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -5896,7 +5956,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-binary-path@^2.1.0: +is-binary-path@^2.1.0, is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== @@ -6032,7 +6092,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -6129,6 +6189,11 @@ is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + is-symbol@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" @@ -7141,14 +7206,6 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - lru-cache@^4.0.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -7237,10 +7294,10 @@ map-obj@^1.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= -map-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" - integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= +map-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5" + integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g== map-visit@^1.0.0: version "1.0.0" @@ -7314,20 +7371,22 @@ memorystream@^0.3.1: resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= -meow@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" - integrity sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig== - dependencies: - camelcase-keys "^4.0.0" - decamelize-keys "^1.0.0" - loud-rejection "^1.0.0" - minimist-options "^3.0.1" - normalize-package-data "^2.3.4" - read-pkg-up "^3.0.0" - redent "^2.0.0" - trim-newlines "^2.0.0" - yargs-parser "^10.0.0" +meow@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-6.0.0.tgz#949196fdf21d979379e3bdccb0411e60f8cffd93" + integrity sha512-x4rYsjigPBDAxY+BGuK83YLhUIqui5wYyZoqb6QJCUOs+0fiYq+i/NV4Jt8OgIfObZFxG9iTyvLDu4UTohGTFw== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.1.1" + decamelize-keys "^1.1.0" + hard-rejection "^2.0.0" + minimist-options "^4.0.1" + normalize-package-data "^2.5.0" + read-pkg-up "^7.0.0" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.8.1" + yargs-parser "^16.1.0" merge-descriptors@1.0.1: version "1.0.1" @@ -7339,16 +7398,21 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.4.tgz#c9269589e6885a60cf80605d9522d4b67ca646e3" - integrity sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A== +merge2@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +microevent.ts@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" + integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== + micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -7416,6 +7480,11 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-indent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256" + integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY= + mini-css-extract-plugin@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz#81d41ec4fe58c713a96ad7c723cdb2d0bd4d70e1" @@ -7443,10 +7512,10 @@ minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist-options@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" - integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ== +minimist-options@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.0.2.tgz#29c4021373ded40d546186725e57761e4b1984a7" + integrity sha512-seq4hpWkYSUh1y7NXxzucwAN9yVlBc3Upgdjz8vLCP97jG8kaOmzYrVH/m7tQ1NYD1wdtZbSLfdy4zFmRWuc/w== dependencies: arrify "^1.0.1" is-plain-obj "^1.1.0" @@ -7712,13 +7781,6 @@ node-releases@^1.1.25: dependencies: semver "^5.3.0" -node-releases@^1.1.40: - version "1.1.41" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.41.tgz#57674a82a37f812d18e3b26118aefaf53a00afed" - integrity sha512-+IctMa7wIs8Cfsa8iYzeaLTFwv5Y4r5jZud+4AnfymzeEXKBCavFX0KBgzVaPVqf0ywa6PrO8/b+bPqdwjGBSg== - dependencies: - semver "^6.3.0" - node-releases@^1.1.42: version "1.1.43" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.43.tgz#2c6ca237f88ce11d49631f11190bb01f8d0549f2" @@ -7726,6 +7788,13 @@ node-releases@^1.1.42: dependencies: semver "^6.3.0" +node-releases@^1.1.44: + version "1.1.45" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.45.tgz#4cf7e9175d71b1317f15ffd68ce63bce1d53e9f2" + integrity sha512-cXvGSfhITKI8qsV116u2FTzH5EWZJfgG7d4cpqwF8I8+1tWpD6AsvvGRKq2onR0DNj1jfqsjkXZsm14JMS7Cyg== + dependencies: + semver "^6.3.0" + nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -7734,7 +7803,7 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" -normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0: +normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -7751,7 +7820,7 @@ normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -8366,6 +8435,11 @@ picomatch@^2.0.4, picomatch@^2.0.5: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== +picomatch@^2.0.7: + version "2.2.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" + integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== + pidtree@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b" @@ -8584,7 +8658,7 @@ postcss@7.0.17, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17 source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7.0.21, postcss@^7.0.23: +postcss@^7.0.21: version "7.0.23" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.23.tgz#9f9759fad661b15964f3cfc3140f66f1e05eadc1" integrity sha512-hOlMf3ouRIFXD+j2VJecwssTwbvsPGJVMzupptg+85WA+i7MwyrydmQAgY3R+m0Bc0exunhbJmijy8u8+vufuQ== @@ -8593,6 +8667,15 @@ postcss@^7.0.21, postcss@^7.0.23: source-map "^0.6.1" supports-color "^6.1.0" +postcss@^7.0.26: + version "7.0.26" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.26.tgz#5ed615cfcab35ba9bbb82414a4fa88ea10429587" + integrity sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -8603,11 +8686,6 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prettier@1.18.2: - version "1.18.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" - integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== - prettier@1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" @@ -8781,10 +8859,10 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== -quick-lru@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" - integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== ramda@0.24.1: version "0.24.1" @@ -8872,14 +8950,6 @@ read-package-tree@5.3.1: readdir-scoped-modules "^1.0.0" util-promisify "^2.1.0" -read-pkg-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" - integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= - dependencies: - find-up "^2.0.0" - read-pkg "^3.0.0" - read-pkg-up@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" @@ -8888,6 +8958,15 @@ read-pkg-up@^4.0.0: find-up "^3.0.0" read-pkg "^3.0.0" +read-pkg-up@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -8897,6 +8976,16 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -8945,6 +9034,13 @@ readdirp@^3.1.1: dependencies: picomatch "^2.0.4" +readdirp@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17" + integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ== + dependencies: + picomatch "^2.0.7" + realpath-native@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" @@ -8952,13 +9048,13 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -redent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" - integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo= +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== dependencies: - indent-string "^3.0.0" - strip-indent "^2.0.0" + indent-string "^4.0.0" + strip-indent "^3.0.0" reflect-metadata@^0.1.2: version "0.1.13" @@ -9247,7 +9343,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.5.0: +resolve@1.x, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.3.2: version "1.12.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== @@ -9285,6 +9381,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +reusify@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -9326,6 +9427,11 @@ run-async@^2.2.0: dependencies: is-promise "^2.1.0" +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -9340,10 +9446,10 @@ rxjs@6.4.0: dependencies: tslib "^1.9.0" -rxjs@6.5.3: - version "6.5.3" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" - integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== +rxjs@6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" + integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== dependencies: tslib "^1.9.0" @@ -10116,10 +10222,12 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" - integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" strip-json-comments@2.0.1, strip-json-comments@~2.0.1: version "2.0.1" @@ -10183,12 +10291,12 @@ stylelint-scss@3.13.0: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.2" -stylelint@12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-12.0.0.tgz#2e8613675f7be11769ce474f45137fdf7751380a" - integrity sha512-TwqtATrFOT07SPlUGyHN7tVhWqxwitn5BlAvyBQy/ekA+Nwu4mLU9L1dvGQPNxHUBLowjvkSW18QzHHR6/FVVQ== +stylelint@13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.0.0.tgz#532007f7154c1a5ed14245d857a5884316f5111f" + integrity sha512-6sjgOJbM3iLhnUtmRO0J1vvxie9VnhIZX/2fCehjylv9Gl9u0ytehGCTm9Lhw2p1F8yaNZn5UprvhCB8C3g/Tg== dependencies: - autoprefixer "^9.7.1" + autoprefixer "^9.7.3" balanced-match "^1.0.0" chalk "^3.0.0" cosmiconfig "^6.0.0" @@ -10197,7 +10305,7 @@ stylelint@12.0.0: file-entry-cache "^5.0.1" get-stdin "^7.0.0" global-modules "^2.0.0" - globby "^9.2.0" + globby "^11.0.0" globjoin "^0.1.4" html-tags "^3.1.0" ignore "^5.1.4" @@ -10208,10 +10316,10 @@ stylelint@12.0.0: lodash "^4.17.15" log-symbols "^3.0.0" mathml-tag-names "^2.1.1" - meow "^5.0.0" + meow "^6.0.0" micromatch "^4.0.2" normalize-selector "^0.2.0" - postcss "^7.0.21" + postcss "^7.0.26" postcss-html "^0.36.0" postcss-jsx "^0.36.3" postcss-less "^3.1.4" @@ -10531,10 +10639,10 @@ tree-kill@1.2.1: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a" integrity sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q== -trim-newlines@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" - integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= +trim-newlines@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" + integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== trim-repeated@^1.0.0: version "1.0.0" @@ -10563,10 +10671,10 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e" integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q== -ts-jest@24.2.0: - version "24.2.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.2.0.tgz#7abca28c2b4b0a1fdd715cd667d65d047ea4e768" - integrity sha512-Yc+HLyldlIC9iIK8xEN7tV960Or56N49MDP7hubCZUeI7EbIOTsas6rXCMB4kQjLACJ7eDOF4xWEO5qumpKsag== +ts-jest@24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.3.0.tgz#b97814e3eab359ea840a1ac112deae68aa440869" + integrity sha512-Hb94C/+QRIgjVZlJyiWwouYUF+siNJHJHknyspaOcZ+OQAIdFG/UrdQVXw/0B8Z3No34xkUXZJpOTy9alOWdVQ== dependencies: bs-logger "0.x" buffer-from "1.x" @@ -10616,16 +10724,16 @@ ts-loader@5.4.5: micromatch "^3.1.4" semver "^5.0.1" -ts-node@8.5.4: - version "8.5.4" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.5.4.tgz#a152add11fa19c221d0b48962c210cf467262ab2" - integrity sha512-izbVCRV68EasEPQ8MSIGBNK9dc/4sYJJKYA+IarMQct1RtEot6Xp0bXuClsbUSnKpg50ho+aOAx8en5c+y4OFw== +ts-node@8.6.2: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35" + integrity sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg== dependencies: arg "^4.1.0" diff "^4.0.1" make-error "^1.1.1" source-map-support "^0.5.6" - yn "^3.0.0" + yn "3.1.1" tsconfig-paths-webpack-plugin@3.2.0: version "3.2.0" @@ -10707,6 +10815,16 @@ type-fest@^0.5.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw== +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.0, type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -11319,6 +11437,13 @@ worker-plugin@3.2.0: dependencies: loader-utils "^1.1.0" +worker-rpc@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" + integrity sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg== + dependencies: + microevent.ts "~0.1.1" + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -11440,7 +11565,7 @@ yargs-parser@10.0.0: dependencies: camelcase "^4.1.0" -yargs-parser@10.x, yargs-parser@^10.0.0: +yargs-parser@10.x: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== @@ -11463,6 +11588,14 @@ yargs-parser@^13.0.0, yargs-parser@^13.1.1: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^16.1.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-16.1.0.tgz#73747d53ae187e7b8dbe333f95714c76ea00ecf1" + integrity sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + yargs-parser@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" @@ -11554,7 +11687,7 @@ yauzl@2.4.1: dependencies: fd-slicer "~1.0.1" -yn@^3.0.0: +yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==