Introduce a dialog for switching between built-in content and a user-selected local folder. Persist the folder handle in IndexedDB so the selection survives page refreshes. Scan the folder for .md, .yarn, and .csv files to build the file index. Update App and Sidebar to pass file tree and heading data, and add TypeScript declarations for the File System Access API.
160 lines
5.0 KiB
TypeScript
160 lines
5.0 KiB
TypeScript
import { Component, createSignal, Show } from "solid-js";
|
|
import {
|
|
loadFromUserFolder,
|
|
switchToBuiltInSource,
|
|
getActiveSource,
|
|
clearIndex,
|
|
} from "../data-loader/file-index";
|
|
|
|
export interface DataSourceDialogProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onSourceChanged: () => void;
|
|
}
|
|
|
|
/**
|
|
* Dialog for choosing how to source content files.
|
|
* Supports:
|
|
* - Built-in content (CLI / webpack bundled files)
|
|
* - Local folder (via File System Access API)
|
|
*/
|
|
export const DataSourceDialog: Component<DataSourceDialogProps> = (props) => {
|
|
const [loading, setLoading] = createSignal(false);
|
|
const [error, setError] = createSignal<string | null>(null);
|
|
const [folderPath, setFolderPath] = createSignal<string | null>(null);
|
|
|
|
const currentSource = () => getActiveSource();
|
|
|
|
const handleFolderPick = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const paths = await loadFromUserFolder();
|
|
|
|
if (paths === null) {
|
|
// User cancelled or API not supported — don't show error for cancellation
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
if (paths.length === 0) {
|
|
setError(
|
|
"No supported files (.md, .yarn, .csv) found in the selected folder.",
|
|
);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
setFolderPath(`${paths.length} files loaded`);
|
|
setLoading(false);
|
|
props.onSourceChanged();
|
|
props.onClose();
|
|
};
|
|
|
|
const handleBuiltIn = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
await switchToBuiltInSource();
|
|
// Force re-index from built-in sources
|
|
clearIndex();
|
|
|
|
setLoading(false);
|
|
props.onSourceChanged();
|
|
props.onClose();
|
|
};
|
|
|
|
return (
|
|
<Show when={props.isOpen}>
|
|
<div
|
|
class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center"
|
|
onClick={props.onClose}
|
|
>
|
|
<div
|
|
class="bg-white rounded-lg shadow-xl w-full max-w-md mx-4 p-6"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<h2 class="text-lg font-bold text-gray-900 mb-4">Content Source</h2>
|
|
|
|
<div class="space-y-3">
|
|
{/* Built-in option */}
|
|
<button
|
|
onClick={handleBuiltIn}
|
|
disabled={loading()}
|
|
class="w-full text-left p-4 rounded-lg border transition-colors"
|
|
classList={{
|
|
"border-blue-500 bg-blue-50":
|
|
currentSource() === "cli" || currentSource() === "webpack",
|
|
"border-gray-200 hover:border-gray-300":
|
|
currentSource() !== "cli" && currentSource() !== "webpack",
|
|
"opacity-50 cursor-not-allowed": loading(),
|
|
}}
|
|
>
|
|
<div class="font-medium text-gray-900">Built-in Content</div>
|
|
<div class="text-sm text-gray-500 mt-1">
|
|
Use the bundled content files shipped with the app.
|
|
</div>
|
|
<Show
|
|
when={
|
|
currentSource() === "cli" || currentSource() === "webpack"
|
|
}
|
|
>
|
|
<div class="text-xs text-blue-600 mt-1 font-medium">Active</div>
|
|
</Show>
|
|
</button>
|
|
|
|
{/* Folder picker option */}
|
|
<button
|
|
onClick={handleFolderPick}
|
|
disabled={
|
|
loading() ||
|
|
typeof window === "undefined" ||
|
|
!("showDirectoryPicker" in window)
|
|
}
|
|
class="w-full text-left p-4 rounded-lg border transition-colors"
|
|
classList={{
|
|
"border-blue-500 bg-blue-50": currentSource() === "folder",
|
|
"border-gray-200 hover:border-gray-300":
|
|
currentSource() !== "folder",
|
|
"opacity-50 cursor-not-allowed":
|
|
loading() || !("showDirectoryPicker" in window),
|
|
}}
|
|
>
|
|
<div class="font-medium text-gray-900">Local Folder</div>
|
|
<div class="text-sm text-gray-500 mt-1">
|
|
Pick a folder on your computer. Supports .md, .yarn, and .csv
|
|
files.
|
|
{"showDirectoryPicker" in window
|
|
? " Will be remembered across sessions."
|
|
: " (Not supported in this browser)"}
|
|
</div>
|
|
<Show when={currentSource() === "folder"}>
|
|
<div class="text-xs text-blue-600 mt-1 font-medium">
|
|
Active{folderPath() ? ` — ${folderPath()}` : ""}
|
|
</div>
|
|
</Show>
|
|
</button>
|
|
</div>
|
|
|
|
<Show when={error()}>
|
|
<div class="mt-4 p-3 bg-red-50 border border-red-200 rounded text-sm text-red-700">
|
|
{error()}
|
|
</div>
|
|
</Show>
|
|
|
|
<div class="mt-6 flex justify-end">
|
|
<button
|
|
onClick={props.onClose}
|
|
class="px-4 py-2 text-sm text-gray-600 hover:text-gray-900 rounded hover:bg-gray-100"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Show>
|
|
);
|
|
};
|
|
|
|
export default DataSourceDialog;
|