Rename parent directory

This commit is contained in:
Myx
2024-11-15 02:26:56 +01:00
parent d3116c89fe
commit 8792699c7c
88 changed files with 6 additions and 6 deletions

View File

@@ -0,0 +1,9 @@
<app-dual-textarea
title="Ascii to text"
[topPlaceholder]="'Enter ascii here...'"
[bottomPlaceholder]="'Text will appear here...'"
[topValue]="convertedAscii"
[bottomValue]="convertedText"
(topChange)="onAsciiChange($event)"
(bottomChange)="onTextChange($event)">
</app-dual-textarea>

View File

@@ -0,0 +1,39 @@
import { Component } from '@angular/core';
import { DualTextareaComponent } from '../../../app/shared/dual-textarea/dual-textarea.component';
@Component({
selector: 'app-ascii-to-text',
templateUrl: './ascii-to-text.component.html',
styleUrls: ['./ascii-to-text.component.scss'],
standalone: true,
imports: [DualTextareaComponent]
})
export class AsciiToTextComponent {
convertedText: string = '';
convertedAscii: string = '';
onAsciiChange(input: string): void {
this.convertedText = this.convertAsciiToText(input);
}
convertAsciiToText(ascii: string): string {
return ascii
.split(' ')
.map(char => String.fromCharCode(parseInt(char, 10)))
.join('');
}
onTextChange(input: string): void {
this.convertedAscii = this.convertTextToAscii(input);
}
convertTextToAscii(text: string): string {
return text
.split('')
.map(char => {
const asciiValue = char.charCodeAt(0).toString();
return asciiValue.padStart(3, '0');
})
.join(' ');
}
}

View File

@@ -0,0 +1,9 @@
<app-dual-textarea
title="base64 to text"
topPlaceholder="Enter base64 here..."
bottomPlaceholder="Text will appear here..."
[topValue]="convertedBase64"
[bottomValue]="convertedText"
(topChange)="base64Decoded($event)"
(bottomChange)="base64Encoded($event)">
</app-dual-textarea>

View File

