mirror of
https://github.com/Polaris-Entertainment/bytefy.git
synced 2026-04-11 10:29:37 +00:00
Rename parent directory
This commit is contained in:
16
bytefy.webapp/.editorconfig
Normal file
16
bytefy.webapp/.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
42
bytefy.webapp/.gitignore
vendored
Normal file
42
bytefy.webapp/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
27
bytefy.webapp/README.md
Normal file
27
bytefy.webapp/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Tools
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.7.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
110
bytefy.webapp/angular.json
Normal file
110
bytefy.webapp/angular.json
Normal file
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"tools": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/tools",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"aot": true,
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/web.config"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "tools:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "tools:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "tools:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/web.config"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13029
bytefy.webapp/package-lock.json
generated
Normal file
13029
bytefy.webapp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
bytefy.webapp/package.json
Normal file
46
bytefy.webapp/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "tools",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.3.0",
|
||||
"@angular/cdk": "^17.3.10",
|
||||
"@angular/common": "^17.3.0",
|
||||
"@angular/compiler": "^17.3.0",
|
||||
"@angular/core": "^17.3.0",
|
||||
"@angular/forms": "^17.3.0",
|
||||
"@angular/platform-browser": "^17.3.0",
|
||||
"@angular/platform-browser-dynamic": "^17.3.0",
|
||||
"@angular/router": "^17.3.0",
|
||||
"@ng-icons/core": "^29.5.1",
|
||||
"@ng-icons/css.gg": "^29.5.1",
|
||||
"@ng-icons/heroicons": "^29.5.1",
|
||||
"angularx-qrcode": "^18.0.2",
|
||||
"primeicons": "^7.0.0",
|
||||
"primeng": "^18.0.0-beta.3",
|
||||
"rxjs": "~7.8.0",
|
||||
"tailwindcss-primeui": "^0.3.4",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.3.7",
|
||||
"@angular/cli": "^17.3.7",
|
||||
"@angular/compiler-cli": "^17.3.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.4.2"
|
||||
}
|
||||
}
|
||||
7
bytefy.webapp/src/app/app.component.html
Normal file
7
bytefy.webapp/src/app/app.component.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div class="darkmode">
|
||||
<app-header></app-header>
|
||||
<div class="main-content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<app-footer></app-footer>
|
||||
</div>
|
||||
26
bytefy.webapp/src/app/app.component.scss
Normal file
26
bytefy.webapp/src/app/app.component.scss
Normal file
@@ -0,0 +1,26 @@
|
||||
.main-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.darkmode {
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
.p-panel {
|
||||
border-radius: unset !important;
|
||||
}
|
||||
|
||||
.p-panel-header {
|
||||
background: unset !important;
|
||||
}
|
||||
|
||||
.p-fileupload-header {
|
||||
background: unset !important;
|
||||
}
|
||||
|
||||
.p-fileupload {
|
||||
background: unset !important;
|
||||
}
|
||||
}
|
||||
29
bytefy.webapp/src/app/app.component.spec.ts
Normal file
29
bytefy.webapp/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have the 'tools' title`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('tools');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, tools');
|
||||
});
|
||||
});
|
||||
28
bytefy.webapp/src/app/app.component.ts
Normal file
28
bytefy.webapp/src/app/app.component.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { HeaderComponent } from './header/header.component';
|
||||
import { PrimeNGConfig } from 'primeng/api';
|
||||
import { FooterComponent } from './footer/footer.component';
|
||||
import { Lara } from 'primeng/themes/lara';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [RouterOutlet, HeaderComponent, FooterComponent],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit{
|
||||
title = 'tools';
|
||||
|
||||
constructor(private config: PrimeNGConfig) {
|
||||
this.config.theme.set({
|
||||
preset: Lara,
|
||||
darkModeSelector: '.darkmode'
|
||||
});
|
||||
}
|
||||
ngOnInit(): void {
|
||||
const element = document.querySelector('html');
|
||||
element?.classList.toggle('darkmode');
|
||||
}
|
||||
}
|
||||
22
bytefy.webapp/src/app/app.config.ts
Normal file
22
bytefy.webapp/src/app/app.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import { provideNgIconsConfig } from '@ng-icons/core';
|
||||
import { HttpClientXsrfModule, provideHttpClient } from '@angular/common/http';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideRouter(routes),
|
||||
provideAnimationsAsync("animations"),
|
||||
provideNgIconsConfig({
|
||||
size: '1.5em',
|
||||
}),
|
||||
provideHttpClient(),
|
||||
importProvidersFrom(HttpClientXsrfModule.withOptions({
|
||||
cookieName: 'X-XSRF-TOKEN',
|
||||
headerName: '2311d8d8-607d-4747-8939-1bde65643254',
|
||||
}))
|
||||
]
|
||||
};
|
||||
65
bytefy.webapp/src/app/app.routes.ts
Normal file
65
bytefy.webapp/src/app/app.routes.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { AsciiToTextComponent } from '../tools/client-side/ascii-to-text/ascii-to-text.component';
|
||||
import { GuidComponent } from '../tools/client-side/guid/guid.component';
|
||||
import { Base64ConverterComponent } from '../tools/client-side/base64-converter/base64-converter.component';
|
||||
import { JwtToJsonComponent } from '../tools/client-side/jwt-to-json/jwt-to-json.component';
|
||||
import { TextToCronComponent } from '../tools/client-side/text-to-cron/text-to-cron.component';
|
||||
import { DdsToPngComponent } from '../tools/client-side/dds-to-png/dds-to-png.component';
|
||||
import { ImageConverterComponent } from '../tools/server-side/image-converter/image-converter.component';
|
||||
import { WordCounterComponent } from '../tools/client-side/word-counter/word-counter.component';
|
||||
import { ColorPickerComponent } from '../tools/client-side/color-picker/color-picker.component';
|
||||
import { QrCodeGeneratorComponent } from '../tools/client-side/qr-code-generator/qr-code-generator.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: 'ascii-to-text',
|
||||
pathMatch: 'full',
|
||||
component: AsciiToTextComponent
|
||||
},
|
||||
{
|
||||
path: 'guid',
|
||||
pathMatch: 'full',
|
||||
component: GuidComponent
|
||||
},
|
||||
{
|
||||
path: 'base64-converter',
|
||||
pathMatch: 'full',
|
||||
component: Base64ConverterComponent
|
||||
},
|
||||
{
|
||||
path: 'jwt-decoder',
|
||||
pathMatch: 'full',
|
||||
component: JwtToJsonComponent
|
||||
},
|
||||
{
|
||||
path: 'text-to-cron',
|
||||
pathMatch: 'full',
|
||||
component: TextToCronComponent
|
||||
},
|
||||
{
|
||||
path: 'dds-to-png',
|
||||
pathMatch: 'full',
|
||||
component: DdsToPngComponent
|
||||
},
|
||||
{
|
||||
path: 'image-converter',
|
||||
pathMatch: 'full',
|
||||
component: ImageConverterComponent
|
||||
},
|
||||
{
|
||||
path: 'text-counter',
|
||||
pathMatch: 'full',
|
||||
component: WordCounterComponent
|
||||
},
|
||||
{
|
||||
path: 'qr-code-generator',
|
||||
pathMatch: 'full',
|
||||
component: QrCodeGeneratorComponent
|
||||
},
|
||||
{
|
||||
path: 'color-picker',
|
||||
pathMatch: 'full',
|
||||
component: ColorPickerComponent
|
||||
}
|
||||
];
|
||||
|
||||
9
bytefy.webapp/src/app/footer/footer.component.html
Normal file
9
bytefy.webapp/src/app/footer/footer.component.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<p>© {{year}} Bytefy. All rights reserved.</p>
|
||||
<nav>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
<a href="/terms-of-service">Terms of Service</a>
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
27
bytefy.webapp/src/app/footer/footer.component.scss
Normal file
27
bytefy.webapp/src/app/footer/footer.component.scss
Normal file
@@ -0,0 +1,27 @@
|
||||
.footer {
|
||||
color: #a5a5a5;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
|
||||
.footer-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
nav {
|
||||
margin-top: 0.5rem;
|
||||
|
||||
a {
|
||||
color: #a5a5a5;
|
||||
margin: 0 0.5rem;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
bytefy.webapp/src/app/footer/footer.component.spec.ts
Normal file
28
bytefy.webapp/src/app/footer/footer.component.spec.ts
Normal 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 { FooterComponent } from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FooterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
11
bytefy.webapp/src/app/footer/footer.component.ts
Normal file
11
bytefy.webapp/src/app/footer/footer.component.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrls: ['./footer.component.scss'],
|
||||
standalone: true
|
||||
})
|
||||
export class FooterComponent {
|
||||
year: number = new Date().getFullYear();
|
||||
}
|
||||
5
bytefy.webapp/src/app/header/header.component.html
Normal file
5
bytefy.webapp/src/app/header/header.component.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<p-megamenu [model]="items">
|
||||
<ng-template pTemplate="start">
|
||||
<img class="logotype" src="../../assets/logo-full-orange-beta-vectorized.svg" alt="Bytefy Logotype" />
|
||||
</ng-template>
|
||||
</p-megamenu>
|
||||
8
bytefy.webapp/src/app/header/header.component.scss
Normal file
8
bytefy.webapp/src/app/header/header.component.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
.logotype {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
::ng-deep .p-megamenu-col-12 {
|
||||
flex-direction: row !important;
|
||||
display: flex !important;
|
||||
}
|
||||
28
bytefy.webapp/src/app/header/header.component.spec.ts
Normal file
28
bytefy.webapp/src/app/header/header.component.spec.ts
Normal 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 { HeaderComponent } from './header.component';
|
||||
|
||||
describe('HeaderComponent', () => {
|
||||
let component: HeaderComponent;
|
||||
let fixture: ComponentFixture<HeaderComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HeaderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
124
bytefy.webapp/src/app/header/header.component.ts
Normal file
124
bytefy.webapp/src/app/header/header.component.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MegaMenuItem } from 'primeng/api';
|
||||
import { MegaMenuModule } from 'primeng/megamenu';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { AvatarModule } from 'primeng/avatar';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss'],
|
||||
standalone: true,
|
||||
imports: [MegaMenuModule, ButtonModule, CommonModule, AvatarModule]
|
||||
})
|
||||
export class HeaderComponent implements OnInit {
|
||||
items: MegaMenuItem[] | undefined;
|
||||
isDarkMode: boolean = true;
|
||||
|
||||
ngOnInit() {
|
||||
this.isDarkMode = window?.matchMedia?.('(prefers-color-scheme:dark)')?.matches;
|
||||
|
||||
this.items = [
|
||||
{
|
||||
label: 'Tools',
|
||||
icon: 'pi pi-wrench',
|
||||
items: [
|
||||
[
|
||||
{
|
||||
label: 'Text Tools',
|
||||
items: [
|
||||
{
|
||||
label: 'Text counter',
|
||||
routerLink: 'text-counter',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
},
|
||||
{
|
||||
label: 'Guid Generator',
|
||||
routerLink: 'guid',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
},
|
||||
{
|
||||
label: 'Jwt decoder',
|
||||
routerLink: 'jwt-decoder',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
},
|
||||
{
|
||||
label: 'Text to Cron Expression',
|
||||
routerLink: 'text-to-cron',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Media Tools',
|
||||
items: [
|
||||
{
|
||||
label: 'Color picker',
|
||||
routerLink: 'color-picker',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Conversion',
|
||||
icon: 'pi pi-box',
|
||||
items: [
|
||||
[
|
||||
{
|
||||
label: 'Convert',
|
||||
items: [
|
||||
{
|
||||
label: 'DDS to PNG',
|
||||
routerLink: 'dds-to-png',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
},
|
||||
{
|
||||
label: 'Image Converter',
|
||||
routerLink: 'image-converter',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
},
|
||||
{
|
||||
label: 'Base64 Converter',
|
||||
routerLink: 'base64-converter',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
},
|
||||
{
|
||||
label: 'Ascii to text',
|
||||
routerLink: 'ascii-to-text',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Generators',
|
||||
icon: 'pi pi-box',
|
||||
items: [
|
||||
[
|
||||
{
|
||||
label: 'Generators',
|
||||
items: [
|
||||
{
|
||||
label: 'QR Code Generator',
|
||||
routerLink: 'qr-code-generator',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
},
|
||||
{
|
||||
label: 'Guid Generator',
|
||||
routerLink: 'guid',
|
||||
routerLinkActiveOptions: { exact: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
12
bytefy.webapp/src/app/models/conversion.model.ts
Normal file
12
bytefy.webapp/src/app/models/conversion.model.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// Contains all models used for conversion
|
||||
|
||||
export interface ProcessedFile {
|
||||
name: string;
|
||||
link: string;
|
||||
format: string;
|
||||
}
|
||||
|
||||
export interface Format {
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<page [header]="title">
|
||||
<ng-template pTemplate="header">
|
||||
<p-tag *ngIf="isBeta" severity="warn" value="Beta"></p-tag>
|
||||
</ng-template>
|
||||
<textarea
|
||||
pInputTextarea
|
||||
(keyup)="onTopChange($event)"
|
||||
[disabled]="topDisabled"
|
||||
pTextarea
|
||||
[value]="topValue"
|
||||
[placeholder]="topPlaceholder">
|
||||
</textarea>
|
||||
<div class="icon">
|
||||
<i class="pi pi-arrow-right-arrow-left" style="font-size: 1rem"></i>
|
||||
</div>
|
||||
<textarea
|
||||
(keyup)="onBottomChange($event)"
|
||||
[disabled]="bottomDisabled"
|
||||
pTextarea
|
||||
[value]="bottomValue"
|
||||
[placeholder]="bottomPlaceholder">
|
||||
</textarea>
|
||||
</page>
|
||||
@@ -0,0 +1,50 @@
|
||||
* {
|
||||
color: #fff;
|
||||
}
|
||||
.icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
// background-color: var(--primary-contrast);
|
||||
|
||||
i {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 1140px;
|
||||
|
||||
.conversion {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70vh;
|
||||
|
||||
p-floatlabel {
|
||||
width: 30vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 175px;
|
||||
padding: 12px 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
resize: none;
|
||||
background-color: var(--primary-contrast);
|
||||
}
|
||||
|
||||
|
||||
::ng-deep .p-panel-header {
|
||||
justify-content: unset !important;
|
||||
|
||||
* {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, Output, EventEmitter, input } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FloatLabelModule } from 'primeng/floatlabel';
|
||||
import { InputTextareaModule } from 'primeng/inputtextarea';
|
||||
import { PanelModule } from 'primeng/panel';
|
||||
import { TagModule } from 'primeng/tag';
|
||||
import { PageComponent } from '../page/page.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dual-textarea',
|
||||
templateUrl: 'dual-textarea.component.html',
|
||||
styleUrls: ['dual-textarea.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
FloatLabelModule,
|
||||
InputTextareaModule,
|
||||
FormsModule,
|
||||
PanelModule,
|
||||
CommonModule,
|
||||
TagModule,
|
||||
PageComponent
|
||||
]
|
||||
|
||||
})
|
||||
export class DualTextareaComponent {
|
||||
@Input() topDisabled: boolean = false;
|
||||
@Input() bottomDisabled: boolean = false;
|
||||
@Input() title: string = 'Dual Textarea';
|
||||
@Input() topPlaceholder: string = 'Left Textarea';
|
||||
@Input() bottomPlaceholder: string = 'Right Textarea';
|
||||
@Input() topValue: string = '';
|
||||
@Input() bottomValue: string = '';
|
||||
@Input() isBeta: boolean = false;
|
||||
@Output() topChange = new EventEmitter<string>();
|
||||
@Output() bottomChange = new EventEmitter<string>();
|
||||
|
||||
onTopChange(event: Event): void {
|
||||
const input = (event.target as HTMLTextAreaElement).value;
|
||||
this.topChange.emit(input);
|
||||
}
|
||||
|
||||
onBottomChange(event: Event): void {
|
||||
const input = (event.target as HTMLTextAreaElement).value;
|
||||
this.bottomChange.emit(input);
|
||||
}
|
||||
}
|
||||
5
bytefy.webapp/src/app/shared/page/page.component.html
Normal file
5
bytefy.webapp/src/app/shared/page/page.component.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<div class="card">
|
||||
<p-panel [header]="header">
|
||||
<ng-content></ng-content>
|
||||
</p-panel>
|
||||
</div>
|
||||
20
bytefy.webapp/src/app/shared/page/page.component.scss
Normal file
20
bytefy.webapp/src/app/shared/page/page.component.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
width: 96vw;
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 1140px;
|
||||
}
|
||||
|
||||
::ng-deep .p-panel-header {
|
||||
justify-content: unset !important;
|
||||
|
||||
* {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
bytefy.webapp/src/app/shared/page/page.component.spec.ts
Normal file
28
bytefy.webapp/src/app/shared/page/page.component.spec.ts
Normal 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 { PageComponent } from './page.component';
|
||||
|
||||
describe('PageComponent', () => {
|
||||
let component: PageComponent;
|
||||
let fixture: ComponentFixture<PageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PageComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
13
bytefy.webapp/src/app/shared/page/page.component.ts
Normal file
13
bytefy.webapp/src/app/shared/page/page.component.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { PanelModule } from 'primeng/panel';
|
||||
|
||||
@Component({
|
||||
selector: 'page',
|
||||
templateUrl: './page.component.html',
|
||||
styleUrls: ['./page.component.scss'],
|
||||
standalone: true,
|
||||
imports: [PanelModule]
|
||||
})
|
||||
export class PageComponent {
|
||||
@Input() header: string = '';
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<page [header]="title">
|
||||
<ng-template pTemplate="header">
|
||||
<p-tag *ngIf="isBeta" severity="warn" value="Beta"></p-tag>
|
||||
</ng-template>
|
||||
|
||||
<p-fileUpload
|
||||
name="file"
|
||||
(onSelect)="onFileSelect($event)"
|
||||
[auto]="true"
|
||||
[accept]="accept"
|
||||
[previewWidth]="isPreview ? '50px' : '0px'"
|
||||
mode="advanced"
|
||||
[url]="url"
|
||||
[withCredentials]="true"
|
||||
[method]="method"
|
||||
[headers]="requestHeaders"
|
||||
>
|
||||
<ng-template
|
||||
*ngIf="fileTypeSelector"
|
||||
pTemplate="header"
|
||||
let-files
|
||||
let-chooseCallback="chooseCallback"
|
||||
let-clearCallback="clearCallback"
|
||||
let-uploadCallback="uploadCallback"
|
||||
>
|
||||
<p-button
|
||||
(onClick)="choose($event, chooseCallback)"
|
||||
icon="pi pi-images"
|
||||
[rounded]="true"
|
||||
[outlined]="true"
|
||||
/>
|
||||
|
||||
<p-autoComplete
|
||||
*ngIf="fileTypeSelector"
|
||||
(onSelect)="onAutoCompleteDropdownClick($event)"
|
||||
[virtualScroll]="true"
|
||||
[suggestions]="filteredFiles"
|
||||
[virtualScrollItemSize]="34"
|
||||
(completeMethod)="onAutoComplete($event)"
|
||||
optionLabel="name"
|
||||
[dropdown]="true"
|
||||
placeholder="Select a output format"
|
||||
/>
|
||||
|
||||
<p-button
|
||||
(onClick)="onUploadEvent()"
|
||||
icon="pi pi-file-arrow-up"
|
||||
[rounded]="true"
|
||||
[outlined]="true"
|
||||
/>
|
||||
</ng-template>
|
||||
|
||||
<ng-template *ngIf="fileTypeSelector" pTemplate="empty">
|
||||
<div>Drag and drop files to here to upload.</div>
|
||||
</ng-template>
|
||||
|
||||
</p-fileUpload>
|
||||
<p-table [value]="processedFiles" *ngIf="processedFiles.length != 0">
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Format</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-file>
|
||||
<tr>
|
||||
<td>{{file.name}}</td>
|
||||
<td>{{file.format}}</td>
|
||||
<td><a [href]="file.link" [download]="file.name">{{file.name}}</a></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
</page>
|
||||
@@ -0,0 +1,13 @@
|
||||
.conversion {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
::ng-deep .p-panel-header {
|
||||
justify-content: unset !important;
|
||||
|
||||
* {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FileSelectEvent, FileUploadEvent, FileUploadModule } from 'primeng/fileupload';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import { PanelModule } from 'primeng/panel';
|
||||
import { TableModule } from 'primeng/table';
|
||||
import { AutoCompleteCompleteEvent, AutoCompleteModule, AutoCompleteSelectEvent } from 'primeng/autocomplete';
|
||||
import { BadgeModule } from 'primeng/badge';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import { TagModule } from 'primeng/tag';
|
||||
import { PageComponent } from '../page/page.component';
|
||||
|
||||
interface ProcessedFile {
|
||||
name: string;
|
||||
link: string;
|
||||
format: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-file-converter',
|
||||
templateUrl: 'file-converter.component.html',
|
||||
styleUrls: ['file-converter.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
FileUploadModule,
|
||||
ButtonModule,
|
||||
PanelModule,
|
||||
TableModule,
|
||||
AutoCompleteModule,
|
||||
BadgeModule,
|
||||
TagModule,
|
||||
PageComponent
|
||||
]
|
||||
})
|
||||
export class FileConverterComponent implements OnInit {
|
||||
_fileFormats: string[] = [];
|
||||
accept: string = '';
|
||||
selected = '';
|
||||
invalidFileTypeMessageSummary: string = '';
|
||||
url: string = '';
|
||||
requestHeaders: any;
|
||||
selectedFile: File[] | null = null;
|
||||
|
||||
@Output() fileSelected = new EventEmitter<File[]>();
|
||||
@Input() isBeta: boolean = false;
|
||||
@Input() filteredFiles: string[] = [];
|
||||
@Input() isPreview: boolean = true;
|
||||
@Input() title: string = 'File Converter';
|
||||
@Input() processedFiles: ProcessedFile[] = [];
|
||||
@Input()
|
||||
set fileFormats(formats: string[]) {
|
||||
this._fileFormats = formats;
|
||||
this.accept = formats.join(',');
|
||||
}
|
||||
|
||||
// File type selector
|
||||
@Output() autoComplete = new EventEmitter<AutoCompleteCompleteEvent>();
|
||||
@Output() selectedFormat = new EventEmitter<string>();
|
||||
@Input() fileTypeSelector: boolean = false;
|
||||
|
||||
// Upload file to server
|
||||
@Input() baseUrl = '';
|
||||
@Input() method : 'post' | 'put' = 'post';
|
||||
@Input() headers: HttpHeaders = new HttpHeaders();
|
||||
@Output() upload = new EventEmitter<FileUploadEvent>();
|
||||
|
||||
get fileFormats(): string[] {
|
||||
return this._fileFormats;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.requestHeaders = this.headers;
|
||||
}
|
||||
|
||||
choose(_: any, callback: () => void) {
|
||||
callback();
|
||||
}
|
||||
|
||||
onFileSelect(event: FileSelectEvent): void {
|
||||
this.selectedFile = event.currentFiles;
|
||||
this.fileSelected.emit(this.selectedFile!);
|
||||
}
|
||||
|
||||
onAutoComplete(event: AutoCompleteCompleteEvent): void {
|
||||
this.autoComplete.emit(event);
|
||||
}
|
||||
|
||||
onAutoCompleteDropdownClick(event: AutoCompleteSelectEvent): void {
|
||||
this.selectedFormat.emit(event.value.name);
|
||||
this.selected = event.value.name;
|
||||
}
|
||||
|
||||
onUploadEvent() {
|
||||
this.upload.emit();
|
||||
}
|
||||
}
|
||||
0
bytefy.webapp/src/assets/.gitkeep
Normal file
0
bytefy.webapp/src/assets/.gitkeep
Normal file
1061
bytefy.webapp/src/assets/logo-full-orange-beta-vectorized.svg
Normal file
1061
bytefy.webapp/src/assets/logo-full-orange-beta-vectorized.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 640 KiB |
BIN
bytefy.webapp/src/assets/logo-full-orange-beta.png
Normal file
BIN
bytefy.webapp/src/assets/logo-full-orange-beta.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 598 KiB |
BIN
bytefy.webapp/src/assets/logo-full-orange.png
Normal file
BIN
bytefy.webapp/src/assets/logo-full-orange.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 589 KiB |
BIN
bytefy.webapp/src/assets/logo-full.png
Normal file
BIN
bytefy.webapp/src/assets/logo-full.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 589 KiB |
BIN
bytefy.webapp/src/assets/logo.png
Normal file
BIN
bytefy.webapp/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 547 KiB |
@@ -0,0 +1,4 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
uploadServiceBaseUrl: 'http://localhost:1337'
|
||||
};
|
||||
4
bytefy.webapp/src/environments/environment.ts
Normal file
4
bytefy.webapp/src/environments/environment.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
uploadServiceBaseUrl: 'https://image.bytefy.net'
|
||||
};
|
||||
BIN
bytefy.webapp/src/favicon.ico
Normal file
BIN
bytefy.webapp/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
16
bytefy.webapp/src/index.html
Normal file
16
bytefy.webapp/src/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Bytefy - Online Tools</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@304&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
5
bytefy.webapp/src/main.ts
Normal file
5
bytefy.webapp/src/main.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { AppComponent } from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
|
||||
29
bytefy.webapp/src/styles.scss
Normal file
29
bytefy.webapp/src/styles.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
@import "primeicons/primeicons.css";
|
||||
body,
|
||||
body .p-component
|
||||
{
|
||||
font-family: "Roboto Mono", monospace;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 304;
|
||||
font-style: normal;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
* {
|
||||
border-radius: unset !important;
|
||||
}
|
||||
|
||||
.p-megamenu {
|
||||
background: unset !important;
|
||||
}
|
||||
|
||||
.p-megamenu-overlay {
|
||||
background: var(--p-surface-800) !important;
|
||||
border: unset !important;
|
||||
border-radius: unset !important;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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(' ');
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<app-file-converter
|
||||
title="DDS to PNG Converter"
|
||||
[isPreview]="false"
|
||||
[processedFiles]="processedFiles"
|
||||
[fileFormats]="fileFormats"
|
||||
(fileSelected)="onFileSelected($event)">
|
||||
</app-file-converter>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
45
bytefy.webapp/src/tools/client-side/guid/guid.component.html
Normal file
45
bytefy.webapp/src/tools/client-side/guid/guid.component.html
Normal 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>
|
||||
33
bytefy.webapp/src/tools/client-side/guid/guid.component.scss
Normal file
33
bytefy.webapp/src/tools/client-side/guid/guid.component.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
100
bytefy.webapp/src/tools/client-side/guid/guid.component.ts
Normal file
100
bytefy.webapp/src/tools/client-side/guid/guid.component.ts
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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.'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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}`;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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`);
|
||||
}
|
||||
}
|
||||
19
bytefy.webapp/src/web.config
Normal file
19
bytefy.webapp/src/web.config
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
<rule name="Angular Routes" stopProcessing="true">
|
||||
<match url=".*" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="./index.html" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
|
||||
</configuration>
|
||||
14
bytefy.webapp/tsconfig.app.json
Normal file
14
bytefy.webapp/tsconfig.app.json
Normal file
@@ -0,0 +1,14 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
32
bytefy.webapp/tsconfig.json
Normal file
32
bytefy.webapp/tsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/out-tsc",
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"lib": [
|
||||
"ES2022",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
14
bytefy.webapp/tsconfig.spec.json
Normal file
14
bytefy.webapp/tsconfig.spec.json
Normal file
@@ -0,0 +1,14 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user