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

View File

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

View File

@@ -1,12 +1,15 @@
<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) {
<div class="controls" (mouseenter)="showControls = true">
<mat-button-toggle-group multiple>
<div class="controls">
<mat-button-toggle-group vertical multiple>
<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]="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]="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-group>
</div>
@@ -24,6 +27,9 @@
@if (showIdentityCard) {
<app-identity-card></app-identity-card>
}
@if (showMobile) {
<app-mobile></app-mobile>
}
@if (showIban) {
<app-iban></app-iban>
}

View File

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

View File

@@ -1,4 +1,4 @@
import { Component, OnInit, HostListener } from '@angular/core';
import { ChangeDetectorRef, Component, ElementRef, HostListener, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
@@ -13,23 +13,29 @@ export class AppComponent implements OnInit {
showPesel = true;
showIdentityCard = true;
showIban = true;
showMobile = true;
showControls = false;
isInitialVisible = true;
constructor() {
constructor(private cdr: ChangeDetectorRef, private eRef: ElementRef) {
}
ngOnInit(): void {
setTimeout(() => {
this.isInitialVisible = false;
this.cdr.detectChanges();
}, 3000);
}
@HostListener('window:mousemove', ['$event'])
onMouseMove(event: MouseEvent) {
// Show if mouse is in the top 20px (hover zone)
if (event.clientY < 20) {
this.showControls = true;
}
// Hide if mouse moves below 120px
else if (event.clientY > 120) {
@HostListener('document:click', ['$event'])
clickout(event: Event) {
const settingsButton = this.eRef.nativeElement.querySelector('.settings-button');
const controls = this.eRef.nativeElement.querySelector('.controls');
if (this.showControls &&
settingsButton && !settingsButton.contains(event.target) &&
controls && !controls.contains(event.target)) {
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 {
height: 100%;
overflow-y: hidden;
}
body {
@@ -37,7 +36,7 @@ body {
}
.generator {
margin: 0 1em 2em 1em;
margin: 1em;
background: rgba( 255, 255, 255, 0.15 );
box-shadow: 0 8px 32px 0 rgba( 0, 38, 135, 0.37 );
border-radius: 3px;