ui service, background images transition

This commit is contained in:
wagonsoftware 2022-11-11 17:08:45 +03:00
parent 5d15b7648d
commit f4ed2a8463
20 changed files with 238 additions and 39 deletions

View File

@ -1,7 +1,8 @@
import { Component, OnInit, AfterViewInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router, RouterEvent } from '@angular/router'; import { Router } from '@angular/router';
import { LocalStorage, LocalStorageService } from 'ngx-webstorage' import { LocalStorage } from 'ngx-webstorage'
import { GoWebViewInit } from './services/go'; import { GoWebViewInit } from './services/go';
import { UiService } from './services/ui.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@ -12,7 +13,10 @@ import { GoWebViewInit } from './services/go';
export class AppComponent implements OnInit{ export class AppComponent implements OnInit{
title = 'go-web'; title = 'go-web';
constructor(private router: Router, private storage: LocalStorageService) { } constructor(
private router: Router,
private ui: UiService
) { }
@LocalStorage('theme', 'dark') @LocalStorage('theme', 'dark')
public theme!: string public theme!: string
@ -23,17 +27,11 @@ export class AppComponent implements OnInit{
console.log("Navigating to route:" + event.detail) console.log("Navigating to route:" + event.detail)
}) })
this.setTheme(this.theme) this.ui.setTheme(this.theme)
this.storage.observe('theme').subscribe(value => { this.ui.detectThemeChange()
this.setTheme(this.theme)
})
} }
ngAfterViewInit(){ ngAfterViewInit(){
GoWebViewInit() GoWebViewInit()
} }
setTheme (theme: string) {
document.querySelector('html')?.setAttribute('data-theme', theme)
}
} }

View File

@ -14,6 +14,8 @@
box-sizing: border-box; box-sizing: border-box;
backdrop-filter: blur(64px); backdrop-filter: blur(64px);
overflow-y: auto; overflow-y: auto;
z-index: 20;
position: relative;
.menu-search-panel { .menu-search-panel {
display: flex; display: flex;

View File

@ -1,4 +1,6 @@
<div class="main-wrapper" [style.background-image]="'url(' + image + ')'"> <div class="main-wrapper">
<div class="backdrop-image">
</div>
<app-main-menu></app-main-menu> <app-main-menu></app-main-menu>
<div class="content"> <div class="content">
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -5,27 +5,54 @@
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
img {
position: absolute;
height: 100%;
top: 0;
left: 50%;
translate: -50% 0%;
}
.main-wrapper { .main-wrapper {
display: flex; display: flex;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
background-size: cover; background-size: cover;
position: inherit; position: inherit;
}
.content { .content {
flex: 1; flex: 1;
background-color: var(--sk-right-content); background-color: var(--sk-right-content);
display: flex; display: flex;
position: relative;
z-index: 20;
backdrop-filter: blur(2rem); backdrop-filter: blur(2rem);
// overflow-y: auto; // overflow-y: auto;
} }
} }
}
:host::ng-deep {
.backdrop-image {
width: 100vw;
height: 100vh;
position: absolute;
background-color: var(--sk-background);
z-index: 0;
left: 0;
top: 0;
.layer {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
z-index: 10;
transition: opacity 3s linear;
&.hidden {
opacity: 0;
}
&.secondary {
z-index: 5;
}
}
}
}

View File

