mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-26 11:12:53 +00:00
refactor(app): resolve file panels once per pane
This commit is contained in:
@@ -7,7 +7,7 @@ import { StyleSheet, UnistylesRuntime, withUnistyles } from "react-native-unisty
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { useSessionStore, type ExplorerFile } from "@/stores/session-store";
|
||||
import { FilePanelPreview, resolveFilePreviewPanel } from "@/file-panels";
|
||||
import { FilePanelPreview, resolveFilePreviewPanel, type FilePanel } from "@/file-panels";
|
||||
import { useAttachmentPreviewUrl } from "@/attachments/use-attachment-preview-url";
|
||||
import { getFileNameFromPath } from "@/attachments/utils";
|
||||
import { resolveFilePreviewReadTarget } from "@/file-explorer/preview-target";
|
||||
@@ -37,6 +37,7 @@ const foregroundMutedColorMapping = (theme: Theme) => ({
|
||||
});
|
||||
|
||||
interface FilePreviewBodyProps {
|
||||
panel: FilePanel | null;
|
||||
preview: ExplorerFile | null;
|
||||
mode?: "preview" | "source";
|
||||
isLoading: boolean;
|
||||
@@ -121,6 +122,7 @@ function TooLargeSource({ size }: { size?: number }) {
|
||||
}
|
||||
|
||||
function FilePreviewBody({
|
||||
panel,
|
||||
preview,
|
||||
mode,
|
||||
isLoading,
|
||||
@@ -134,7 +136,6 @@ function FilePreviewBody({
|
||||
}: FilePreviewBodyProps) {
|
||||
const { t } = useTranslation();
|
||||
const filePath = location.path;
|
||||
const { settings } = useAppSettings();
|
||||
|
||||
if (isLoading && !preview) {
|
||||
return (
|
||||
@@ -153,11 +154,6 @@ function FilePreviewBody({
|
||||
);
|
||||
}
|
||||
|
||||
const panel = resolveFilePreviewPanel({
|
||||
preview,
|
||||
path: filePath,
|
||||
associations: settings.filePanelAssociations,
|
||||
});
|
||||
const showSource = preview.kind === "text" && (Boolean(location.lineStart) || mode === "source");
|
||||
if (panel && !showSource) {
|
||||
return (
|
||||
@@ -274,6 +270,7 @@ export function FilePane({
|
||||
|
||||
return (
|
||||
<FilePanePresentation
|
||||
panel={panel}
|
||||
serverId={serverId}
|
||||
workspaceRoot={normalizedWorkspaceRoot}
|
||||
client={client}
|
||||
@@ -312,6 +309,7 @@ function isEditableTextFile(input: {
|
||||
}
|
||||
|
||||
function FilePanePresentation({
|
||||
panel,
|
||||
serverId,
|
||||
workspaceRoot,
|
||||
client,
|
||||
@@ -334,6 +332,7 @@ function FilePanePresentation({
|
||||
navigationRevision,
|
||||
imagePreviewUri,
|
||||
}: {
|
||||
panel: FilePanel | null;
|
||||
serverId: string;
|
||||
workspaceRoot: string;
|
||||
client: DaemonClient | null;
|
||||
@@ -369,6 +368,7 @@ function FilePanePresentation({
|
||||
if (editable && client && readTarget && preview?.kind === "text") {
|
||||
return (
|
||||
<EditableFilePane
|
||||
panel={panel}
|
||||
key={`${serverId}:${readTarget.cwd}:${readTarget.path}`}
|
||||
serverId={serverId}
|
||||
workspaceRoot={workspaceRoot}
|
||||
@@ -421,6 +421,7 @@ function FilePanePresentation({
|
||||
/>
|
||||
) : null}
|
||||
<FilePreviewBody
|
||||
panel={panel}
|
||||
preview={preview}
|
||||
mode={previewMode}
|
||||
isLoading={isLoading}
|
||||
@@ -437,6 +438,7 @@ function FilePanePresentation({
|
||||
}
|
||||
|
||||
function EditableFilePane({
|
||||
panel,
|
||||
serverId,
|
||||
workspaceRoot,
|
||||
client,
|
||||
@@ -454,6 +456,7 @@ function EditableFilePane({
|
||||
location,
|
||||
navigationRevision,
|
||||
}: {
|
||||
panel: FilePanel | null;
|
||||
serverId: string;
|
||||
workspaceRoot: string;
|
||||
client: DaemonClient;
|
||||
@@ -601,6 +604,7 @@ function EditableFilePane({
|
||||
/>
|
||||
) : (
|
||||
<FilePreviewBody
|
||||
panel={panel}
|
||||
preview={renderedPreview}
|
||||
mode={mode}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -50,21 +50,29 @@ describe("filePreviewRenderKind", () => {
|
||||
});
|
||||
|
||||
describe("built-in file panels", () => {
|
||||
it("keeps image classification independent of extension and associations", () => {
|
||||
const resolution = resolveFilePanel({
|
||||
file: { path: "/capture", kind: "image", size: 512, hasBytes: true },
|
||||
candidates: builtinFilePanels,
|
||||
associations: { "ext:": "builtin:markdown" },
|
||||
});
|
||||
expect(resolution.panel?.id).toBe("builtin:image");
|
||||
expect(
|
||||
resolveFilePanel({
|
||||
file: { path: "capture.png", kind: "binary", size: 512, hasBytes: true },
|
||||
it.each(["png", "jpg", "jpeg", "gif", "webp", "svg"])(
|
||||
"registers the image extension %s",
|
||||
(extension) => {
|
||||
const resolution = resolveFilePanel({
|
||||
file: {
|
||||
path: `/capture.${extension.toUpperCase()}`,
|
||||
kind: "image",
|
||||
size: 512,
|
||||
hasBytes: true,
|
||||
},
|
||||
candidates: builtinFilePanels,
|
||||
associations: {},
|
||||
}).panel,
|
||||
).toBeNull();
|
||||
});
|
||||
associations: { [`ext:${extension}`]: "builtin:markdown" },
|
||||
});
|
||||
expect(resolution.panel?.id).toBe("builtin:image");
|
||||
expect(
|
||||
resolveFilePanel({
|
||||
file: { path: "capture.png", kind: "binary", size: 512, hasBytes: false },
|
||||
candidates: builtinFilePanels,
|
||||
associations: {},
|
||||
}).panel,
|
||||
).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
it("does not impose a new preview size limit on built-in text panels", () => {
|
||||
expect(
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { filePanelExtension, type FilePanelCandidate } from "./resolve";
|
||||
|
||||
const markdownPanel = {
|
||||
id: "builtin:markdown",
|
||||
files: { extensions: ["md", "markdown"] },
|
||||
document: "text",
|
||||
maxBytes: Infinity,
|
||||
} as const satisfies FilePanelCandidate;
|
||||
|
||||
export const builtinFilePanels = [
|
||||
{
|
||||
id: "builtin:markdown",
|
||||
files: { extensions: ["md", "markdown"] },
|
||||
document: "text",
|
||||
maxBytes: Infinity,
|
||||
},
|
||||
markdownPanel,
|
||||
{
|
||||
id: "builtin:html",
|
||||
files: { extensions: ["html", "htm"] },
|
||||
@@ -15,14 +17,12 @@ export const builtinFilePanels = [
|
||||
},
|
||||
{
|
||||
id: "builtin:image",
|
||||
files: { extensions: [], kind: "image" },
|
||||
files: { extensions: ["png", "jpg", "jpeg", "gif", "webp", "svg"] },
|
||||
document: "bytes",
|
||||
maxBytes: Infinity,
|
||||
},
|
||||
] as const satisfies readonly FilePanelCandidate[];
|
||||
|
||||
export function isRenderedMarkdownFile(path: string): boolean {
|
||||
return builtinFilePanels[0].files.extensions.some(
|
||||
(extension) => extension === filePanelExtension(path),
|
||||
);
|
||||
return markdownPanel.files.extensions.some((extension) => extension === filePanelExtension(path));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ export { resolveFilePanel, type FilePanelCandidate, type FilePanelFile } from ".
|
||||
export { builtinFilePanels, isRenderedMarkdownFile } from "./catalog";
|
||||
export { FilePanelPreview } from "./builtin";
|
||||
|
||||
export type FilePanel = (typeof builtinFilePanels)[number];
|
||||
|
||||
export function resolveFilePreviewPanel(input: {
|
||||
preview: ExplorerFile | null;
|
||||
path: string;
|
||||
|
||||
@@ -8,8 +8,6 @@ export interface FilePanelFile {
|
||||
export interface FilePanelSelector {
|
||||
extensions: readonly string[];
|
||||
filenames?: readonly string[];
|
||||
/** Image classification comes from the file read, independent of its extension. */
|
||||
kind?: "image";
|
||||
}
|
||||
|
||||
export interface FilePanelCandidate {
|
||||
@@ -36,7 +34,6 @@ function filePanelBasename(path: string): string {
|
||||
}
|
||||
|
||||
function matchesSelector(file: FilePanelFile, selector: FilePanelSelector): boolean {
|
||||
if (selector.kind) return selector.kind === file.kind;
|
||||
if (selector.filenames?.includes(filePanelBasename(file.path))) return true;
|
||||
const extension = filePanelExtension(file.path);
|
||||
return selector.extensions.some((entry) => entry.toLowerCase() === extension);
|
||||
|
||||
Reference in New Issue
Block a user