Add file/folder selection to browsers, fix numeric sorting

This commit is contained in:
2024-07-14 14:08:32 +09:30
parent f79df6ff27
commit e4e980bc70
3 changed files with 82 additions and 12 deletions

View File

@@ -1,11 +1,19 @@
<h2> <h2>
@if(workspaceName()) { {{ workspaceName() }} } @else { No Worspace Selected} @if(workspaceName()) { {{ workspaceName() }} } @else if(!isFileSelected()) {
No Worspace Selected}
</h2> </h2>
@if(!selectedDirectory()) { @if(!isFileSelected()) {
<div> <div>
<button mat-button (click)="selectDirectory()" style="margin: auto"> <button mat-button (click)="selectDirectory()" style="margin: auto">
Open Folder Open Folder
</button> </button>
<input
#fileSelector
type="file"
(change)="selectFilesBrowser()"
webkitdirectory
multiple
/>
</div> </div>
}@else { }@else {
<mat-tree [dataSource]="dataSource()" [treeControl]="treeControl"> <mat-tree [dataSource]="dataSource()" [treeControl]="treeControl">

View File

@@ -22,3 +22,7 @@ h2 {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
} }
input {
display: none;
}

View File

@@ -2,11 +2,13 @@ import { FlatTreeControl } from '@angular/cdk/tree';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import {
Component, Component,
ElementRef,
OnDestroy, OnDestroy,
OnInit, OnInit,
computed, computed,
output, output,
signal, signal,
viewChild,
} from '@angular/core'; } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@@ -24,7 +26,7 @@ export interface FileOrFolder {
isDirectory: boolean; isDirectory: boolean;
name: string; name: string;
children?: FileOrFolder[]; children?: FileOrFolder[];
path: string; path?: string;
} }
interface FileNode { interface FileNode {
@@ -43,6 +45,9 @@ interface FileNode {
export class FileTreeComponent implements OnInit, OnDestroy { export class FileTreeComponent implements OnInit, OnDestroy {
fileSelected = output<FileOrFolder>(); fileSelected = output<FileOrFolder>();
protected fileSelector =
viewChild<ElementRef<HTMLInputElement>>('fileSelector');
// File tree // File tree
protected hasChild = (_: number, node: FileNode) => node.expandable; protected hasChild = (_: number, node: FileNode) => node.expandable;
@@ -77,6 +82,7 @@ export class FileTreeComponent implements OnInit, OnDestroy {
// Folder selection // Folder selection
protected selectedDirectory = signal<string | null>(null); protected selectedDirectory = signal<string | null>(null);
protected files = signal<FileOrFolder[]>([]); protected files = signal<FileOrFolder[]>([]);
protected isFileSelected = signal<boolean>(false);
protected workspaceName = computed(() => { protected workspaceName = computed(() => {
const directory = this.selectedDirectory(); const directory = this.selectedDirectory();
if (directory) { if (directory) {
@@ -104,6 +110,15 @@ export class FileTreeComponent implements OnInit, OnDestroy {
} }
async selectDirectory() { 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({ const selectedDirectory = await open({
directory: true, directory: true,
multiple: false, multiple: false,
@@ -118,26 +133,69 @@ export class FileTreeComponent implements OnInit, OnDestroy {
const entries = await readDir(selectedDirectory, { const entries = await readDir(selectedDirectory, {
recursive: true, recursive: true,
}); });
const splitNumbers = /(\d)+|(\D)+/;
this.files.set( this.files.set(
entries entries.sort(this.sortFiles).map((entry) => this.mapEntry(entry))
.sort(this.sortFiles(splitNumbers))
.map((entry) => this.mapEntry(entry, splitNumbers))
); );
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 { return {
isDirectory: entry.children != null, isDirectory: entry.children != null,
name: entry.name || '', name: entry.name || '',
children: entry.children children: entry.children
?.sort(this.sortFiles(splitNumbers)) ?.sort(this.sortFiles)
.map((entry) => this.mapEntry(entry, splitNumbers)), .map((entry) => this.mapEntry(entry)),
path: entry.path, path: entry.path,
}; };
} }
private sortFiles = (splitNumbers: RegExp) => (a: FileEntry, b: FileEntry) => private sortFiles = (a: { name?: string }, b: { name?: string }) =>
a.name?.localeCompare(b.name ?? '') ?? 0; 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);
}
} }