26 Commits

Author SHA1 Message Date
27f023964f Merge branch 'feature/scroll-to-top' into dev 2024-06-27 14:22:16 +03:00
b43c8db305 feat: scroll to top button 2024-06-27 14:21:58 +03:00
e3fc28e440 fix: forgot about it 2024-06-27 14:21:45 +03:00
c9638403ab Merge branch 'fix/regex' into dev 2024-06-27 13:21:11 +03:00
02ae9c6745 fix: fixed searching text 2024-06-27 13:20:42 +03:00
c8cb378123 feat: back btn in translator 2024-06-21 15:30:21 +03:00
47060bcb2d fix: scroll in router-outlet 2024-06-21 15:00:39 +03:00
fb3acd5182 Merge branch 'feature/add-nitroplus-translator' into dev 2024-06-20 23:48:07 +03:00
cb1d7e24b7 fix: nitroplus website 2024-06-20 23:47:22 +03:00
0d6a56c9a3 feat: nitroplus translate. Needs to fix 2024-06-20 16:55:57 +03:00
7cc5cbaac3 Nx migration 2024-05-13 16:59:23 +03:00
cf6128c248 Обновить README.md 2024-03-11 15:38:56 +03:00
1fd6dd632e Merge pull request 'Some additions' (#4) from dev into master
Reviewed-on: #4
2024-03-11 15:37:03 +03:00
e291379602 Icon title translate 2024-03-11 14:37:32 +03:00
1237ee4439 Style updates 2024-03-10 14:36:37 +03:00
935d1f5dd9 Modal window btns cursor fix 2024-03-10 13:56:25 +03:00
2a31e85e50 Hide lang modal on click 2024-03-10 13:56:09 +03:00
c4665737dc Lang save 2024-03-10 13:53:57 +03:00
e3dac5bc83 Merge pull request 'Modal window and translations' (#3) from dev into master
Reviewed-on: #3
2024-03-10 00:20:08 +03:00
90582949d5 Added i18n translations 2024-03-10 00:14:00 +03:00
a9436a5af7 Min sizes 2024-03-09 18:25:06 +03:00
8cd5dd1f8e Added info modal window 2024-03-09 18:16:20 +03:00
f9a7b812fe Merge pull request 'Full reformat' (#2) from dev into master
Reviewed-on: #2
2024-03-09 14:07:55 +03:00
4d09e6ee11 Full reformat 2024-03-09 14:05:35 +03:00
9ad38ab3cd Forgot base 2024-03-07 00:51:35 +03:00
3fc58bae1d Merge pull request 'First website version' (#1) from dev into master
Reviewed-on: #1
2024-03-07 00:18:30 +03:00
114 changed files with 1924 additions and 8996 deletions

View File

@@ -1,13 +1,9 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"ignorePatterns": ["**/*"],
"overrides": [
{
"files": [
"*.ts"
],
"files": ["*.ts"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
@@ -34,14 +30,31 @@
}
},
{
"files": [
"*.html"
],
"files": ["*.html"],
"extends": [
"plugin:@angular-eslint/template/recommended",
"plugin:@angular-eslint/template/accessibility"
],
"rules": {}
},
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
}
]
],
"plugins": ["@nx"]
}

8
.gitignore vendored
View File

@@ -40,3 +40,11 @@ testem.log
# System files
.DS_Store
Thumbs.db
.nx/cache
.nx/workspace-data
# env
*.env*
!*.env.example

5
.prettierignore Normal file
View File

@@ -0,0 +1,5 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data

View File

@@ -1,4 +1,8 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
"recommendations": [
"angular.ng-template",
"nrwl.angular-console",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

View File

@@ -6,7 +6,8 @@
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/node_modules": true,
"**/.angular": true,
}
}
// "**/node_modules": true,
"**/.angular": true
},
"editor.formatOnSave": true
}

View File

@@ -2,26 +2,6 @@
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.2.
## Development server
## Visitor counter
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.
![NwaifuWeb](https://count.getloli.com/get/@NwaifuWeb?theme=gelbooru)

View File

@@ -1,116 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "pnpm",
"schematicCollections": [
"@angular-eslint/schematics"
]
},
"newProjectRoot": "projects",
"projects": {
"NwaifuWeb": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "less"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/nwaifu-web",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "less",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.less"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "NwaifuWeb:build:production"
},
"development": {
"buildTarget": "NwaifuWeb:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "NwaifuWeb:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "less",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.less"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
}
}
}

View File

@@ -0,0 +1,41 @@
{
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": [
"plugin:@angular-eslint/template/recommended",
"plugin:@angular-eslint/template/accessibility"
],
"rules": {}
}
],
"extends": "../../.eslintrc.json"
}

View File

@@ -0,0 +1,86 @@
{
"name": "NwaifuWeb",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"generators": {
"@schematics/angular:component": {
"style": "less"
}
},
"sourceRoot": "apps/NwaifuWeb/src",
"prefix": "app",
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/apps/NwaifuWeb",
"index": "apps/NwaifuWeb/src/index.html",
"browser": "apps/NwaifuWeb/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "apps/NwaifuWeb/tsconfig.app.json",
"inlineStyleLanguage": "less",
"assets": ["apps/NwaifuWeb/src/favicon.ico", "apps/NwaifuWeb/src/assets"],
"styles": ["apps/NwaifuWeb/src/styles.less"],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "NwaifuWeb:build:production"
},
"development": {
"buildTarget": "NwaifuWeb:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "NwaifuWeb:build"
}
},
"test": {
"executor": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "apps/NwaifuWeb/tsconfig.spec.json",
"inlineStyleLanguage": "less",
"assets": ["apps/NwaifuWeb/src/favicon.ico", "apps/NwaifuWeb/src/assets"],
"styles": ["apps/NwaifuWeb/src/styles.less"],
"scripts": []
}
},
"lint": {
"executor": "@nx/eslint:lint",
"options": {
"lintFilePatterns": ["apps/NwaifuWeb/**/*.ts", "apps/NwaifuWeb/**/*.html"]
}
}
}
}

View File

@@ -0,0 +1,6 @@
<header>
<app-panel></app-panel>
</header>
<main>
<router-outlet></router-outlet>
</main>

View File

@@ -0,0 +1,8 @@
main {
overflow-y: auto;
width: 100%;
height: calc(100% - 2rem);
background-image: url("../assets/img/wallpaper.png");
background-repeat: no-repeat;
background-size: 100% 100%;
}

View File

