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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chytanka",
"version": "0.13.56",
"version": "0.13.58",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export abstract class ReadBaseComponent {
constructor() {
this.route.pathFromRoot[0].queryParams.subscribe(q => {
const vm = q['vm'] // view mode param
this.viewerService.setViewModeOptionByCode(vm)

if(vm && vm !== this.viewerService.viewModeOption().code)
this.viewerService.setViewModeOptionByCode(vm)

const pl = q['list'] // playlist

Expand All @@ -32,7 +34,7 @@ export abstract class ReadBaseComponent {
this.playlistService.resetPlaylist();

this.plObserv = this.playlistService.getPlaylist(pl).subscribe(data => {
if (!isPlaylist) return;
if (!isPlaylist(data)) return;

this.playlistService.setPlaylist(data)
})
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/facades/language-facade.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class LanguageFacadeService {
route.queryParams.subscribe(q => {
const l = q['lang'];

if (l == this.lang.lang()) return;

if (l) this.lang.setLang(l);
if (l && this.lang.manifests.has(l)) {
this.lang.updateManifest();
Expand Down
3 changes: 2 additions & 1 deletion src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import { SourceCopyrightComponent } from './ui/source-copyright/source-copyright
import { SourceCopyrightLogoComponent } from './ui/source-copyright-logo/source-copyright-logo.component';
import { SloganComponent } from './ui/slogan/slogan.component';
import { ToggleBarComponent } from './ui/toggle-bar/toggle-bar.component';
import { SvgTextBoxComponent } from './ui/svg-text-box/svg-text-box.component';

const components = [GamepadCursorComponent, TruncatePipe, TextEmbracerComponent, OverlayComponent, ViewModeBarComponent, MadeInUkraineComponent, DialogComponent, LangToggleComponent, TitleCardComponent, LoadingComponent, SeparatorComponent, FileChangeComponent, ChytankaLogoWithTagsComponent, FileSizePipe, VibrateHapticDirective, SircleBlurComponent, DropZoneComponent, SourceCopyrightComponent, SourceCopyrightLogoComponent, SloganComponent, NsfwWarningComponent, ImgMetaDirective, NewTabDirective, PagesIndicatorComponent, WarmFilterComponent, WarmControlComponent, ToggleBarComponent]
const components = [GamepadCursorComponent, TruncatePipe, TextEmbracerComponent, OverlayComponent, ViewModeBarComponent, MadeInUkraineComponent, DialogComponent, LangToggleComponent, TitleCardComponent, LoadingComponent, SeparatorComponent, FileChangeComponent, ChytankaLogoWithTagsComponent, FileSizePipe, VibrateHapticDirective, SircleBlurComponent, DropZoneComponent, SourceCopyrightComponent, SourceCopyrightLogoComponent, SloganComponent, NsfwWarningComponent, ImgMetaDirective, NewTabDirective, PagesIndicatorComponent, WarmFilterComponent, WarmControlComponent, ToggleBarComponent, SvgTextBoxComponent]

@NgModule({
declarations: [...components],
Expand Down
17 changes: 17 additions & 0 deletions src/app/shared/ui/svg-text-box/svg-text-box.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<svg dir="ltr" frame="1" [attr.viewBox]="viewBox()" [style.maxWidth]="maxWidth()">

<text #textEl fill="currentColor" [attr.font-size]="fontSize()" text-anchor="middle">
@if(link().length > 0) {
<a [routerLink]="link()" [queryParams]="params()">
@for (line of lines(); track $index) {
<tspan x="50%" [attr.y]="getTspanY($index)">{{ line }}</tspan>
}
</a>
} @else {
@for (line of lines(); track $index) {
<tspan x="50%" [attr.y]="getTspanY($index)">{{ line }}</tspan>
}
}
</text>

</svg>
30 changes: 30 additions & 0 deletions src/app/shared/ui/svg-text-box/svg-text-box.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
:host {
font-family: monospace;
}

svg {
display: block;
width: 100%;
height: 100%;

text {
text-anchor: middle;
font-family: inherit;
direction: ltr;
text-transform: uppercase;
}
}

:host.bubble {
svg {
background-color: white;
border: 1px solid black;
border-radius: 2px;
}

&.info {
svg {
background-color: #f0e8e0;
}
}
}
53 changes: 53 additions & 0 deletions src/app/shared/ui/svg-text-box/svg-text-box.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { AfterViewInit, Component, computed, ElementRef, input, signal, ViewChild } from '@angular/core';

@Component({
selector: 'svg-text-box',
standalone: false,

templateUrl: './svg-text-box.component.html',
styleUrl: './svg-text-box.component.scss'
})
export class SvgTextBoxComponent implements AfterViewInit {

link = input<string | any[]>("");
params = input<any>({});

box = signal({ x: 0, y: 0, width: 0, height: 0 });

viewBox = computed(() => {
const box = this.box();
return `0 0 ${box.width} ${box.height}`;
});

maxWidth = computed(() => this.box().width + 'px');

ngAfterViewInit(): void {
queueMicrotask(() => this.updateBox());
}

updateBox() {
const bbox = this.textEl.nativeElement.getBBox();
this.box.set({
x: bbox.x - this.padding(),
y: bbox.y - this.padding(),
width: bbox.width + this.padding() * 2,
height: bbox.height + this.padding() * 2
});
}

@ViewChild('textEl', { static: true }) textEl!: ElementRef<SVGTextElement>;

lines = input<string[]>([]);

fontSize = input<number>(14);
width = input<number>(280);
padding = input<number>(10);
lineHeightRatio = input<number>(1.15);


protected getTspanY(index: number): string {
const lineHeight = this.fontSize() * this.lineHeightRatio();

return `${lineHeight * index + this.fontSize() + this.padding()}`;
}
}
8 changes: 8 additions & 0 deletions src/app/shared/utils/phrases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ export class Phrases {
softMode = "Soft mode"
baseSettings = "Base settings"
fileSettings = "File settings"
viewModeChangeHint = "You can change the reading orientation in the top right corner"
showHidePanelHint = "Tap 👆 anywhere to toggle the panel"
keyboardHorNavigationHint = "for navigation: ⬅️➡️ or AD"
keyboardVerNavigationHint = "for navigation: ⬆️⬇️ or WS"
keyboardFullscreenHint = "Toggle fullscreen: F"
keyboardShareHint = "Share link: Ctrl+E"
keyboardPlaylistHint = "Open playlist: Ctrl+P"
keyboardDownloadHint = "Download for offline reading: Ctrl+S"
getByKey = (key: string) => (Object.keys(this).includes(key)) ? this[key as keyof Phrases] : null;

static getTemplate(phrase: string, value: string) {
Expand Down
55 changes: 23 additions & 32 deletions src/app/viewer/viewer/components/hint-page/hint-page.component.html
Original file line number Diff line number Diff line change
@@ -1,48 +1,39 @@
@let ph = lang.ph();
<manga-page [dir]="viewer.viewModeOption().dir" style="margin: auto 0;">
<svg dir="ltr" frame="3" viewBox="0 0 300 200">
<text x="50%" y="42%" font-size="28" style="font-family:'Troubleside'">
{{lang.ph().getByKey(viewer.viewModeOption().hintPhraceKey)}}
</text>
<text x="50%" y="58%" font-size="24">{{viewer.viewModeOption().emoji}}</text>
</svg>

<svg dir="ltr" frame="1" viewBox="0 0 280 280">
<rect x="20" y="80" width="240" height="120" rx="4" stroke-width="2" fill="white" stroke="black" />
<text fill="currentColor" font-size="14">
<tspan x="50%" y="38%">Тицьни 👆</tspan>
<tspan x="50%" y="46%">у будь-якому місці,</tspan>
<tspan x="50%" y="54%">щоб показати/приховати</tspan>
<tspan x="50%" y="62%">панень</tspan>
</text>
</svg>
<svg-text-box dir="ltr" style="font-family:'Troubleside'" frame="3" [fontSize]="32" [lines]="[
ph.getByKey(viewer.viewModeOption().hintPhraceKey) + '',
viewer.viewModeOption().emoji
]" />

@let viewModeChangeHint = splitPhrase(ph.viewModeChangeHint);
<svg dir="ltr" frame="3" class="abs" viewBox="0 0 200 150">
<text class="hand-text" fill="currentColor" font-size="11">
<tspan x="50%" y="38%">Вгорі справа можна змінити</tspan>
<tspan x="50%" y="48%">орієнтацію читання</tspan>
<tspan x="50%" y="38%">{{viewModeChangeHint[0]}}</tspan>
<tspan x="50%" y="48%">{{viewModeChangeHint[1]}}</tspan>
</text>
</svg>

<svg dir="ltr" frame="2" viewBox="0 0 320 320">
<rect x="-20" y="85" width="360" height="150" rx="4" stroke-width="2" fill="white" stroke="black"></rect>
<text fill="currentColor" font-size="14">
<tspan x="50%" y="34%">для навігації: ⬅➡ або ad</tspan>
<tspan x="50%" y="42%">Fullscreen: f</tspan>
<tspan x="50%" y="50%">Поділитися посиланням: Ctrl+E</tspan>
<tspan x="50%" y="58%">Відкрити список епізодів: Ctrl+P</tspan>
<tspan x="50%" y="66%">Скачати для перегляду офлайн: Ctrl+S</tspan>
</text>
</svg>
@let showHidePanelHint = splitPhrase(ph.showHidePanelHint, 3);
<svg-text-box class="bubble" dir="ltr" frame="1" [lines]="showHidePanelHint" />
<svg-text-box class="bubble" dir="ltr" frame="2" [lines]="[
viewer.viewModeOption().mode === 'pages' ? ph.keyboardHorNavigationHint : ph.keyboardVerNavigationHint,
ph.keyboardFullscreenHint,
ph.keyboardShareHint,
ph.keyboardPlaylistHint,

]" />
<!-- ph.keyboardDownloadHint -->

<img frame="4" style="display: block;" src="/assets/icons/chtnk.svg" alt="Chytanka logo">

<div dir="ltr" frame="5">
@if (getPrevIndex() >=0 && playlist()[getPrevIndex()]; as prev) {
<a dir="ltr" [routerLink]="['/', prev.site, prev.id]"
[queryParams]="{lang: lang.lang(), list: playlistLink, vm: viewer.viewModeOption().code}">⏪
{{prev.title??
lang.ph().untitled + ' ' + (getPrevIndex() + 1)}}</a>
@let label = '⏪' + (prev.title ?? ph.untitled) + ' ' + (getPrevIndex() + 1);
@let routerLink = ['/', prev.site, prev.id];
@let queryParams = {lang: lang.lang(), list: playlistLink(), vm: viewer.viewModeOption().code};
<svg-text-box class="bubble" dir="ltr" frame="1" [lines]="[label]" [link]="routerLink" [params]="queryParams" />
}
</div>

</manga-page>
</manga-page>
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
:host {
&.long {
manga-page {
--page-ratio: 10 / 16;
max-height: unset;
width: 100%;
}
}
}

svg-text-box {
display: grid;
place-content: center;
width: 100%;
height: 100%;
padding: 1ch;
}

svg {
display: block;
width: 100%;
height: 100%;

text {
text-anchor: middle;
font-family: monospace;
font-family: monospace;
direction: ltr;
text-transform: uppercase;
}
Expand Down
36 changes: 31 additions & 5 deletions src/app/viewer/viewer/components/hint-page/hint-page.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { LangService } from '../../../../shared/data-access/lang.service';
import { Playlist, PlaylistItem } from '../../../../playlist/data-access/playlist.service';

@Component({
selector: 'app-hint-page',
templateUrl: './hint-page.component.html',
styleUrl: './hint-page.component.scss',
standalone: false
selector: 'app-hint-page',
templateUrl: './hint-page.component.html',
styleUrl: './hint-page.component.scss',
standalone: false
})
export class HintPageComponent {
viewer: ViewerService = inject(ViewerService)
Expand All @@ -19,7 +19,7 @@ export class HintPageComponent {


getCyrrentIndex() {
for (let i = 0; i < this.playlist.length; i++) {
for (let i = 0; i < this.playlist().length; i++) {
const item = this.playlist()[i];
if (this.currentPlaylistItem()?.id == item.id && this.currentPlaylistItem()?.site == item.site)
return i;
Expand All @@ -35,4 +35,30 @@ export class HintPageComponent {
return ((index - 1) >= 0) ? (index - 1) : -1;
}

splitPhrase(str: string, lines: number = 2): string[] {
const words = str.split(" ");
const result: string[] = [];

const chunkSize = Math.ceil(words.length / lines);

for (let i = 0; i < lines; i++) {
const start = i * chunkSize;
const end = start + chunkSize;

const chunk = words.slice(start, end).join(" ");
if (chunk) result.push(chunk);
}

return result;
}

getTspanY(index: number, total: number): string {
const start = 40; // де починаємо (%)
const end = 60; // де закінчуємо (%)

const step = total > 1 ? (end - start) / (total - 1) : 0;

return `${start + step * index}%`;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
<div dir="ltr" frame="4">
<p class="text-center">{{lang.ph().episodeEnd}}</p>
@if (getNextIndex() >=0 && playlist()[getNextIndex()]; as next) {
@let label = next.title ?? lang.ph().untitled + ' ' + (getNextIndex() + 1) + '⏩';
@let routerLink=['/', next.site, next.id];
@let queryParams = {lang: lang.lang(), list: playlistLink(), vm: vm.code};

<a dir="ltr" [routerLink]="['/', next.site, next.id]"
[queryParams]="{lang: lang.lang(), list: playlistLink, vm: vm.code}">
{{next.title??
lang.ph().untitled + ' ' + (getNextIndex() + 1)}} ⏩</a>
<svg-text-box class="bubble" dir="ltr" frame="1" [lines]="[label]" [link]="routerLink" [params]="queryParams" />
}
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ app-manga-page-even.long,
.last-hint-page.long {
height: unset;
width: 100%;
--page-ratio: 10 / 16;
}

[frame="1"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class ThanksPageComponent {


getCyrrentIndex() {
for (let i = 0; i < this.playlist.length; i++) {
for (let i = 0; i < this.playlist().length; i++) {
const item = this.playlist()[i];
if (this.currentPlaylistItem()?.id == item.id && this.currentPlaylistItem()?.site == item.site)
return i;
Expand All @@ -35,7 +35,7 @@ export class ThanksPageComponent {
const index = this.getCyrrentIndex();
if (index < 0) return -1;

return ((index + 1) < this.playlist.length) ? index + 1 : -1;
return ((index + 1) < this.playlist().length) ? index + 1 : -1;
}

readonly logo = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { GamepadService } from '../../../../shared/data-access/gamepad.service';
import { GamepadButton } from '../../../../shared/models';
import { FileService } from '../../../../file/data-access/file.service';
import { ViewerService } from '../../../services';
import { Router } from '@angular/router';

@Component({
selector: 'app-viewer-header',
Expand All @@ -28,6 +29,7 @@ export class ViewerHeaderComponent {
file = inject(FileService);
embedHelper = inject(EmbedHalperService);
gamepad = inject(GamepadService);
router = inject(Router);
// #endregion

// #region 📥 Inputs / Outputs
Expand Down Expand Up @@ -88,6 +90,11 @@ export class ViewerHeaderComponent {
setViewModeOption(e: any) {
this.viewer.setViewModeOption(e);
this.viewer.saveViewModeOption();
const queryParams = { vm: e.code };
this.router.navigate([], {
queryParams: { ...queryParams },
queryParamsHandling: 'merge',
});
}
//#endregion

Expand Down
Loading
Loading