@ -1,4 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, ElementRef, inject, OnInit } from '@angular/core';
import { asapScheduler, asyncScheduler, of, queueScheduler, scheduled, Subscription } from 'rxjs';
import { UiService } from 'src/app/services/ui.service';
@Component({ @Component({
selector: 'app-main-root', selector: 'app-main-root',
@ -6,9 +8,91 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./main-root.component.scss'], styleUrls: ['./main-root.component.scss'],
}) })
export class MainRootComponent implements OnInit { export class MainRootComponent implements OnInit {
image: string = 'assets/games/minecraft/backgrounds/7.webp'; image: string = 'assets/games/minecraft/backgrounds/compressed/7.webp';
uiService = inject(UiService)
subs: Map<string, Subscription> = new Map()
timer: number = 0
constructor() {} queue: string[] = []
transitionEnded: boolean = true
ngOnInit(): void {} constructor(
private element: ElementRef<HTMLElement>
) {}
ngOnInit(): void {
this.observeImageChange()
this.setImage(this.image, true)
}
ngOnDestroy(): void {
//Called once, before the instance is destroyed.
//Add 'implements OnDestroy' to the class.
this.subs.forEach(sub => sub.unsubscribe())
}
observeImageChange() {
this.subs.set(
'image-change',
this.uiService.imageChange.subscribe({
next: (url: string) => {
this.queueImageChange(url)
// this.setImage(url)
}
})
)
}
queueImageChange(url: string) {
if (this.queue.length > 0 || !this.transitionEnded) {
this.queue.push(url)
return
}
this.setImage(url)
}
next() {
if (!this.queue.length) return
this.setImage(this.queue.splice(0, 1)[0])
}
setImage(url: string, init = false) {
if (!init)
this.transitionEnded = false
this.image = url
let wrapper = this.element.nativeElement.querySelector<HTMLElement>('.backdrop-image')
if (!wrapper) return
const image = document.createElement('img')
image.onload = () => {
if (!wrapper || init) return
let current = wrapper.querySelector('.layer:not(.secondary)'), secondary = wrapper.querySelectorAll('.layer.secondary')
current?.classList.add('hidden')
clearTimeout(this.timer)
this.timer = window.setTimeout(() => {
secondary.forEach(value => value.classList.remove('secondary'))
current?.remove()
this.transitionEnded = true
this.next()
}, 3200)
}
image.src = url
const layer = document.createElement('div')
layer.classList.add('layer')
if (!init)
layer.classList.add('secondary')
layer.style.backgroundImage = `url(${url})`
wrapper.append(layer)
}
} }

View File

@ -20,7 +20,7 @@
</ng-template> </ng-template>
<ng-template #field let-field="field"> <ng-template #field let-field="field">
<div class="field"> <div class="field" [ngClass]="{'filled': field.large}">
<skirda-subtitle>{{field.label}}</skirda-subtitle> <skirda-subtitle>{{field.label}}</skirda-subtitle>
<skirda-text *ngIf="!field.large">{{field.value}}</skirda-text> <skirda-text *ngIf="!field.large">{{field.value}}</skirda-text>
<skirda-heading size="4" *ngIf="field.large">{{field.value}}</skirda-heading> <skirda-heading size="4" *ngIf="field.large">{{field.value}}</skirda-heading>

View File

@ -7,7 +7,7 @@
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
display: flex; display: flex;
gap: var(--sk-gap-xxxl); gap: var(--sk-gap-m);
padding-bottom: var(--sk-gap-xxxl); padding-bottom: var(--sk-gap-xxxl);
} }
@ -16,6 +16,12 @@
flex-direction: column; flex-direction: column;
gap: var(--sk-gap-s); gap: var(--sk-gap-s);
&.filled {
background-color: rgba(255, 255, 255, 0.25);
padding: 0.75rem 1.25rem;
border-radius: 0.5rem;
}
skirda-text { skirda-text {
line-height: 140%; line-height: 140%;
} }

View File

