Added mobile phone generator and made selecting generators bettter, probably... #8

Merged
mastah merged 1 commits from feature/mobile into develop 2026-01-30 01:02:08 +01:00
9 changed files with 199 additions and 35 deletions
Showing only changes of commit 79bddf9f39 - Show all commits

View File

@@ -1,7 +1,6 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './components/app/app.component'; import { AppComponent } from './components/app/app.component';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@@ -14,6 +13,7 @@ import { NipComponent } from './components/nip/nip.component';
import { RegonComponent } from './components/regon/regon.component'; import { RegonComponent } from './components/regon/regon.component';
import { IbanComponent } from './components/iban/iban.component'; import { IbanComponent } from './components/iban/iban.component';
import { IdentityCardComponent } from './components/identitycard/identitycard.component'; import { IdentityCardComponent } from './components/identitycard/identitycard.component';
import { MobileComponent } from './components/mobile/mobile.component';
import {NgOptimizedImage} from "@angular/common"; import {NgOptimizedImage} from "@angular/common";
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
@@ -26,11 +26,11 @@ import { environment } from '../environments/environment';
NipComponent, NipComponent,
RegonComponent, RegonComponent,
IbanComponent, IbanComponent,
IdentityCardComponent IdentityCardComponent,
MobileComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
BrowserAnimationsModule,
FormsModule, FormsModule,
MatInputModule, MatInputModule,
MatIconModule, MatIconModule,

View File

