import { FunctionComponent } from "preact/src/index.d.ts";
import type { Package } from "$classes/package.ts";
import { I_File } from "$modules/zip.ts";
import { join } from "$std/path/mod.ts";
import { guessLangFromPath } from "./Code.tsx";
import { existsSync } from "$std/fs/mod.ts";
import { Sort } from "$utils/sort_utils.ts";
type I_ComplexFile = {
name: string
full_path: string
relative_path: string
type: "file" | "folder"
is_directory: boolean
children?: I_ComplexFile[]
} & ({
type: "folder"
children: I_ComplexFile[]
} | {
type: "file"
children: never
})
export interface PackageFolderProps {
package: Package
folder: {
path: string[]
tree: I_ComplexFile[]
}
}
const DEBUG_EXTRACT = false;
export const ExtractChildren = (root: string, files: I_File[]): I_ComplexFile[] => {
if(DEBUG_EXTRACT) console.log(`Scanning for files in "${root}"`);
const current_items = files.filter(file => {
if(!file.relative_path.trim()) return false;
if(DEBUG_EXTRACT) console.log(` - ${file.relative_path}`);
const relative = file.relative_path.replace(root, '').replace(/^\//, '');
if(!relative) return false;
if(!file.relative_path.startsWith(root)) return false;
if(DEBUG_EXTRACT) console.log(` - Checking ${file.relative_path.replace(root, '').replace(/^\//, '')}`);
if(file.relative_path.replace(root, '').replace(/^\//, '').includes('/')) return false;
if(DEBUG_EXTRACT) console.log(` - PICKED`);
return true;
})
files = files.filter(file => !current_items.includes(file) && file.relative_path.trim());
if(DEBUG_EXTRACT) console.log(` - found ${current_items.length} items`);
if(DEBUG_EXTRACT) console.log(` - reduced input files list to ${files.length} items`);
if(current_items.length == 0) return [];
return current_items.map(item => {
const out = {
name: item.name,
full_path: item.full_path,
relative_path: item.relative_path,
is_directory: item.is_directory,
} as Partial<I_ComplexFile>;
if(item.is_directory) {
out.type = 'folder';
out.children = ExtractChildren(`${root ? `${root}/` : ''}${item.name}`, files);
} else {
out.type = 'file';
}
return out as I_ComplexFile;
}).sort(Sort.File)
}
export const ExtractFolderProps = (pathname: string, _package: Package): PackageFolderProps['folder'] | null => {
const folder = ExtractFolder(pathname, _package);
if(!folder) return null;
const {
name,
url_path,
disk_path,
} = folder;
if(!existsSync(disk_path, { isDirectory: true })) return null;
const tree = ExtractChildren(name, _package.files_and_folders);
return {
path: url_path.split('/'),
tree
}
}
export const ExtractFolder = (pathname: string, _package: Package) => {
const [_namespace, _name, ...folder_path_complex] = pathname.replace(/^\/+/, '').replace(/\/\*\./g, '/.').split('/');
const folder_name = folder_path_complex.join('/').trim();
if(folder_name.includes(":")) return null;
const disk_path = join(_package.path!, folder_name).replace(/\/\*\./g, '/.');
const full_folder_path = `${_package.namespace}/${_package.name}@${_package.versions.current}/${folder_name}`;
return {
name: folder_name,
disk_path,
url_path: full_folder_path,
}
}
const SVG = {
File: {
Code: () => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
),
Plain: () => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</svg>
)
},
Folder: () => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
</svg>
)
} as const;
const RenderFolderTreeRecursive: FunctionComponent<{tree: I_ComplexFile[], package: Package}> = ({tree, package: _package}) => {
return <>
{
tree.map(item => {
if(item.type == 'folder') {
return (
<li>
<details open>
<summary>
<SVG.Folder />
<a href={`${_package.url}/${item.relative_path}`.replace(/\/\./g, '/*.')} class="link-hover w-fit">
{ item.name }
</a>
</summary>
<ul>
<RenderFolderTreeRecursive tree={item.children} package={_package} />
</ul>
</details>
</li>
)
} else {
return (
<li>
<a href={`${_package.url}/${item.relative_path}`.replace(/\/\./g, '/*.')}>
{ guessLangFromPath(item.name) == 'txt' ?
<SVG.File.Plain />
:
<SVG.File.Code />
}
{ item.name }
</a>
</li>
)
}
})
}
</>
}
export const PackageFolderBrowser: FunctionComponent<PackageFolderProps> = ({ folder, package: _package }) => {
const show_back = ![_package.url, _package.versioned_url].includes(`/${folder.path.join('/')}`.replace(/\/$/, ''))
let back_url = `/${join(folder.path.join('/'), '../')}`.replace(/\/{2,}/, '').replace(/\/$/, '');
if([_package.url, _package.versioned_url].includes(back_url)) {
back_url += '?tab=browse';
}
return (
<ul class="menu bg-base-200 rounded-box w-full">
<li><a class="pointer-events-none">
<SVG.Folder />
{ folder.path.join('/') }
</a></li>
<li />
{ show_back &&
<li><a href={back_url}>...</a></li>
}
<RenderFolderTreeRecursive tree={folder.tree} package={_package} />
</ul>
)
}