diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index a5d2089..1a91a67 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -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,
diff --git a/src/app/components/app/app.component.html b/src/app/components/app/app.component.html
index dcdb62c..6c578e3 100644
--- a/src/app/components/app/app.component.html
+++ b/src/app/components/app/app.component.html
@@ -1,12 +1,15 @@
-
+
@if (showControls) {
-
-
+
+
REGON
NIP
PESEL
DOWÓD
+ MOBILNY
IBAN
@@ -24,6 +27,9 @@
@if (showIdentityCard) {
}
+ @if (showMobile) {
+
+ }
@if (showIban) {
}
diff --git a/src/app/components/app/app.component.scss b/src/app/components/app/app.component.scss
index 9e4b78f..263d3a0 100644
--- a/src/app/components/app/app.component.scss
+++ b/src/app/components/app/app.component.scss
@@ -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);
+ 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%;
- &:first-child {
- border-left: none !important;
+ &: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 {
diff --git a/src/app/components/app/app.component.ts b/src/app/components/app/app.component.ts
index a13f133..a92b09d 100644
--- a/src/app/components/app/app.component.ts
+++ b/src/app/components/app/app.component.ts
@@ -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;
}
}
diff --git a/src/app/components/mobile/mobile.component.html b/src/app/components/mobile/mobile.component.html
new file mode 100644
index 0000000..73d80f8
--- /dev/null
+++ b/src/app/components/mobile/mobile.component.html
@@ -0,0 +1,19 @@
+
diff --git a/src/app/components/mobile/mobile.component.scss b/src/app/components/mobile/mobile.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/mobile/mobile.component.ts b/src/app/components/mobile/mobile.component.ts
new file mode 100644
index 0000000..9bf005e
--- /dev/null
+++ b/src/app/components/mobile/mobile.component.ts
@@ -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");
+ }
+}
diff --git a/src/app/service/mobile.service.ts b/src/app/service/mobile.service.ts
new file mode 100644
index 0000000..b2fdfe7
--- /dev/null
+++ b/src/app/service/mobile.service.ts
@@ -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;
+ }
+}
diff --git a/src/styles.scss b/src/styles.scss
index 58a0759..a0f2da4 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -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;