ngx-screen-saver
Version:
Screen saver component for Angular.
363 lines (348 loc) • 14 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Component, Input, NgModule } from '@angular/core';
import p5 from 'p5';
import { BehaviorSubject, merge, fromEvent, timer } from 'rxjs';
import { startWith, throttleTime, tap, switchMap } from 'rxjs/operators';
const reverseLoop = (arr, callback) => {
for (let i = arr.length - 1; i >= 0; i--) {
callback(arr[i], i);
}
};
class Star {
constructor(p5, x, y) {
this.p5 = p5;
this.x = x;
this.y = y;
this.MAX_SIZE = 5;
this.size = 1;
this.decay = false;
this.destroy = false;
this.growth = this.p5.random(15, 50) / 500;
}
update() {
if (this.decay === false) {
this.size = this.size + this.growth;
}
if (this.decay === true) {
this.size = this.size - this.growth;
}
if (this.size >= this.MAX_SIZE) {
this.decay = true;
}
if (this.size <= 0) {
this.destroy = true;
}
}
draw() {
this.p5.noStroke();
this.p5.fill(255);
this.p5.circle(this.x, this.y, this.size);
}
}
const MAX_STARS = 100;
const starsArr = [];
const stars = (p5) => {
const width = p5.windowWidth;
const height = p5.windowHeight;
let backgroundImage;
// PRELOAD
p5.preload = () => {
backgroundImage = p5.loadImage('assets/ngx-screen-saver/galaxy.jpg');
};
// SETUP
p5.setup = () => {
p5.createCanvas(width, height).addClass('ngx-screen-saver');
p5.image(backgroundImage, 0, 0);
for (let i = 0; i < MAX_STARS; i++) {
const star = new Star(p5, p5.random(0, width), p5.random(0, height));
starsArr.push(star);
}
};
// DRAW
p5.draw = () => {
p5.image(backgroundImage, 0, 0);
reverseLoop(starsArr, (star, idx) => {
star.update();
star.draw();
if (star.destroy) {
starsArr.splice(idx, 1);
const newStar = new Star(p5, p5.random(0, width), p5.random(0, height));
starsArr.push(newStar);
}
});
};
};
const dvd = (p5) => {
const width = p5.windowWidth;
const height = p5.windowHeight;
let dvdLogo;
let posX = 300;
let posY = 100;
let velX;
let velY;
// PRELOAD
p5.preload = () => {
dvdLogo = p5.loadImage('assets/ngx-screen-saver/dvd_logo.png');
};
// SETUP
p5.setup = () => {
p5.createCanvas(width, height).addClass('ngx-screen-saver');
p5.background(0);
velX = 7;
velY = 5;
};
// DRAW
p5.draw = () => {
p5.background(0);
p5.image(dvdLogo, posX, posY);
posX = posX + velX;
posY = posY + velY;
if (posX + dvdLogo.width >= width || posX <= 0) {
velX = -velX;
}
if (posY + dvdLogo.height >= height || posY <= 0) {
velY = -velY;
}
};
};
const randomHslFromRange = (p5, hue, margin) => {
let randomNumber = p5.random(hue - margin, hue + margin);
if (randomNumber < 0) {
randomNumber = 360 - randomNumber;
}
if (randomNumber > 360) {
randomNumber = randomNumber - 360;
}
p5.colorMode(p5.HSL);
return p5.color(randomNumber, 100, 50);
};
class Particle {
constructor(p5, pos, vel, size, color, rotation) {
this.p5 = p5;
this.pos = pos;
this.vel = vel;
this.size = size;
this.color = color;
this.rotation = rotation;
if (this.rotation) {
this.vel.rotate(this.rotation);
this.vel.setMag(this.p5.random(1, 2));
}
}
update() {
this.pos.add(this.vel);
}
draw() {
this.p5.noStroke();
this.p5.fill(this.color);
this.p5.circle(this.pos.x, this.pos.y, this.size);
}
}
class Firework {
constructor(p5, posX, maxHeight) {
this.p5 = p5;
this.posX = posX;
this.maxHeight = maxHeight;
this.pos = this.p5.createVector(this.posX, 0);
this.vel = this.p5.createVector(0, this.p5.random(1.5, 2.5));
this.randomHue = this.p5.random(0, 360);
this.phase = 'fly';
this.explosionParticlesCreated = false;
this.fireParticles = [];
this.explosionParticles = [];
}
update() {
this.p5.push();
this.p5.colorMode(this.p5.HSL);
if (this.phase === 'fly') {
this.pos.add(this.vel);
this.fireParticles.push(new Particle(this.p5, this.p5.createVector(this.pos.x + this.p5.random(-2, 2), this.pos.y + this.p5.random(-2, 2)), this.p5.createVector(0, 0), 3, randomHslFromRange(this.p5, 30, 20)));
if (this.fireParticles.length > 30) {
this.fireParticles.splice(0, 1);
}
if (this.pos.y >= this.maxHeight) {
this.phase = 'explode';
}
}
if (this.phase === 'explode') {
if (this.fireParticles.length > 0) {
this.fireParticles.splice(0, 1);
}
if (this.explosionParticlesCreated === false) {
for (let i = 0; i < 150; i++) {
const newParticle = new Particle(this.p5, this.pos.copy(), this.p5.createVector(1, 0), 5, randomHslFromRange(this.p5, this.randomHue, 20), this.p5.random(0, 360));
this.explosionParticles.push(newParticle);
}
this.explosionParticlesCreated = true;
}
if (this.explosionParticles.length <= 0) {
this.phase = 'dispose';
}
}
this.p5.pop();
}
draw() {
this.p5.noStroke();
if (this.fireParticles.length > 0) {
reverseLoop(this.fireParticles, (particle, idx) => {
particle.update();
particle.draw();
});
}
if (this.explosionParticles.length > 0) {
reverseLoop(this.explosionParticles, (particle, idx) => {
if (this.pos.dist(particle.pos) <= 50) {
particle.update();
particle.draw();
}
else {
this.explosionParticles.splice(idx, 1);
}
});
}
}
}
const fireworks = (p5) => {
const WIDTH = p5.windowWidth;
const HEIGHT = p5.windowHeight;
const MAX_FIREWORKS = 10;
const SAFE_MARGIN = 40;
const SAFE_SPACE = {
xStart: SAFE_MARGIN,
xEnd: WIDTH - SAFE_MARGIN,
yStart: 0,
yEnd: HEIGHT - SAFE_MARGIN,
};
const fireworksArr = [];
let backgroundImg;
// PRELOAD
p5.preload = () => {
backgroundImg = p5.loadImage('assets/ngx-screen-saver/fireworks_bg.jpg');
};
// SETUP
p5.setup = () => {
p5.createCanvas(WIDTH, HEIGHT).addClass('ngx-screen-saver');
p5.angleMode(p5.DEGREES);
backgroundImg.resize(WIDTH, HEIGHT);
for (let i = 0; i < MAX_FIREWORKS; i++) {
fireworksArr.push(new Firework(p5, p5.random(SAFE_SPACE.xStart, SAFE_SPACE.xEnd), p5.random(SAFE_SPACE.yStart, SAFE_SPACE.yEnd)));
}
};
// DRAW
p5.draw = () => {
const heightOffset = HEIGHT - backgroundImg.height;
p5.image(backgroundImg, 0, heightOffset);
p5.translate(0, HEIGHT); // moves the origin to bottom left
p5.scale(1, -1); // flips the y values so y increases "up"
reverseLoop(fireworksArr, (firework, idx) => {
firework.update();
firework.draw();
if (firework.phase === 'dispose') {
fireworksArr.splice(idx, 1);
fireworksArr.push(new Firework(p5, p5.random(SAFE_SPACE.xStart, SAFE_SPACE.xEnd), p5.random(SAFE_SPACE.yStart, SAFE_SPACE.yEnd)));
}
});
};
};
class IdleDetectionService {
constructor() {
this.isIdleSubject = new BehaviorSubject(false);
this.isIdle$ = this.isIdleSubject.asObservable();
this.activityEvents$ = merge(fromEvent(window, 'mousemove'), fromEvent(window, 'resize'), fromEvent(document, 'keydown'), fromEvent(document, 'mousedown'), fromEvent(document, 'touchstart'));
}
startIdleDetection(idleAfterMs) {
this.idleAfterMs = idleAfterMs;
this.idleDetectionSubscription = this.activityEvents$
.pipe(
// trigger idle detection before any user activity events
startWith(undefined), throttleTime(100), tap(() => {
this.isIdleSubject.next(false);
}), switchMap(() => {
return timer(this.idleAfterMs).pipe(tap(() => {
this.isIdleSubject.next(true);
}));
}))
.subscribe();
}
stopIdleDetection() {
this.idleDetectionSubscription?.unsubscribe();
}
}
IdleDetectionService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: IdleDetectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
IdleDetectionService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: IdleDetectionService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: IdleDetectionService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
class NgxScreenSaverComponent {
constructor(idleDetectionService) {
this.idleDetectionService = idleDetectionService;
this.idleAfterMs = 10000;
this.variant = 'fireworks';
this.opacity = 1;
this.zIndex = 1;
this.showScreenSaver = false;
this.screenSavers = {
stars,
dvd,
fireworks,
};
}
ngOnInit() {
document.documentElement.style.setProperty('--ngx-screen-saver-opacity', this.opacity.toString());
document.documentElement.style.setProperty('--ngx-screen-saver-z-index', this.zIndex.toString());
this.idleDetectionService.startIdleDetection(this.idleAfterMs);
this.isIdleSubscription = this.idleDetectionService.isIdle$.subscribe((isIdle) => {
if (isIdle === true) {
this.showScreenSaver = true;
this.screenSaver = new p5(this.screenSavers[this.variant], document.querySelector('ngx-screen-saver'));
}
else {
this.showScreenSaver = false;
this.screenSaver?.remove();
this.screenSaver = undefined;
}
});
}
ngOnDestroy() {
this.idleDetectionService.stopIdleDetection();
this.isIdleSubscription.unsubscribe();
}
}
NgxScreenSaverComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: NgxScreenSaverComponent, deps: [{ token: IdleDetectionService }], target: i0.ɵɵFactoryTarget.Component });
NgxScreenSaverComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.3", type: NgxScreenSaverComponent, selector: "ngx-screen-saver", inputs: { idleAfterMs: "idleAfterMs", variant: "variant", opacity: "opacity", zIndex: "zIndex" }, ngImport: i0, template: '', isInline: true, styles: ["::ng-deep :root{--ngx-screen-saver-opacity: 1;--ngx-screen-saver-z-index: 1}::ng-deep .ngx-screen-saver{position:fixed;top:0;left:0;opacity:var(--ngx-screen-saver-opacity);z-index:var(--ngx-screen-saver-z-index)}::ng-deep #p5_loading{display:none}\n"] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: NgxScreenSaverComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-screen-saver', template: '', styles: ["::ng-deep :root{--ngx-screen-saver-opacity: 1;--ngx-screen-saver-z-index: 1}::ng-deep .ngx-screen-saver{position:fixed;top:0;left:0;opacity:var(--ngx-screen-saver-opacity);z-index:var(--ngx-screen-saver-z-index)}::ng-deep #p5_loading{display:none}\n"] }]
}], ctorParameters: function () { return [{ type: IdleDetectionService }]; }, propDecorators: { idleAfterMs: [{
type: Input
}], variant: [{
type: Input
}], opacity: [{
type: Input
}], zIndex: [{
type: Input
}] } });
class NgxScreenSaverModule {
}
NgxScreenSaverModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: NgxScreenSaverModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
NgxScreenSaverModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: NgxScreenSaverModule, declarations: [NgxScreenSaverComponent], exports: [NgxScreenSaverComponent] });
NgxScreenSaverModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: NgxScreenSaverModule, imports: [[]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.3", ngImport: i0, type: NgxScreenSaverModule, decorators: [{
type: NgModule,
args: [{
declarations: [NgxScreenSaverComponent],
imports: [],
exports: [NgxScreenSaverComponent],
}]
}] });
/*
* Public API Surface of ngx-screen-saver
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxScreenSaverComponent, NgxScreenSaverModule };
//# sourceMappingURL=ngx-screen-saver.mjs.map