Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion frontend/app/view/preview/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { BlockNodeModel } from "@/app/block/blocktypes";
import { Button } from "@/app/element/button";
import { CopyButton } from "@/app/element/copybutton";
import { CenteredDiv } from "@/app/element/quickelems";
import { TypeAheadModal } from "@/app/modals/typeaheadmodal";
import { ContextMenuModel } from "@/app/store/contextmenu";
Expand Down Expand Up @@ -39,9 +40,10 @@ import {
} from "@/util/util";
import { Monaco } from "@monaco-editor/react";
import clsx from "clsx";
import { Atom, atom, Getter, PrimitiveAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai";
import { Atom, atom, Getter, PrimitiveAtom, useAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai";
import { loadable } from "jotai/utils";
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import { createRef, memo, useCallback, useEffect, useMemo, useState } from "react";
import { TransformComponent, TransformWrapper, useControls } from "react-zoom-pan-pinch";
import { CSVView } from "./csvview";
Expand Down Expand Up @@ -161,6 +163,7 @@ export class PreviewModel implements ViewModel {
fileContent: WritableAtom<Promise<string>, [string], void>;
newFileContent: PrimitiveAtom<string | null>;
connectionError: PrimitiveAtom<string>;
errorMsgAtom: PrimitiveAtom<ErrorMsg>;

openFileModal: PrimitiveAtom<boolean>;
openFileModalDelay: PrimitiveAtom<boolean>;
Expand Down Expand Up @@ -195,6 +198,7 @@ export class PreviewModel implements ViewModel {
this.filterOutNowsh = atom(true);
this.monacoRef = createRef();
this.connectionError = atom("");
this.errorMsgAtom = atom(null) as PrimitiveAtom<ErrorMsg | null>;
this.viewIcon = atom((get) => {
const blockData = get(this.blockAtom);
if (blockData?.meta?.icon) {
Expand Down Expand Up @@ -1123,6 +1127,7 @@ function PreviewView({
model: PreviewModel;
}) {
const connStatus = useAtomValue(model.connStatus);
const [errorMsg, setErrorMsg] = useAtom(model.errorMsgAtom);
if (connStatus?.status != "connected") {
return null;
}
Expand All @@ -1143,6 +1148,7 @@ function PreviewView({
<>
{/* <OpenFileModal blockId={blockId} model={model} blockRef={blockRef} /> */}
<div key="fullpreview" className="full-preview scrollbar-hide-until-hover">
{errorMsg && <ErrorOverlay errorMsg={errorMsg} resetOverlay={() => setErrorMsg(null)} />}
<div ref={contentRef} className="full-preview-content">
<SpecializedView parentRef={contentRef} model={model} />
</div>
Expand Down Expand Up @@ -1226,4 +1232,76 @@ const OpenFileModal = memo(
}
);

const ErrorOverlay = memo(({ errorMsg, resetOverlay }: { errorMsg: ErrorMsg; resetOverlay: () => void }) => {
const showDismiss = errorMsg.showDismiss ?? true;
const buttonClassName = "outlined grey font-size-11 vertical-padding-3 horizontal-padding-7";

let iconClass = "fa-solid fa-circle-exclamation text-[var(--error-color)] text-base";
if (errorMsg.level == "warning") {
iconClass = "fa-solid fa-triangle-exclamation text-[var(--warning-color)] text-base";
}

const handleCopyToClipboard = useCallback(async () => {
await navigator.clipboard.writeText(errorMsg.text);
}, [errorMsg.text]);

return (
<div className="absolute top-[0] left-1.5 right-1.5 z-[var(--zindex-block-mask-inner)] overflow-hidden bg-[var(--conn-status-overlay-bg-color)] backdrop-blur-[50px] rounded-md shadow-lg">
<div className="flex flex-row justify-between p-2.5 pl-3 font-[var(--base-font)] text-[var(--secondary-text-color)]">
<div
className={clsx("flex flex-row items-center gap-3 grow min-w-0", {
"items-start": true,
})}
>
<i className={iconClass}></i>

<div className="flex flex-col items-start gap-1 grow w-full">
<div className="max-w-full text-xs font-semibold leading-4 tracking-[0.11px] text-white">
{errorMsg.status}
</div>

<OverlayScrollbarsComponent
className="group text-xs font-normal leading-[15px] tracking-[0.11px] text-wrap max-h-20 rounded-lg py-1.5 pl-0 relative w-full"
options={{ scrollbars: { autoHide: "leave" } }}
>
<CopyButton
className="invisible group-hover:visible flex absolute top-0 right-1 rounded backdrop-blur-lg p-1 items-center justify-end gap-1"
onClick={handleCopyToClipboard}
title="Copy"
/>
<div>{errorMsg.text}</div>
</OverlayScrollbarsComponent>
{errorMsg.buttons?.map((buttonDef) => (
<Button
className={buttonClassName}
onClick={() => {
buttonDef.onClick();
resetOverlay();
}}
key={crypto.randomUUID()}
>
{buttonDef.text}
</Button>
))}
</div>

{showDismiss && (
<div className="flex items-start">
<Button
className={clsx(buttonClassName, "fa-xmark fa-solid")}
onClick={() => {
if (errorMsg.closeAction) {
errorMsg.closeAction();
}
resetOverlay();
}}
/>
</div>
)}
</div>
</div>
</div>
);
});

export { PreviewView };
14 changes: 14 additions & 0 deletions frontend/types/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,20 @@ declare global {
absParent: string;
relName: string;
};

type ErrorButtonDef = {
text: string;
onClick: () => void;
};

type ErrorMsg = {
status: string;
text: string;
level?: "error" | "warning";
buttons?: Array<ErrorButtonDef>;
closeAction?: () => void;
showDismiss?: boolean;
};
}

export {};
Loading