@@ -1,14 +1,14 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { TestBed } from "@angular/core/testing";
import { AppComponent } from "./app.component";
describe('AppComponent', () => {
describe("AppComponent", () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
it("should create the app", () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
@@ -17,13 +17,13 @@ describe('AppComponent', () => {
it(`should have the 'NwaifuWeb' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('NwaifuWeb');
expect(app.title).toEqual("NwaifuWeb");
});
it('should render title', () => {
it("should render title", () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, NwaifuWeb');
expect(compiled.querySelector("h1")?.textContent).toContain("Hello, NwaifuWeb");
});
});

View File

@@ -0,0 +1,14 @@
import { Component } from "@angular/core";
import { RouterModule } from "@angular/router";
import { PanelComponent } from "./modules/panel/panel.component";
@Component({
selector: "app-root",
standalone: true,
imports: [PanelComponent, RouterModule],
templateUrl: "./app.component.html",
styleUrl: "./app.component.less",
})
export class AppComponent {
title = "Nwaifu";
}

View File

@@ -0,0 +1,25 @@
import { APP_INITIALIZER, ApplicationConfig } from "@angular/core";
import { provideHttpClient } from "@angular/common/http";
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { TranslateService } from "./services/translate.service";
export function setupTranslateServiceFactory(service: TranslateService) {
const lang = localStorage.getItem("lang") ?? "en";
return () => service.use(lang);
}
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
TranslateService,
{
provide: APP_INITIALIZER,
useFactory: setupTranslateServiceFactory,
deps: [TranslateService],
multi: true,
},
],
};

View File

@@ -0,0 +1,19 @@
import { Routes } from "@angular/router";
import { MainPageComponent } from './pages/main/main.component';
import { NitroplusComponent } from './pages/nitroplus-translator/nitroplus-translator.component';
export const routes: Routes = [
{
path: '',
children: [
{
path: '',
component: MainPageComponent
},
{
path: 'translator',
component: NitroplusComponent
}
]
},
];

View File

@@ -0,0 +1,6 @@
export interface Link {
id: number;
text: string;
url: string;
svg: string;
}

View File

@@ -0,0 +1,4 @@
<div class="desktop-icon">
<img [src]="image" [alt]="alt" [title]="name" />
<span>{{ name }}</span>
</div>

View File

@@ -0,0 +1,17 @@
.desktop-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 5rem;
height: 6rem;
color: white;
font-size: 0.7rem;
user-select: none;
padding: 1rem;
border-radius: 10px;
transition: 0.2s ease-in-out;
&:hover {
background-color: rgba(25, 17, 19, 0.7);
}
}

View File

@@ -0,0 +1,17 @@
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { RouterModule } from '@angular/router';
@Component({
selector: 'desktop-icon',
templateUrl: './desktop-icon.component.html',
styleUrls: ['./desktop-icon.component.less'],
standalone: true,
imports: [CommonModule, RouterModule]
})
export class DesktopIconComponent {
@Input() image: string = '';
@Input() alt: string = '';
@Input() name: string = '';
@Input({required: false}) click: ()=>void = ()=>{};
}

View File

@@ -0,0 +1,5 @@
<section class="dock">
@for(link of links; track link.id) {
<app-link [url]="link.url" [svg]="link.svg" [text]="link.text"></app-link>
}
</section>

View File

@@ -0,0 +1,27 @@
.dock {
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
width: 330px;
height: 100px;
background-color: rgba(171, 178, 181, 30%);
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 0 1rem;
margin-block-end: 1rem;
border-radius: 35px;
app-link {
width: 100%;
height: 100%;
padding: 0 0.5rem;
&:first-child {
padding-inline-start: 0;
}
&:last-child {
padding-inline-end: 0;
}
}
}

View File

@@ -0,0 +1,34 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { Link } from "../../interfaces/link";
import { LinkComponent } from "../link/link.component";
@Component({
standalone: true,
selector: "app-dock",
imports: [CommonModule, LinkComponent],
templateUrl: "./dock.component.html",
styleUrls: ["./dock.component.less"],
})
export class DockComponent {
readonly links: Link[] = [
{
id: 0,
svg: "../../../assets/svg/logo-telegram.svg",
url: "https://t.me/neur0w0men",
text: "TELEGRAM_LABEL",
},
{
id: 1,
svg: "../../../assets/svg/logo-github.svg",
url: "https://github.com/MrSedan",
text: "GITHUB_LABEL",
},
{
id: 2,
svg: "../../../assets/svg/logo-gitea.svg",
url: "https://git.nwaifu.su",
text: "GITEA_LABEL",
},
];
}

View File

@@ -0,0 +1,4 @@
<a [href]="url">
<img [src]="svg" [alt]="url" [title]="text | translate" />
<span>{{ text | translate }}</span>
</a>

View File

@@ -0,0 +1,36 @@
img {
width: 100%;
height: 70px;
margin: auto 0;
}
a {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
gap: 0;
transition: 0.5s ease-in-out;
span {
opacity: 0;
visibility: hidden;
transform: scale(0);
overflow: hidden;
color: var(--white);
text-align: center;
font-size: 0.8rem;
height: 0;
font-weight: 600;
transition: all 0.6s ease-in-out;
}
&:hover {
transform: translateY(-1rem);
span {
opacity: 1;
height: auto;
transform: scale(1);
visibility: visible;
overflow: visible;
}
}
}

View File

@@ -0,0 +1,16 @@
import { CommonModule } from "@angular/common";
import { Component, Input } from "@angular/core";
import { TranslationPipe } from "../../pipes/translation.pipe";
@Component({
standalone: true,
selector: "app-link",
imports: [CommonModule, TranslationPipe],
templateUrl: "./link.component.html",
styleUrls: ["./link.component.less"],
})
export class LinkComponent {
@Input() url: string = "#";
@Input() svg: string = "";
@Input() text: string = "";
}

View File

@@ -0,0 +1 @@
<div class="modal-window" #modal></div>

View File

@@ -0,0 +1,31 @@
import { CommonModule } from "@angular/common";
import { AfterViewInit, Component, ElementRef, ViewChild } from "@angular/core";
import { DialogRef, DialogService } from "@ngneat/dialog";
import { WindowComponent } from "../window/window.component";
@Component({
selector: "app-modal",
templateUrl: "./modal.component.html",
styleUrls: ["./modal.component.less"],
imports: [CommonModule],
standalone: true,
})
export class ModalComponent implements AfterViewInit {
dialogRef: DialogRef | undefined = undefined;
@ViewChild("modal") modal: ElementRef | undefined = undefined;
constructor(private dialogService: DialogService, private ref: ElementRef) {}
ngAfterViewInit(): void {
this.dialogRef = this.dialogService.open(WindowComponent, {
resizable: true,
draggable: true,
backdrop: false,
closeButton: false,
container: this.modal ?? this.ref,
enableClose: false,
dragConstraint: "constrain",
id: "info-modal",
minWidth: "300px",
minHeight: "10rem",
});
}
}

View File

@@ -0,0 +1,29 @@
<div class="panel">
<div class="desktops">
<div class="ellipse"></div>
<div class="circle"></div>
</div>
<div class="time">
<span>{{ time }}</span>
</div>
<div class="right-menu">
<span
class="lang cursor-pointer"
(click)="toggleModal()"
(keydown)="toggleModal()"
tabindex="0"
#lang_btn
>{{ getLang() }}</span
>
<a href="https://git.nwaifu.su/neuro_llc/NwaifuWeb" [title]="source_code_btn_title | translate">
<fa-icon [icon]="faGithub" class="sourcecode-icon"></fa-icon>
</a>
<fa-icon [icon]="faVolume"></fa-icon>
<fa-icon [icon]="faPower"></fa-icon>
</div>
</div>
<div class="lang-choose" #lang_modal>
<p (click)="useLang('en')" (keypress)="useLang('en')" tabindex="0"><b>en</b> - English</p>
<p (click)="useLang('ru')" (keypress)="useLang('ru')" tabindex="0"><b>ru</b> - Русский</p>
<p (click)="useLang('ja')" (keypress)="useLang('ja')" tabindex="0"><b>ja</b> - 日本語</p>
</div>

View File

@@ -0,0 +1,107 @@
.panel {
z-index: 99999;
position: relative;
left: 0;
top: 0;
width: 100%;
height: 2rem;
background-color: #000;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 1rem;
}
.desktops {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: 0.5rem;
width: auto;
height: 100%;
.ellipse {
width: 60px;
height: 0.8rem;
border-radius: 50px;
background-color: var(--white);
}
.circle {
width: 0.8rem;
height: 0.8rem;
border-radius: 50px;
background-color: #b1b2b5;
}
}
.time {
margin: auto 0;
width: auto;
span {
font-weight: 600;
color: var(--white);
line-height: 100%;
vertical-align: middle;
text-align: center;
font-size: 1rem;
}
}
.right-menu {
height: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 0.7rem;
color: var(--white);
margin: auto 0;
align-items: center;
span {
font-weight: 600;
user-select: none;
-webkit-user-select: none;
}
fa-icon {
font-size: 1.2rem;
cursor: pointer;
}
}
.lang-choose {
position: absolute;
display: none;
&.active {
display: flex;
}
top: 0;
flex-direction: column;
justify-content: space-around;
gap: 0;
background-color: var(--black);
margin-block-start: 3rem;
z-index: 9999;
margin-inline-end: 1rem;
color: var(--white);
padding: 1rem 0;
border-radius: 15px;
&::before {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%) rotate(45deg);
vertical-align: middle;
top: -0.5rem;
width: 0;
height: 0;
border: 10px solid var(--black);
}
p {
line-height: 2rem;
width: 100%;
padding: 0.5rem 1rem;
user-select: none;
-webkit-user-select: none;
cursor: pointer;
&:hover {
background-color: #000;
}
}
}

View File

@@ -0,0 +1,88 @@
import { CommonModule } from "@angular/common";
import { Component, ElementRef, HostListener, ViewChild } from "@angular/core";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { faGithub } from "@fortawesome/free-brands-svg-icons";
import { faPowerOff, faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
import { TranslationPipe } from "../../pipes/translation.pipe";
import { PanelSevice } from "../../services/panel.service";
import { TranslateService } from "../../services/translate.service";
@Component({
standalone: true,
selector: "app-panel",
imports: [CommonModule, FontAwesomeModule, TranslationPipe],
templateUrl: "./panel.component.html",
styleUrls: ["./panel.component.less"],
})
export class PanelComponent {
source_code_btn_title = "SOURCE_CODE_TITLE";
public time = "";
faGithub = faGithub;
faVolume = faVolumeHigh;
faPower = faPowerOff;
showLangModalBool = false;
@ViewChild("lang_modal") langModal: ElementRef<HTMLDivElement> | null = null;
@ViewChild("lang_btn") langBtn: ElementRef<HTMLSpanElement> | null = null;
constructor(private panelService: PanelSevice, private translateService: TranslateService) {
this.time = this.getTime();
setInterval(() => {
this.time = this.getTime();
}, 1000);
}
private getTime() {
const time = this.panelService.getTime();
return time.toLocaleDateString(this.translateService.translate("TIME_SCHEMA"), {
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
});
}
goToSource() {
window.open("https://git.nwaifu.su/neuro_llc/NwaifuWeb", "_blank");
}
@HostListener("window:resize")
private moveLangModal() {
if (!this.langModal || !this.langBtn) {
return;
}
const x = this.langBtn.nativeElement.getBoundingClientRect().x;
this.langModal.nativeElement.style.left = `calc(${x}px - 3.5rem)`;
}
@HostListener("window:click", ["$event"])
private closeLangModal(event: MouseEvent) {
if (
this.langModal &&
this.langBtn &&
!this.langModal.nativeElement.contains(event.target as Node) &&
!this.langBtn.nativeElement.contains(event.target as Node)
) {
this.langModal.nativeElement.classList.remove("active");
}
}
toggleModal() {
if (this.langModal) {
this.langModal.nativeElement.classList.toggle("active");
if (this.langModal.nativeElement.classList.contains("active")) {
this.moveLangModal();
}
}
}
useLang(lang: string) {
this.langModal && this.langModal.nativeElement.classList.remove("active");
this.translateService.use(lang);
}
getLang() {
return this.translateService.lang;
}
}

View File

@@ -0,0 +1,10 @@
<div class="panel">
<span>{{ modal_title | translate }}</span>
<div class="btns">
<fa-icon [icon]="faMenu"></fa-icon>
<fa-icon [icon]="faClose"></fa-icon>
</div>
</div>
<div class="content">
<h1>{{ modal_text | translate }}</h1>
</div>

View File

@@ -0,0 +1,24 @@
.panel {
width: 100%;
display: flex;
height: 3rem;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 0 1rem;
background-color: var(--black);
color: var(--white);
span {
font-size: 600;
}
.btns {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: 1rem;
}
}
.content {
margin: 1rem;
}

View File

@@ -0,0 +1,22 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { faBars, faXmark } from "@fortawesome/free-solid-svg-icons";
import { DialogRef } from "@ngneat/dialog";
import { TranslationPipe } from "../../pipes/translation.pipe";
@Component({
selector: "app-window",
standalone: true,
imports: [CommonModule, FontAwesomeModule, TranslationPipe],
templateUrl: "./window.component.html",
styleUrls: ["./window.component.less"],
// changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WindowComponent {
modal_text = "MODAL_TEXT";
modal_title = "MODAL_TITLE";
faClose = faXmark;
faMenu = faBars;
constructor(public ref: DialogRef) {}
}

View File

@@ -0,0 +1,5 @@
<div class="icons">
<a routerLink='/translator'><desktop-icon alt='translator' name='Translator' image='../../../assets/svg/logo-gitea.svg'></desktop-icon></a>
</div>
<app-modal></app-modal>
<app-dock></app-dock>

View File

@@ -0,0 +1,9 @@
.icons {
margin: 2rem 3rem;
position: absolute;
}
:host {
display: block;
width: 100%;
height: 100%;
}

View File

@@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { DesktopIconComponent } from '../../modules/desktop-icon/desktop-icon.component';
import { DockComponent } from '../../modules/dock/dock.component';
import { ModalComponent } from '../../modules/modal/modal.component';
@Component({
selector: 'app-main-page',
templateUrl: './main.component.html',
styleUrls: ['./main.component.less'],
imports: [DockComponent, ModalComponent, CommonModule, DesktopIconComponent, RouterLink],
standalone: true
})
export class MainPageComponent {}

View File

@@ -0,0 +1,9 @@
@if (elements_data.length) {
<h1>Всего: {{ elements_data.length }}</h1>
<h2>Файл: {{ fileName }}</h2>
}
<div id="elements">
@for (item of elements_data; track $index) {
<app-translate-block #translateBlock [index]="$index" [item]="item"></app-translate-block>
}
</div>

View File

@@ -0,0 +1,19 @@
#elements {
display: flex;
flex-direction: column;
gap: 3rem;
align-items: center;
margin-inline: 2rem;
}
h1, h2 {
color: #efdee0;
text-align: center;
margin: 1rem 2rem;
}
app-translate-block {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}

View File

@@ -0,0 +1,34 @@
import { CommonModule } from "@angular/common";
import { Component, Input, OnInit, QueryList, ViewChildren } from "@angular/core";
import { NpsFile, TranslateData } from "../../dto/translate_data.dto";
import { TranslateBlockComponent } from "../translate_block/translate_block.component";
@Component({
selector: "app-text-list",
templateUrl: "./text_list.component.html",
styleUrls: ["./text_list.component.scss"],
standalone: true,
imports: [CommonModule, TranslateBlockComponent],
})
export class TextListComponent implements OnInit {
@ViewChildren("translateBlock") translate_blocks: QueryList<TranslateBlockComponent> | null =
null;
fileName = "";
elements_data: TranslateData[] = [];
@Input() set elements(el: TranslateData[]) {
this.elements_data = el;
localStorage.setItem("translations", JSON.stringify(this.elements_data));
}
get elements() {
return this.elements_data;
}
ngOnInit(): void {
const data = localStorage.getItem("original_file");
if (data) {
const file: NpsFile = JSON.parse(data);
this.fileName = file.file_name;
}
}
}

View File

@@ -0,0 +1,18 @@
<div class="element">
<h2>{{ index + 1 }}</h2>
<div class="fields">
<nwui-textarea [contenteditable]="false">{{ item.english_text }}</nwui-textarea>
<nwui-textarea
#translatedText
[contenteditable]="isEditing"
(leave)="saveTranslate($event)"
[disabled]="!isEditing"
>{{ item.translated_text }}</nwui-textarea
>
</div>
<div class="btns">
<nwui-button (click)="sendToGoogleTranslate()" [disabled]="translateLoading">Translate</nwui-button>
<nwui-button (click)="isEditing = !isEditing" [disabled]="isEditing">Edit</nwui-button>
<nwui-button (click)="clear()" [disabled]="!item.translated_text.length">Clear</nwui-button>
</div>
</div>

View File

@@ -0,0 +1,39 @@
.element {
display: flex;
background-color: #413738;
padding: 3rem 5rem;
border-radius: 30px;
flex-direction: row;
width: 100%;
max-width: 1200px;
align-items: center;
gap: 2rem;
justify-content: space-between;
h2 {
color: #efdee0;
}
.fields {
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
max-width: 1200px;
}
}
@media (max-width: 768px) {
.element {
flex-direction: column;
}
}
.btns {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
padding-inline: 1rem;
nwui-button {
width: 100%;
}
}

View File

@@ -0,0 +1,75 @@
import { CommonModule } from '@angular/common';
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { NWUIButtonComponent, NWUITextAreaComponent } from '@nwaifu-ui';
import { LocalStorageKeys } from '../../consts';
import { TranslateData } from '../../dto/translate_data.dto';
import { ETranslateService } from '../../services/translate.enums';
import { TranslateService } from '../../services/translate.service';
@Component({
selector: 'app-translate-block',
templateUrl: './translate_block.component.html',
styleUrls: ['./translate_block.component.scss'],
standalone: true,
imports: [CommonModule, NWUIButtonComponent, NWUITextAreaComponent],
providers: [TranslateService],
})
export class TranslateBlockComponent implements OnInit {
@ViewChild('translatedText') translatedText: NWUITextAreaComponent | null = null;
@Input({ required: true }) item: TranslateData = { english_text: '', translated_text: '' };
@Input({ required: true }) index = 0;
translateLoading = false;
isEditing = false;
saveChanges() {
const data: TranslateData[] = JSON.parse(localStorage.getItem(LocalStorageKeys.TRANSLATIONS) ?? '[]');
if (!data.length) {
alert('No data');
return;
}
data[this.index] = this.item;
localStorage.setItem('translations', JSON.stringify(data));
}
ngOnInit(): void {
this.isEditing = this.item.translated_text === '';
}
constructor(private translateService: TranslateService) {}
private sendToTranslate(service: ETranslateService = ETranslateService.GOOGLE) {
this.translateLoading = true;
this.translateService.translate(this.item.english_text, service).subscribe((text) => {
if (this.translatedText) if (this.translatedText.ref) this.translatedText.ref.nativeElement.textContent = text;
this.item.translated_text = text;
this.isEditing = false;
this.translateLoading = false;
this.saveChanges();
});
}
sendToGoogleTranslate() {
this.sendToTranslate(ETranslateService.GOOGLE);
}
sendToDeeplTranslate() {
this.sendToTranslate(ETranslateService.DEEPL);
}
sendToPromptTranslate() {
this.sendToTranslate(ETranslateService.PROMPT);
}
saveTranslate(text: string) {
this.isEditing = false;
if (this.translatedText) if (this.translatedText.ref) this.translatedText.ref.nativeElement.textContent = '';
this.item.translated_text = text;
this.saveChanges();
}
clear() {
this.item.translated_text = '';
this.isEditing = true;
if (this.translatedText) if (this.translatedText.ref) this.translatedText.ref.nativeElement.textContent = '';
this.saveChanges();
}
}

View File

@@ -0,0 +1,4 @@
export enum LocalStorageKeys {
TRANSLATIONS = 'translations',
ORIGINAL_FILE = 'original_file',
}

View File

@@ -0,0 +1,9 @@
export interface TranslateData {
english_text: string;
translated_text: string;
}
export interface NpsFile {
file_name: string;
original_text: string;
translated_text?: string;
}

View File

@@ -0,0 +1,6 @@
import { LocalStorageKeys } from '../consts';
import { NpsFile } from '../dto/translate_data.dto';
export function saveOriginalFile(file: NpsFile) {
localStorage.setItem(LocalStorageKeys.ORIGINAL_FILE, JSON.stringify(file));
}

View File

@@ -0,0 +1,22 @@
import { TranslateData } from "../dto/translate_data.dto";
export function parse(text: string): TranslateData[] {
// Find all TEXT attr data
const result: TranslateData[] = [];
const re = /<[^>]*TEXT="(?<textAttr>[^>"]+)"[^>]*>|<[^>]*>|\/\/.*|\s*\n\s*/gm;
for (const match of text.matchAll(re)) {
if (match.groups?.["textAttr"])
result.push({ english_text: match.groups?.["textAttr"], translated_text: "" });
}
const replaced_text = text.replace(re, "\n").replace(/\s*\n\s*/gm, "\n");
result.push(
...replaced_text
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0)
.map((line) => ({ english_text: line, translated_text: "" })),
);
console.log(result);
return result;
}

View File

@@ -0,0 +1,33 @@
<div class="btns">
<div id="op_btns">
<nwui-button (click)="fileInput.click()">
<span><i class="lni lni-upload"></i> Upload</span>
<input
type="file"
(change)="submitFile($event)"
accept=".nps"
#fileInput
style="display: none"
/>
</nwui-button>
<nwui-button (click)="onSaveClicked()">
<span><i class="lni lni-save"></i> Save</span>
</nwui-button>
@if (this.elements.length) {
<nwui-button (click)="clearAllTranslations()" [disabled]="!has_translations">
<span><i class="lni lni-trash-can"></i> Clear translations</span>
</nwui-button>
<nwui-button (click)="getAllTranslations()">
<span><i class="lni lni-google"></i> Translate all</span>
</nwui-button>
<nwui-button (click)="onClearClicked()">
<span><i class="lni lni-trash-can"></i> Clear</span>
</nwui-button>
}
</div>
<button id="close_win" (click)="onCloseClicked()"><i class="lni lni-close"></i></button>
</div>
<app-text-list [elements]="elements"></app-text-list>
<nwui-button class="to-top-btn" (click)="scrollToTop()"
><i class="lni lni-arrow-up"></i
></nwui-button>

View File

@@ -0,0 +1,58 @@
.btns {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
overflow-x: auto;
scrollbar-color: #413738;
nwui-button {
margin: 2rem;
}
#op_btns {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
#close_win {
color: var(--white);
font-weight: 600;
margin-inline: 2rem;
background-color: #413738;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0.5rem;
border-radius: 50px;
transition: 0.2s linear;
i {
transition: 0.3s linear;
}
&:hover {
background-color: #5b3f45;
transform: scale(1.1);
i {
transform: scale(1.1);
}
}
}
}
:host {
background-color: #191113;
display: block;
min-height: calc(100vh - 2rem);
scrollbar-color: #413738;
}
.to-top-btn {
position: fixed;
width: 2rem;
left: 2rem;
bottom: -5rem;
transition: bottom 0.3s ease-in-out;
&.active {
bottom: 1rem;
}
}

View File

@@ -0,0 +1,143 @@
import { CommonModule } from "@angular/common";
import { AfterViewInit, Component, OnInit, ViewChild } from "@angular/core";
import { Router } from "@angular/router";
import { NWUIButtonComponent } from "@nwaifu-ui";
import { fromEvent, map } from "rxjs";
import { TextListComponent } from "./components/text_list/text_list.component";
import { LocalStorageKeys } from "./consts";
import { NpsFile, TranslateData } from "./dto/translate_data.dto";
import { saveOriginalFile } from "./lib/file_tools";
import { parse } from "./lib/parser";
@Component({
standalone: true,
imports: [TextListComponent, CommonModule, NWUIButtonComponent],
selector: "app-nitroplus",
templateUrl: "./nitroplus-translator.component.html",
styleUrl: "./nitroplus-translator.component.less",
})
export class NitroplusComponent implements OnInit, AfterViewInit {
title = "NitroPlusTranslator";
elements: TranslateData[] = [];
@ViewChild("fileInput") fileInput: HTMLInputElement | null = null;
@ViewChild(TextListComponent) text_list: TextListComponent | null = null;
constructor(private readonly router: Router) {}
ngOnInit(): void {
const data = localStorage.getItem(LocalStorageKeys.TRANSLATIONS);
if (data) {
try {
this.elements = JSON.parse(data);
} catch (e) {
console.error(e);
alert("Error while loading");
localStorage.removeItem(LocalStorageKeys.TRANSLATIONS);
this.elements = [];
}
}
}
ngAfterViewInit(): void {
const container = document.querySelector("main");
if (container) {
fromEvent(container, "scroll")
.pipe(map(() => container.scrollTop > 100))
.subscribe((val) => {
if (val) document.querySelector(".to-top-btn")?.classList.add("active");
else document.querySelector(".to-top-btn")?.classList.remove("active");
});
}
}
scrollToTop() {
const container = document.querySelector("main");
if (container) {
container.scrollTo({ top: 0, behavior: "smooth" });
}
}
submitFile($event: Event) {
const target = $event.target as HTMLInputElement;
if (target.files) {
const file = target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
if (reader.result) {
this.onFileLoaded(reader.result.toString());
const original_file: NpsFile = {
file_name: file.name,
original_text: reader.result.toString(),
};
saveOriginalFile(original_file);
target.value = "";
}
};
reader.readAsText(file);
}
}
}
onFileLoaded(text: string) {
const parsed = parse(text);
this.elements = parsed;
}
onClearClicked() {
this.elements = [];
localStorage.removeItem(LocalStorageKeys.TRANSLATIONS);
}
onSaveClicked() {
const original_file: NpsFile = JSON.parse(
localStorage.getItem(LocalStorageKeys.ORIGINAL_FILE) ??
'{"file_name":"", "original_text":""}',
);
if (original_file.file_name && original_file.original_text) {
const data: TranslateData[] = JSON.parse(
localStorage.getItem(LocalStorageKeys.TRANSLATIONS) ?? "[]",
);
if (!data.length) {
alert("No data");
return;
}
original_file.translated_text = original_file.original_text;
data.forEach((el) => {
original_file.translated_text = original_file.translated_text?.replace(
el.english_text,
el.translated_text,
);
});
const element = document.createElement("a");
const file = new Blob([original_file.translated_text], { type: "text/plain" });
element.href = URL.createObjectURL(file);
element.download = original_file.file_name;
element.click();
}
}
//TODO: Dialog windows for clear op
clearAllTranslations() {
if (this.text_list)
if (this.text_list.translate_blocks) {
this.text_list.translate_blocks
.filter((item) => item.item.translated_text)
.forEach((item) => item.clear());
}
}
getAllTranslations() {
if (this.text_list)
if (this.text_list.translate_blocks)
this.text_list.translate_blocks.forEach((item) => item.sendToGoogleTranslate());
}
get has_translations(): boolean {
return this.elements.some((i) => i.translated_text);
}
onCloseClicked() {
this.router.navigate(["/"]);
}
}

View File

@@ -0,0 +1 @@
export type GoogleTranslateResponse = Array<Array<string>>;

View File

@@ -0,0 +1,5 @@
export enum ETranslateService {
GOOGLE = 'google',
DEEPL = 'deepl',
PROMPT = 'prompt',
}

View File

@@ -0,0 +1,34 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, map } from 'rxjs';
import { GoogleTranslateResponse } from './translate.dto';
import { ETranslateService } from './translate.enums';
@Injectable({ providedIn: 'root' })
export class TranslateService {
constructor(private http: HttpClient) {}
private googleTranslate(text: string): Observable<string> {
return this.http
.get<GoogleTranslateResponse>(
'https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=ru&dt=t&q=' + encodeURIComponent(text),
)
.pipe(
map((response) => {
let result = '';
response[0].forEach((el) => {
result += el[0] + ' ';
});
return result;
}),
);
}
translate(text: string, service: ETranslateService = ETranslateService.GOOGLE): Observable<string> {
switch (service) {
case ETranslateService.GOOGLE:
return this.googleTranslate(text);
default:
return this.googleTranslate(text);
}
}
}

View File

@@ -0,0 +1,14 @@
import { Pipe, PipeTransform } from "@angular/core";
import { TranslateService } from "../services/translate.service";
@Pipe({
name: "translate",
standalone: true,
pure: false,
})
export class TranslationPipe implements PipeTransform {
constructor(private translateService: TranslateService) {}
transform(key: string) {
return this.translateService.data[key] ?? key;
}
}

View File

@@ -0,0 +1,10 @@
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class PanelSevice {
getTime() {
return new Date();
}
}

View File

@@ -0,0 +1,31 @@
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
@Injectable({ providedIn: "root" })
export class TranslateService {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: Record<string, any> = {};
lang: string | null = null;
constructor(private http: HttpClient) {}
use(lang: string): Promise<object> {
this.lang = lang ?? "en";
localStorage.setItem("lang", this.lang);
return new Promise<object>((resolve) => {
const langPath = `assets/i18n/${this.lang}.json`;
this.http.get(langPath).subscribe({
next: (response) => {
this.data = response ?? {};
resolve(this.data);
},
error: () => {
this.data = {};
resolve(this.data);
},
});
});
}
translate(key: string) {
return this.data[key] ?? key;
}
}

View File

@@ -0,0 +1,9 @@
{
"MODAL_TITLE": "Info",
"TIME_SCHEMA": "en-US",
"MODAL_TEXT": "Hello, World!",
"TELEGRAM_LABEL": "Telegram channel",
"GITHUB_LABEL": "Admin's Github",
"GITEA_LABEL": "Neuro LLC Gitea",
"SOURCE_CODE_TITLE": "Source code"
}

View File

@@ -0,0 +1,9 @@
{
"MODAL_TITLE": "情報",
"TIME_SCHEMA": "ja-JP",
"MODAL_TEXT": "こんにちは、世界!",
"TELEGRAM_LABEL": "Telegramチャンネル",
"GITHUB_LABEL": "管理者Github",
"GITEA_LABEL": "Neuro LLC Gitea",
"SOURCE_CODE_TITLE": "ソースコード"
}

View File

@@ -0,0 +1,9 @@
{
"MODAL_TITLE": "Информация",
"TIME_SCHEMA": "ru-RU",
"MODAL_TEXT": "Здравствуйте, мир!",
"TELEGRAM_LABEL": "Телеграм канал",
"GITHUB_LABEL": "Github админа",
"GITEA_LABEL": "Neuro LLC Gitea",
"SOURCE_CODE_TITLE": "Исходный код"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{fill:#609926;}
.st1{fill:#FFFFFF;}
</style>
<g id="Guides">
</g>
<g id="Icon">
<circle class="st0" cx="512" cy="512" r="512"/>
<g>
<path class="st1" d="M762.2,350.3c-100.9,5.3-160.7,8-212,8.5v114.1l-16-7.9l-0.1-106.1c-58.9,0-110.7-3.1-209.1-8.6
c-12.3-0.1-29.5-2.4-47.9-2.5c-47.1-0.1-110.2,33.5-106.7,118C175.8,597.6,296,609.9,344,610.9c5.3,24.7,61.8,110.1,103.6,114.6
H631C740.9,717.3,823.3,351.7,762.2,350.3z M216.2,467.6c-4.7-36.6,11.8-74.8,73.2-73.2C296.1,462,307,501.5,329,561.9
C272.8,554.5,225,536.2,216.2,467.6z M631.8,551.1l-51.3,105.6c-6.5,13.4-22.7,19-36.2,12.5l-105.6-51.3
c-13.4-6.5-19-22.7-12.5-36.2l51.3-105.6c6.5-13.4,22.7-19,36.2-12.5l105.6,51.3C632.7,521.5,638.3,537.7,631.8,551.1z"/>
<path class="st1" d="M555,609.9c0.1-0.2,0.2-0.3,0.2-0.5c17.2-35.2,24.3-49.8,19.8-62.4c-3.9-11.1-15.5-16.6-36.7-26.6
c-0.8-0.4-1.7-0.8-2.5-1.2c0.2-2.3-0.1-4.7-1-7c-0.8-2.3-2.1-4.3-3.7-6l13.6-27.8l-11.9-5.8L519.1,501c-2,0-4.1,0.3-6.2,1
c-8.9,3.2-13.5,13-10.3,21.9c0.7,1.9,1.7,3.5,2.8,5l-23.6,48.4c-1.9,0-3.8,0.3-5.7,1c-8.9,3.2-13.5,13-10.3,21.9
c3.2,8.9,13,13.5,21.9,10.3c8.9-3.2,13.5-13,10.3-21.9c-0.9-2.5-2.3-4.6-4-6.3l23-47.2c2.5,0.2,5,0,7.5-0.9
c2.1-0.8,3.9-1.9,5.5-3.3c0.9,0.4,1.9,0.9,2.7,1.3c17.4,8.2,27.9,13.2,30,19.1c2.6,7.5-5.1,23.4-19.3,52.3
c-0.1,0.2-0.2,0.5-0.4,0.7c-2.2-0.1-4.4,0.2-6.5,1c-8.9,3.2-13.5,13-10.3,21.9c3.2,8.9,13,13.5,21.9,10.3
c8.9-3.2,13.5-13,10.3-21.9C557.8,613.6,556.5,611.6,555,609.9z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>github [#142]</title>
<desc>Created with Sketch.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-140.000000, -7559.000000)" fill="#000000">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M94,7399 C99.523,7399 104,7403.59 104,7409.253 C104,7413.782 101.138,7417.624 97.167,7418.981 C96.66,7419.082 96.48,7418.762 96.48,7418.489 C96.48,7418.151 96.492,7417.047 96.492,7415.675 C96.492,7414.719 96.172,7414.095 95.813,7413.777 C98.04,7413.523 100.38,7412.656 100.38,7408.718 C100.38,7407.598 99.992,7406.684 99.35,7405.966 C99.454,7405.707 99.797,7404.664 99.252,7403.252 C99.252,7403.252 98.414,7402.977 96.505,7404.303 C95.706,7404.076 94.85,7403.962 94,7403.958 C93.15,7403.962 92.295,7404.076 91.497,7404.303 C89.586,7402.977 88.746,7403.252 88.746,7403.252 C88.203,7404.664 88.546,7405.707 88.649,7405.966 C88.01,7406.684 87.619,7407.598 87.619,7408.718 C87.619,7412.646 89.954,7413.526 92.175,7413.785 C91.889,7414.041 91.63,7414.493 91.54,7415.156 C90.97,7415.418 89.522,7415.871 88.63,7414.304 C88.63,7414.304 88.101,7413.319 87.097,7413.247 C87.097,7413.247 86.122,7413.234 87.029,7413.87 C87.029,7413.87 87.684,7414.185 88.139,7415.37 C88.139,7415.37 88.726,7417.2 91.508,7416.58 C91.513,7417.437 91.522,7418.245 91.522,7418.489 C91.522,7418.76 91.338,7419.077 90.839,7418.982 C86.865,7417.627 84,7413.783 84,7409.253 C84,7403.59 88.478,7399 94,7399" id="github-[#142]">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="70" xmlns="http://www.w3.org/2000/svg" height="70" id="screenshot-ad7c5ac4-c816-80ae-8004-02dbaf2b6165" viewBox="0 0 70 70" style="-webkit-print-color-adjust: exact;" fill="none" version="1.1"><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf2b6165" width="512" version="1.1" height="512" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf2ec056"><defs id="defs2987" rx="0" ry="0" style="fill: rgb(0, 0, 0);"/></g><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf2fbd4c" rx="0" ry="0" style="fill: rgb(0, 0, 0);"/><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf302fad" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf32aa29"><g class="fills" id="fills-ad7c5ac4-c816-80ae-8004-02dbaf32aa29"><path rx="0" ry="0" d="M67.177,35.000C67.177,52.818,52.771,67.262,35.000,67.262C17.229,67.262,2.823,52.818,2.823,35.000C2.823,17.182,17.229,2.738,35.000,2.738C52.771,2.738,67.177,17.182,67.177,35.000ZZ" style="display: inline; fill: rgb(37, 155, 215); fill-opacity: 1;"/></g></g><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf3467db"><g class="fills" id="fills-ad7c5ac4-c816-80ae-8004-02dbaf3467db"><path rx="0" ry="0" d="M16.299,36.189C16.299,36.189,15.466,35.439,15.608,35.004C15.786,34.457,17.188,34.312,17.188,34.312L48.310,22.061C48.310,22.061,49.426,21.710,49.890,21.962C50.421,22.251,50.779,23.543,50.779,23.543L44.555,50.712C44.555,50.712,44.224,51.207,43.962,51.305C43.529,51.467,42.579,51.206,42.579,51.206L34.379,44.883L29.242,50.021L27.858,48.934L24.796,39.252Z" style="display: inline; fill: rgb(255, 255, 255);"/></g></g></g><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf352fb3" rx="0" ry="0" style="fill: rgb(0, 0, 0);"><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf361901"><g class="fills" id="fills-ad7c5ac4-c816-80ae-8004-02dbaf361901"><path rx="0" ry="0" d="M24.944,39.252L43.913,26.952L44.605,27.149L44.358,27.693L29.340,41.277L28.649,49.477L27.809,48.885Z" style="display: inline; fill: rgb(177, 200, 211); fill-opacity: 1;"/></g></g><g id="shape-ad7c5ac4-c816-80ae-8004-02dbaf372ee4"><g class="fills" id="fills-ad7c5ac4-c816-80ae-8004-02dbaf372ee4"><path rx="0" ry="0" d="M29.489,41.327C29.489,41.524,34.280,45.031,34.280,45.031L29.390,49.971L28.649,49.280Z" style="display: inline; fill: rgb(136, 189, 216); fill-opacity: 1;"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>NwaifuWeb</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link href="https://cdn.lineicons.com/4.0/lineicons.css" rel="stylesheet" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View 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));

View File

@@ -22,6 +22,33 @@
box-sizing: border-box;
font-family: Montserrat, sans-serif;
font-optical-sizing: auto;
font-weight: 800;
font-weight: 400;
font-style: normal;
}
html, body {
width: 100%;
height: 100%;
position: relative;
}
ngneat-dialog {
.ngneat-dialog-backdrop {
pointer-events: none;
.ngneat-dialog-content {
pointer-events: all;
border-radius: 10px;
span {
font-weight: 600;
user-select: none;
}
.btns {
fa-icon {
z-index: 999;
cursor: not-allowed;
}
}
.ngneat-drag-marker {
height: 3rem;
}
}
}
}

View File

@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"],
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {},
"exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View File

@@ -1,33 +1,33 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist/out-tsc",
"target": "ES2022",
"useDefineForClassFields": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"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"
]
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
},
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.editor.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["jasmine"]
},
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
}

BIN
bun.lockb Executable file

Binary file not shown.

43
karma.conf.js Normal file
View File

@@ -0,0 +1,43 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
const { join } = require("path");
const { constants } = require("karma");
module.exports = () => {
return {
basePath: "",
frameworks: ["jasmine", "@angular-devkit/build-angular"],
plugins: [
require("karma-jasmine"),
require("karma-chrome-launcher"),
require("karma-jasmine-html-reporter"),
require("karma-coverage"),
require("@angular-devkit/build-angular/plugins/karma"),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: join(__dirname, "./coverage"),
subdir: ".",
reporters: [{ type: "html" }, { type: "text-summary" }],
},
reporters: ["progress", "kjhtml"],
port: 9876,
colors: true,
logLevel: constants.LOG_INFO,
autoWatch: true,
browsers: ["Chrome"],
singleRun: true,
};
};

0
libs/.gitkeep Normal file
View File

64
migrations.json Normal file
View File

@@ -0,0 +1,64 @@
{
"migrations": [
{
"cli": "nx",
"version": "19.2.0-beta.2",
"description": "Updates the default workspace data directory to .nx/workspace-data",
"implementation": "./src/migrations/update-19-2-0/move-workspace-data-directory",
"package": "nx",
"name": "19-2-0-move-graph-cache-directory"
},
{
"cli": "nx",
"version": "19.2.2-beta.0",
"description": "Updates the nx wrapper.",
"implementation": "./src/migrations/update-17-3-0/update-nxw",
"package": "nx",
"name": "19-2-2-update-nx-wrapper"
},
{
"version": "19.2.4-beta.0",
"description": "Set project name in nx.json explicitly",
"implementation": "./src/migrations/update-19-2-4/set-project-name",
"x-repair-skip": true,
"package": "nx",
"name": "19-2-4-set-project-name"
},
{
"cli": "nx",
"version": "19.1.0-beta.2",
"requires": {
"@angular/core": ">=18.0.0"
},
"description": "Update the @angular/cli package version to ~18.0.0.",
"factory": "./src/migrations/update-19-1-0/update-angular-cli",
"package": "@nx/angular",
"name": "update-angular-cli-version-18-0-0"
},
{
"cli": "nx",
"version": "19.2.1-beta.0",
"requires": {
"@angular-eslint/eslint-plugin": ">=18.0.0"
},
"description": "Installs the '@typescript-eslint/utils' package when having installed '@angular-eslint/eslint-plugin' or '@angular-eslint/eslint-plugin-template' with version >=18.0.0.",
"factory": "./src/migrations/update-19-2-1/add-typescript-eslint-utils",
"package": "@nx/angular",
"name": "add-typescript-eslint-utils"
},
{
"version": "18.0.0",
"description": "Updates two-way bindings that have an invalid expression to use the longform expression instead.",
"factory": "./migrations/invalid-two-way-bindings/bundle",
"package": "@angular/core",
"name": "invalid-two-way-bindings"
},
{
"version": "18.0.0",
"description": "Replace deprecated HTTP related modules with provider functions",
"factory": "./migrations/http-providers/bundle",
"package": "@angular/core",
"name": "migration-http-providers"
}
]
}

46
nwaifu-ui/.eslintrc.json Normal file
View File

@@ -0,0 +1,46 @@
{
"extends": [
"../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"extends": [
"plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "nwui",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "nwui",
"style": "kebab-case"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@nx/angular-template"
],
"rules": {}
}
]
}

7
nwaifu-ui/README.md Normal file
View File

@@ -0,0 +1,7 @@
# nwaifu-ui
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test nwaifu-ui` to execute the unit tests.

9
nwaifu-ui/project.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "nwaifu-ui",
"$schema": "../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "nwaifu-ui/src",
"prefix": "nwui",
"projectType": "library",
"tags": [],
"targets": {}
}

1
nwaifu-ui/src/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './lib';

View File

@@ -0,0 +1 @@
<button [disabled]="disabled" [type]="type"><ng-content></ng-content></button>

View File

@@ -0,0 +1,28 @@
button {
outline: none;
border: none;
cursor: pointer;
background-color: #5b3f45;
word-break: keep-all;
padding: 1em 1.5em;
color: #f5f6fa;
border-radius: 15px;
transition: ease-in-out 0.2s;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
&:hover,
&:active {
transform: scale(1.2);
}
&:disabled {
opacity: 0.5;
&:hover,
&:active {
cursor: not-allowed;
transform: scale(1);
}
}
}

View File

@@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
@Component({
selector: 'nwui-button',
templateUrl: './button.component.html',
styleUrls: ['./button.component.scss'],
standalone: true,
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NWUIButtonComponent {
@Input() disabled = false;
@Input() type = 'button';
}

View File

@@ -0,0 +1 @@
export * from './button.component';

View File

@@ -0,0 +1,2 @@
export * from './button';
export * from './textarea';

View File

@@ -0,0 +1 @@
export * from './textarea.component';

View File

@@ -0,0 +1,5 @@
<div [attr.contenteditable]="contenteditable" [className]="className" #ref (blur)="leaveFn()">
@if(!value) {
<ng-content></ng-content>
}
</div>

View File

@@ -0,0 +1,20 @@
.nwui-textarea {
border-radius: 8px;
height: auto;
color: #efdee0;
outline: none;
padding-inline: 2rem 1rem;
background-color: transparent;
border: 2px solid #e4bdc3;
font-size: 1rem;
font-weight: inherit;
min-height: 3rem;
line-height: 3rem;
&:focus {
border: 2px solid #efdee0;
}
&.disabled {
opacity: 0.5;
}
word-wrap: break-word;
}

View File

@@ -0,0 +1,35 @@
import { CommonModule } from '@angular/common';
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
@Component({
selector: 'nwui-textarea',
imports: [CommonModule],
standalone: true,
styleUrls: ['./textarea.component.scss'],
templateUrl: './textarea.component.html',
// changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NWUITextAreaComponent implements AfterViewInit {
@Input() disabled = false;
@Input() value = '';
@Input() contenteditable = true;
@Output() leave = new EventEmitter<string>();
@ViewChild('ref') ref: ElementRef<HTMLDivElement> | null = null;
get className(): string {
return `nwui-textarea ${this.disabled && !this.contenteditable ? 'disabled' : ''}`;
}
leaveFn() {
if (this.ref) {
const target = this.ref.nativeElement;
const text = target.textContent || '';
if (this.leave) this.leave.emit(text);
target.textContent = text;
}
}
ngAfterViewInit(): void {
if (this.ref && this.value) this.ref.nativeElement.textContent = this.value;
}
}

View File

@@ -0,0 +1 @@
export * from './components';

27
nwaifu-ui/tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": false,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
],
"extends": "../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

1
nx-cloud.env.example Normal file
View File

@@ -0,0 +1 @@
NX_CLOUD_ACCESS_TOKEN=TOKEN

40
nx.json Normal file
View File

@@ -0,0 +1,40 @@
{
"defaultBase": "main",
"namedInputs": {
"sharedGlobals": [],
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/**/*.spec.[jt]s",
"!{projectRoot}/karma.conf.js",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.js"
]
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/karma.conf.js"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json", "{workspaceRoot}/eslint.config.js"],
"cache": true
},
"@nx/eslint:lint": {
"cache": true,
"inputs": [
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore",
"{workspaceRoot}/eslint.config.js"
]
}
},
"nxCloudAccessToken": "${NX_CLOUD_ACCESS_TOKEN}"
}

View File

@@ -3,53 +3,67 @@
"version": "0.0.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint"
"start": "nx serve NwaifuWeb",
"build": "nx build NwaifuWeb --base-href https://nwaifu.su/ --configuration production",
"watch": "nx build NwaifuWeb --watch --configuration development",
"test": "nx test",
"lint": "nx lint"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.2.0",
"@angular/common": "^17.2.0",
"@angular/compiler": "^17.2.0",
"@angular/core": "^17.2.0",
"@angular/forms": "^17.2.0",
"@angular/platform-browser": "^17.2.0",
"@angular/platform-browser-dynamic": "^17.2.0",
"@angular/router": "^17.2.0",
"@angular/animations": "18.0.3",
"@angular/common": "18.0.3",
"@angular/compiler": "18.0.3",
"@angular/core": "18.0.3",
"@angular/forms": "18.0.3",
"@angular/platform-browser": "18.0.3",
"@angular/platform-browser-dynamic": "18.0.3",
"@angular/router": "18.0.3",
"@fortawesome/angular-fontawesome": "0.14.1",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@ngneat/dialog": "5.0.0",
"@nx/eslint-plugin": "^19.3.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.2.2",
"@angular-eslint/builder": "17.2.1",
"@angular-eslint/eslint-plugin": "17.2.1",
"@angular-eslint/eslint-plugin-template": "17.2.1",
"@angular-eslint/schematics": "17.2.1",
"@angular-eslint/template-parser": "17.2.1",
"@angular/cli": "^17.2.2",
"@angular/compiler-cli": "^17.2.0",
"@angular-devkit/build-angular": "18.0.5",
"@angular-devkit/core": "18.0.5",
"@angular-devkit/schematics": "18.0.5",
"@angular-eslint/eslint-plugin": "18.0.1",
"@angular-eslint/eslint-plugin-template": "18.0.1",
"@angular-eslint/template-parser": "18.0.1",
"@angular/cli": "~18.0.0",
"@angular/compiler-cli": "18.0.3",
"@nx/angular": "19.3.0",
"@nx/eslint": "19.3.0",
"@nx/js": "19.3.0",
"@nx/workspace": "19.3.0",
"@schematics/angular": "18.0.5",
"@swc-node/register": "1.9.2",
"@swc/core": "1.5.7",
"@swc/helpers": "0.5.11",
"@types/bun": "latest",
"@types/jasmine": "~5.1.0",
"@typescript-eslint/eslint-plugin": "6.19.0",
"@typescript-eslint/parser": "6.19.0",
"@typescript-eslint/utils": "^8.0.0-alpha.28",
"autoprefixer": "^10.4.18",
"eslint": "^8.56.0",
"eslint": "~8.57.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",
"postcss": "^8.4.35",
"nx": "19.3.0",
"postcss": "^8.4.38",
"prettier": "^3.3.2",
"tailwindcss": "^3.4.1",
"typescript": "~5.3.2"
"typescript": "~5.4.5"
}
}

8602
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
<main class="main">
<section class="content">
<app-heading></app-heading>
<app-info></app-info>
</section>
</main>

View File

@@ -1,9 +0,0 @@
.main {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
min-height: 100vh;
background-color: #303952;
}

View File

@@ -1,15 +0,0 @@
import { Component } from "@angular/core";
import { RouterOutlet } from "@angular/router";
import { HeadingComponent } from "./modules/heading/heading.component";
import { InfoComponent } from "./modules/info/info.component";
@Component({
selector: "app-root",
standalone: true,
imports: [RouterOutlet, InfoComponent, HeadingComponent],
templateUrl: "./app.component.html",
styleUrl: "./app.component.less",
})
export class AppComponent {
title = "NwaifuWeb";
}

View File

@@ -1,8 +0,0 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};

Some files were not shown because too many files have changed in this diff Show More