33
44import { BlockNodeModel } from "@/app/block/blocktypes" ;
55import { Button } from "@/app/element/button" ;
6+ import { CopyButton } from "@/app/element/copybutton" ;
67import { CenteredDiv } from "@/app/element/quickelems" ;
78import { TypeAheadModal } from "@/app/modals/typeaheadmodal" ;
89import { ContextMenuModel } from "@/app/store/contextmenu" ;
@@ -39,9 +40,10 @@ import {
3940} from "@/util/util" ;
4041import { Monaco } from "@monaco-editor/react" ;
4142import clsx from "clsx" ;
42- import { Atom , atom , Getter , PrimitiveAtom , useAtomValue , useSetAtom , WritableAtom } from "jotai" ;
43+ import { Atom , atom , Getter , PrimitiveAtom , useAtom , useAtomValue , useSetAtom , WritableAtom } from "jotai" ;
4344import { loadable } from "jotai/utils" ;
4445import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api" ;
46+ import { OverlayScrollbarsComponent } from "overlayscrollbars-react" ;
4547import { createRef , memo , useCallback , useEffect , useMemo , useState } from "react" ;
4648import { TransformComponent , TransformWrapper , useControls } from "react-zoom-pan-pinch" ;
4749import { CSVView } from "./csvview" ;
@@ -161,6 +163,7 @@ export class PreviewModel implements ViewModel {
161163 fileContent : WritableAtom < Promise < string > , [ string ] , void > ;
162164 newFileContent : PrimitiveAtom < string | null > ;
163165 connectionError : PrimitiveAtom < string > ;
166+ errorMsgAtom : PrimitiveAtom < ErrorMsg > ;
164167
165168 openFileModal : PrimitiveAtom < boolean > ;
166169 openFileModalDelay : PrimitiveAtom < boolean > ;
@@ -195,6 +198,7 @@ export class PreviewModel implements ViewModel {
195198 this . filterOutNowsh = atom ( true ) ;
196199 this . monacoRef = createRef ( ) ;
197200 this . connectionError = atom ( "" ) ;
201+ this . errorMsgAtom = atom ( null ) as PrimitiveAtom < ErrorMsg | null > ;
198202 this . viewIcon = atom ( ( get ) => {
199203 const blockData = get ( this . blockAtom ) ;
200204 if ( blockData ?. meta ?. icon ) {
@@ -1123,6 +1127,7 @@ function PreviewView({
11231127 model : PreviewModel ;
11241128} ) {
11251129 const connStatus = useAtomValue ( model . connStatus ) ;
1130+ const [ errorMsg , setErrorMsg ] = useAtom ( model . errorMsgAtom ) ;
11261131 if ( connStatus ?. status != "connected" ) {
11271132 return null ;
11281133 }
@@ -1143,6 +1148,7 @@ function PreviewView({
11431148 < >
11441149 { /* <OpenFileModal blockId={blockId} model={model} blockRef={blockRef} /> */ }
11451150 < div key = "fullpreview" className = "full-preview scrollbar-hide-until-hover" >
1151+ { errorMsg && < ErrorOverlay errorMsg = { errorMsg } resetOverlay = { ( ) => setErrorMsg ( null ) } /> }
11461152 < div ref = { contentRef } className = "full-preview-content" >
11471153 < SpecializedView parentRef = { contentRef } model = { model } />
11481154 </ div >
@@ -1226,4 +1232,76 @@ const OpenFileModal = memo(
12261232 }
12271233) ;
12281234
1235+ const ErrorOverlay = memo ( ( { errorMsg, resetOverlay } : { errorMsg : ErrorMsg ; resetOverlay : ( ) => void } ) => {
1236+ const showDismiss = errorMsg . showDismiss ?? true ;
1237+ const buttonClassName = "outlined grey font-size-11 vertical-padding-3 horizontal-padding-7" ;
1238+
1239+ let iconClass = "fa-solid fa-circle-exclamation text-[var(--error-color)] text-base" ;
1240+ if ( errorMsg . level == "warning" ) {
1241+ iconClass = "fa-solid fa-triangle-exclamation text-[var(--warning-color)] text-base" ;
1242+ }
1243+
1244+ const handleCopyToClipboard = useCallback ( async ( ) => {
1245+ await navigator . clipboard . writeText ( errorMsg . text ) ;
1246+ } , [ errorMsg . text ] ) ;
1247+
1248+ return (
1249+ < 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" >
1250+ < div className = "flex flex-row justify-between p-2.5 pl-3 font-[var(--base-font)] text-[var(--secondary-text-color)]" >
1251+ < div
1252+ className = { clsx ( "flex flex-row items-center gap-3 grow min-w-0" , {
1253+ "items-start" : true ,
1254+ } ) }
1255+ >
1256+ < i className = { iconClass } > </ i >
1257+
1258+ < div className = "flex flex-col items-start gap-1 grow w-full" >
1259+ < div className = "max-w-full text-xs font-semibold leading-4 tracking-[0.11px] text-white" >
1260+ { errorMsg . status }
1261+ </ div >
1262+
1263+ < OverlayScrollbarsComponent
1264+ 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"
1265+ options = { { scrollbars : { autoHide : "leave" } } }
1266+ >
1267+ < CopyButton
1268+ className = "invisible group-hover:visible flex absolute top-0 right-1 rounded backdrop-blur-lg p-1 items-center justify-end gap-1"
1269+ onClick = { handleCopyToClipboard }
1270+ title = "Copy"
1271+ />
1272+ < div > { errorMsg . text } </ div >
1273+ </ OverlayScrollbarsComponent >
1274+ { errorMsg . buttons ?. map ( ( buttonDef ) => (
1275+ < Button
1276+ className = { buttonClassName }
1277+ onClick = { ( ) => {
1278+ buttonDef . onClick ( ) ;
1279+ resetOverlay ( ) ;
1280+ } }
1281+ key = { crypto . randomUUID ( ) }
1282+ >
1283+ { buttonDef . text }
1284+ </ Button >
1285+ ) ) }
1286+ </ div >
1287+
1288+ { showDismiss && (
1289+ < div className = "flex items-start" >
1290+ < Button
1291+ className = { clsx ( buttonClassName , "fa-xmark fa-solid" ) }
1292+ onClick = { ( ) => {
1293+ if ( errorMsg . closeAction ) {
1294+ errorMsg . closeAction ( ) ;
1295+ }
1296+ resetOverlay ( ) ;
1297+ } }
1298+ />
1299+ </ div >
1300+ ) }
1301+ </ div >
1302+ </ div >
1303+ </ div >
1304+ ) ;
1305+ } ) ;
1306+
12291307export { PreviewView } ;
0 commit comments