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 @@ -9,6 +9,7 @@
"files": [
"/favicon.ico",
"/index.html",
"/index.csr.html",
"/manifest.webmanifest",
"/*.css",
"/*.js"
Expand Down
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.43",
"version": "0.13.48",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
34 changes: 33 additions & 1 deletion src/app/file/data-access/file.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Injectable, signal, WritableSignal } from '@angular/core';
import { effect, inject, Injectable, signal, WritableSignal } from '@angular/core';
import { FILE_PATH } from '../../app-routing.module';
import { Router } from '@angular/router';

@Injectable({
providedIn: 'root'
})
export class FileService {
router = inject(Router);

private _file: WritableSignal<File | null> = signal<File | null>(null);

public set file(f: File) {
Expand All @@ -14,8 +18,36 @@ export class FileService {
return this._file;
}

constructor() {
effect(() => this.navigate());
}

reset() {
this._file.set(null);
}

private getRouteType(file: File): string | undefined {
const fileType = file.type || file.name.split('.').pop()?.toLowerCase();

if (!fileType) return undefined;

if (fileType.includes('pdf')) return 'pdf';
if (fileType.includes('mobi')) return 'mobi';
if (/zip|cbz/.test(fileType)) return 'zip';

return undefined;
}

private navigate() {
const file = this.file();
if (file === null) return;

const t = this.getRouteType(file)

if (t) {
const url = `/${FILE_PATH}/${t}`;
this.router.navigateByUrl(url)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ form {
backdrop-filter: blur(var(--blur));
z-index: 3;
border-radius: .5ch;
max-width: 80ch;
width: 100%;
position: relative;
}
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 @@ -37,8 +37,9 @@ import { PageComponent } from './ui/viewer/components/page/page.component';
import { EpisodeInfoTableComponent } from './ui/viewer/components/episode-info-table/episode-info-table.component';
import { EpisodeShareFormComponent } from './ui/viewer/components/episode-share-form/episode-share-form.component';
import { EpisodeDownloadFormComponent } from './ui/viewer/components/episode-download-form/episode-download-form.component';
import { DropZoneComponent } from './ui/drop-zone/drop-zone.component';

const components = [GamepadCursorComponent, TruncatePipe, TextEmbracerComponent, ViewerComponent, OverlayComponent, ViewModeBarComponent, MadeInUkraineComponent, DialogComponent, LangToggleComponent, TitleCardComponent, LoadingComponent, SeparatorComponent, FileChangeComponent, ChytankaLogoWithTagsComponent, FileSizePipe, VibrateHapticDirective, SircleBlurComponent, PageComponent, EpisodeInfoTableComponent, EpisodeShareFormComponent, EpisodeDownloadFormComponent]
const components = [GamepadCursorComponent, TruncatePipe, TextEmbracerComponent, ViewerComponent, OverlayComponent, ViewModeBarComponent, MadeInUkraineComponent, DialogComponent, LangToggleComponent, TitleCardComponent, LoadingComponent, SeparatorComponent, FileChangeComponent, ChytankaLogoWithTagsComponent, FileSizePipe, VibrateHapticDirective, SircleBlurComponent, PageComponent, EpisodeInfoTableComponent, EpisodeShareFormComponent, EpisodeDownloadFormComponent, DropZoneComponent]

@NgModule({
declarations: [
Expand Down
2 changes: 2 additions & 0 deletions src/app/shared/ui/drop-zone/drop-zone.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<span>📃</span>
<p>{{lang.ph().dropIt}}</p>
22 changes: 22 additions & 0 deletions src/app/shared/ui/drop-zone/drop-zone.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
:host {
font-size: 3rem;
position: fixed;
inset: 0;
display: grid;
place-content: center;
z-index: 100;
background-color: var(--surface);
text-align: center;

color: #166496;
font-weight: bold;

&::before {
content: '';
position: absolute;
inset: 2ch;
outline: .25ch dashed #166496;
z-index: 1;
border-radius: .25ch;
}
}
13 changes: 13 additions & 0 deletions src/app/shared/ui/drop-zone/drop-zone.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component, inject } from '@angular/core';
import { LangService } from '../../data-access/lang.service';

@Component({
selector: 'drop-zone',
standalone: false,

templateUrl: './drop-zone.component.html',
styleUrl: './drop-zone.component.scss'
})
export class DropZoneComponent {
lang = inject(LangService);
}
12 changes: 4 additions & 8 deletions src/app/shared/ui/file-change/file-change.component.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
<!-- <input type="file" (input)="onFileSelected($event)" [accept]="accept.join(', ')" > -->
<button [vibrateHaptic]="10" class="button border" (click)="openFileDialog()"
[title]="lang.ph().openFile+hotKeyHint()">{{label()}}</button>

<button [vibrateHaptic]="10" class="button border" (click)="openFileDialog()" [title]="lang.ph().openFile+' (Ctrl+O)'">{{label()}}</button>

@if(showDragAndDropZone){
<div class="drop-zone" (click)="showDragAndDropZone = false" (drop)="dropHandler($event)">
<span>📃</span>
<p>{{lang.ph().dropIt}}</p>
</div>
@if(showDragAndDropZone()){
<drop-zone (click)="showDragAndDropZone.set(false)" (drop)="dropHandler($event)" />
}
23 changes: 0 additions & 23 deletions src/app/shared/ui/file-change/file-change.component.scss
Original file line number Diff line number Diff line change
@@ -1,26 +1,3 @@
.drop-zone {
font-size: 3rem;
position: fixed;
inset: 0;
display: grid;
place-content: center;
z-index: 100;
background-color: var(--surface);
text-align: center;

color: #166496;
font-weight: bold;

&::before {
content: '';
position: absolute;
inset: 2ch;
outline: .25ch dashed #166496;
z-index: 1;
border-radius: .25ch;
}
}

.button {
width: 100%;
text-align: unset;
Expand Down
120 changes: 61 additions & 59 deletions src/app/shared/ui/file-change/file-change.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Component, HostListener, inject, input, OnInit, PLATFORM_ID } from '@angular/core';
import { Component, computed, HostListener, inject, input, OnInit, PLATFORM_ID, signal } from '@angular/core';
import { FileService } from '../../../file/data-access/file.service';
import { Router } from '@angular/router';
import { FILE_PATH } from '../../../app-routing.module';
import { LangService } from '../../data-access/lang.service';
import { isPlatformServer } from '@angular/common';

Expand All @@ -17,76 +15,63 @@ type LaunchParams = {
standalone: false
})
export class FileChangeComponent implements OnInit {
platformId = inject(PLATFORM_ID)
fs = inject(FileService)
router = inject(Router)
lang = inject(LangService)
private platformId = inject(PLATFORM_ID)
private fs = inject(FileService)
protected lang = inject(LangService)

accept = input<string[]>([])
label = input<string>("Open File")
label = input<string>(this.lang.ph().openFile)

input: HTMLInputElement | undefined;
showDragAndDropZone: boolean = false;
private input: HTMLInputElement | undefined;
protected showDragAndDropZone = signal(false);

private readonly ctrlKey = signal('Ctrl');
private readonly symbolKey = signal('KeyO');
private readonly hotKey = computed(() => `${this.ctrlKey()}+${this.symbolKey()}`);
protected readonly hotKeyHint = computed(() => ` (${this.hotKey().replace('Key', '')})`);

ngOnInit(): void {
if (isPlatformServer(this.platformId)) return;

this.initFileInput();
this.initializeFileLaunchQueue();
}

if ("launchQueue" in window) {
(window as any).launchQueue.setConsumer(async (launchParams: LaunchParams) => {
private fileHandler(file: File | undefined) {
if (!file) return;

if (!launchParams.files?.length) return;
this.fs.file = file;
}

for (const handle of launchParams.files) {
//#region Launch Queue

if (handle.kind === "file") {
const fileHandle = handle as FileSystemFileHandle;
console.log("fileHandle", fileHandle)
const file = await fileHandle.getFile();
console.log("File:", file.name);
private initializeFileLaunchQueue() {
if ("launchQueue" in window) {
(window as any).launchQueue.setConsumer(async (launchParams: LaunchParams) => {
const fileHandle = launchParams.files?.find(f => f.kind === "file") as FileSystemFileHandle | undefined;
if (!fileHandle) return;

this.fileHandler(file);
return;
}
}
const file = await fileHandle.getFile();
this.fileHandler(file);
});
}
}

onFileSelected(event: any) {
const file: File = event.target.files[0];
//#endregion

this.fileHandler(file)
}

fileHandler(file: File | undefined) {
if (!file) return;
//#region File Input

this.fs.file = file;

// should be output
const t = this.getRouteType(file)

if (t) {
const url = `/${FILE_PATH}/${t}`;
this.router.navigateByUrl(url)
}
protected openFileDialog() {
if (this.input) this.input.click();
}

getRouteType(file: File): string | undefined {
const fileType = file.type || file.name.split('.').pop()?.toLowerCase();

if (!fileType) return undefined;

if (fileType.includes('pdf')) return 'pdf';
if (fileType.includes('mobi')) return 'mobi';
if (/zip|cbz/.test(fileType)) return 'zip';
private onFileSelected(event: any) {
const file: File = event.target.files[0];

return undefined;
this.fileHandler(file)
}

initFileInput() {
private initFileInput() {
this.input = document.createElement('input')

this.input.type = 'file';
Expand All @@ -97,34 +82,51 @@ export class FileChangeComponent implements OnInit {
}
}

openFileDialog() {
if (this.input) this.input.click();
}
//#endregion

@HostListener('window:keydown', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
//#region Drag & Drop

const code = event.ctrlKey ? `Ctrl+${event.code}` : event.code
@HostListener('window:keydown', ['$event'])
protected handleKeyboardEvent(event: KeyboardEvent) {
const code = event.ctrlKey ? `${this.ctrlKey()}+${event.code}` : event.code;

if (code == "Ctrl+KeyO") {
if (code == this.hotKey()) {
event.preventDefault();

this.openFileDialog()
}
}

@HostListener('document:dragover', ["$event"])
dragOverHandler = (ev: DragEvent) => { ev.preventDefault(); this.showDragAndDropZone = true }
protected dragOverHandler = (ev: DragEvent) => {
ev.preventDefault();

const dt = ev.dataTransfer;
if (!dt) return;

const hasFile =
dt.items && Array.from(dt.items).some(i => i.kind === 'file');

const hasText =
dt.types?.includes('text/plain') ||
Array.from(dt.items || []).some(i => i.kind === 'string');

if (hasFile && !hasText) {
this.showDragAndDropZone.set(true);
}
}

@HostListener('dragleave', ["$event"])
@HostListener('dragend', ["$event"])
dragLeaveHandler = (ev: DragEvent) => { ev.preventDefault(); this.showDragAndDropZone = false }
protected dragLeaveHandler = (ev: DragEvent) => { ev.preventDefault(); this.showDragAndDropZone.set(false) }

dropHandler(ev: DragEvent) {
protected dropHandler(ev: DragEvent) {
ev.preventDefault();

const file: File | undefined = ev.dataTransfer?.files[0];

this.fileHandler(file)
}

//#endregion
}
15 changes: 2 additions & 13 deletions src/app/shared/ui/text-embracer/text-embracer.component.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
@for(item of letters(); track item) {
@for(item of letters(); track $index) {
<span><span>{{item}}</span></span>
}

<svg style="position: fixed; user-select: none; pointer-events: none;">
<filter id="oneBitShadow" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-color="#166496" x="2" y="2" width="4" height="4" result="flood"/>
<feComposite in="flood" in2="flood" operator="over" x="0" y="0" width="6" height="6" result="composite"/>
<feTile x="0" y="0" width="1000" height="1000" in="composite" result="tile"/>
<feOffset dx="6" dy="6" in="SourceGraphic" result="offset"/>
<feComposite in="tile" in2="offset" operator="in" result="composite2"/>
<feComposite in="SourceGraphic" in2="composite2" operator="over" result="composite3"/>
</filter>
</svg>
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
left: -0.05ch;
top: -0.05ch;
position: relative;
// filter: url("#oneBitShadow");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ app-view-mode-bar {
margin-left: auto;
}

::ng-deep app-file-change button {
border: unset !important;
}

.open-chtnk-link {
aspect-ratio: 1;
width: 2rem;
Expand Down
Loading
Loading