ui service, background images transition
@ -1,7 +1,8 @@
|
||||
import { Component, OnInit, AfterViewInit } from '@angular/core';
|
||||
import { Router, RouterEvent } from '@angular/router';
|
||||
import { LocalStorage, LocalStorageService } from 'ngx-webstorage'
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { LocalStorage } from 'ngx-webstorage'
|
||||
import { GoWebViewInit } from './services/go';
|
||||
import { UiService } from './services/ui.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@ -12,7 +13,10 @@ import { GoWebViewInit } from './services/go';
|
||||
export class AppComponent implements OnInit{
|
||||
title = 'go-web';
|
||||
|
||||
constructor(private router: Router, private storage: LocalStorageService) { }
|
||||
constructor(
|
||||
private router: Router,
|
||||
private ui: UiService
|
||||
) { }
|
||||
|
||||
@LocalStorage('theme', 'dark')
|
||||
public theme!: string
|
||||
@ -23,17 +27,11 @@ export class AppComponent implements OnInit{
|
||||
console.log("Navigating to route:" + event.detail)
|
||||
})
|
||||
|
||||
this.setTheme(this.theme)
|
||||
this.storage.observe('theme').subscribe(value => {
|
||||
this.setTheme(this.theme)
|
||||
})
|
||||
this.ui.setTheme(this.theme)
|
||||
this.ui.detectThemeChange()
|
||||
}
|
||||
|
||||
ngAfterViewInit(){
|
||||
GoWebViewInit()
|
||||
}
|
||||
|
||||
setTheme (theme: string) {
|
||||
document.querySelector('html')?.setAttribute('data-theme', theme)
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
box-sizing: border-box;
|
||||
backdrop-filter: blur(64px);
|
||||
overflow-y: auto;
|
||||
z-index: 20;
|
||||
position: relative;
|
||||
|
||||
.menu-search-panel {
|
||||
display: flex;
|
||||
|
@ -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>
|
||||
<div class="content">
|
||||
<router-outlet></router-outlet>
|
||||
|
@ -5,27 +5,54 @@
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
translate: -50% 0%;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-size: cover;
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
background-color: var(--sk-right-content);
|
||||
display: flex;
|
||||
backdrop-filter: blur(2rem);
|
||||
// overflow-y: auto;
|
||||
.content {
|
||||
flex: 1;
|
||||
background-color: var(--sk-right-content);
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
backdrop-filter: blur(2rem);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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({
|
||||
selector: 'app-main-root',
|
||||
@ -6,9 +8,91 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./main-root.component.scss'],
|
||||
})
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template #field let-field="field">
|
||||
<div class="field">
|
||||
<div class="field" [ngClass]="{'filled': field.large}">
|
||||
<skirda-subtitle>{{field.label}}</skirda-subtitle>
|
||||
<skirda-text *ngIf="!field.large">{{field.value}}</skirda-text>
|
||||
<skirda-heading size="4" *ngIf="field.large">{{field.value}}</skirda-heading>
|
||||
|
@ -7,7 +7,7 @@
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: var(--sk-gap-xxxl);
|
||||
gap: var(--sk-gap-m);
|
||||
padding-bottom: var(--sk-gap-xxxl);
|
||||
}
|
||||
|
||||
@ -16,6 +16,12 @@
|
||||
flex-direction: column;
|
||||
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 {
|
||||
line-height: 140%;
|
||||
}
|
||||
|
@ -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 { NavigationEnd, Router } from '@angular/router';
|
||||
import { PopupConfiguration } from 'projects/ui/src/lib/popup/popup.class';
|
||||
import { PopupDirective } from 'projects/ui/src/lib/popup/popup.directive';
|
||||
import { PopupService } from 'projects/ui/src/lib/popup/popup.service';
|
||||
import { first, Subscription } from 'rxjs';
|
||||
import { Game } from 'src/app/interfaces/game.interface';
|
||||
import { UiService } from 'src/app/services/ui.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-game-page',
|
||||
@ -13,6 +15,7 @@ import { Game } from 'src/app/interfaces/game.interface';
|
||||
})
|
||||
export class GamePageComponent implements OnInit, OnDestroy {
|
||||
// @ViewChild(PopupDirective, { static: true }) popupPortal!: PopupDirective;
|
||||
uiService = inject(UiService)
|
||||
subs: Map<string, Subscription> = new Map();
|
||||
game: Game = {
|
||||
gameId: 'minecraft',
|
||||
@ -24,14 +27,43 @@ export class GamePageComponent implements OnInit, OnDestroy {
|
||||
currentSection: string = 'overview';
|
||||
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 {
|
||||
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>) {
|
||||
// this.subs.set(
|
||||
// 'popup-events',
|
||||
|
16
src/app/services/ui.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
27
src/app/services/ui.service.ts
Normal 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)
|
||||
}
|
||||
}
|
BIN
src/assets/games/minecraft/backgrounds/compressed/0.webp
Normal file
After Width: | Height: | Size: 892 KiB |
BIN
src/assets/games/minecraft/backgrounds/compressed/1.webp
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
src/assets/games/minecraft/backgrounds/compressed/2.webp
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
src/assets/games/minecraft/backgrounds/compressed/3.webp
Normal file
After Width: | Height: | Size: 254 KiB |
BIN
src/assets/games/minecraft/backgrounds/compressed/4.webp
Normal file
After Width: | Height: | Size: 188 KiB |
BIN
src/assets/games/minecraft/backgrounds/compressed/5.webp
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
src/assets/games/minecraft/backgrounds/compressed/6.webp
Normal file
After Width: | Height: | Size: 118 KiB |
BIN
src/assets/games/minecraft/backgrounds/compressed/7.webp
Normal file
After Width: | Height: | Size: 264 KiB |
BIN
src/assets/games/minecraft/backgrounds/compressed/8.webp
Normal file
After Width: | Height: | Size: 239 KiB |
@ -1,6 +1,6 @@
|
||||
@use 'src/light';
|
||||
@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 {
|
||||
background-color: var(--sk-background);
|
||||
@ -9,10 +9,12 @@ html, body {
|
||||
margin: 0;
|
||||
font-family: var(--font);
|
||||
user-select: none;
|
||||
letter-spacing: 0.04rem;
|
||||
}
|
||||
|
||||
button, input, textarea {
|
||||
font-family: var(--font);
|
||||
letter-spacing: 0.04rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@ -36,12 +38,15 @@ html[data-theme="dark"]: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 */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-app-region: no-drag;
|
||||
// cursor: default;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
|