Add file/folder selection to browsers, fix numeric sorting
This commit is contained in:
@@ -1,11 +1,19 @@
|
||||
<h2>
|
||||
@if(workspaceName()) { {{ workspaceName() }} } @else { No Worspace Selected}
|
||||
@if(workspaceName()) { {{ workspaceName() }} } @else if(!isFileSelected()) {
|
||||
No Worspace Selected}
|
||||
</h2>
|
||||
@if(!selectedDirectory()) {
|
||||
@if(!isFileSelected()) {
|
||||
<div>
|
||||
<button mat-button (click)="selectDirectory()" style="margin: auto">
|
||||
Open Folder
|
||||
</button>
|
||||
<input
|
||||
#fileSelector
|
||||
type="file"
|
||||
(change)="selectFilesBrowser()"
|
||||
webkitdirectory
|
||||
multiple
|
||||
/>
|
||||
</div>
|
||||
}@else {
|
||||
<mat-tree [dataSource]="dataSource()" [treeControl]="treeControl">
|
||||
|
||||
@@ -22,3 +22,7 @@ h2 {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -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<FileOrFolder>();
|
||||
|
||||
protected fileSelector =
|
||||
viewChild<ElementRef<HTMLInputElement>>('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<string | null>(null);
|
||||
protected files = signal<FileOrFolder[]>([]);
|
||||
protected isFileSelected = signal<boolean>(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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user