@ -1,10 +1,12 @@
import { OnDestroy, TemplateRef, ViewChild } from '@angular/core'; import { inject, OnDestroy, TemplateRef, ViewChild } from '@angular/core';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { PopupConfiguration } from 'projects/ui/src/lib/popup/popup.class'; import { PopupConfiguration } from 'projects/ui/src/lib/popup/popup.class';
import { PopupDirective } from 'projects/ui/src/lib/popup/popup.directive'; import { PopupDirective } from 'projects/ui/src/lib/popup/popup.directive';
import { PopupService } from 'projects/ui/src/lib/popup/popup.service'; import { PopupService } from 'projects/ui/src/lib/popup/popup.service';
import { first, Subscription } from 'rxjs'; import { first, Subscription } from 'rxjs';
import { Game } from 'src/app/interfaces/game.interface'; import { Game } from 'src/app/interfaces/game.interface';
import { UiService } from 'src/app/services/ui.service';
@Component({ @Component({
selector: 'app-game-page', selector: 'app-game-page',
@ -13,6 +15,7 @@ import { Game } from 'src/app/interfaces/game.interface';
}) })
export class GamePageComponent implements OnInit, OnDestroy { export class GamePageComponent implements OnInit, OnDestroy {
// @ViewChild(PopupDirective, { static: true }) popupPortal!: PopupDirective; // @ViewChild(PopupDirective, { static: true }) popupPortal!: PopupDirective;
uiService = inject(UiService)
subs: Map<string, Subscription> = new Map(); subs: Map<string, Subscription> = new Map();
game: Game = { game: Game = {
gameId: 'minecraft', gameId: 'minecraft',
@ -24,14 +27,43 @@ export class GamePageComponent implements OnInit, OnDestroy {
currentSection: string = 'overview'; currentSection: string = 'overview';
friends: string[] = ['svensken', 'cyberdream', 'e11te']; friends: string[] = ['svensken', 'cyberdream', 'e11te'];
constructor() {} // private popup: PopupService tempImages = [
'assets/games/minecraft/backgrounds/compressed/0.webp',
'assets/games/minecraft/backgrounds/compressed/1.webp',
'assets/games/minecraft/backgrounds/compressed/2.webp',
'assets/games/minecraft/backgrounds/compressed/3.webp',
'assets/games/minecraft/backgrounds/compressed/4.webp',
'assets/games/minecraft/backgrounds/compressed/5.webp',
'assets/games/minecraft/backgrounds/compressed/6.webp',
'assets/games/minecraft/backgrounds/compressed/7.webp',
'assets/games/minecraft/backgrounds/compressed/8.webp',
]
ngOnInit(): void {} constructor(
private router: Router
) {} // private popup: PopupService
ngOnInit(): void {
this.observeNavigation()
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.subs.forEach((value) => value.unsubscribe()); this.subs.forEach((value) => value.unsubscribe());
} }
observeNavigation() {
this.router.events.subscribe({
next: (event) => {
if (event instanceof NavigationEnd)
this.uiService.setImage(this.getRandomImage())
}
})
}
getRandomImage() {
return this.tempImages[Math.floor(0 + Math.random() * ((this.tempImages.length - 1) + 1 - 0))]
}
// openPopup(template?: TemplateRef<any>) { // openPopup(template?: TemplateRef<any>) {
// this.subs.set( // this.subs.set(
// 'popup-events', // 'popup-events',

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UiService } from './ui.service';
describe('UiService', () => {
let service: UiService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UiService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,27 @@
import { EventEmitter, inject, Injectable, Output } from '@angular/core';
import { LocalStorageService } from 'ngx-webstorage';
@Injectable({
providedIn: 'root'
})
export class UiService {
storage = inject(LocalStorageService)
@Output() imageChange: EventEmitter<string> = new EventEmitter()
constructor() { }
setTheme (theme: string) {
document.querySelector('html')?.setAttribute('data-theme', theme)
}
detectThemeChange() {
this.storage.observe('theme').subscribe(value => {
this.setTheme(value)
})
}
setImage(url: string) {
this.imageChange.emit(url)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

View File

@ -1,6 +1,6 @@
@use 'src/light'; @use 'src/light';
@use 'src/dark'; @use 'src/dark';
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700;800;900&display=swap');
html, body { html, body {
background-color: var(--sk-background); background-color: var(--sk-background);
@ -9,10 +9,12 @@ html, body {
margin: 0; margin: 0;
font-family: var(--font); font-family: var(--font);
user-select: none; user-select: none;
letter-spacing: 0.04rem;
} }
button, input, textarea { button, input, textarea {
font-family: var(--font); font-family: var(--font);
letter-spacing: 0.04rem;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@ -36,12 +38,15 @@ html[data-theme="dark"]:root {
} }
:root { :root {
--font: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; --font: 'Inter Tight', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
} }
/* Works on Firefox */ /* Works on Firefox */
* { * {
scrollbar-width: thin; scrollbar-width: thin;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
// cursor: default;
} }
*::-webkit-scrollbar { *::-webkit-scrollbar {