@@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { Base64ConverterComponent } from './base64-converter.component';
describe('Base64ConverterComponent', () => {
let component: Base64ConverterComponent;
let fixture: ComponentFixture<Base64ConverterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ Base64ConverterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(Base64ConverterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import { DualTextareaComponent } from '../../../app/shared/dual-textarea/dual-textarea.component';
@Component({
selector: 'app-base64-converter',
templateUrl: './base64-converter.component.html',
styleUrls: ['./base64-converter.component.scss'],
standalone: true,
imports: [DualTextareaComponent]
})
export class Base64ConverterComponent {
convertedBase64: string = '';
convertedText: string = '';
base64Encoded(event: string): void {
this.convertedBase64 = btoa(event);
this.convertedText = event;
}
base64Decoded(event: string): void {
this.convertedText = atob(event);
this.convertedBase64 = event;
}
}

View File

@@ -0,0 +1,7 @@
<page header="Color Picker">
<div class="color-picker">
<label for="color-input">Choose Color:</label>
<input #colorPicker type="color" id="color-input" [formControl]="colorControl" (input)="onColorChange(colorPicker.value)" />
<input #colorText type="text" [formControl]="colorControl" (input)="onColorChange(colorText.value)" />
</div>
</page>

View File

@@ -0,0 +1,23 @@
* {
color: #FFF;
}
.color-picker {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.color-picker input[type="color"] {
width: 50px;
height: 50px;
border: none;
cursor: pointer;
}
.color-picker input[type="text"] {
padding: 5px;
font-size: 16px;
width: 100px;
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ColorPickerComponent } from './color-picker.component';
describe('ColorPickerComponent', () => {
let component: ColorPickerComponent;
let fixture: ComponentFixture<ColorPickerComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ColorPickerComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ColorPickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,28 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { PageComponent } from '../../../app/shared/page/page.component';
@Component({
selector: 'app-color-picker',
standalone: true,
imports: [
CommonModule,
FormsModule,
InputTextModule,
ButtonModule,
ReactiveFormsModule,
PageComponent
],
templateUrl: './color-picker.component.html',
styleUrl: './color-picker.component.scss'
})
export class ColorPickerComponent {
colorControl = new FormControl('#ff0000');
onColorChange(value: string) {
this.colorControl.setValue(value);
}
}

View File

@@ -0,0 +1,7 @@
<app-file-converter
title="DDS to PNG Converter"
[isPreview]="false"
[processedFiles]="processedFiles"
[fileFormats]="fileFormats"
(fileSelected)="onFileSelected($event)">
</app-file-converter>

View File

@@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { DdsToPngComponent } from './dds-to-png.component';
describe('DdsToPngComponent', () => {
let component: DdsToPngComponent;
let fixture: ComponentFixture<DdsToPngComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DdsToPngComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DdsToPngComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,47 @@
import { Component, OnInit } from '@angular/core';
import { FileConverterComponent } from '../../../app/shared/upload/file-converter.component';
import { DdsToPngService } from './dds-to-png.service';
import { ProcessedFile } from '../../../app/models/conversion.model';
@Component({
selector: 'app-dds-to-png',
templateUrl: './dds-to-png.component.html',
styleUrls: ['./dds-to-png.component.scss'],
standalone: true,
imports: [FileConverterComponent]
})
export class DdsToPngComponent {
processedFiles: ProcessedFile[] = [];
fileFormats: string[] = [".dds"];
constructor(private ddsToPngService: DdsToPngService) { }
onFileSelected(input: File[]): void {
if (input.length > 0) {
const file = input[0];
const reader = new FileReader();
reader.onload = async () => {
try {
const ddsArrayBuffer = reader.result as ArrayBuffer;
const pngDataUrl = await this.ddsToPngService.ddsToPng(ddsArrayBuffer);
const blob = await (await fetch(pngDataUrl)).blob();
const blobUrl = URL.createObjectURL(blob);
const processedFile: ProcessedFile = {
name: file.name.replace('.dds', '.png'),
link: blobUrl,
format: 'png'
};
this.processedFiles.push(processedFile);
console.log('Processed Files:', this.processedFiles);
} catch (error) {
console.error('Error:', error);
}
};
reader.readAsArrayBuffer(file);
}
}
}

View File

@@ -0,0 +1,220 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DdsToPngService {
parseHeaders(arrayBuffer: ArrayBuffer) {
const header = new DataView(arrayBuffer, 0, 128);
const height = header.getUint32(12, true);
const width = header.getUint32(16, true);
const fourCC = header.getUint32(84, true);
return { width, height, fourCC };
}
decodeDXT1(src: Uint8Array, width: number, height: number): Uint8Array {
const rgba = new Uint8Array(width * height * 4);
let srcIndex = 0;
for (let y = 0; y < height; y += 4) {
for (let x = 0; x < width; x += 4) {
const c0 = src[srcIndex] | (src[srcIndex + 1] << 8);
const c1 = src[srcIndex + 2] | (src[srcIndex + 3] << 8);
const code = src[srcIndex + 4] | (src[srcIndex + 5] << 8) | (src[srcIndex + 6] << 16) | (src[srcIndex + 7] << 24);
srcIndex += 8;
const colors = new Uint8Array(16);
this.decodeColors(c0, c1, colors);
for (let blockY = 0; blockY < 4; blockY++) {
for (let blockX = 0; blockX < 4; blockX++) {
const pixelIndex = ((code >> (2 * (blockY * 4 + blockX))) & 0x03) * 4;
const dstPixelIndex = ((y + blockY) * width + (x + blockX)) * 4;
rgba[dstPixelIndex] = colors[pixelIndex];
rgba[dstPixelIndex + 1] = colors[pixelIndex + 1];
rgba[dstPixelIndex + 2] = colors[pixelIndex + 2];
rgba[dstPixelIndex + 3] = colors[pixelIndex + 3];
}
}
}
}
return rgba;
}
decodeDXT3(src: Uint8Array, width: number, height: number): Uint8Array {
const rgba = new Uint8Array(width * height * 4);
let srcIndex = 0;
for (let y = 0; y < height; y += 4) {
for (let x = 0; x < width; x += 4) {
const alpha = new Uint8Array(16);
for (let i = 0; i < 8; i++) {
const byte = src[srcIndex++];
alpha[i * 2] = (byte & 0x0F) * 17;
alpha[i * 2 + 1] = (byte >> 4) * 17;
}
const c0 = src[srcIndex] | (src[srcIndex + 1] << 8);
const c1 = src[srcIndex + 2] | (src[srcIndex + 3] << 8);
const code = src[srcIndex + 4] | (src[srcIndex + 5] << 8) | (src[srcIndex + 6] << 16) | (src[srcIndex + 7] << 24);
srcIndex += 8;
const colors = new Uint8Array(16);
this.decodeColors(c0, c1, colors);
for (let blockY = 0; blockY < 4; blockY++) {
for (let blockX = 0; blockX < 4; blockX++) {
const pixelIndex = ((code >> (2 * (blockY * 4 + blockX))) & 0x03) * 4;
const dstPixelIndex = ((y + blockY) * width + (x + blockX)) * 4;
rgba[dstPixelIndex] = colors[pixelIndex];
rgba[dstPixelIndex + 1] = colors[pixelIndex + 1];
rgba[dstPixelIndex + 2] = colors[pixelIndex + 2];
rgba[dstPixelIndex + 3] = alpha[blockY * 4 + blockX];
}
}
}
}
return rgba;
}
decodeDXT5(src: Uint8Array, width: number, height: number): Uint8Array {
const rgba = new Uint8Array(width * height * 4);
let srcIndex = 0;
for (let y = 0; y < height; y += 4) {
for (let x = 0; x < width; x += 4) {
const alpha0 = src[srcIndex++];
const alpha1 = src[srcIndex++];
const alphaCode = src[srcIndex] | (src[srcIndex + 1] << 8) | (src[srcIndex + 2] << 16) | (src[srcIndex + 3] << 24) | (src[srcIndex + 4] << 32) | (src[srcIndex + 5] << 40);
srcIndex += 6;
const alphas = new Uint8Array(8);
alphas[0] = alpha0;
alphas[1] = alpha1;
if (alpha0 > alpha1) {
for (let i = 1; i < 7; i++) {
alphas[i + 1] = ((7 - i) * alpha0 + i * alpha1) / 7;
}
} else {
for (let i = 1; i < 5; i++) {
alphas[i + 1] = ((5 - i) * alpha0 + i * alpha1) / 5;
}
alphas[6] = 0;
alphas[7] = 255;
}
const c0 = src[srcIndex] | (src[srcIndex + 1] << 8);
const c1 = src[srcIndex + 2] | (src[srcIndex + 3] << 8);
const code = src[srcIndex + 4] | (src[srcIndex + 5] << 8) | (src[srcIndex + 6] << 16) | (src[srcIndex + 7] << 24);
srcIndex += 8;
const colors = new Uint8Array(16);
this.decodeColors(c0, c1, colors);
for (let blockY = 0; blockY < 4; blockY++) {
for (let blockX = 0; blockX < 4; blockX++) {
const pixelIndex = ((code >> (2 * (blockY * 4 + blockX))) & 0x03) * 4;
const alphaIndex = (alphaCode >> (3 * (blockY * 4 + blockX))) & 0x07;
const dstPixelIndex = ((y + blockY) * width + (x + blockX)) * 4;
rgba[dstPixelIndex] = colors[pixelIndex];
rgba[dstPixelIndex + 1] = colors[pixelIndex + 1];
rgba[dstPixelIndex + 2] = colors[pixelIndex + 2];
rgba[dstPixelIndex + 3] = alphas[alphaIndex];
}
}
}
}
return rgba;
}
decodeColors(c0: number, c1: number, colors: Uint8Array) {
const r0 = (c0 >> 11) & 0x1F;
const g0 = (c0 >> 5) & 0x3F;
const b0 = c0 & 0x1F;
const r1 = (c1 >> 11) & 0x1F;
const g1 = (c1 >> 5) & 0x3F;
const b1 = c1 & 0x1F;
colors[0] = (r0 << 3) | (r0 >> 2);
colors[1] = (g0 << 2) | (g0 >> 4);
colors[2] = (b0 << 3) | (b0 >> 2);
colors[3] = 255;
colors[4] = (r1 << 3) | (r1 >> 2);
colors[5] = (g1 << 2) | (g1 >> 4);
colors[6] = (b1 << 3) | (b1 >> 2);
colors[7] = 255;
if (c0 > c1) {
for (let i = 0; i < 3; i++) {
colors[8 + i] = (2 * colors[i] + colors[4 + i]) / 3;
colors[12 + i] = (colors[i] + 2 * colors[4 + i]) / 3;
}
colors[11] = colors[15] = 255;
} else {
for (let i = 0; i < 3; i++) {
colors[8 + i] = (colors[i] + colors[4 + i]) / 2;
colors[12 + i] = 0;
}
colors[11] = 255;
colors[15] = 0;
}
}
ddsToPng(arrayBuffer: ArrayBuffer): Promise<string> {
return new Promise((resolve, reject) => {
try {
const { width, height, fourCC } = this.parseHeaders(arrayBuffer);
let rgbaData: Uint8Array;
const src = new Uint8Array(arrayBuffer, 128);
switch (fourCC) {
case 0x31545844: // 'DXT1' in ASCII
rgbaData = this.decodeDXT1(src, width, height);
break;
case 0x33545844: // 'DXT3' in ASCII
rgbaData = this.decodeDXT3(src, width, height);
break;
case 0x35545844: // 'DXT5' in ASCII
rgbaData = this.decodeDXT5(src, width, height);
break;
default:
throw new Error('Unsupported DDS format');
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
if (!context) {
reject('Failed to get canvas context');
return;
}
const imageData = context.createImageData(width, height);
imageData.data.set(rgbaData);
context.putImageData(imageData, 0, 0);
canvas.toBlob((blob) => {
if (blob) {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result as string);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
} else {
reject('Failed to create blob');
}
}, 'image/png');
} catch (error) {
reject(`Error converting DDS to PNG: ${error}`);
}
});
}
}

View File

@@ -0,0 +1,45 @@
<!-- Custom design, no page component -->
<p-panel header="Guid Generator">
<div>
<div *ngFor="let setting of settings" class="guid-row">
<p-radiobutton [inputId]="setting.code" name="category" [value]="setting" [(ngModel)]="selectedGuid" />
<label [for]="setting.code">
{{ setting.name }}
</label>
</div>
</div>
<ng-template pTemplate="footer">
<div>
<div class="icon-wrapper">
<p-button
pTooltip="Regenerate new Guid."
icon="pi pi-refresh"
[rounded]="true"
[text]="true"
(onClick)="generateGuid(true)"
/>
<p-button
pTooltip="Copy selected Guid to clipboard."
icon="pi pi-clipboard"
severity="secondary"
[rounded]="true"
[text]="true"
(onClick)="onCopyToClipboard()"
/>
<p-button
pTooltip="Change casing."
icon="pi"
severity="secondary"
[rounded]="true"
[text]="true"
(onClick)="onCasingChange()"
>
<ng-template pTemplate="uppercase">
<ng-icon name="cssFormatUppercase"></ng-icon>
</ng-template>
</p-button>
</div>
</div>
</ng-template>
</p-panel>

View File

@@ -0,0 +1,33 @@
:host {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
width: 98vw;
color: #fff;
p-panel {
display: flex;
flex-direction: column;
width: 1140px;
}
.guid-row {
padding: 8px 0px;
border-bottom: solid 1px var(--p-button-text-secondary-color);
}
p-floatlabel {
width: 30vw;
}
ng-icon {
color: var(--p-button-text-secondary-color)
}
.icon-wrapper{
display: flex;
align-items: center;
margin: 10px 0px;
}
}

View File

@@ -0,0 +1,100 @@
import { Component, OnInit } from '@angular/core';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { FormsModule } from '@angular/forms';
import { FloatLabelModule } from 'primeng/floatlabel';
import { RadioButtonModule, } from 'primeng/radiobutton';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { Clipboard } from '@angular/cdk/clipboard';
import { ButtonModule } from 'primeng/button';
import { PanelModule } from 'primeng/panel';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { cssFormatUppercase } from '@ng-icons/css.gg';
import { DividerModule } from 'primeng/divider';
import { TooltipModule } from 'primeng/tooltip';
interface setting {
name: string;
code: string;
}
@Component({
selector: 'app-guid',
templateUrl: './guid.component.html',
styleUrls: ['./guid.component.scss'],
standalone: true,
viewProviders: [provideIcons({cssFormatUppercase})],
imports: [
NgIconComponent,
PanelModule,
DividerModule,
FloatLabelModule,
InputTextareaModule,
FormsModule,
RadioButtonModule,
CommonModule,
ToggleButtonModule,
ButtonModule,
TooltipModule
]
})
export class GuidComponent implements OnInit {
settings: setting[] | undefined;
selectedGuid: setting | undefined;
guid: string = '';
isUppercase: boolean = false;
constructor(private clipboard: Clipboard) { }
ngOnInit() {
this.generateGuid(false);
this.setGuids();
}
onCopyToClipboard(): void {
this.clipboard.copy(this.selectedGuid?.name!);
}
onCasingChange(): void {
this.isUppercase = !this.isUppercase;
if (this.isUppercase) {
this.guid = this.guid.toUpperCase();
} else {
this.guid = this.guid.toLowerCase();
}
this.setGuids();
}
generateGuid(input: boolean) {
this.guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(character) {
const randomValue = Math.random() * 16 | 0;
const value = character === 'x' ? randomValue : (randomValue & 0x3 | 0x8);
return value.toString(16);
});
if (this.isUppercase) {
this.guid = this.guid.toUpperCase();
} else {
this.guid = this.guid.toLowerCase();
}
if (input)
this.setGuids();
}
setGuids() {
this.settings = [
{ name: this.guid, code: '00' },
{ name: `"${this.guid}"`, code: '01' },
{ name: `{${this.guid}}`, code: '02' },
{ name: `new Guid("${this.guid}")`, code: '03' },
{ name: `[Guid("${this.guid}")]`, code: '04' },
];
if (this.settings!.length > 0) {
this.selectedGuid = this.settings![0];
}
}
}

View File

@@ -0,0 +1,8 @@
<app-dual-textarea
title="Decode JWT Token"
topPlaceholder="Enter JWT Token here..."
bottomPlaceholder="Json will appear here..."
[bottomDisabled]="true"
[bottomValue]="readableToken"
(topChange)="decodeJwtToken($event)">
</app-dual-textarea>

View File

@@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { JwtToJsonComponent } from './jwt-to-json.component';
describe('JwtToJsonComponent', () => {
let component: JwtToJsonComponent;
let fixture: ComponentFixture<JwtToJsonComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ JwtToJsonComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(JwtToJsonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,43 @@
import { Component } from '@angular/core';
import { DualTextareaComponent } from '../../../app/shared/dual-textarea/dual-textarea.component';
@Component({
selector: 'app-jwt-to-json',
templateUrl: './jwt-to-json.component.html',
styleUrls: ['./jwt-to-json.component.scss'],
standalone: true,
imports: [DualTextareaComponent]
})
export class JwtToJsonComponent {
readableToken: string = '';
decodeBase64Url(base64Url: string): string {
const base64 = base64Url
.replace(/-/g, '+')
.replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64)
.split('')
.map(char => '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
return jsonPayload;
}
decodeJwtToken(token: string): void {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT Token');
}
const payload = this.decodeBase64Url(parts[1]);
try {
this.readableToken = JSON.stringify(JSON.parse(payload), null, 2);
} catch (error) {
this.readableToken = 'Invalid JWT Token';
}
}
}

View File

@@ -0,0 +1,89 @@
<div class="card flex justify-center">
<p-panel header="QR Code Generator">
<ng-template pTemplate="header">
<p-tag *ngIf="isBeta" severity="warn" value="Beta"></p-tag>
</ng-template>
<div class="wrapper">
<p-message *ngIf="error.error" severity="error">{{error.message}}</p-message>
<qrcode
(qrCodeURL)="onCodeUrlChanged($event)"
[qrdata]="qrCodeData"
[allowEmptyString]="true"
[ariaLabel]="'QR Code image with the following content...'"
[cssClass]="'center'"
[colorDark]="colorCode"
[colorLight]="backgroundColorCode"
[elementType]="'canvas'"
[errorCorrectionLevel]="level"
[imageSrc]="innerQrCodeimage"
[imageHeight]="75"
[imageWidth]="75"
[margin]="4"
[scale]="1"
[title]="qrCodeData"
[width]="qrCodeSize"
></qrcode>
<p-toolbar>
<div class="p-toolbar-group-start">
<input type="file" style="display:none" #fileSelector (change)="onFileSelected($event)" />
<p-button icon="pi pi-print" class="mr-2 miniButtons" (click)="onPrintButtonClick()"/>
<p-button icon="pi pi-upload" class="miniButtons" type="file" (click)="fileSelector.click()"/>
</div>
<div class="p-toolbar-group-center" *ngIf="!isMobile">
<input type="text" pInputText formControlName="text" [value]="qrCodeData" (keyup)="onDataTextInputChanged($event)" />
<p-button [icon]="expandIcon" severity="info" (click)="onExpandButtonClick()" />
</div>
<div class="p-toolbar-group-end">
<a [href]="qrCodeImageUrl" download="qrcode" >
<p-button label="Save" icon="pi pi-download" class="button-size" />
</a>
</div>
<div class="p-toolbar-group-center" *ngIf="isMobile">
<input type="text" pInputText formControlName="text" [value]="qrCodeData" (keyup)="onDataTextInputChanged($event)" />
<p-button [icon]="expandIcon" severity="info" (click)="onExpandButtonClick()" />
</div>
</p-toolbar>
</div>
<textarea
*ngIf="extendedInput"
(keyup)="onDataTextInputChanged($event)"
[value]="qrCodeData"
pInputTextarea
[autoResize]="true"
[fluid]="true"
rows="5"
cols="30"
placeholder="Contents of QR Code"
></textarea>
<p-accordion [multiple]="true" [activeIndex]="activeTabs">
<p-accordionTab
header="More settings"
value="1"
>
<div class="vertical">
<p>Size of QR Code</p>
<input type="number" max="1000" pInputText autocomplete="off" placeholder="Size" value="300" (keyup)="onSizeInputChanged($event)" />
<p>Image in QR code</p>
<input pInputText autocomplete="off" placeholder="Image url" (keyup)="onImageLinkInputChanged($event)"/>
<p>Error correction level</p>
<p-selectbutton [options]="correctionLevel" optionLabel="level" (onChange)="onLevelSelected($event)">
<ng-template pTemplate="item" let-item>
<i>{{item.level}}</i>
</ng-template>
</p-selectbutton>
<p>Color</p>
<p-colorpicker [inline]="true" (onChange)="onColorChanged($event)"/>
<p>Background color</p>
<p-colorpicker [inline]="true" (onChange)="onBackgroundColorChanged($event)"/>
</div>
</p-accordionTab>
</p-accordion>
</p-panel>
</div>

View File

@@ -0,0 +1,103 @@
:host {
display: flex;
justify-content: center;
margin-top: 20px;
width: 96vw;
.miniButtons {
margin: 0px 4px;
}
.wrapper {
display: flex;
flex-direction: column;
background-color: #000000;
}
a.disabled {
pointer-events: none;
cursor: default;
}
.card {
display: flex;
flex-direction: column;
width: 1140px;
}
.center {
display: flex;
justify-content: center;
}
.p-toolbar-group-end, .p-toolbar-group-center, .p-toolbar-group-start {
width: 300px;
::ng-deep {
.p-button {
height: 39px !important;
}
}
}
.p-toolbar-group-end {
display: flex;
justify-content: flex-end;
}
.p-toolbar-group-center .p-inputtext {
width: calc(100% - 44px);
}
qrcode {
display: flex;
justify-content: center;
}
.vertical {
display: flex;
flex-direction: column;
}
::ng-deep .p-panel-header {
justify-content: unset !important;
* {
margin-right: 5px;
}
}
@media only screen and (max-width: 1021px) {
.p-toolbar-group-end, .p-toolbar-group-start {
width: unset;
::ng-deep {
.p-button {
height: 40px !important;
}
p-button {
height: 40px !important;
margin: 0px 4px 0px 0px !important;
}
}
}
.p-toolbar-group-end {
display: flex;
justify-content: flex-start;
::ng-deep {
p-button {
margin: 0px 0px 0px 0px !important;
}
}
}
.p-toolbar-group-center {
width: 100%;
.p-inputtext {
width: calc(100% - 44px);
}
}
}
}

View File

@@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { QrCodeGeneratorComponent } from './qr-code-generator.component';
describe('QrCodeGeneratorComponent', () => {
let component: QrCodeGeneratorComponent;
let fixture: ComponentFixture<QrCodeGeneratorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ QrCodeGeneratorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(QrCodeGeneratorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,177 @@
import { Component, OnInit, Renderer2 } from '@angular/core';
import { QRCodeModule } from 'angularx-qrcode';
import { AccordionModule } from 'primeng/accordion';
import { PanelModule } from 'primeng/panel';
import { TagModule } from 'primeng/tag';
import { InputTextModule } from 'primeng/inputtext';
import { ButtonModule } from 'primeng/button';
import { ToolbarModule } from 'primeng/toolbar';
import { CommonModule } from '@angular/common';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { FloatLabelModule } from 'primeng/floatlabel';
import { FileUploadModule } from 'primeng/fileupload';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { MessageModule } from 'primeng/message';
import { SelectButtonChangeEvent, SelectButtonModule } from 'primeng/selectbutton';
import { ColorPickerChangeEvent, ColorPickerModule } from 'primeng/colorpicker';
export interface QrCodeError {
error: boolean;
message: string;
}
@Component({
selector: 'app-qr-code-generator',
templateUrl: './qr-code-generator.component.html',
styleUrls: ['./qr-code-generator.component.scss'],
standalone: true,
imports: [
QRCodeModule,
AccordionModule,
PanelModule,
TagModule,
InputTextModule,
ButtonModule,
ToolbarModule,
CommonModule,
FloatLabelModule,
InputTextareaModule,
FileUploadModule,
MessageModule,
SelectButtonModule,
ColorPickerModule
]
})
export class QrCodeGeneratorComponent implements OnInit {
qrCodeData: string = 'bytefy.net';
isBeta = true;
extendedInput = false;
activeTabs: number[] = [1];
expandIcon = 'pi pi-angle-up';
inputedValue: string = '';
qrCodeImageUrl: SafeUrl = '';
unsafeUrl: string = '';
downloadEnabled = false;
error: QrCodeError = {
error: false,
message: ''
};
colorCode: string = '#000000';
backgroundColorCode: string = '#ffffff';
level: "L" | "M" | "Q" | "H" | "low" | "medium" | "quartile" | "high" = 'H';
innerQrCodeimage: string = '';
qrCodeSize: number = 300;
correctionLevel: any[] = [
{ level: 'L' },
{ level: 'M' },
{ level: 'Q' },
{ level: 'H' }
];
isMobile: boolean = false;
constructor(private renderer: Renderer2, private sanitizer: DomSanitizer) {}
ngOnInit(): void {
this.isMobile = window.innerWidth < 1021;
window.addEventListener('resize', () => {
this.isMobile = window.innerWidth < 1021;
});
}
onImageLinkInputChanged(event: Event): void {
this.innerQrCodeimage = (event.target as HTMLInputElement).value;
}
onLevelSelected(level: SelectButtonChangeEvent): void {
this.level = level.value;
}
onFileSelected(event: Event): void {
const file = (event.target as HTMLInputElement).files?.item(0);
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = () => {
this.qrCodeData = reader.result as string;
this.validateInput();
};
reader.readAsText(file);
}
onDataTextInputChanged(event: Event): void {
const input = (event.target as HTMLInputElement).value;
this.qrCodeData = input
this.inputedValue = input;
this.validateInput();
}
onSizeInputChanged(event: Event): void {
let size = parseInt((event.target as HTMLInputElement).value, 10);
if(size <= 1000){
this.qrCodeSize = size;
this.error.error = false;
}
else
this.error = {
error: true,
message: 'Too large image size, change the field "Size of QR Code". Maximum value is 1000.'
};
}
onColorChanged(event: ColorPickerChangeEvent): void {
this.colorCode = event.value.toString();
}
onBackgroundColorChanged(event: ColorPickerChangeEvent): void {
this.backgroundColorCode = event.value.toString();
}
onExpandButtonClick(): void {
this.extendedInput = !this.extendedInput;
this.expandIcon = this.extendedInput ? 'pi pi-angle-down' : 'pi pi-angle-up';
}
onCodeUrlChanged(url: SafeUrl): void {
this.qrCodeImageUrl = url;
}
onPrintButtonClick(): void {
const iframe = this.renderer.createElement('iframe');
this.renderer.setStyle(iframe, 'position', 'absolute');
this.renderer.setStyle(iframe, 'width', '0');
this.renderer.setStyle(iframe, 'height', '0');
this.renderer.setStyle(iframe, 'border', '0');
document.body.appendChild(iframe);
iframe.onload = () => {
const doc = iframe.contentDocument || iframe.contentWindow?.document;
if (doc) {
const img = doc.createElement('img');
img.src = this.sanitizer.sanitize(4, this.qrCodeImageUrl);
doc.body.appendChild(img);
iframe.contentWindow?.print();
setTimeout(() => {
document.body.removeChild(iframe);
}, 1000);
}
};
iframe.srcdoc = '<html><head><title>Bytefy.net Print QR Code</title></head><body></body></html>';
}
validateInput(): void {
this.error = {
error: this.qrCodeData.length > 1269,
message: 'File size is too large.'
};
}
}

View File

@@ -0,0 +1,9 @@
<app-dual-textarea
title="Text to Cron Expression"
topPlaceholder="every 5 minutes"
bottomPlaceholder="*/5 * * * *"
[bottomDisabled]="true"
[bottomValue]="cronExpression"
[isBeta]="true"
(topChange)="getCronExpression($event)">
</app-dual-textarea>

View File

@@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { TextToCronComponent } from './text-to-cron.component';
describe('TextToCronComponent', () => {
let component: TextToCronComponent;
let fixture: ComponentFixture<TextToCronComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TextToCronComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TextToCronComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,49 @@
import { Component } from '@angular/core';
import { DualTextareaComponent } from '../../../app/shared/dual-textarea/dual-textarea.component';
@Component({
selector: 'app-text-to-cron',
templateUrl: './text-to-cron.component.html',
styleUrls: ['./text-to-cron.component.scss'],
standalone: true,
imports: [DualTextareaComponent]
})
export class TextToCronComponent {
cronExpression: string = '';
getCronExpression(description: string): string {
let minute = "*";
let hour = "*";
let dayOfMonth = "*";
let month = "*";
let dayOfWeek = "*";
description = description.toLowerCase().trim();
const parts = description.split(" ");
parts.forEach((part, index) => {
if (part === "minute" || part === "minutes") {
if (index > 0 && !isNaN(parseInt(parts[index - 1]))) {
minute = `*/${parts[index - 1]}`;
}
} else if (part === "hour" || part === "hours") {
if (index > 0 && !isNaN(parseInt(parts[index - 1]))) {
hour = `*/${parts[index - 1]}`;
}
} else if (part === "day" && parts[index + 1] === "of") {
if (parts[index + 2] === "month" && index > 0 && !isNaN(parseInt(parts[index - 1]))) {
dayOfMonth = `${parts[index - 1]}`;
} else if (parts[index + 2] === "week" && index > 0 && !isNaN(parseInt(parts[index - 1]))) {
dayOfWeek = `${parts[index - 1]}`;
}
} else if (part === "month" || part === "months") {
if (index > 0 && !isNaN(parseInt(parts[index - 1]))) {
month = `*/${parts[index - 1]}`;
}
}
});
this.cronExpression = `${minute} ${hour} ${dayOfMonth} ${month} ${dayOfWeek}`;
return `${minute} ${hour} ${dayOfMonth} ${month} ${dayOfWeek}`;
}
}

View File

@@ -0,0 +1,8 @@
<app-dual-textarea
title="Text Counter"
topPlaceholder="Enter Text here..."
bottomPlaceholder="Stats will appear here..."
[bottomDisabled]="true"
[bottomValue]="this.result"
(topChange)="onTextChange($event)">
</app-dual-textarea>

View File

@@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { WordCounterComponent } from './word-counter.component';
describe('WordCounterComponent', () => {
let component: WordCounterComponent;
let fixture: ComponentFixture<WordCounterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WordCounterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WordCounterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,35 @@
import { Component, OnInit } from '@angular/core';
import { DualTextareaComponent } from '../../../app/shared/dual-textarea/dual-textarea.component';
@Component({
selector: 'app-word-counter',
templateUrl: './word-counter.component.html',
styleUrls: ['./word-counter.component.scss'],
standalone: true,
imports: [DualTextareaComponent]
})
export class WordCounterComponent {
words: number = 0;
characters: number = 0;
spaces: number = 0;
lines: number = 0;
sentences: number = 0;
paragraphs: number = 0;
result = '';
onTextChange(text: string): void {
this.words = text.split(/\s+/).filter(w => w.length > 0).length;
this.characters = text.length;
this.spaces = text.split(' ').length - 1;
this.lines = text.split('\n').length;
this.sentences = text.split(/[.!?]+/).length - 1;
this.paragraphs = text.split('\n\n').length;
this.result = `Words: ${this.words}\n`;
this.result += `Characters: ${this.characters}\n`;
this.result += `Spaces: ${this.spaces}\n`;
this.result += `Lines: ${this.lines}\n`;
this.result += `Sentences: ${this.sentences}\n`;
this.result += `Paragraphs: ${this.paragraphs}`;
}
}

View File

@@ -0,0 +1,17 @@
<app-file-converter
title="Image converter"
method="post"
[isPreview]="false"
[fileTypeSelector]="true"
[processedFiles]="processedFiles"
[fileFormats]="fileFormats"
[filteredFiles]="filteredFormats"
[baseUrl]="url"
[headers]="headers"
[isBeta]="true"
(autoComplete)="filterFormats($event)"
(fileSelected)="onFileSelected($event)"
(selectedFormat)="onFormatSelected($event)"
(upload)="onUploadClicked()"
>
</app-file-converter>

View File

@@ -0,0 +1,28 @@
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ImageConverterComponent } from './image-converter.component';
describe('ImageConverterComponent', () => {
let component: ImageConverterComponent;
let fixture: ComponentFixture<ImageConverterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ImageConverterComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ImageConverterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,101 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { map, Subscription } from 'rxjs';
import { DropdownModule } from 'primeng/dropdown';
import { AutoCompleteCompleteEvent, AutoCompleteModule } from 'primeng/autocomplete';
import { FormsModule } from '@angular/forms';
import { ImageService } from './image-converter.service';
import { CommonModule } from '@angular/common';
import { FileConverterComponent } from "../../../app/shared/upload/file-converter.component";
import { Format, ProcessedFile } from '../../../app/models/conversion.model';
import { HttpHeaders } from '@angular/common/http';
@Component({
selector: 'app-image-converter',
templateUrl: 'image-converter.component.html',
styleUrls: ['image-converter.component.scss'],
standalone: true,
imports: [DropdownModule, AutoCompleteModule, FormsModule, CommonModule, FileConverterComponent]
})
export class ImageConverterComponent implements OnInit, OnDestroy {
constructor(private ImageService: ImageService) { }
url = 'http://localhost:1337/convert';
filteredFormats: string[] = [];
formats: Format[] = [];
selectedFormat: string | undefined;
subscriptions: Subscription[] = [];
selected = '';
headers = new HttpHeaders();
processedFiles: ProcessedFile[] = [];
fileFormats: string[] = ["image/*"];
selectedFile: File[] | null = null;
filterFormats(event: AutoCompleteCompleteEvent) {
let filtered: any[] = [];
let query = event.query;
for (let index = 0; index < (this.formats as any[]).length; index++) {
let format = (this.formats as any[])[index];
if (format.name.toLowerCase().indexOf(query.toLowerCase()) == 0) {
filtered.push(format);
}
}
this.filteredFormats = filtered;
}
onUploadClicked() {
if (this.selectedFormat && this.selectedFile) {
this.subscriptions.push(
this.ImageService.getMimeType(this.selectedFormat).subscribe((typeResponse) => {
this.subscriptions.push(
this.ImageService.convertImage(this.selectedFile![0], this.selectedFormat!)
.pipe(map((response: any) => {
const blob = new Blob([response], { type: typeResponse });
const blobUrl = URL.createObjectURL(blob);
const processedFile = {
name: this.selectedFile![0].name.replace(/\.[^/.]+$/, `.${this.selectedFormat?.toLowerCase()}`),
link: blobUrl,
format: this.selectedFormat
} as ProcessedFile;
this.processedFiles.push(processedFile);
}))
.subscribe()
);
})
);
}
}
onFormatSelected(format: string) {
this.selectedFormat = format;
}
onFileSelected(input: File[]): void {
this.selectedFile = input;
}
ngOnInit(): void {
this.subscriptions.push(this.ImageService.setAntiforgeryToken().subscribe());
this.subscriptions.push(this.ImageService.getFormats()
.pipe(map(formats => {
this.formats = formats.map(format => {
return {
name: format,
code: format.toLowerCase()
} as Format;
});
}))
.subscribe());
this.headers = new HttpHeaders({
'2311d8d8-607d-4747-8939-1bde65643254': localStorage.getItem('imgToken')!,
});
}
ngOnDestroy(): void {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
}

View File

@@ -0,0 +1,42 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { map, Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class ImageService {
private baseUrl = environment.uploadServiceBaseUrl;
constructor(private http: HttpClient) { }
convertImage(image: File, format: string): Observable<any> {
const formData = new FormData();
formData.append('file', image);
let imgToken = localStorage.getItem('imgToken');
const headers = new HttpHeaders({
'2311d8d8-607d-4747-8939-1bde65643254': imgToken!
});
return this.http.post(`${this.baseUrl}/convert/${format}`, formData, { headers, responseType: 'blob' }); }
setAntiforgeryToken(): Observable<string> {
return this.http.get<string>(`${this.baseUrl}/antiforgery/token`, { responseType: 'text' as 'json' }).pipe(
map((token) => {
localStorage.setItem('imgToken', token.replace('"', ''));
return token;
})
);
}
getMimeType(simpleType: string): Observable<string> {
return this.http.get<string>(`${this.baseUrl}/mimetype/${simpleType}`, { responseType: 'text' as 'json' });
}
getFormats(): Observable<string[]> {
return this.http.get<string[]>(`${this.baseUrl}/formats`);
}
}