2023-03-26 19:29:24 +01:00
|
|
|
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
|
|
|
|
|
|
|
|
|
import { CSSProperties, useContext } from 'react';
|
|
|
|
|
import { TreeSelect } from 'primereact/treeselect';
|
|
|
|
|
import TreeNode from 'primereact/treenode';
|
|
|
|
|
import { ModelContext, FSContext } from './contexts';
|
2023-03-26 23:39:41 +01:00
|
|
|
// import { isFileWritable } from '../state/model';
|
|
|
|
|
import { join } from '../fs/filesystem';
|
2023-03-27 00:20:40 +01:00
|
|
|
import { defaultSourcePath } from '../state/initial-state';
|
2023-03-27 19:34:52 +01:00
|
|
|
import { zipArchives } from '../fs/zip-archives';
|
2023-03-26 21:36:47 +01:00
|
|
|
|
2023-03-27 22:38:22 +01:00
|
|
|
const biasedCompare = (a: string, b: string) =>
|
|
|
|
|
a === 'openscad' ? -1 : b === 'openscad' ? 1 : a.localeCompare(b);
|
|
|
|
|
|
2023-03-26 21:36:47 +01:00
|
|
|
function listFilesAsNodes(fs: FS, path: string, accept?: (path: string) => boolean): TreeNode[] {
|
2023-03-26 19:29:24 +01:00
|
|
|
const files: [string, string][] = []
|
|
|
|
|
const dirs: [string, string][] = []
|
|
|
|
|
for (const name of fs.readdirSync(path)) {
|
|
|
|
|
if (name.startsWith('.')) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-03-27 19:34:52 +01:00
|
|
|
const childPath = join(path, name);
|
2023-03-26 21:36:47 +01:00
|
|
|
if (accept && !accept(childPath)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-03-26 19:29:24 +01:00
|
|
|
const stat = fs.lstatSync(childPath);
|
|
|
|
|
const isDirectory = stat.isDirectory();
|
|
|
|
|
if (!isDirectory && !name.endsWith('.scad')) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
(isDirectory ? dirs : files).push([name, childPath]);
|
|
|
|
|
}
|
2023-03-27 22:38:22 +01:00
|
|
|
[files, dirs].forEach(arr => arr.sort(([a], [b]) => biasedCompare(a, b)));
|
2023-03-26 19:29:24 +01:00
|
|
|
|
|
|
|
|
const nodes: TreeNode[] = []
|
|
|
|
|
for (const [arr, isDirectory] of [[files, false], [dirs, true]] as [[string, string][], boolean][]) {
|
|
|
|
|
for (const [name, path] of arr) {
|
2023-03-27 19:34:52 +01:00
|
|
|
let children: TreeNode[] = [];
|
|
|
|
|
let label = name;
|
|
|
|
|
if (path.lastIndexOf('/') === 0) {
|
|
|
|
|
const config = zipArchives[name];
|
|
|
|
|
if (config && config.gitOrigin) {
|
|
|
|
|
const repoUrl = config.gitOrigin.repoUrl;
|
|
|
|
|
if (!children) children = [];
|
|
|
|
|
|
|
|
|
|
children.push({
|
|
|
|
|
icon: 'pi pi-github',
|
|
|
|
|
label: repoUrl,
|
|
|
|
|
key: repoUrl,
|
|
|
|
|
selectable: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (const [label, link] of Object.entries(config.docs ?? [])) {
|
|
|
|
|
children.push({
|
|
|
|
|
icon: 'pi pi-book',
|
|
|
|
|
label,
|
|
|
|
|
key: link,
|
|
|
|
|
selectable: true,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-26 19:29:24 +01:00
|
|
|
}
|
2023-03-27 19:34:52 +01:00
|
|
|
|
|
|
|
|
if (isDirectory) {
|
|
|
|
|
children = [...children, ...listFilesAsNodes(fs, path, accept)];
|
|
|
|
|
if (children.length == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-26 19:29:24 +01:00
|
|
|
nodes.push({
|
2023-03-26 23:39:41 +01:00
|
|
|
// icon: isDirectory ? 'pi pi-folder' : isFileWritable(path) ? 'pi pi-file' : 'pi pi-lock',
|
2023-03-27 00:20:40 +01:00
|
|
|
icon: isDirectory ? 'pi pi-folder' : path === defaultSourcePath ? 'pi pi-home' : 'pi pi-file',
|
2023-03-27 19:34:52 +01:00
|
|
|
label,
|
2023-03-26 19:29:24 +01:00
|
|
|
data: path,
|
|
|
|
|
key: path,
|
|
|
|
|
children,
|
2023-03-27 19:34:52 +01:00
|
|
|
selectable: !isDirectory
|
2023-03-26 19:29:24 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default function FilePicker({className, style}: {className?: string, style?: CSSProperties}) {
|
|
|
|
|
const model = useContext(ModelContext);
|
|
|
|
|
if (!model) throw new Error('No model');
|
|
|
|
|
const state = model.state;
|
|
|
|
|
|
|
|
|
|
const fs = useContext(FSContext);
|
|
|
|
|
|
2023-03-27 00:20:40 +01:00
|
|
|
const fsItems = fs && listFilesAsNodes(fs, '/')
|
2023-03-26 19:29:24 +01:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<TreeSelect
|
|
|
|
|
className={className}
|
|
|
|
|
title='OpenSCAD Playground Files'
|
|
|
|
|
value={state.params.sourcePath}
|
2023-03-27 19:34:52 +01:00
|
|
|
resetFilterOnHide={true}
|
|
|
|
|
filterBy="key"
|
|
|
|
|
onChange={e => {
|
|
|
|
|
const key = e.value;
|
|
|
|
|
if (typeof key === 'string') {
|
|
|
|
|
if (key.startsWith('https://')) {
|
|
|
|
|
window.open(key, '_blank')
|
|
|
|
|
} else {
|
|
|
|
|
model.openFile(key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}}
|
2023-03-26 19:29:24 +01:00
|
|
|
filter
|
|
|
|
|
style={style}
|
|
|
|
|
options={fsItems} />
|
|
|
|
|
)
|
|
|
|
|
}
|