Added ui support for loading screen setting
All checks were successful
Build on push / build-api (push) Successful in 29s
Build on push / build-web (push) Successful in 49s

This commit is contained in:
Sven Heidemann 2025-01-08 16:56:31 +01:00
parent 7b6cef1ae5
commit ff341ddcb5
20 changed files with 322 additions and 234 deletions

View File

@ -12,7 +12,7 @@ class ShortUrlCreateInput(InputABC):
self._target_url = self.option("targetUrl", str, required=True) self._target_url = self.option("targetUrl", str, required=True)
self._description = self.option("description", str) self._description = self.option("description", str)
self._group_id = self.option("groupId", int) self._group_id = self.option("groupId", int)
self._loading_screen = self.option("loadingScreen", str) self._loading_screen = self.option("loadingScreen", bool)
@property @property
def short_url(self) -> str: def short_url(self) -> str:

View File

@ -13,7 +13,7 @@ class ShortUrlUpdateInput(InputABC):
self._target_url = self.option("targetUrl", str) self._target_url = self.option("targetUrl", str)
self._description = self.option("description", str) self._description = self.option("description", str)
self._group_id = self.option("groupId", int) self._group_id = self.option("groupId", int)
self._loading_screen = self.option("loadingScreen", str) self._loading_screen = self.option("loadingScreen", bool)
@property @property
def id(self) -> int: def id(self) -> int:

View File

@ -1 +1 @@
0.2.0 0.2.1

56
web/.eslintrc.json Normal file
View File

@ -0,0 +1,56 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/component-class-suffix": [
"error",
{
"suffixes": [
"Page",
"Component"
]
}
],
"@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": {}
}
]
}

12
web/.prettierrc.json Normal file
View File

@ -0,0 +1,12 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"semi": true,
"bracketSpacing": true,
"arrowParens": "avoid",
"trailingComma": "es5",
"bracketSameLine": true,
"printWidth": 80,
"endOfLine": "auto"
}

View File

