Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ngsw-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"/index.html",
"/index.csr.html",
"/manifest.webmanifest",
"/manifest-uk.webmanifest",
"/*.css",
"/*.js"
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@if(episode$ | async; as episode){
@if(episode$() | async; as episode){

@if (!(loading$ | async)) {
<app-viewer [playlist]="playlist" [playlistLink]="playlistLink" [currentPlaylistItem]="currentPlaylistItem"
@if (!(loading$() | async)) {
<app-viewer [playlistInput]="playlist()" [playlistLink]="playlistLink()" [currentPlaylistItem]="currentPlaylistItem()"
[episode]="episode">
<div ngProjectAs="source-logo"><ng-content select="source-logo" /></div>
<ng-content />
Expand All @@ -10,20 +10,21 @@

}

@if(error$ | async; as error){
@if(error$() | async; as error){
<div style="padding: 1rem; margin: auto;">
<div class="error-message">
<h1>🤨</h1>
<p>{{ error }}</p>
<footer>
<a class="button mediun border" [routerLink]="'/'">🏠</a>
<button autofocus class="button border large primary" (click)="onRefreshData()">{{lang.ph().tryAgain}}</button>
<button autofocus class="button border large primary"
(click)="onRefreshData()">{{lang.ph().tryAgain}}</button>
</footer>
</div>
</div>
}

@if(loading$ | async; as loading){
@if(loading$() | async; as loading){
<loading />
} @else {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { Component, EventEmitter, Input, Output, inject, input } from '@angular/core';
import { Component, output, inject, input } from '@angular/core';
import { LangService } from '../../../../shared/data-access/lang.service';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { CompositionEpisode } from '../../utils';
import { Playlist, PlaylistItem, PlaylistService } from '../../../../playlist/data-access/playlist.service';
import { Playlist, PlaylistItem } from '../../../../playlist/data-access/playlist.service';

@Component({
selector: 'app-common-read',
templateUrl: './common-read.component.html',
styleUrl: './common-read.component.scss',
standalone: false
selector: 'app-common-read',
templateUrl: './common-read.component.html',
styleUrl: './common-read.component.scss',
standalone: false
})
export class CommonReadComponent {
public lang: LangService = inject(LangService);

@Input({ required: true }) error$!: BehaviorSubject<string | null>;
@Input({ required: true }) loading$!: BehaviorSubject<boolean>;
@Input({ required: true }) episode$!: Observable<CompositionEpisode | null>;
error$ = input.required<BehaviorSubject<string | null>>();
loading$ = input.required<BehaviorSubject<boolean>>();
episode$ = input.required<Observable<CompositionEpisode | null>>();

@Input() playlist: Playlist = [];
@Input() playlistLink: string = "";
@Input() currentPlaylistItem: PlaylistItem | undefined;
playlist = input<Playlist>([]);
playlistLink = input("");
currentPlaylistItem = input<PlaylistItem | undefined>();

@Output() refreshData: EventEmitter<any> = new EventEmitter<any>();
refreshData = output();

onRefreshData() {
this.refreshData.emit();
Expand Down
32 changes: 20 additions & 12 deletions src/app/link-parser/ui/parser-form/parser-form.component.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
@let online = net.online();
@let openFileLabel = (online) ? '📃 '+lang.ph().orOpenFile: '📃 '+lang.ph().openFile;

<div class="form-wrapper">
@if (online === false ) {
<div class="offline-banner">
🚫 No internet connection
</div>
}
<h1 class="logo-text"> <app-text-embracer [ngClass]="setts.theme()" [text]="lang.ph().shortTitle" /> </h1>
<!-- <div style="display: flex; gap: 1ch; align-items: center;"> -->
<form (submit)="onSubmit()">
<input [vibrateHaptic]="5" [vibrateClick]="false" [vibrateInput]="true" name="chtnk_url" type="url" required autofocus [placeholder]="'🔗 '+lang.ph().enterLink"
(input)="inputLink($event)" [value]="link()">
</form>
<!-- </div> -->
<!-- <div style="display: flex; gap: 1ch; align-items: center;">
<span style="font-family: 'Courier New', Courier, monospace; opacity: .8;">{{lang.ph().orOpenFile}}</span>
<app-file-change style="margin-left: auto;" [accept]="parser.supportFiles()" />
</div> -->
<app-file-change [label]="'📃 '+lang.ph().orOpenFile+' ('+parser.supportFiles()+')'"/>
@if (online) {
<form (submit)="onSubmit()">
<input [vibrateHaptic]="5" [vibrateClick]="false" [vibrateInput]="true" name="chtnk_url" type="url" required
autofocus [placeholder]="'🔗 '+lang.ph().enterLink" (input)="inputLink($event)" [value]="link()">
</form>
}

<app-file-change [accept]="osAcceptSupport()?parser.supportFiles():[]"
[label]="openFileLabel+' ('+parser.supportFiles()+')'" />

</div>

<div class="slogan-wrapper">
Expand All @@ -28,7 +35,8 @@ <h2 class="slogan-header">
}
</h2>
@if (linkParams()) {
<a [vibrateHaptic]="10" class="button primary large go-btn" [routerLink]="['',linkParams()?.site, linkParams64()?.id]">
<a [vibrateHaptic]="10" class="button primary large go-btn"
[routerLink]="['',linkParams()?.site, linkParams64()?.id]">
<span>{{lang.ph().letsgo}} </span>
<img class="favicon" [src]="favicons[linkParams()?.site]" [alt]="linkParams()?.site">
<small class="site-address" [title]="linkParams()?.id">{{linkParams()?.id | truncate}}</small>
Expand Down
18 changes: 18 additions & 0 deletions src/app/link-parser/ui/parser-form/parser-form.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,22 @@ input[type=url]::placeholder {
&:active {
box-shadow: 0 0 transparent;
}
}

.offline-banner {
border-radius: .5ch;
--c: oklch(0.553 0.2211 20.3);
--shadow-color: oklch(from var(--c) var(--avarage-l-2) c h);
--shadow-2: 2px 2px var(--surface), var(--shadow-distance-2) var(--shadow-color);

font-family: 'Courier New', Courier, monospace;
--gl: radial-gradient(circle 1px at 0px 0px, oklch(from currentColor l c h /.5) 1px, transparent 0);
--bg-1: var(--gl) 0px 0px / 8px 8px;
background: var(--bg-1);
box-shadow: var(--shadow-1);
border: var(--border-size) solid var(--border-color);
display: inline-block;
padding: 1ch;
font-weight: bold;
text-align: center;
}
11 changes: 7 additions & 4 deletions src/app/link-parser/ui/parser-form/parser-form.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { AfterContentInit, AfterViewInit, ChangeDetectionStrategy, Component, computed, inject, PLATFORM_ID, signal, Signal, WritableSignal } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, inject, PLATFORM_ID, signal, Signal, WritableSignal } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { LangService } from '../../../shared/data-access/lang.service';
import { Base64 } from '../../../shared/utils';
import { LinkParserSettingsService } from '../../data-access/link-parser-settings.service';
import { LinkParserService } from '../../data-access/link-parser.service';
import { ImgurLinkParser, MangadexLinkParser, TelegraphLinkParser, RedditLinkParser, ZenkoLinkParser, NhentaiLinkParser, YandereParser, PixivLinkParser, BlankaryLinkParser, JsonLinkParser } from '../../utils';
import { ComickLinkParser } from '../../utils/comick-link-parser';
import { ImgurLinkParser, MangadexLinkParser, TelegraphLinkParser, RedditLinkParser, ZenkoLinkParser, NhentaiLinkParser, YandereParser, PixivLinkParser, JsonLinkParser } from '../../utils';
import { ImgchestLinkParser } from '../../utils/imgchest-link-parser';
import { MetaTagsService } from '../../../shared/data-access/meta-tags.service';
import { NetworkService, BrowserService } from '../../../shared/data-access/';

@Component({
selector: 'app-parser-form',
Expand All @@ -20,6 +19,8 @@ export class ParserFormComponent {
private router: Router = inject(Router);
private route: ActivatedRoute = inject(ActivatedRoute);
setts = inject(LinkParserSettingsService)
net = inject(NetworkService);
browser = inject(BrowserService);
platformId = inject(PLATFORM_ID)
link: WritableSignal<string> = signal('');
linkParams: Signal<any> = computed(() => this.parser.parse(this.link()));
Expand All @@ -31,6 +32,8 @@ export class ParserFormComponent {
};
});

osAcceptSupport = signal(["Windows", "Android", "Linux"].includes(this.browser.brouserInfo().os));

constructor(public parser: LinkParserService, public lang: LangService) {
this.initParser();
}
Expand Down
59 changes: 59 additions & 0 deletions src/app/shared/data-access/browser.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { inject, Injectable, PLATFORM_ID, signal, } from '@angular/core';
import { isPlatformServer } from '@angular/common';

export interface BrowserInfo {
name: string;
os: string;
isMobile: boolean;
}

@Injectable({
providedIn: 'root'
})
export class BrowserService {
private platformId = inject(PLATFORM_ID)

brouserInfo = signal(this.detectBrowser());

constructor() { }

detectBrowser(): BrowserInfo {
if (isPlatformServer(this.platformId)) return {
name: 'Unknown',
os: 'Unknown',
isMobile: false
};

const ua = navigator.userAgent;
const platform = navigator.platform.toLowerCase();

const isMobile = /Mobi|Android|iPhone|iPad|iPod|webOS|BlackBerry/i.test(ua);

let name = 'Unknown';

if (/Chrome|Chromium/i.test(ua) && !/Edg|OPR/i.test(ua)) {
name = 'Chromium';
} else if (/Edg/i.test(ua)) {
name = 'Edge';
} else if (/Firefox/i.test(ua)) {
name = 'Firefox';
} else if (/Safari/i.test(ua) && !/Chrome|Chromium|OPR|Edg/i.test(ua)) {
name = 'Safari';
} else if (/OPR|Opera/i.test(ua)) {
name = 'Opera';
}

let os = 'Unknown';
if (/Win/i.test(platform)) os = 'Windows';
else if (/Mac/i.test(platform)) os = 'Mac';
else if (/Linux/i.test(platform)) os = 'Linux';
else if (/iPhone|iPad|iPod/i.test(ua)) os = 'iOS';
else if (/Android/i.test(ua)) os = 'Android';

return {
name,
os,
isMobile
};
}
}
6 changes: 5 additions & 1 deletion src/app/shared/data-access/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export * from './dom-manipulation.service'
export * from './viewer.service'
export * from './viewer.service'
export * from './gamepad.service'
export * from './browser.service'
export * from './lang.service'
export * from './network.service'
62 changes: 62 additions & 0 deletions src/app/shared/data-access/network.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { isPlatformServer } from '@angular/common';
import { computed, inject, Injectable, PLATFORM_ID, signal } from '@angular/core';

@Injectable({
providedIn: 'root'
})
export class NetworkService {
private platformId = inject(PLATFORM_ID)

private _online = signal<boolean>(navigator.onLine);
private _verified = signal<boolean | null>(null);

readonly online = computed(() => this._online());
readonly verified = computed(() => this._verified());
readonly isReallyOnline = computed(() => {
const verified = this._verified();
return verified === null ? this._online() : verified;
});

constructor() {
if (isPlatformServer(this.platformId)) return;

window.addEventListener('online', () => {
this._online.set(true);
this.verify();
});

window.addEventListener('offline', () => {
this._online.set(false);
this._verified.set(false);
});

this.verify();

setInterval(() => this.verify(), 30000);
}

async verify(timeout = 3000): Promise<boolean> {
if (!navigator.onLine) {
this._verified.set(false);
return false;
}

try {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);

await fetch('/favicon.ico', {
method: 'HEAD',
cache: 'no-store',
signal: controller.signal,
});

clearTimeout(id);
this._verified.set(true);
return true;
} catch {
this._verified.set(false);
return false;
}
}
}
18 changes: 9 additions & 9 deletions src/app/shared/directives/vibrate-haptic.directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Directive, HostListener, inject, Input } from '@angular/core';
import { Directive, HostListener, inject, input } from '@angular/core';
import { VibrationService } from '../data-access/vibration.service';

@Directive({
Expand All @@ -8,40 +8,40 @@ import { VibrationService } from '../data-access/vibration.service';
export class VibrateHapticDirective {
vibration = inject(VibrationService);

@Input() vibrateHaptic: number | number[] = 10;
vibrateHaptic = input<number | number[]>(10);

@Input() vibrateTouch: boolean = false;
@Input() vibrateClick: boolean = true;
@Input() vibrateInput: boolean = false;
vibrateTouch = input(false);
vibrateClick = input(true);
vibrateInput = input(false);

constructor() { }

@HostListener('pointerdown')
onPointerDown() {
if (!this.vibrateTouch) return;

this.vibration.vibrate(this.vibrateHaptic);
this.vibration.vibrate(this.vibrateHaptic());
}

@HostListener('touchstart')
onTouchStart() {
if (!this.vibrateTouch) return;

this.vibration.vibrate(this.vibrateHaptic);
this.vibration.vibrate(this.vibrateHaptic());
}

@HostListener('click')
onClick() {
if (!this.vibrateClick) return;

this.vibration.vibrate(this.vibrateHaptic);
this.vibration.vibrate(this.vibrateHaptic());
}

@HostListener('input')
onInput() {
if (!this.vibrateInput) return;

this.vibration.vibrate(this.vibrateHaptic);
this.vibration.vibrate(this.vibrateHaptic());
}

}
Loading
Loading