mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-22 17:22:47 +00:00
fix(file-panels): Settings offers Source where the picker does, and review follow-ups
- Settings > Files offers Source on every key except those a built-in panel of another kind (Image) claims, so a plugin that alone claims a text extension shows the same choices as the picker. - Delete the unused isSourceFilePanel. - The Markdown example warns with the file name when its unmount write fails, rather than dropping the edit silently. - Document that PluginFile stat fields are read at render time and that watch or the read hooks are how a panel learns they changed.
This commit is contained in:
@@ -41,10 +41,3 @@ export function filePanelCatalog<Plugin extends FilePanelCandidate>(
|
||||
): readonly (RenderingFilePanel | Plugin | SourceFilePanel)[] {
|
||||
return [...renderingPanels, ...pluginPanels, sourceFilePanel];
|
||||
}
|
||||
|
||||
/** Source is Paseo's own editor, so the file tab renders it rather than the panel host. */
|
||||
export function isSourceFilePanel<Panel extends { id: string }>(
|
||||
panel: Panel | SourceFilePanel,
|
||||
): panel is SourceFilePanel {
|
||||
return panel.id === sourceFilePanel.id;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { isSourceFilePanel } from "./catalog";
|
||||
export { FilePanelView } from "./view";
|
||||
export type { FilePanel } from "./panels";
|
||||
export {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { buildFilePanelSettingsRows } from "./rows";
|
||||
type HostedCandidate = FilePanelCandidate & { serverId?: string };
|
||||
|
||||
const builtinMarkdown: HostedCandidate = filePanelCatalog([])[0];
|
||||
const builtinImage: HostedCandidate = filePanelCatalog([])[2];
|
||||
const source: HostedCandidate = sourceFilePanel;
|
||||
|
||||
/** What Settings reads: the built-ins and every host's plugin panels, in catalog order. */
|
||||
@@ -49,21 +50,28 @@ describe("buildFilePanelSettingsRows", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("offers Source only on keys a built-in text panel claims, and asks only between several", () => {
|
||||
const pdf: HostedCandidate = {
|
||||
id: "plugin:pdf-panel/pdf",
|
||||
it("offers Source on a key only a plugin panel claims, as the picker does for a text file", () => {
|
||||
const toml: HostedCandidate = {
|
||||
id: "plugin:toml/toml",
|
||||
builtin: false,
|
||||
files: { extensions: ["pdf"] },
|
||||
files: { extensions: ["toml"] },
|
||||
};
|
||||
expect(rows(catalog(pdf))).toEqual([]);
|
||||
expect(rows(catalog(pdf), { "ext:pdf": pdf.id })).toEqual([
|
||||
{ selectorKey: "ext:pdf", candidates: [pdf], associatedPanelId: pdf.id },
|
||||
expect(rows(catalog(toml))).toEqual([
|
||||
{ selectorKey: "ext:toml", candidates: [toml, source], associatedPanelId: null },
|
||||
]);
|
||||
const other: HostedCandidate = { ...dockerfile, id: "plugin:other/dockerfile" };
|
||||
expect(rows(catalog(dockerfile, other))[0]?.candidates).toEqual([dockerfile, other, source]);
|
||||
});
|
||||
|
||||
it("does not offer Source on a key only plugin panels claim: their files' kind is unknown", () => {
|
||||
const other: HostedCandidate = { ...dockerfile, id: "plugin:other/dockerfile" };
|
||||
expect(rows(catalog(dockerfile, other))[0]?.candidates).toEqual([dockerfile, other]);
|
||||
it("does not offer Source on a key the built-in image panel claims", () => {
|
||||
const viewer: HostedCandidate = {
|
||||
id: "plugin:viewer/png",
|
||||
builtin: false,
|
||||
files: { extensions: ["png"] },
|
||||
};
|
||||
expect(rows(catalog(viewer))).toEqual([
|
||||
{ selectorKey: "ext:png", candidates: [builtinImage, viewer], associatedPanelId: null },
|
||||
]);
|
||||
});
|
||||
|
||||
it("offers a panel installed on several hosts once, as the first host has it", () => {
|
||||
@@ -84,7 +92,7 @@ describe("buildFilePanelSettingsRows", () => {
|
||||
|
||||
it("keeps a remembered key whose panel is on no connected host", () => {
|
||||
expect(rows(catalog(), { "ext:pdf": "plugin:pdf-panel/pdf" })).toEqual([
|
||||
{ selectorKey: "ext:pdf", candidates: [], associatedPanelId: "plugin:pdf-panel/pdf" },
|
||||
{ selectorKey: "ext:pdf", candidates: [source], associatedPanelId: "plugin:pdf-panel/pdf" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -41,8 +41,9 @@ export function buildFilePanelSettingsRows<Candidate extends FilePanelCandidate>
|
||||
|
||||
/**
|
||||
* The panels offered under a key, by the resolver's matching rule. A panel claiming every file of
|
||||
* a kind (Source) is offered where a built-in panel claiming the key reads that kind: without a
|
||||
* file, that is the only knowledge of whether the key's files are text.
|
||||
* a kind (Source) is offered unless a built-in panel claiming the key reads another kind: without a
|
||||
* file, that is the only knowledge of the key's kind. A remembered panel that does not fit the file
|
||||
* when it opens is ignored there.
|
||||
*/
|
||||
function offeredCandidates<Candidate extends FilePanelCandidate>(
|
||||
candidates: readonly Candidate[],
|
||||
@@ -54,7 +55,7 @@ function offeredCandidates<Candidate extends FilePanelCandidate>(
|
||||
);
|
||||
return candidates.filter((candidate) =>
|
||||
candidate.files === "all"
|
||||
? claiming.some((claimant) => claimant.fileKind === candidate.fileKind)
|
||||
? !claiming.some((claimant) => claimant.builtin && claimant.fileKind !== candidate.fileKind)
|
||||
: claiming.includes(candidate),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/**
|
||||
* The file a file panel shows. Stable for the life of the tab's target. The stat fields are
|
||||
* refreshed when the file changes on disk.
|
||||
* The file a file panel shows. Stable for the life of the tab's target. The stat fields hold the
|
||||
* latest version Paseo knows; read them while rendering. Changing them does not re-render the
|
||||
* panel: `watch`, or a re-render from the read hooks, is how a panel learns they changed.
|
||||
*/
|
||||
export interface PluginFile {
|
||||
/** Absolute path on the host. */
|
||||
|
||||
@@ -85,7 +85,12 @@ function MarkdownEditor({ file, tab, theme }: PluginFilePanelProps) {
|
||||
unwatch();
|
||||
clearTimeout(saveTimer.current);
|
||||
// Switching panels keeps the edit. Closing the tab asks first and drops it if discarded.
|
||||
if (unsaved.current !== null) void file.writeText(unsaved.current).catch(() => {});
|
||||
// The panel is gone, so a failed write (a conflict, a lost connection) can only be reported.
|
||||
if (unsaved.current !== null) {
|
||||
void file.writeText(unsaved.current).catch((cause) => {
|
||||
console.warn(`Unsaved edits to ${file.name} were not written: ${messageOf(cause)}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [file, load]);
|
||||
|
||||
|
||||
@@ -1806,7 +1806,9 @@ props described under [Theme and layout](#theme-and-layout), plus:
|
||||
| `location` | `PluginFileLocation` | `lineStart?`, `lineEnd?`, and `revision`, which increments on every navigation. |
|
||||
|
||||
Paseo mounts your panel once it knows the file exists and its size, without reading it. The
|
||||
`PluginFile` stat fields refresh when the file changes on disk:
|
||||
`file` object is stable, and its stat fields hold the latest version Paseo knows. Read them while
|
||||
rendering; changing them does not re-render your panel. A `watch` listener, or a re-render from
|
||||
`useFileText`, `useFileBytes`, or `useFileUrl`, is how your panel learns they changed:
|
||||
|
||||
| Member | Meaning |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
|
||||
Reference in New Issue
Block a user