@ -1,17 +1,16 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from '@angular/core';
import { SidebarService } from "src/app/service/sidebar.service"; import { SidebarService } from 'src/app/service/sidebar.service';
import { Router } from "@angular/router"; import { Router } from '@angular/router';
import { HttpClient } from "@angular/common/http"; import { HttpClient } from '@angular/common/http';
import { ShortUrlDto } from "src/app/model/entities/short-url"; import { ShortUrlDto } from 'src/app/model/entities/short-url';
import { SettingsService } from "src/app/service/settings.service"; import { SettingsService } from 'src/app/service/settings.service';
import { AppSettings } from "src/app/model/config/app-settings"; import { AppSettings } from 'src/app/model/config/app-settings';
import { GuiService } from "src/app/service/gui.service"; import { GuiService } from 'src/app/service/gui.service';
import { SpinnerService } from "src/app/service/spinner.service";
@Component({ @Component({
selector: "app-redirect", selector: 'app-redirect',
templateUrl: "./redirect.component.html", templateUrl: './redirect.component.html',
styleUrl: "./redirect.component.scss", styleUrl: './redirect.component.scss',
}) })
export class RedirectComponent implements OnInit { export class RedirectComponent implements OnInit {
defaultSecs = 5; defaultSecs = 5;
@ -24,7 +23,7 @@ export class RedirectComponent implements OnInit {
private router: Router, private router: Router,
private http: HttpClient, private http: HttpClient,
private settingsService: SettingsService, private settingsService: SettingsService,
private gui: GuiService, private gui: GuiService
) { ) {
this.sidebar.hide(); this.sidebar.hide();
this.gui.hide(); this.gui.hide();
@ -36,19 +35,19 @@ export class RedirectComponent implements OnInit {
handleUrl() { handleUrl() {
let shortUrl = this.router.url; let shortUrl = this.router.url;
if (shortUrl === "/") { if (shortUrl === '/') {
return; return;
} }
if (shortUrl.startsWith("/")) { if (shortUrl.startsWith('/')) {
shortUrl = shortUrl.substring(1); shortUrl = shortUrl.substring(1);
} }
this.http this.http
.get<ShortUrlDto | undefined>(`${this.settings.api.url}/find/${shortUrl}`) .get<ShortUrlDto | undefined>(`${this.settings.api.url}/find/${shortUrl}`)
.subscribe(async (response) => { .subscribe(async response => {
if (!response) { if (!response) {
await this.router.navigate(["/404"]); await this.router.navigate(['/404']);
return; return;
} }

View File

@ -1,10 +1,9 @@
import { KeycloakService } from "keycloak-angular"; import { KeycloakService } from 'keycloak-angular';
import { environment } from "src/environments/environment"; import { SettingsService } from 'src/app/service/settings.service';
import { SettingsService } from "src/app/service/settings.service";
export function initializeKeycloak( export function initializeKeycloak(
keycloak: KeycloakService, keycloak: KeycloakService,
settings: SettingsService, settings: SettingsService
): Promise<boolean> { ): Promise<boolean> {
return keycloak.init({ return keycloak.init({
config: { config: {
@ -13,7 +12,7 @@ export function initializeKeycloak(
clientId: settings.settings.keycloak.clientId, clientId: settings.settings.keycloak.clientId,
}, },
initOptions: { initOptions: {
onLoad: "check-sso", onLoad: 'check-sso',
}, },
enableBearerInterceptor: false, enableBearerInterceptor: false,
}); });

View File

@ -1,5 +1,4 @@
import { DbModel } from "src/app/model/entities/db-model"; import { DbModel } from 'src/app/model/entities/db-model';
import { Permission } from "src/app/model/entities/role";
export interface Group extends DbModel { export interface Group extends DbModel {
name: string; name: string;

View File

@ -1,11 +1,11 @@
import { DbModel } from "src/app/model/entities/db-model"; import { DbModel } from 'src/app/model/entities/db-model';
import { Permission } from "src/app/model/entities/role"; import { Group } from 'src/app/model/entities/group';
import { Group } from "src/app/model/entities/group";
export interface ShortUrl extends DbModel { export interface ShortUrl extends DbModel {
shortUrl: string; shortUrl: string;
targetUrl: string; targetUrl: string;
description: string; description: string;
loadingScreen: boolean;
visits: number; visits: number;
group?: Group; group?: Group;
} }
@ -20,6 +20,7 @@ export interface ShortUrlCreateInput {
shortUrl: string; shortUrl: string;
targetUrl: string; targetUrl: string;
description: string; description: string;
loadingScreen: boolean;
groupId: number; groupId: number;
} }
@ -28,5 +29,6 @@ export interface ShortUrlUpdateInput {
shortUrl: string; shortUrl: string;
targetUrl: string; targetUrl: string;
description: string; description: string;
loadingScreen: boolean;
groupId: number; groupId: number;
} }

View File

@ -44,9 +44,17 @@
formControlName="description"/> formControlName="description"/>
</div> </div>
<div class="form-page-input"> <div class="form-page-input">
<p class="label">{{ 'common.group' | translate }}</p> <p class="label">{{ 'short_url.loading_screen' | translate }}</p>
<p-dropdown <p-checkbox
class="value" class="value"
[binary]="true"
formControlName="loadingScreen"/>
</div>
<div class="form-page-input">
<p class="label">{{ 'common.group' | translate }}</p>
<div
class="value">
<p-dropdown
[options]="groups" [options]="groups"
formControlName="groupId" formControlName="groupId"
[showClear]="true" [showClear]="true"
@ -56,5 +64,6 @@
optionValue="id" optionValue="id"
></p-dropdown> ></p-dropdown>
</div> </div>
</div>
</ng-template> </ng-template>
</app-form-page> </app-form-page>

View File

@ -1,19 +1,19 @@
import { Component } from "@angular/core"; import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from "@angular/forms"; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ToastService } from "src/app/service/toast.service"; import { ToastService } from 'src/app/service/toast.service';
import { FormPageBase } from "src/app/core/base/form-page-base"; import { FormPageBase } from 'src/app/core/base/form-page-base';
import { import {
ShortUrl, ShortUrl,
ShortUrlCreateInput, ShortUrlCreateInput,
ShortUrlUpdateInput, ShortUrlUpdateInput,
} from "src/app/model/entities/short-url"; } from 'src/app/model/entities/short-url';
import { ShortUrlsDataService } from "src/app/modules/admin/short-urls/short-urls.data.service"; import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
import { Group } from "src/app/model/entities/group"; import { Group } from 'src/app/model/entities/group';
@Component({ @Component({
selector: "app-short-url-form-page", selector: 'app-short-url-form-page',
templateUrl: "./short-url-form-page.component.html", templateUrl: './short-url-form-page.component.html',
styleUrl: "./short-url-form-page.component.scss", styleUrl: './short-url-form-page.component.scss',
}) })
export class ShortUrlFormPageComponent extends FormPageBase< export class ShortUrlFormPageComponent extends FormPageBase<
ShortUrl, ShortUrl,
@ -25,7 +25,7 @@ export class ShortUrlFormPageComponent extends FormPageBase<
constructor(private toast: ToastService) { constructor(private toast: ToastService) {
super(); super();
this.dataService.getAllGroups().subscribe((groups) => { this.dataService.getAllGroups().subscribe(groups => {
this.groups = groups; this.groups = groups;
}); });
@ -38,7 +38,7 @@ export class ShortUrlFormPageComponent extends FormPageBase<
this.dataService this.dataService
.load([{ id: { equal: this.nodeId } }]) .load([{ id: { equal: this.nodeId } }])
.subscribe((apiKey) => { .subscribe(apiKey => {
this.node = apiKey.nodes[0]; this.node = apiKey.nodes[0];
this.setForm(this.node); this.setForm(this.node);
}); });
@ -53,22 +53,23 @@ export class ShortUrlFormPageComponent extends FormPageBase<
id: new FormControl<number | undefined>(undefined), id: new FormControl<number | undefined>(undefined),
shortUrl: new FormControl<string | undefined>( shortUrl: new FormControl<string | undefined>(
undefined, undefined,
Validators.required, Validators.required
), ),
targetUrl: new FormControl<string | undefined>( targetUrl: new FormControl<string | undefined>(
undefined, undefined,
Validators.required, Validators.required
), ),
description: new FormControl<string | undefined>(undefined), description: new FormControl<string | undefined>(undefined),
loadingScreen: new FormControl<boolean | undefined>(undefined),
groupId: new FormControl<number | undefined>(undefined), groupId: new FormControl<number | undefined>(undefined),
}); });
this.form.controls["id"].disable(); this.form.controls['id'].disable();
} }
private generateRandomString(length: number): string { private generateRandomString(length: number): string {
const characters = const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = ""; let result = '';
const charactersLength = characters.length; const charactersLength = characters.length;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength)); result += characters.charAt(Math.floor(Math.random() * charactersLength));
@ -77,56 +78,63 @@ export class ShortUrlFormPageComponent extends FormPageBase<
} }
setForm(node?: ShortUrl) { setForm(node?: ShortUrl) {
this.form.controls["id"].setValue(node?.id); this.form.controls['id'].setValue(node?.id);
this.form.controls["shortUrl"].setValue( this.form.controls['shortUrl'].setValue(
node?.shortUrl ?? this.generateRandomString(8), node?.shortUrl ?? this.generateRandomString(8)
); );
this.form.controls["targetUrl"].setValue(node?.targetUrl); this.form.controls['targetUrl'].setValue(node?.targetUrl);
this.form.controls["description"].setValue(node?.description); this.form.controls['description'].setValue(node?.description);
this.form.controls["groupId"].setValue(node?.group?.id); this.form.controls['loadingScreen'].setValue(node?.loadingScreen);
this.form.controls['groupId'].setValue(node?.group?.id);
} }
getCreateInput(): ShortUrlCreateInput { getCreateInput(): ShortUrlCreateInput {
return { return {
shortUrl: this.form.controls["shortUrl"].value ?? undefined, shortUrl: this.form.controls['shortUrl'].value ?? undefined,
targetUrl: this.form.controls["targetUrl"].pristine targetUrl: this.form.controls['targetUrl'].pristine
? undefined ? undefined
: (this.form.controls["targetUrl"].value ?? undefined), : (this.form.controls['targetUrl'].value ?? undefined),
description: this.form.controls["description"].pristine description: this.form.controls['description'].pristine
? undefined ? undefined
: (this.form.controls["description"].value ?? undefined), : (this.form.controls['description'].value ?? undefined),
groupId: this.form.controls["groupId"].pristine loadingScreen: this.form.controls['loadingScreen'].pristine
? undefined ? undefined
: (this.form.controls["groupId"].value ?? undefined), : (this.form.controls['loadingScreen'].value ?? undefined),
groupId: this.form.controls['groupId'].pristine
? undefined
: (this.form.controls['groupId'].value ?? undefined),
}; };
} }
getUpdateInput(): ShortUrlUpdateInput { getUpdateInput(): ShortUrlUpdateInput {
if (!this.node?.id) { if (!this.node?.id) {
throw new Error("Node id is missing"); throw new Error('Node id is missing');
} }
return { return {
id: this.form.controls["id"].value, id: this.form.controls['id'].value,
shortUrl: this.form.controls["shortUrl"].pristine shortUrl: this.form.controls['shortUrl'].pristine
? undefined ? undefined
: (this.form.controls["shortUrl"].value ?? undefined), : (this.form.controls['shortUrl'].value ?? undefined),
targetUrl: this.form.controls["targetUrl"].pristine targetUrl: this.form.controls['targetUrl'].pristine
? undefined ? undefined
: (this.form.controls["targetUrl"].value ?? undefined), : (this.form.controls['targetUrl'].value ?? undefined),
description: this.form.controls["description"].pristine description: this.form.controls['description'].pristine
? undefined ? undefined
: (this.form.controls["description"].value ?? undefined), : (this.form.controls['description'].value ?? undefined),
groupId: this.form.controls["groupId"].pristine loadingScreen: this.form.controls['loadingScreen'].pristine
? undefined ? undefined
: (this.form.controls["groupId"].value ?? undefined), : (this.form.controls['loadingScreen'].value ?? undefined),
groupId: this.form.controls['groupId'].pristine
? undefined
: (this.form.controls['groupId'].value ?? undefined),
}; };
} }
create(apiKey: ShortUrlCreateInput): void { create(apiKey: ShortUrlCreateInput): void {
this.dataService.create(apiKey).subscribe(() => { this.dataService.create(apiKey).subscribe(() => {
this.spinner.hide(); this.spinner.hide();
this.toast.success("action.created"); this.toast.success('action.created');
this.close(); this.close();
}); });
} }
@ -134,7 +142,7 @@ export class ShortUrlFormPageComponent extends FormPageBase<
update(apiKey: ShortUrlUpdateInput): void { update(apiKey: ShortUrlUpdateInput): void {
this.dataService.update(apiKey).subscribe(() => { this.dataService.update(apiKey).subscribe(() => {
this.spinner.hide(); this.spinner.hide();
this.toast.success("action.created"); this.toast.success('action.created');
this.close(); this.close();
}); });
} }

View File

@ -1,25 +1,25 @@
import { Injectable, Provider } from "@angular/core"; import { Injectable, Provider } from '@angular/core';
import { Observable } from "rxjs"; import { Observable } from 'rxjs';
import { import {
Create, Create,
Delete, Delete,
PageDataService, PageDataService,
Restore, Restore,
Update, Update,
} from "src/app/core/base/page.data.service"; } from 'src/app/core/base/page.data.service';
import { Filter } from "src/app/model/graphql/filter/filter.model"; import { Filter } from 'src/app/model/graphql/filter/filter.model';
import { Sort } from "src/app/model/graphql/filter/sort.model"; import { Sort } from 'src/app/model/graphql/filter/sort.model';
import { Apollo, gql } from "apollo-angular"; import { Apollo, gql } from 'apollo-angular';
import { QueryResult } from "src/app/model/entities/query-result"; import { QueryResult } from 'src/app/model/entities/query-result';
import { DB_MODEL_FRAGMENT } from "src/app/model/graphql/db-model.query"; import { DB_MODEL_FRAGMENT } from 'src/app/model/graphql/db-model.query';
import { catchError, map } from "rxjs/operators"; import { catchError, map } from 'rxjs/operators';
import { SpinnerService } from "src/app/service/spinner.service"; import { SpinnerService } from 'src/app/service/spinner.service';
import { import {
ShortUrl, ShortUrl,
ShortUrlCreateInput, ShortUrlCreateInput,
ShortUrlUpdateInput, ShortUrlUpdateInput,
} from "src/app/model/entities/short-url"; } from 'src/app/model/entities/short-url';
import { Group } from "src/app/model/entities/group"; import { Group } from 'src/app/model/entities/group';
@Injectable() @Injectable()
export class ShortUrlsDataService export class ShortUrlsDataService
@ -32,7 +32,7 @@ export class ShortUrlsDataService
{ {
constructor( constructor(
private spinner: SpinnerService, private spinner: SpinnerService,
private apollo: Apollo, private apollo: Apollo
) { ) {
super(); super();
} }
@ -41,7 +41,7 @@ export class ShortUrlsDataService
filter?: Filter[] | undefined, filter?: Filter[] | undefined,
sort?: Sort[] | undefined, sort?: Sort[] | undefined,
skip?: number | undefined, skip?: number | undefined,
take?: number | undefined, take?: number | undefined
): Observable<QueryResult<ShortUrl>> { ): Observable<QueryResult<ShortUrl>> {
return this.apollo return this.apollo
.query<{ shortUrls: QueryResult<ShortUrl> }>({ .query<{ shortUrls: QueryResult<ShortUrl> }>({
@ -60,6 +60,7 @@ export class ShortUrlsDataService
shortUrl shortUrl
targetUrl targetUrl
description description
loadingScreen
visits visits
group { group {
id id
@ -81,12 +82,12 @@ export class ShortUrlsDataService
}, },
}) })
.pipe( .pipe(
catchError((err) => { catchError(err => {
this.spinner.hide(); this.spinner.hide();
throw err; throw err;
}), })
) )
.pipe(map((result) => result.data.shortUrls)); .pipe(map(result => result.data.shortUrls));
} }
loadById(id: number): Observable<ShortUrl> { loadById(id: number): Observable<ShortUrl> {
@ -99,6 +100,7 @@ export class ShortUrlsDataService
shortUrl shortUrl
targetUrl targetUrl
description description
loadingScreen
visits visits
group { group {
id id
@ -116,12 +118,12 @@ export class ShortUrlsDataService
}, },
}) })
.pipe( .pipe(
catchError((err) => { catchError(err => {
this.spinner.hide(); this.spinner.hide();
throw err; throw err;
}), })
) )
.pipe(map((result) => result.data.shortUrls.nodes[0])); .pipe(map(result => result.data.shortUrls.nodes[0]));
} }
create(object: ShortUrlCreateInput): Observable<ShortUrl | undefined> { create(object: ShortUrlCreateInput): Observable<ShortUrl | undefined> {
@ -146,17 +148,18 @@ export class ShortUrlsDataService
shortUrl: object.shortUrl, shortUrl: object.shortUrl,
targetUrl: object.targetUrl, targetUrl: object.targetUrl,
description: object.description, description: object.description,
loadingScreen: object.loadingScreen,
groupId: object.groupId, groupId: object.groupId,
}, },
}, },
}) })
.pipe( .pipe(
catchError((err) => { catchError(err => {
this.spinner.hide(); this.spinner.hide();
throw err; throw err;
}), })
) )
.pipe(map((result) => result.data?.shortUrl.create)); .pipe(map(result => result.data?.shortUrl.create));
} }
update(object: ShortUrlUpdateInput): Observable<ShortUrl | undefined> { update(object: ShortUrlUpdateInput): Observable<ShortUrl | undefined> {
@ -182,17 +185,18 @@ export class ShortUrlsDataService
shortUrl: object.shortUrl, shortUrl: object.shortUrl,
targetUrl: object.targetUrl, targetUrl: object.targetUrl,
description: object.description, description: object.description,
loadingScreen: object.loadingScreen,
groupId: object.groupId, groupId: object.groupId,
}, },
}, },
}) })
.pipe( .pipe(
catchError((err) => { catchError(err => {
this.spinner.hide(); this.spinner.hide();
throw err; throw err;
}), })
) )
.pipe(map((result) => result.data?.shortUrl.update)); .pipe(map(result => result.data?.shortUrl.update));
} }
delete(object: ShortUrl): Observable<boolean> { delete(object: ShortUrl): Observable<boolean> {
@ -210,12 +214,12 @@ export class ShortUrlsDataService
}, },
}) })
.pipe( .pipe(
catchError((err) => { catchError(err => {
this.spinner.hide(); this.spinner.hide();
throw err; throw err;
}), })
) )
.pipe(map((result) => result.data?.shortUrl.delete ?? false)); .pipe(map(result => result.data?.shortUrl.delete ?? false));
} }
restore(object: ShortUrl): Observable<boolean> { restore(object: ShortUrl): Observable<boolean> {
@ -233,12 +237,12 @@ export class ShortUrlsDataService
}, },
}) })
.pipe( .pipe(
catchError((err) => { catchError(err => {
this.spinner.hide(); this.spinner.hide();
throw err; throw err;
}), })
) )
.pipe(map((result) => result.data?.shortUrl.restore ?? false)); .pipe(map(result => result.data?.shortUrl.restore ?? false));
} }
getAllGroups() { getAllGroups() {
@ -256,12 +260,12 @@ export class ShortUrlsDataService
`, `,
}) })
.pipe( .pipe(
catchError((err) => { catchError(err => {
this.spinner.hide(); this.spinner.hide();
throw err; throw err;
}), })
) )
.pipe(map((result) => result.data.groups.nodes)); .pipe(map(result => result.data.groups.nodes));
} }
static provide(): Provider[] { static provide(): Provider[] {

View File

@ -14,7 +14,13 @@
</div> </div>
<div class="grid-container"> <div class="grid-container">
<span class="grid-label font-bold">{{ 'short_url.visits' | translate }}:</span> <span class="grid-label font-bold">{{ 'short_url.visits' | translate }}:</span>
<span class="grid-value">{{ url.visits }}</span> <span class="grid-value highlight2">{{ url.visits }}</span>
</div>
<div class="grid-container">
<span class="grid-label font-bold">{{ 'short_url.loading_screen' | translate }}:</span>
<span class="grid-value">
<div class="pi pi-{{ url.loadingScreen ? 'check-circle' : 'times-circle' }}"></div>
</span>
</div> </div>
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end">

View File

@ -1,18 +1,18 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from '@angular/core';
import { PageBase } from "src/app/core/base/page-base"; import { PageBase } from 'src/app/core/base/page-base';
import { ToastService } from "src/app/service/toast.service"; import { ToastService } from 'src/app/service/toast.service';
import { ConfirmationDialogService } from "src/app/service/confirmation-dialog.service"; import { ConfirmationDialogService } from 'src/app/service/confirmation-dialog.service';
import { PermissionsEnum } from "src/app/model/auth/permissionsEnum"; import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
import { ShortUrl } from "src/app/model/entities/short-url"; import { ShortUrl } from 'src/app/model/entities/short-url';
import { ShortUrlsDataService } from "src/app/modules/admin/short-urls/short-urls.data.service"; import { ShortUrlsDataService } from 'src/app/modules/admin/short-urls/short-urls.data.service';
import { ShortUrlsColumns } from "src/app/modules/admin/short-urls/short-urls.columns"; import { ShortUrlsColumns } from 'src/app/modules/admin/short-urls/short-urls.columns';
import { AuthService } from "src/app/service/auth.service"; import { AuthService } from 'src/app/service/auth.service';
import { Filter } from "src/app/model/graphql/filter/filter.model"; import { Filter } from 'src/app/model/graphql/filter/filter.model';
@Component({ @Component({
selector: "app-short-urls", selector: 'app-short-urls',
templateUrl: "./short-urls.page.html", templateUrl: './short-urls.page.html',
styleUrl: "./short-urls.page.scss", styleUrl: './short-urls.page.scss',
}) })
export class ShortUrlsPage export class ShortUrlsPage
extends PageBase<ShortUrl, ShortUrlsDataService, ShortUrlsColumns> extends PageBase<ShortUrl, ShortUrlsDataService, ShortUrlsColumns>
@ -28,7 +28,7 @@ export class ShortUrlsPage
private hide_deleted_filter: Filter = { deleted: { equal: false } }; private hide_deleted_filter: Filter = { deleted: { equal: false } };
get showDeleted() { get showDeleted() {
return !this.filter.some( return !this.filter.some(
(f) => JSON.stringify(f) === JSON.stringify(this.hide_deleted_filter), f => JSON.stringify(f) === JSON.stringify(this.hide_deleted_filter)
); );
} }
@ -43,7 +43,7 @@ export class ShortUrlsPage
constructor( constructor(
private toast: ToastService, private toast: ToastService,
private confirmation: ConfirmationDialogService, private confirmation: ConfirmationDialogService,
private auth: AuthService, private auth: AuthService
) { ) {
super(true, { super(true, {
read: [PermissionsEnum.shortUrls], read: [PermissionsEnum.shortUrls],
@ -57,19 +57,19 @@ export class ShortUrlsPage
async ngOnInit() { async ngOnInit() {
this.hasPermissions = { this.hasPermissions = {
read: await this.auth.hasAnyPermissionLazy( read: await this.auth.hasAnyPermissionLazy(
this.requiredPermissions.read ?? [], this.requiredPermissions.read ?? []
), ),
create: await this.auth.hasAnyPermissionLazy( create: await this.auth.hasAnyPermissionLazy(
this.requiredPermissions.create ?? [], this.requiredPermissions.create ?? []
), ),
update: await this.auth.hasAnyPermissionLazy( update: await this.auth.hasAnyPermissionLazy(
this.requiredPermissions.update ?? [], this.requiredPermissions.update ?? []
), ),
delete: await this.auth.hasAnyPermissionLazy( delete: await this.auth.hasAnyPermissionLazy(
this.requiredPermissions.delete ?? [], this.requiredPermissions.delete ?? []
), ),
restore: await this.auth.hasAnyPermissionLazy( restore: await this.auth.hasAnyPermissionLazy(
this.requiredPermissions.restore ?? [], this.requiredPermissions.restore ?? []
), ),
}; };
} }
@ -78,29 +78,22 @@ export class ShortUrlsPage
this.loading = true; this.loading = true;
this.dataService this.dataService
.load(this.filter, this.sort, this.skip, this.take) .load(this.filter, this.sort, this.skip, this.take)
.subscribe((result) => { .subscribe(result => {
this.result = result; this.result = result;
this.shortUrlsWithoutGroup = this.getShortUrlsWithoutGroup(); this.shortUrlsWithoutGroup = this.getShortUrlsWithoutGroup();
this.groupedShortUrls = this.getShortUrlsWithGroup(); this.groupedShortUrls = this.getShortUrlsWithGroup();
console.warn(
"result",
result,
this.shortUrlsWithoutGroup,
this.groupedShortUrls,
);
this.loading = false; this.loading = false;
}); });
} }
delete(group: ShortUrl): void { delete(group: ShortUrl): void {
this.confirmation.confirmDialog({ this.confirmation.confirmDialog({
header: "dialog.delete.header", header: 'dialog.delete.header',
message: "dialog.delete.message", message: 'dialog.delete.message',
accept: () => { accept: () => {
this.loading = true; this.loading = true;
this.dataService.delete(group).subscribe(() => { this.dataService.delete(group).subscribe(() => {
this.toast.success("action.deleted"); this.toast.success('action.deleted');
this.load(); this.load();
}); });
}, },
@ -110,12 +103,12 @@ export class ShortUrlsPage
restore(group: ShortUrl): void { restore(group: ShortUrl): void {
this.confirmation.confirmDialog({ this.confirmation.confirmDialog({
header: "dialog.restore.header", header: 'dialog.restore.header',
message: "dialog.restore.message", message: 'dialog.restore.message',
accept: () => { accept: () => {
this.loading = true; this.loading = true;
this.dataService.restore(group).subscribe(() => { this.dataService.restore(group).subscribe(() => {
this.toast.success("action.restored"); this.toast.success('action.restored');
this.load(); this.load();
}); });
}, },
@ -144,23 +137,23 @@ export class ShortUrlsPage
} }
open(url: string) { open(url: string) {
window.open(url, "_blank"); window.open(url, '_blank');
} }
copy(val: string) { copy(val: string) {
navigator.clipboard.writeText(`${window.origin}/${val}`).then(() => { navigator.clipboard.writeText(`${window.origin}/${val}`).then(() => {
this.toast.info("common.copied", "common.copied_to_clipboard"); this.toast.info('common.copied', 'common.copied_to_clipboard');
}); });
} }
getShortUrlsWithoutGroup(): ShortUrl[] { getShortUrlsWithoutGroup(): ShortUrl[] {
return this.result.nodes.filter((shortUrl) => !shortUrl.group); return this.result.nodes.filter(shortUrl => !shortUrl.group);
} }
getShortUrlsWithGroup() { getShortUrlsWithGroup() {
const groupedShortUrls: { [key: string]: ShortUrl[] } = {}; const groupedShortUrls: { [key: string]: ShortUrl[] } = {};
this.result.nodes.forEach((shortUrl) => { this.result.nodes.forEach(shortUrl => {
if (!shortUrl.group) return; if (!shortUrl.group) return;
const groupName = shortUrl.group.name; const groupName = shortUrl.group.name;

View File

@ -1,5 +1,5 @@
<ng-template #menuBtn let-element> <ng-template #menuBtn let-element>
<button <a
class="flex w-full gap-5 items-center justify-center p-2 rounded-xl hover:bg hover:cursor-pointer" class="flex w-full gap-5 items-center justify-center p-2 rounded-xl hover:bg hover:cursor-pointer"
(click)="(element.command)" (click)="(element.command)"
[routerLink]="element.routerLink"> [routerLink]="element.routerLink">
@ -15,7 +15,7 @@
<span class="pi pi-angle-right"></span> <span class="pi pi-angle-right"></span>
</button> </button>
</div> </div>
</button> </a>
</ng-template> </ng-template>
<div <div

View File

@ -1,7 +1,7 @@
<p-sidebar <p-sidebar
[visible]="true" [visible]="true"
position="right" position="right"
[style]="{ width: 'min-content', minWidth: '350px' }" [style]="{ width: 'min-content', minWidth: '450px' }"
(onHide)="onClose.emit()" (onHide)="onClose.emit()"
(visibleChange)="!$event ? onClose.emit() : undefined"> (visibleChange)="!$event ? onClose.emit() : undefined">
<ng-template pTemplate="headless"> <ng-template pTemplate="headless">

View File

@ -1,67 +1,66 @@
import { inject, NgModule } from "@angular/core"; import { inject, NgModule } from '@angular/core';
import { CommonModule } from "@angular/common"; import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { StepsModule } from "primeng/steps"; import { StepsModule } from 'primeng/steps';
import { DialogModule } from "primeng/dialog"; import { DialogModule } from 'primeng/dialog';
import { ToastModule } from "primeng/toast"; import { ToastModule } from 'primeng/toast';
import { ProgressSpinnerModule } from "primeng/progressspinner"; import { ProgressSpinnerModule } from 'primeng/progressspinner';
import { ButtonModule } from "primeng/button"; import { ButtonModule } from 'primeng/button';
import { ButtonGroupModule } from "primeng/buttongroup"; import { ButtonGroupModule } from 'primeng/buttongroup';
import { ConfirmDialogModule } from "primeng/confirmdialog"; import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { TableModule } from "primeng/table"; import { TableModule } from 'primeng/table';
import { InputTextModule } from "primeng/inputtext"; import { InputTextModule } from 'primeng/inputtext';
import { CheckboxModule } from "primeng/checkbox"; import { CheckboxModule } from 'primeng/checkbox';
import { DropdownModule } from "primeng/dropdown"; import { DropdownModule } from 'primeng/dropdown';
import { TranslateModule } from "@ngx-translate/core"; import { TranslateModule } from '@ngx-translate/core';
import { ImageModule } from "primeng/image"; import { ImageModule } from 'primeng/image';
import { TabMenuModule } from "primeng/tabmenu"; import { TabMenuModule } from 'primeng/tabmenu';
import { MenubarModule } from "primeng/menubar"; import { MenubarModule } from 'primeng/menubar';
import { PanelMenuModule } from "primeng/panelmenu"; import { PanelMenuModule } from 'primeng/panelmenu';
import { CardModule } from "primeng/card"; import { CardModule } from 'primeng/card';
import { MultiSelectModule } from "primeng/multiselect"; import { MultiSelectModule } from 'primeng/multiselect';
import { SplitButtonModule } from "primeng/splitbutton"; import { SplitButtonModule } from 'primeng/splitbutton';
import { CalendarModule } from "primeng/calendar"; import { CalendarModule } from 'primeng/calendar';
import { PaginatorModule } from "primeng/paginator"; import { PaginatorModule } from 'primeng/paginator';
import { DataViewModule } from "primeng/dataview"; import { DataViewModule } from 'primeng/dataview';
import { TagModule } from "primeng/tag"; import { TagModule } from 'primeng/tag';
import { SidebarModule } from "primeng/sidebar"; import { SidebarModule } from 'primeng/sidebar';
import { FileUploadModule } from "primeng/fileupload"; import { FileUploadModule } from 'primeng/fileupload';
import { MenuModule } from "primeng/menu"; import { MenuModule } from 'primeng/menu';
import { ChipModule } from "primeng/chip"; import { ChipModule } from 'primeng/chip';
import { BadgeModule } from "primeng/badge"; import { BadgeModule } from 'primeng/badge';
import { ContextMenuModule } from "primeng/contextmenu"; import { ContextMenuModule } from 'primeng/contextmenu';
import { DragDropModule } from "primeng/dragdrop"; import { DragDropModule } from 'primeng/dragdrop';
import { InputSwitchModule } from "primeng/inputswitch"; import { InputSwitchModule } from 'primeng/inputswitch';
import { SelectButtonModule } from "primeng/selectbutton"; import { SelectButtonModule } from 'primeng/selectbutton';
import { BreadcrumbModule } from "primeng/breadcrumb"; import { BreadcrumbModule } from 'primeng/breadcrumb';
import { OverlayPanelModule } from "primeng/overlaypanel"; import { OverlayPanelModule } from 'primeng/overlaypanel';
import { TerminalModule } from "primeng/terminal"; import { TerminalModule } from 'primeng/terminal';
import { RatingModule } from "primeng/rating"; import { RatingModule } from 'primeng/rating';
import { FocusTrapModule } from "primeng/focustrap"; import { FocusTrapModule } from 'primeng/focustrap';
import { InputMaskModule } from "primeng/inputmask"; import { InputMaskModule } from 'primeng/inputmask';
import { TriStateCheckboxModule } from "primeng/tristatecheckbox"; import { TriStateCheckboxModule } from 'primeng/tristatecheckbox';
import { InputTextareaModule } from "primeng/inputtextarea"; import { InputTextareaModule } from 'primeng/inputtextarea';
import { MenuBarComponent } from "src/app/modules/shared/components/menu-bar/menu-bar.component"; import { MenuBarComponent } from 'src/app/modules/shared/components/menu-bar/menu-bar.component';
import { SideMenuComponent } from "./components/side-menu/side-menu.component"; import { SideMenuComponent } from './components/side-menu/side-menu.component';
import { import {
CustomActionDirective, CustomActionDirective,
TableComponent, TableComponent,
} from "./components/table/table.component"; } from './components/table/table.component';
import { BoolPipe } from "src/app/modules/shared/pipes/bool.pipe"; import { BoolPipe } from 'src/app/modules/shared/pipes/bool.pipe';
import { CustomDatePipe } from "src/app/modules/shared/pipes/customDate.pipe"; import { CustomDatePipe } from 'src/app/modules/shared/pipes/customDate.pipe';
import { FormPageComponent } from "src/app/modules/shared/components/slidein/form-page.component"; import { FormPageComponent } from 'src/app/modules/shared/components/slidein/form-page.component';
import { import {
FormPageContentDirective, FormPageContentDirective,
FormPageHeaderDirective, FormPageHeaderDirective,
} from "src/app/modules/shared/form"; } from 'src/app/modules/shared/form';
import { ProtectPipe } from "./pipes/protect.pipe"; import { ProtectPipe } from './pipes/protect.pipe';
import { provideApollo } from "apollo-angular"; import { provideApollo } from 'apollo-angular';
import { HttpLink } from "apollo-angular/http"; import { HttpLink } from 'apollo-angular/http';
import { environment } from "src/environments/environment"; import { InMemoryCache } from '@apollo/client/core';
import { InMemoryCache } from "@apollo/client/core"; import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideHttpClient, withInterceptors } from "@angular/common/http"; import { tokenInterceptor } from 'src/app/core/token.interceptor';
import { tokenInterceptor } from "src/app/core/token.interceptor"; import { SettingsService } from 'src/app/service/settings.service';
import { SettingsService } from "src/app/service/settings.service";
const sharedModules = [ const sharedModules = [
StepsModule, StepsModule,
@ -137,13 +136,13 @@ const sharedComponents = [
defaultOptions: { defaultOptions: {
watchQuery: { watchQuery: {
fetchPolicy: "no-cache", fetchPolicy: 'no-cache',
}, },
query: { query: {
fetchPolicy: "no-cache", fetchPolicy: 'no-cache',
}, },
mutate: { mutate: {
fetchPolicy: "no-cache", fetchPolicy: 'no-cache',
}, },
}, },
}; };

View File

@ -1,13 +1,13 @@
import { HostListener, Injectable } from "@angular/core"; import { HostListener, Injectable } from '@angular/core';
import { BehaviorSubject, filter } from "rxjs"; import { BehaviorSubject, filter } from 'rxjs';
import { Logger } from "src/app/service/logger.service"; import { Logger } from 'src/app/service/logger.service';
import { NavigationEnd, Router } from "@angular/router"; import { NavigationEnd, Router } from '@angular/router';
import { SidebarService } from "src/app/service/sidebar.service"; import { SidebarService } from 'src/app/service/sidebar.service';
const logger = new Logger("GuiService"); const logger = new Logger('GuiService');
@Injectable({ @Injectable({
providedIn: "root", providedIn: 'root',
}) })
export class GuiService { export class GuiService {
isMobile$ = new BehaviorSubject<boolean>(this.isMobileByWindowWith()); isMobile$ = new BehaviorSubject<boolean>(this.isMobileByWindowWith());
@ -17,10 +17,10 @@ export class GuiService {
constructor( constructor(
private router: Router, private router: Router,
private sidebarService: SidebarService, private sidebarService: SidebarService
) { ) {
this.router.events this.router.events
.pipe(filter((event) => event instanceof NavigationEnd)) .pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => { .subscribe(() => {
if (this.isMobile$.value) { if (this.isMobile$.value) {
this.sidebarService.hide(); this.sidebarService.hide();
@ -28,8 +28,8 @@ export class GuiService {
}); });
} }
@HostListener("window:resize", ["$event"]) @HostListener('window:resize', ['$event'])
onResize(event: any) { onResize() {
this.checkWindowSize(); this.checkWindowSize();
} }
@ -42,7 +42,7 @@ export class GuiService {
} }
public checkWindowSize() { public checkWindowSize() {
logger.debug("check window size", { logger.debug('check window size', {
mobile: this.isMobileByWindowWith(), mobile: this.isMobileByWindowWith(),
tablet: this.isTabletByWindowWith(), tablet: this.isTabletByWindowWith(),
size: window.innerWidth, size: window.innerWidth,
@ -64,9 +64,9 @@ export class GuiService {
b = parseInt(hex.slice(5, 7), 16); b = parseInt(hex.slice(5, 7), 16);
if (alpha) { if (alpha) {
return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")"; return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
} else { } else {
return "rgb(" + r + ", " + g + ", " + b + ")"; return 'rgb(' + r + ', ' + g + ', ' + b + ')';
} }
} }

View File

@ -107,6 +107,7 @@
}, },
"short_url": { "short_url": {
"count_header": "Url(s)", "count_header": "Url(s)",
"loading_screen": "Ladebildschirm",
"short_url": "URL", "short_url": "URL",
"target_url": "Ziel", "target_url": "Ziel",
"visits": "Aufrufe" "visits": "Aufrufe"

View File

@ -107,6 +107,7 @@
}, },
"short_url": { "short_url": {
"count_header": "Url(s)", "count_header": "Url(s)",
"loading_screen": "Loading screen",
"short_url": "URL", "short_url": "URL",
"target_url": "Target", "target_url": "Target",
"visits": "Visits" "visits": "Visits"