diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index ebf24621..3fff84f5 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -56,6 +56,7 @@ import { SidenavMenuComponent } from './components/sidenav-menu/sidenav-menu.com
import { SidenavComponent } from './components/sidenav/sidenav.component';
import { MediaTypePipe } from './pipes/media-type.pipe';
import { MarkdownPreviewComponent } from './components/markdown-preview/markdown-preview.component';
+import { GuideComponent } from './components/guide/guide.component';
@NgModule({
declarations: [
@@ -86,6 +87,7 @@ import { MarkdownPreviewComponent } from './components/markdown-preview/markdown
EntityFeatureSettingsLightsComponent,
EntityFeatureSettingsMeshComponent,
MarkdownPreviewComponent,
+ GuideComponent,
],
imports: [
BrowserModule,
diff --git a/src/app/components/guide/guide.component.html b/src/app/components/guide/guide.component.html
new file mode 100644
index 00000000..0b01e53a
--- /dev/null
+++ b/src/app/components/guide/guide.component.html
@@ -0,0 +1,7 @@
+
+
+
+
click & hold with 1 finger or left mouse button to rotate
+scroll mouse wheel or pinch to zoom in and out
+click & hold with 2 fingers or right mouse button to pan
+click to dismiss
diff --git a/src/app/components/guide/guide.component.scss b/src/app/components/guide/guide.component.scss
new file mode 100644
index 00000000..c5da5e1c
--- /dev/null
+++ b/src/app/components/guide/guide.component.scss
@@ -0,0 +1,47 @@
+:host {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ translate: -50% -50%;
+ pointer-events: none;
+
+ display: grid;
+ grid-template-columns: repeat(3, min(25vw, 25ch));
+ grid-template-rows: 1fr auto;
+ grid-column-gap: 1rem;
+ grid-row-gap: 2rem;
+ background-color: rgba(0, 0, 0, 0.7);
+ border-radius: 0.75rem;
+ padding: 1rem;
+ column-gap: 1rem;
+
+ color: white;
+
+ align-items: center;
+ justify-items: center;
+ text-align: center;
+
+ img {
+ max-height: 8rem;
+ max-width: 4rem;
+ }
+
+ transition-property: transform, filter, backdrop-filter;
+ transition-duration: 500ms;
+ transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
+ filter: opacity(0);
+ transform: translateY(1rem);
+ &.visible {
+ filter: opacity(1);
+ transform: translateY(0%);
+ }
+
+ p {
+ margin: 0;
+ }
+
+ p.dismiss {
+ grid-column: span 3;
+ font-weight: bold;
+ }
+}
diff --git a/src/app/components/guide/guide.component.spec.ts b/src/app/components/guide/guide.component.spec.ts
new file mode 100644
index 00000000..2c31f780
--- /dev/null
+++ b/src/app/components/guide/guide.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GuideComponent } from './guide.component';
+
+describe('GuideComponent', () => {
+ let component: GuideComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ GuideComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(GuideComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/guide/guide.component.ts b/src/app/components/guide/guide.component.ts
new file mode 100644
index 00000000..cb82c5c7
--- /dev/null
+++ b/src/app/components/guide/guide.component.ts
@@ -0,0 +1,33 @@
+import { AfterViewInit, Component, HostBinding } from '@angular/core';
+import { fromEvent } from 'rxjs';
+import { LoadingscreenhandlerService } from 'src/app/services/babylon/loadingscreen';
+
+@Component({
+ selector: 'app-guide',
+ templateUrl: './guide.component.html',
+ styleUrls: ['./guide.component.scss'],
+})
+export class GuideComponent implements AfterViewInit {
+ @HostBinding('class.visible')
+ get visible() {
+ return this.isVisible;
+ }
+
+ private isVisible = false;
+
+ constructor(private loadingScreenHandler: LoadingscreenhandlerService) {}
+
+ ngAfterViewInit(): void {
+ this.loadingScreenHandler.isLoading$.subscribe(isLoading => {
+ if (isLoading) return;
+
+ setTimeout(() => {
+ this.isVisible = true;
+ }, 1_000);
+ });
+
+ fromEvent(document, 'click').subscribe(() => {
+ this.isVisible = false;
+ });
+ }
+}
diff --git a/src/app/components/scene/scene.component.html b/src/app/components/scene/scene.component.html
index 4d761d78..52934a0d 100644
--- a/src/app/components/scene/scene.component.html
+++ b/src/app/components/scene/scene.component.html
@@ -3,6 +3,7 @@
+
diff --git a/src/app/services/babylon/loadingscreen.ts b/src/app/services/babylon/loadingscreen.ts
index 0f76e7e3..ecd99f28 100644
--- a/src/app/services/babylon/loadingscreen.ts
+++ b/src/app/services/babylon/loadingscreen.ts
@@ -30,8 +30,8 @@ export class LoadingScreen implements ILoadingScreen {
/**
* Function called to display the loading screen
*/
- public displayLoadingUI(): void {
- if (!this.loadingScreenHandler.isLoading) {
+ public async displayLoadingUI() {
+ if (!(await firstValueFrom(this.loadingScreenHandler.isLoading$))) {
this.loadingScreenHandler.updateOpacity('1');
}
}
@@ -39,8 +39,8 @@ export class LoadingScreen implements ILoadingScreen {
/**
* Function called to hide the loading screen
*/
- public hideLoadingUI(): void {
- if (this.loadingScreenHandler.isLoading) {
+ public async hideLoadingUI() {
+ if (await firstValueFrom(this.loadingScreenHandler.isLoading$)) {
// setTimeout of half a second to prevent pop-in
// of some bigger meshes
setTimeout(() => this.loadingScreenHandler.updateOpacity('0'), 500);
@@ -82,7 +82,7 @@ export class LoadingScreen implements ILoadingScreen {
}
import { Injectable } from '@angular/core';
-import { BehaviorSubject } from 'rxjs';
+import { BehaviorSubject, firstValueFrom } from 'rxjs';
@Injectable({
providedIn: 'root',
@@ -92,6 +92,8 @@ export class LoadingscreenhandlerService {
public opacity = this.OpacitySubject.asObservable();
private TextSubject = new BehaviorSubject('Loading');
public loadingText = this.TextSubject.asObservable();
+ private isLoading = new BehaviorSubject(false);
+ public isLoading$ = this.isLoading.asObservable();
private StyleSubject = new BehaviorSubject({
left: '0px',
@@ -102,18 +104,11 @@ export class LoadingscreenhandlerService {
public loadingStyle = this.StyleSubject.asObservable();
- public isLoading = false;
public backgroundColor = '#111111';
public logo = 'assets/img/kompakkt-icon.png';
- constructor() {}
-
public updateOpacity(newOpacity: string): void {
- if (parseFloat(newOpacity) > 0.5) {
- this.isLoading = true;
- } else {
- this.isLoading = false;
- }
+ this.isLoading.next(parseFloat(newOpacity) > 0.5);
this.OpacitySubject.next(newOpacity);
}
diff --git a/src/app/services/processing/processing.service.ts b/src/app/services/processing/processing.service.ts
index 27f2bb42..1680dd14 100644
--- a/src/app/services/processing/processing.service.ts
+++ b/src/app/services/processing/processing.service.ts
@@ -488,7 +488,8 @@ export class ProcessingService {
public async loadEntity(newEntity: IEntity, overrideUrl?: string) {
const baseURL = overrideUrl ?? this.baseUrl;
- if (!this.loadingScreenHandler.isLoading && newEntity.processed && newEntity.mediaType) {
+ const isLoading = await firstValueFrom(this.loadingScreenHandler.isLoading$);
+ if (!isLoading && newEntity.processed && newEntity.mediaType) {
if (!newEntity.dataSource.isExternal) {
this.entityMediaType = newEntity.mediaType;
await this.initialiseEntitySettingsData(newEntity);
diff --git a/src/assets/icons/guide/left_click_move.svg b/src/assets/icons/guide/left_click_move.svg
new file mode 100644
index 00000000..e6aef845
--- /dev/null
+++ b/src/assets/icons/guide/left_click_move.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/icons/guide/right_click_move.svg b/src/assets/icons/guide/right_click_move.svg
new file mode 100644
index 00000000..8fe5ef61
--- /dev/null
+++ b/src/assets/icons/guide/right_click_move.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/icons/guide/scroll_wheel.svg b/src/assets/icons/guide/scroll_wheel.svg
new file mode 100644
index 00000000..a575378c
--- /dev/null
+++ b/src/assets/icons/guide/scroll_wheel.svg
@@ -0,0 +1,6 @@
+