mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-11-28 13:10:24 +08:00
the primary purpose of this PR is to fix a showstopper bug in the CodeEditor component by setting "path" to a stable UUID. the bug was that it started as empty string, so it created a shared model between all of the codeeditor components. now each will get their own monaco model. also took this opportunity to do more more preview view refactoring, splitting up code, and more tailwind migrations.
167 lines
5.8 KiB
TypeScript
167 lines
5.8 KiB
TypeScript
// Copyright 2025, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import { CenteredDiv } from "@/app/element/quickelems";
|
|
import { RpcApi } from "@/app/store/wshclientapi";
|
|
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
|
import { BlockHeaderSuggestionControl } from "@/app/suggestion/suggestion";
|
|
import { globalStore } from "@/store/global";
|
|
import { isBlank, makeConnRoute } from "@/util/util";
|
|
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
|
import { memo, useEffect } from "react";
|
|
import { CSVView } from "./csvview";
|
|
import { DirectoryPreview } from "./preview-directory";
|
|
import { CodeEditPreview } from "./preview-edit";
|
|
import { ErrorOverlay } from "./preview-error-overlay";
|
|
import { MarkdownPreview } from "./preview-markdown";
|
|
import type { PreviewModel } from "./preview-model";
|
|
import { StreamingPreview } from "./preview-streaming";
|
|
|
|
export type SpecializedViewProps = {
|
|
model: PreviewModel;
|
|
parentRef: React.RefObject<HTMLDivElement>;
|
|
};
|
|
|
|
const SpecializedViewMap: { [view: string]: ({ model }: SpecializedViewProps) => React.JSX.Element } = {
|
|
streaming: StreamingPreview,
|
|
markdown: MarkdownPreview,
|
|
codeedit: CodeEditPreview,
|
|
csv: CSVViewPreview,
|
|
directory: DirectoryPreview,
|
|
};
|
|
|
|
function canPreview(mimeType: string): boolean {
|
|
if (mimeType == null) {
|
|
return false;
|
|
}
|
|
return mimeType.startsWith("text/markdown") || mimeType.startsWith("text/csv");
|
|
}
|
|
|
|
function CSVViewPreview({ model, parentRef }: SpecializedViewProps) {
|
|
const fileContent = useAtomValue(model.fileContent);
|
|
const fileName = useAtomValue(model.statFilePath);
|
|
return <CSVView parentRef={parentRef} readonly={true} content={fileContent} filename={fileName} />;
|
|
}
|
|
|
|
const SpecializedView = memo(({ parentRef, model }: SpecializedViewProps) => {
|
|
const specializedView = useAtomValue(model.specializedView);
|
|
const mimeType = useAtomValue(model.fileMimeType);
|
|
const setCanPreview = useSetAtom(model.canPreview);
|
|
const path = useAtomValue(model.statFilePath);
|
|
|
|
useEffect(() => {
|
|
setCanPreview(canPreview(mimeType));
|
|
}, [mimeType, setCanPreview]);
|
|
|
|
if (specializedView.errorStr != null) {
|
|
return <CenteredDiv>{specializedView.errorStr}</CenteredDiv>;
|
|
}
|
|
const SpecializedViewComponent = SpecializedViewMap[specializedView.specializedView];
|
|
if (!SpecializedViewComponent) {
|
|
return <CenteredDiv>Invalid Specialized View Component ({specializedView.specializedView})</CenteredDiv>;
|
|
}
|
|
return <SpecializedViewComponent key={path} model={model} parentRef={parentRef} />;
|
|
});
|
|
|
|
const fetchSuggestions = async (
|
|
model: PreviewModel,
|
|
query: string,
|
|
reqContext: SuggestionRequestContext
|
|
): Promise<FetchSuggestionsResponse> => {
|
|
const conn = await globalStore.get(model.connection);
|
|
let route = makeConnRoute(conn);
|
|
if (isBlank(conn) || conn.startsWith("aws:")) {
|
|
route = null;
|
|
}
|
|
if (reqContext?.dispose) {
|
|
RpcApi.DisposeSuggestionsCommand(TabRpcClient, reqContext.widgetid, { noresponse: true, route: route });
|
|
return null;
|
|
}
|
|
const fileInfo = await globalStore.get(model.statFile);
|
|
if (fileInfo == null) {
|
|
return null;
|
|
}
|
|
const sdata = {
|
|
suggestiontype: "file",
|
|
"file:cwd": fileInfo.path,
|
|
query: query,
|
|
widgetid: reqContext.widgetid,
|
|
reqnum: reqContext.reqnum,
|
|
"file:connection": conn,
|
|
};
|
|
return await RpcApi.FetchSuggestionsCommand(TabRpcClient, sdata, {
|
|
route: route,
|
|
});
|
|
};
|
|
|
|
function PreviewView({
|
|
blockRef,
|
|
contentRef,
|
|
model,
|
|
}: {
|
|
blockId: string;
|
|
blockRef: React.RefObject<HTMLDivElement>;
|
|
contentRef: React.RefObject<HTMLDivElement>;
|
|
model: PreviewModel;
|
|
}) {
|
|
const connStatus = useAtomValue(model.connStatus);
|
|
const [errorMsg, setErrorMsg] = useAtom(model.errorMsgAtom);
|
|
const connection = useAtomValue(model.connectionImmediate);
|
|
const fileInfo = useAtomValue(model.statFile);
|
|
|
|
useEffect(() => {
|
|
console.log("fileInfo or connection changed", fileInfo, connection);
|
|
if (!fileInfo) {
|
|
return;
|
|
}
|
|
setErrorMsg(null);
|
|
}, [connection, fileInfo]);
|
|
|
|
if (connStatus?.status != "connected") {
|
|
return null;
|
|
}
|
|
const handleSelect = (s: SuggestionType, queryStr: string): boolean => {
|
|
if (s == null) {
|
|
if (isBlank(queryStr)) {
|
|
globalStore.set(model.openFileModal, false);
|
|
return true;
|
|
}
|
|
model.handleOpenFile(queryStr);
|
|
return true;
|
|
}
|
|
model.handleOpenFile(s["file:path"]);
|
|
return true;
|
|
};
|
|
const handleTab = (s: SuggestionType, query: string): string => {
|
|
if (s["file:mimetype"] == "directory") {
|
|
return s["file:name"] + "/";
|
|
} else {
|
|
return s["file:name"];
|
|
}
|
|
};
|
|
const fetchSuggestionsFn = async (query, ctx) => {
|
|
return await fetchSuggestions(model, query, ctx);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div key="fullpreview" className="flex flex-col w-full overflow-hidden scrollbar-hide-until-hover">
|
|
{errorMsg && <ErrorOverlay errorMsg={errorMsg} resetOverlay={() => setErrorMsg(null)} />}
|
|
<div ref={contentRef} className="flex-grow overflow-hidden">
|
|
<SpecializedView parentRef={contentRef} model={model} />
|
|
</div>
|
|
</div>
|
|
<BlockHeaderSuggestionControl
|
|
blockRef={blockRef}
|
|
openAtom={model.openFileModal}
|
|
onClose={() => model.updateOpenFileModalAndError(false)}
|
|
onSelect={handleSelect}
|
|
onTab={handleTab}
|
|
fetchSuggestions={fetchSuggestionsFn}
|
|
placeholderText="Open File..."
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export { PreviewView };
|