diff --git a/package.json b/package.json index 7e987ba..d74f5ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chytanka", - "version": "0.13.56", + "version": "0.13.58", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/src/app/@site-modules/@common-read/utils/read-base-component.ts b/src/app/@site-modules/@common-read/utils/read-base-component.ts index 52c1b3e..99cb96b 100644 --- a/src/app/@site-modules/@common-read/utils/read-base-component.ts +++ b/src/app/@site-modules/@common-read/utils/read-base-component.ts @@ -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 @@ -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) }) diff --git a/src/app/core/facades/language-facade.service.ts b/src/app/core/facades/language-facade.service.ts index 75fc322..df97aec 100644 --- a/src/app/core/facades/language-facade.service.ts +++ b/src/app/core/facades/language-facade.service.ts @@ -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(); diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 1504bb7..044b00f 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -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], diff --git a/src/app/shared/ui/svg-text-box/svg-text-box.component.html b/src/app/shared/ui/svg-text-box/svg-text-box.component.html new file mode 100644 index 0000000..5ed5d85 --- /dev/null +++ b/src/app/shared/ui/svg-text-box/svg-text-box.component.html @@ -0,0 +1,17 @@ + + + + @if(link().length > 0) { + + @for (line of lines(); track $index) { + {{ line }} + } + + } @else { + @for (line of lines(); track $index) { + {{ line }} + } + } + + + \ No newline at end of file diff --git a/src/app/shared/ui/svg-text-box/svg-text-box.component.scss b/src/app/shared/ui/svg-text-box/svg-text-box.component.scss new file mode 100644 index 0000000..a10721e --- /dev/null +++ b/src/app/shared/ui/svg-text-box/svg-text-box.component.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/src/app/shared/ui/svg-text-box/svg-text-box.component.ts b/src/app/shared/ui/svg-text-box/svg-text-box.component.ts new file mode 100644 index 0000000..9697475 --- /dev/null +++ b/src/app/shared/ui/svg-text-box/svg-text-box.component.ts @@ -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(""); + params = input({}); + + 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; + + lines = input([]); + + fontSize = input(14); + width = input(280); + padding = input(10); + lineHeightRatio = input(1.15); + + + protected getTspanY(index: number): string { + const lineHeight = this.fontSize() * this.lineHeightRatio(); + + return `${lineHeight * index + this.fontSize() + this.padding()}`; + } +} \ No newline at end of file diff --git a/src/app/shared/utils/phrases.ts b/src/app/shared/utils/phrases.ts index 5249654..ac62425 100644 --- a/src/app/shared/utils/phrases.ts +++ b/src/app/shared/utils/phrases.ts @@ -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) { diff --git a/src/app/viewer/viewer/components/hint-page/hint-page.component.html b/src/app/viewer/viewer/components/hint-page/hint-page.component.html index e7737dc..e2f5eac 100644 --- a/src/app/viewer/viewer/components/hint-page/hint-page.component.html +++ b/src/app/viewer/viewer/components/hint-page/hint-page.component.html @@ -1,48 +1,39 @@ +@let ph = lang.ph(); - - - {{lang.ph().getByKey(viewer.viewModeOption().hintPhraceKey)}} - - {{viewer.viewModeOption().emoji}} - - - - - Тицьни 👆 - у будь-якому місці, - щоб показати/приховати - панень - - + + @let viewModeChangeHint = splitPhrase(ph.viewModeChangeHint); - Вгорі справа можна змінити - орієнтацію читання + {{viewModeChangeHint[0]}} + {{viewModeChangeHint[1]}} - - - - для навігації: ⬅➡ або ad - Fullscreen: f - Поділитися посиланням: Ctrl+E - Відкрити список епізодів: Ctrl+P - Скачати для перегляду офлайн: Ctrl+S - - + @let showHidePanelHint = splitPhrase(ph.showHidePanelHint, 3); + + + Chytanka logo
@if (getPrevIndex() >=0 && playlist()[getPrevIndex()]; as prev) { - ⏪ - {{prev.title?? - lang.ph().untitled + ' ' + (getPrevIndex() + 1)}} + @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}; + }
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/app/viewer/viewer/components/hint-page/hint-page.component.scss b/src/app/viewer/viewer/components/hint-page/hint-page.component.scss index 1e3f532..9629793 100644 --- a/src/app/viewer/viewer/components/hint-page/hint-page.component.scss +++ b/src/app/viewer/viewer/components/hint-page/hint-page.component.scss @@ -1,12 +1,21 @@ :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%; @@ -14,7 +23,7 @@ svg { text { text-anchor: middle; - font-family: monospace; + font-family: monospace; direction: ltr; text-transform: uppercase; } diff --git a/src/app/viewer/viewer/components/hint-page/hint-page.component.ts b/src/app/viewer/viewer/components/hint-page/hint-page.component.ts index 55f2092..8fe5858 100644 --- a/src/app/viewer/viewer/components/hint-page/hint-page.component.ts +++ b/src/app/viewer/viewer/components/hint-page/hint-page.component.ts @@ -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) @@ -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; @@ -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}%`; + } + } diff --git a/src/app/viewer/viewer/components/thanks-page/thanks-page.component.html b/src/app/viewer/viewer/components/thanks-page/thanks-page.component.html index 86040d0..0583cb7 100644 --- a/src/app/viewer/viewer/components/thanks-page/thanks-page.component.html +++ b/src/app/viewer/viewer/components/thanks-page/thanks-page.component.html @@ -8,11 +8,11 @@

{{lang.ph().episodeEnd}}

@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}; - - {{next.title?? - lang.ph().untitled + ' ' + (getNextIndex() + 1)}} ⏩ + }
diff --git a/src/app/viewer/viewer/components/thanks-page/thanks-page.component.scss b/src/app/viewer/viewer/components/thanks-page/thanks-page.component.scss index fa6ebb6..16e4674 100644 --- a/src/app/viewer/viewer/components/thanks-page/thanks-page.component.scss +++ b/src/app/viewer/viewer/components/thanks-page/thanks-page.component.scss @@ -2,6 +2,7 @@ app-manga-page-even.long, .last-hint-page.long { height: unset; width: 100%; + --page-ratio: 10 / 16; } [frame="1"], diff --git a/src/app/viewer/viewer/components/thanks-page/thanks-page.component.ts b/src/app/viewer/viewer/components/thanks-page/thanks-page.component.ts index a1266f4..0424bad 100644 --- a/src/app/viewer/viewer/components/thanks-page/thanks-page.component.ts +++ b/src/app/viewer/viewer/components/thanks-page/thanks-page.component.ts @@ -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; @@ -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 = { diff --git a/src/app/viewer/viewer/components/viewer-header/viewer-header.component.ts b/src/app/viewer/viewer/components/viewer-header/viewer-header.component.ts index 27347da..4696dfb 100644 --- a/src/app/viewer/viewer/components/viewer-header/viewer-header.component.ts +++ b/src/app/viewer/viewer/components/viewer-header/viewer-header.component.ts @@ -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', @@ -28,6 +29,7 @@ export class ViewerHeaderComponent { file = inject(FileService); embedHelper = inject(EmbedHalperService); gamepad = inject(GamepadService); + router = inject(Router); // #endregion // #region 📥 Inputs / Outputs @@ -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 diff --git a/src/app/viewer/viewer/viewer.component.html b/src/app/viewer/viewer/viewer.component.html index 4162542..166b192 100644 --- a/src/app/viewer/viewer/viewer.component.html +++ b/src/app/viewer/viewer/viewer.component.html @@ -15,10 +15,10 @@ @for(img of episode().images; track img.src; let i = $index) { - + } } @else { -

😢 {{lang.ph().noPagesFound}}

-

{{lang.ph().noPagesFoundDesc}}

- 🏠 + + + + 🏠 }
diff --git a/src/assets/icons/chtnk.svg b/src/assets/icons/chtnk.svg index 105ef42..d386e22 100644 --- a/src/assets/icons/chtnk.svg +++ b/src/assets/icons/chtnk.svg @@ -1,6 +1,6 @@ - diff --git a/src/assets/langs/uk.json b/src/assets/langs/uk.json index d4e0185..7457f8d 100644 --- a/src/assets/langs/uk.json +++ b/src/assets/langs/uk.json @@ -72,5 +72,13 @@ "trueMode": "True mode", "softMode": "Soft mode", "baseSettings": "Основні налаштування", - "fileSettings": "Налаштування файлів" + "fileSettings": "Налаштування файлів", + "viewModeChangeHint": "Вгорі справа можна змінити орієнтацію читання", + "showHidePanelHint": "Тицьни 👆 у будь-якому місці, щоб показати/приховати панель", + "keyboardHorNavigationHint": "для навігації: ⬅️➡️ або AD", + "keyboardVerNavigationHint": "для навігації: ⬆️⬇️ або WS", + "keyboardFullscreenHint": "Переключити повноекранний режим: F", + "keyboardShareHint": "Поділитися посиланням: Ctrl+E", + "keyboardPlaylistHint": "Відкрити список для читання: Ctrl+P", + "keyboardDownloadHint": "Скачати для перегляду офлайн: Ctrl+S" } \ No newline at end of file diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts index ae33c1d..3847b71 100644 --- a/src/environments/environment.development.ts +++ b/src/environments/environment.development.ts @@ -1,7 +1,7 @@ const PROXY = `http://192.168.10.107:3003/api?url=` export const environment = { - version: "0.13.56-2026.4.9", + version: "0.13.58-2026.4.11", prod: false, proxy: PROXY, blankaryoHost: `https://blankary.com/page/`, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index c3bbb2f..551f35a 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,7 +1,7 @@ const PROXY = `https://proxy.chytanka.ink/api?url=` export const environment = { - version: "0.13.56-2026.4.9", + version: "0.13.58-2026.4.11", prod: true, proxy: PROXY, blankaryoHost: `https://blankary.com/page/`,