@@ -1,12 +1,15 @@
<div class="gradient"></div> <div class="gradient"></div>
<div class="hover-zone"></div> <button class="settings-button" [class.initial-visible]="isInitialVisible" [class.controls-open]="showControls" (click)="showControls = !showControls">
<mat-icon>settings</mat-icon>
</button>
@if (showControls) { @if (showControls) {
<div class="controls" (mouseenter)="showControls = true"> <div class="controls">
<mat-button-toggle-group multiple> <mat-button-toggle-group vertical multiple>
<mat-button-toggle [checked]="showRegon" (change)="showRegon = $event.source.checked">REGON</mat-button-toggle> <mat-button-toggle [checked]="showRegon" (change)="showRegon = $event.source.checked">REGON</mat-button-toggle>
<mat-button-toggle [checked]="showNip" (change)="showNip = $event.source.checked">NIP</mat-button-toggle> <mat-button-toggle [checked]="showNip" (change)="showNip = $event.source.checked">NIP</mat-button-toggle>
<mat-button-toggle [checked]="showPesel" (change)="showPesel = $event.source.checked">PESEL</mat-button-toggle> <mat-button-toggle [checked]="showPesel" (change)="showPesel = $event.source.checked">PESEL</mat-button-toggle>
<mat-button-toggle [checked]="showIdentityCard" (change)="showIdentityCard = $event.source.checked">DOWÓD</mat-button-toggle> <mat-button-toggle [checked]="showIdentityCard" (change)="showIdentityCard = $event.source.checked">DOWÓD</mat-button-toggle>
<mat-button-toggle [checked]="showMobile" (change)="showMobile = $event.source.checked">MOBILNY</mat-button-toggle>
<mat-button-toggle [checked]="showIban" (change)="showIban = $event.source.checked">IBAN</mat-button-toggle> <mat-button-toggle [checked]="showIban" (change)="showIban = $event.source.checked">IBAN</mat-button-toggle>
</mat-button-toggle-group> </mat-button-toggle-group>
</div> </div>
@@ -24,6 +27,9 @@
@if (showIdentityCard) { @if (showIdentityCard) {
<app-identity-card></app-identity-card> <app-identity-card></app-identity-card>
} }
@if (showMobile) {
<app-mobile></app-mobile>
}
@if (showIban) { @if (showIban) {
<app-iban></app-iban> <app-iban></app-iban>
} }

View File

@@ -1,22 +1,55 @@
.hover-zone { .settings-button {
position: fixed; position: fixed;
top: 0; top: 10px;
left: 0; right: 10px;
right: 0; z-index: 11;
height: 10px; background: rgba(255, 255, 255, 0.15);
z-index: 9; backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
color: white;
cursor: pointer;
box-shadow: 0 4px 16px 0 rgba(0, 38, 135, 0.2);
transition: background-color 0.3s, transform 0.3s, opacity 0.5s ease-in-out;
padding: 0;
opacity: 0;
&.initial-visible, &:hover, &.controls-open {
opacity: 1;
}
&:hover {
background-color: rgba(255, 255, 255, 0.3);
transform: rotate(30deg);
}
mat-icon {
font-size: 24px;
width: 24px;
height: 24px;
}
} }
.controls { .controls {
display: flex; display: flex;
justify-content: center; flex-direction: column;
padding: 1em; align-items: flex-end;
padding: 0;
position: fixed; position: fixed;
top: 0; top: 60px;
left: 0; right: 10px;
right: 0;
z-index: 10; z-index: 10;
animation: slideIn 300ms ease-out forwards; animation: slideIn 300ms ease-out forwards;
pointer-events: none;
& > * {
pointer-events: auto;
}
mat-button-toggle-group { mat-button-toggle-group {
background: rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.15);
@@ -25,16 +58,19 @@
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 16px 0 rgba(0, 38, 135, 0.2); box-shadow: 0 4px 16px 0 rgba(0, 38, 135, 0.2);
overflow: hidden; overflow: hidden;
flex-direction: column;
} }
mat-button-toggle { mat-button-toggle {
color: white; color: white;
font-family: 'Comfortaa', sans-serif; font-family: 'Comfortaa', sans-serif;
border-left: 1px solid rgba(255, 255, 255, 0.2) !important; border-bottom: 1px solid rgba(255, 255, 255, 0.2) !important;
background-color: rgba(26, 35, 126, 0.4); border-left: none !important;
background-color: rgba(51, 98, 188, 0.4);
width: 100%;
&:first-child { &:last-child {
border-left: none !important; border-bottom: none !important;
} }
&.mat-button-toggle-checked { &.mat-button-toggle-checked {
@@ -49,7 +85,7 @@
} }
.generators { .generators {
padding-top: 2em; padding-top: 10px;
& > * { & > * {
display: block; display: block;
@@ -59,7 +95,7 @@
@keyframes slideIn { @keyframes slideIn {
from { from {
transform: translateY(-100%); transform: translateY(-20px);
opacity: 0; opacity: 0;
} }
to { to {

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, HostListener } from '@angular/core'; import { ChangeDetectorRef, Component, ElementRef, HostListener, OnInit } from '@angular/core';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
@@ -13,23 +13,29 @@ export class AppComponent implements OnInit {
showPesel = true; showPesel = true;
showIdentityCard = true; showIdentityCard = true;
showIban = true; showIban = true;
showMobile = true;
showControls = false; showControls = false;
isInitialVisible = true;
constructor() { constructor(private cdr: ChangeDetectorRef, private eRef: ElementRef) {
} }
ngOnInit(): void { ngOnInit(): void {
setTimeout(() => {
this.isInitialVisible = false;
this.cdr.detectChanges();
}, 3000);
} }
@HostListener('window:mousemove', ['$event']) @HostListener('document:click', ['$event'])
onMouseMove(event: MouseEvent) { clickout(event: Event) {
// Show if mouse is in the top 20px (hover zone) const settingsButton = this.eRef.nativeElement.querySelector('.settings-button');
if (event.clientY < 20) { const controls = this.eRef.nativeElement.querySelector('.controls');
this.showControls = true;
} if (this.showControls &&
// Hide if mouse moves below 120px settingsButton && !settingsButton.contains(event.target) &&
else if (event.clientY > 120) { controls && !controls.contains(event.target)) {
this.showControls = false; this.showControls = false;
} }
} }

View File

@@ -0,0 +1,19 @@
<div class="generator">
<div class="title">
<span>KOMÓRKA</span>
<div class="titleButtons">
</div>
</div>
<div class="container">
<input type="text" [formControl]="valueField" />
<div class="buttons">
<img id="mobileGenerateButton" ngSrc="assets/autorenew_black_24dp.svg" title="Generuj numer telefonu" alt="Generuj numer telefonu" style="margin-left: 1em;" (click)="generate()"
height="24" width="24"/>
<img id="mobileCopyButton" ngSrc="assets/content_copy_black_24dp.svg" title="Skopiuj numer telefonu do schowka" alt="Skopiuj numer telefonu do schowka" style="margin: auto 1em;" (click)="copyToClipboard()"
height="24" width="24"/>
<img id="mobileHelpButton" ngSrc="assets/info_black_24dp.svg" title="Szczegóły dotyczące numeracji w Polsce" alt="Definicja numerów w Polsce na Wikipedii" (click)="navigateToDocs()"
height="24" width="24"/>
</div>
</div>
</div>

View File

@@ -0,0 +1,35 @@
import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormControl } from '@angular/forms';
import { MobileService } from 'src/app/service/mobile.service';
import { ClipboardService } from 'src/app/service/gui/clipboard.service';
@Component({
selector: 'app-mobile',
templateUrl: './mobile.component.html',
styleUrls: ['./mobile.component.scss'],
standalone: false
})
export class MobileComponent implements OnInit {
public valueField: FormControl;
constructor(private mobileService: MobileService, private clipboardService: ClipboardService) {
this.valueField = new FormControl('');
this.valueField.setValidators([(control: AbstractControl) => this.mobileService.validateMobile(control.value)]);
}
ngOnInit(): void {
this.generate();
}
generate(): void {
this.valueField.setValue(this.mobileService.generateMobile());
}
copyToClipboard(): void {
this.clipboardService.copyToClipboard(this.valueField.value);
}
navigateToDocs(): void {
window.open('https://pl.wikipedia.org/wiki/Numery_telefonu_w_Polsce', "_blank");
}
}

View File

@@ -0,0 +1,63 @@
import { Injectable } from '@angular/core';
import { CommonService } from './common.service';
import { ValidationErrors } from '@angular/forms';
@Injectable({
providedIn: 'any'
})
export class MobileService {
private static readonly mobilePrefixes: string[] = [
'450', '451', '452', '453', '454', '455', '456', '457', '458', '459',
'500', '501', '502', '503', '504', '505', '506', '507', '508', '509',
'510', '511', '512', '513', '514', '515', '516', '517', '518', '519',
'530', '531', '532', '533', '534', '535', '536', '537', '538', '539',
'570', '571', '572', '573', '574', '575', '576', '577', '578', '579',
'600', '601', '602', '603', '604', '605', '606', '607', '608', '609',
'660', '661', '662', '663', '664', '665', '666', '667', '668', '669',
'690', '691', '692', '693', '694', '695', '696', '697', '698', '699',
'720', '721', '722', '723', '724', '725', '726', '727', '728', '729',
'730', '731', '732', '733', '734', '735', '736', '737', '738', '739',
'780', '781', '782', '783', '784', '785', '786', '787', '788', '789',
'790', '791', '792', '793', '794', '795', '796', '797', '798', '799',
'880', '881', '882', '883', '884', '885', '886', '887', '888', '889'
];
constructor(private common: CommonService) { }
public generateMobile(): string {
const prefixIndex = this.common.getRandomInt(0, MobileService.mobilePrefixes.length - 1);
const prefix = MobileService.mobilePrefixes[prefixIndex];
const rest = this.common.getRandomInt(0, 999999);
return prefix + this.common.pad(rest, 6);
}
public validateMobile(mobile: string): ValidationErrors | null {
const errors: ValidationErrors = {};
if (!mobile) {
return null;
}
if (mobile.length < 9) {
errors.tooShort = true;
}
if (mobile.length > 9) {
errors.tooLong = true;
}
if (!CommonService.numericRegExp.test(mobile)) {
errors.invalidPattern = true;
}
if (mobile.length >= 3) {
const prefix = mobile.substring(0, 3);
if (!MobileService.mobilePrefixes.includes(prefix)) {
errors.invalidPrefix = true;
}
}
return Object.keys(errors).length > 0 ? errors : null;
}
}

View File

@@ -5,7 +5,6 @@
html, body { html, body {
height: 100%; height: 100%;
overflow-y: hidden;
} }
body { body {
@@ -37,7 +36,7 @@ body {
} }
.generator { .generator {
margin: 0 1em 2em 1em; margin: 1em;
background: rgba( 255, 255, 255, 0.15 ); background: rgba( 255, 255, 255, 0.15 );
box-shadow: 0 8px 32px 0 rgba( 0, 38, 135, 0.37 ); box-shadow: 0 8px 32px 0 rgba( 0, 38, 135, 0.37 );
border-radius: 3px; border-radius: 3px;