From e4e980bc70a36568402fb8be15f6b77c0634b22c Mon Sep 17 00:00:00 2001 From: vato007 Date: Sun, 14 Jul 2024 14:08:32 +0930 Subject: [PATCH] Add file/folder selection to browsers, fix numeric sorting --- src/app/file-tree/file-tree.component.html | 12 +++- src/app/file-tree/file-tree.component.scss | 4 ++ src/app/file-tree/file-tree.component.ts | 78 +++++++++++++++++++--- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/src/app/file-tree/file-tree.component.html b/src/app/file-tree/file-tree.component.html index de5fcd3..1062e22 100644 --- a/src/app/file-tree/file-tree.component.html +++ b/src/app/file-tree/file-tree.component.html @@ -1,11 +1,19 @@

- @if(workspaceName()) { {{ workspaceName() }} } @else { No Worspace Selected} + @if(workspaceName()) { {{ workspaceName() }} } @else if(!isFileSelected()) { + No Worspace Selected}

-@if(!selectedDirectory()) { +@if(!isFileSelected()) {
+
}@else { diff --git a/src/app/file-tree/file-tree.component.scss b/src/app/file-tree/file-tree.component.scss index 33f31b9..25abf41 100644 --- a/src/app/file-tree/file-tree.component.scss +++ b/src/app/file-tree/file-tree.component.scss @@ -22,3 +22,7 @@ h2 { overflow: hidden; white-space: nowrap; } + +input { + display: none; +} diff --git a/src/app/file-tree/file-tree.component.ts b/src/app/file-tree/file-tree.component.ts index a821863..5207318 100644 --- a/src/app/file-tree/file-tree.component.ts +++ b/src/app/file-tree/file-tree.component.ts @@ -2,11 +2,13 @@ import { FlatTreeControl } from '@angular/cdk/tree'; import { CommonModule } from '@angular/common'; import { Component, + ElementRef, OnDestroy, OnInit, computed, output, signal, + viewChild, } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; @@ -24,7 +26,7 @@ export interface FileOrFolder { isDirectory: boolean; name: string; children?: FileOrFolder[]; - path: string; + path?: string; } interface FileNode { @@ -43,6 +45,9 @@ interface FileNode { export class FileTreeComponent implements OnInit, OnDestroy { fileSelected = output(); + protected fileSelector = + viewChild>('fileSelector'); + // File tree protected hasChild = (_: number, node: FileNode) => node.expandable; @@ -77,6 +82,7 @@ export class FileTreeComponent implements OnInit, OnDestroy { // Folder selection protected selectedDirectory = signal(null); protected files = signal([]); + protected isFileSelected = signal(false); protected workspaceName = computed(() => { const directory = this.selectedDirectory(); if (directory) { @@ -104,6 +110,15 @@ export class FileTreeComponent implements OnInit, OnDestroy { } async selectDirectory() { + try { + await this.selectDirectoryNative(); + } catch (err) { + // Tauri failed, try using browser to select files + this.fileSelector()?.nativeElement.click(); + } + } + + private async selectDirectoryNative() { const selectedDirectory = await open({ directory: true, multiple: false, @@ -118,26 +133,69 @@ export class FileTreeComponent implements OnInit, OnDestroy { const entries = await readDir(selectedDirectory, { recursive: true, }); - const splitNumbers = /(\d)+|(\D)+/; this.files.set( - entries - .sort(this.sortFiles(splitNumbers)) - .map((entry) => this.mapEntry(entry, splitNumbers)) + entries.sort(this.sortFiles).map((entry) => this.mapEntry(entry)) ); + this.isFileSelected.set(true); } } - private mapEntry(entry: FileEntry, splitNumbers: RegExp): FileOrFolder { + protected selectFilesBrowser() { + const files = this.fileSelector()?.nativeElement.files; + if (files && files.length > 0) { + const mappedFiles: FileOrFolder[] = []; + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (file.webkitRelativePath?.includes('/')) { + // Got a file in a folder, so put it into the appropriate folder in the tree + const splitFilePath = file.webkitRelativePath.split('/'); + let currentChildren: FileOrFolder[] | undefined = mappedFiles; + for (let j = 0; j < splitFilePath.length - 1; j++) { + const relativePath = splitFilePath[j]; + let matchingChild: FileOrFolder | undefined = currentChildren?.find( + (mappedFile) => mappedFile.name === relativePath + ); + if (!matchingChild) { + matchingChild = { + isDirectory: true, + name: relativePath, + children: [], + }; + currentChildren?.push(matchingChild); + } + currentChildren = matchingChild.children; + } + currentChildren?.push({ isDirectory: false, name: file.name }); + } else { + mappedFiles.push({ isDirectory: false, name: file.name }); + } + } + this.recursiveSort(mappedFiles); + this.files.set(mappedFiles); + this.isFileSelected.set(true); + } + } + + private mapEntry(entry: FileEntry): FileOrFolder { return { isDirectory: entry.children != null, name: entry.name || '', children: entry.children - ?.sort(this.sortFiles(splitNumbers)) - .map((entry) => this.mapEntry(entry, splitNumbers)), + ?.sort(this.sortFiles) + .map((entry) => this.mapEntry(entry)), path: entry.path, }; } - private sortFiles = (splitNumbers: RegExp) => (a: FileEntry, b: FileEntry) => - a.name?.localeCompare(b.name ?? '') ?? 0; + private sortFiles = (a: { name?: string }, b: { name?: string }) => + new Intl.Collator(undefined, { numeric: true }).compare(a.name!, b.name!); + + private recursiveSort(files: FileOrFolder[]) { + for (const file of files) { + if (file.children) { + this.recursiveSort(file.children); + } + } + files.sort(this.sortFiles); + } }