2024-05-14 08:45:41 +02:00
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
2024-08-23 09:18:49 +02:00
import { TypeAheadModal } from "@/app/modals/typeaheadmodal" ;
2024-07-09 08:13:12 +02:00
import { ContextMenuModel } from "@/app/store/contextmenu" ;
2024-09-04 03:27:41 +02:00
import { tryReinjectKey } from "@/app/store/keymodel" ;
2024-09-04 06:08:51 +02:00
import { WshServer } from "@/app/store/wshserver" ;
2024-05-14 18:37:41 +02:00
import { Markdown } from "@/element/markdown" ;
2024-09-04 03:27:41 +02:00
import { NodeModel } from "@/layout/index" ;
2024-09-13 12:36:15 +02:00
import { atoms , createBlock , getConnStatusAtom , globalStore , refocusNode } from "@/store/global" ;
import { modalsModel } from "@/store/modalmodel" ;
2024-06-12 02:42:10 +02:00
import * as services from "@/store/services" ;
2024-05-28 21:12:28 +02:00
import * as WOS from "@/store/wos" ;
2024-07-18 03:49:27 +02:00
import { getWebServerEndpoint } from "@/util/endpoints" ;
2024-08-20 07:07:35 +02:00
import * as historyutil from "@/util/historyutil" ;
import * as keyutil from "@/util/keyutil" ;
2024-05-14 21:29:41 +02:00
import * as util from "@/util/util" ;
2024-09-09 21:42:47 +02:00
import { makeConnRoute } from "@/util/util" ;
2024-09-03 01:48:10 +02:00
import { Monaco } from "@monaco-editor/react" ;
2024-06-04 03:22:26 +02:00
import clsx from "clsx" ;
2024-05-28 21:12:28 +02:00
import * as jotai from "jotai" ;
2024-07-09 00:04:48 +02:00
import { loadable } from "jotai/utils" ;
2024-09-03 01:48:10 +02:00
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api" ;
import * as React from "react" ;
import { createRef , useCallback , useState } from "react" ;
2024-08-01 21:53:49 +02:00
import { CenteredDiv } from "../../element/quickelems" ;
import { CodeEditor } from "../codeeditor/codeeditor" ;
2024-08-01 22:06:18 +02:00
import { CSVView } from "./csvview" ;
2024-05-29 09:00:36 +02:00
import { DirectoryPreview } from "./directorypreview" ;
2024-07-18 08:41:33 +02:00
import "./preview.less" ;
2024-05-14 08:45:41 +02:00
2024-05-17 07:48:23 +02:00
const MaxFileSize = 1024 * 1024 * 10 ; // 10MB
2024-07-09 00:04:48 +02:00
const MaxCSVSize = 1024 * 1024 * 1 ; // 1MB
2024-09-03 01:48:10 +02:00
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 ,
} ;
2024-09-10 22:23:02 +02:00
const textApplicationMimetypes = [
"application/sql" ,
"application/pem-certificate-chain" ,
"application/x-php" ,
"application/x-httpd-php" ,
"application/liquid" ,
"application/graphql" ,
"application/javascript" ,
"application/typescript" ,
"application/x-javascript" ,
"application/x-typescript" ,
"application/dart" ,
"application/vnd.dart" ,
"application/x-ruby" ,
"application/sql" ,
"application/wasm" ,
"application/x-latex" ,
"application/x-sh" ,
"application/x-python" ,
] ;
2024-07-18 01:11:16 +02:00
function isTextFile ( mimeType : string ) : boolean {
2024-09-03 01:48:10 +02:00
if ( mimeType == null ) {
return false ;
}
2024-07-18 01:11:16 +02:00
return (
mimeType . startsWith ( "text/" ) ||
2024-09-10 22:23:02 +02:00
textApplicationMimetypes . includes ( mimeType ) ||
2024-07-18 01:11:16 +02:00
( mimeType . startsWith ( "application/" ) &&
( mimeType . includes ( "json" ) || mimeType . includes ( "yaml" ) || mimeType . includes ( "toml" ) ) ) ||
2024-09-10 22:23:02 +02:00
mimeType . includes ( "xml" )
2024-07-18 01:11:16 +02:00
) ;
}
2024-08-31 20:57:45 +02:00
function canPreview ( mimeType : string ) : boolean {
2024-09-03 01:48:10 +02:00
if ( mimeType == null ) {
return false ;
}
2024-08-31 20:57:45 +02:00
return mimeType . startsWith ( "text/markdown" ) || mimeType . startsWith ( "text/csv" ) ;
}
2024-09-03 01:48:10 +02:00
function isStreamingType ( mimeType : string ) : boolean {
if ( mimeType == null ) {
return false ;
}
return (
mimeType . startsWith ( "application/pdf" ) ||
mimeType . startsWith ( "video/" ) ||
mimeType . startsWith ( "audio/" ) ||
mimeType . startsWith ( "image/" )
) ;
}
2024-09-13 12:36:15 +02:00
const previewTipText = `
# # # Preview
Preview is the generic type of block used for viewing files . This can take many different forms based on the type of file being viewed .
You can use \ ` wsh view [path] \` from any Wave terminal window to open a preview block with the contents of the specified path (e.g. \` wsh view . \` or \` wsh view ~/myimage.jpg \` ).
# # # # Directory
When looking at a directory , preview will show a file viewer much like MacOS ' *Finder* application or Windows' * File Explorer * application . This variant is slightly more geared toward software development with the focus on seeing what is shown by the \ ` ls -alh \` command.
# # # # # View a New File
The simplest way to view a new file is to double click its row in the file viewer . Alternatively , while the block is focused , you can use the & uarr ; and & darr ; arrow keys to select a row and press enter to preview the associated file .
# # # # # View the Parent Directory
In the directory view , this is as simple as opening the \ ` .. \` file as if it were a regular file. This can be done with the method above. You can also use the keyboard shortcut \` Cmd + ArrowUp \` .
# # # # # Navigate Back and Forward
When looking at a file , you can navigate back by clicking the back button in the block header or the keyboard shortcut \ ` Cmd + ArrowLeft \` . You can always navigate back and forward using \` Cmd + ArrowLeft \` and \` Cmd + ArrowRight \` .
# # # # # Filter the List of Files
While the block is focused , you can filter by filename by typing a substring of the filename you ' re working for . To clear the filter , you can click the & # x2715 ; on the filter dropdown or press esc .
# # # # # Sort by a File Column
To sort a file by a specific column , click on the header for that column . If you click the header again , it will reverse the sort order .
# # # # # Hide and Show Hidden Files
At the right of the block header , there is an & # 128065 ; & # 65039 ; button . Clicking this button hides and shows hidden files .
# # # # # Refresh the Directory
At the right of the block header , there is a refresh button . Clicking this button refreshes the directory contents .
# # # # # Navigate to Common Directories
At the left of the block header , there is a file icon . Clicking and holding on this icon opens a menu where you can select a common folder to navigate to . The available options are * Home * , * Desktop * , * Downloads * , and * Root * .
# # # # # Open a New Terminal in the Current Directory
If you right click the header of the block ( alternatively , click the gear icon ) , one of the menu items listed is * * Open Terminal in New Block * * . This will create a new terminal block at your current directory .
# # # # # Open a New Terminal in a Child Directory
If you want to open a terminal for a child directory instead , you can right click on that file ' s row to get the * * Open Terminal in New Block * * option . Clicking this will open a terminal at that directory . Note that this option is only available for children that are directories .
# # # # # Open a New Preview for a Child
To open a new Preview Block for a Child , you can right click on that file ' s row and select the * * Open Preview in New Block * * option .
# # # # Markdown
Opening a markdown file will bring up a view of the rendered markdown . These files cannot be edited in the preview at this time .
# # # # Images / Media
Opening a picture will bring up the image of that picture . Opening a video will bring up a player that lets you watch the video .
` ;
2024-09-03 01:48:10 +02:00
2024-07-09 00:04:48 +02:00
export class PreviewModel implements ViewModel {
2024-08-23 01:25:53 +02:00
viewType : string ;
2024-07-09 00:04:48 +02:00
blockId : string ;
2024-09-03 01:48:10 +02:00
nodeModel : NodeModel ;
2024-07-09 00:04:48 +02:00
blockAtom : jotai.Atom < Block > ;
2024-09-09 21:35:53 +02:00
viewIcon : jotai.Atom < string | IconButtonDecl > ;
2024-07-09 00:04:48 +02:00
viewName : jotai.Atom < string > ;
2024-07-18 08:41:33 +02:00
viewText : jotai.Atom < HeaderElem [ ] > ;
2024-09-09 21:35:53 +02:00
preIconButton : jotai.Atom < IconButtonDecl > ;
endIconButtons : jotai.Atom < IconButtonDecl [ ] > ;
2024-08-29 08:47:45 +02:00
previewTextRef : React.RefObject < HTMLDivElement > ;
2024-08-30 22:56:53 +02:00
editMode : jotai.Atom < boolean > ;
2024-08-31 20:57:45 +02:00
canPreview : jotai.PrimitiveAtom < boolean > ;
2024-09-03 01:48:10 +02:00
specializedView : jotai.Atom < Promise < { specializedView ? : string ; errorStr ? : string } > > ;
loadableSpecializedView : jotai.Atom < Loadable < { specializedView ? : string ; errorStr ? : string } > > ;
2024-09-02 21:34:49 +02:00
manageConnection : jotai.Atom < boolean > ;
2024-09-05 09:21:08 +02:00
connStatus : jotai.Atom < ConnStatus > ;
2024-07-09 00:04:48 +02:00
2024-09-03 01:48:10 +02:00
metaFilePath : jotai.Atom < string > ;
statFilePath : jotai.Atom < Promise < string > > ;
normFilePath : jotai.Atom < Promise < string > > ;
loadableStatFilePath : jotai.Atom < Loadable < string > > ;
2024-09-04 06:18:52 +02:00
loadableFileInfo : jotai.Atom < Loadable < FileInfo > > ;
2024-08-17 03:45:45 +02:00
connection : jotai.Atom < string > ;
2024-07-09 00:04:48 +02:00
statFile : jotai.Atom < Promise < FileInfo > > ;
fullFile : jotai.Atom < Promise < FullFile > > ;
fileMimeType : jotai.Atom < Promise < string > > ;
fileMimeTypeLoadable : jotai.Atom < Loadable < string > > ;
fileContent : jotai.Atom < Promise < string > > ;
2024-09-10 23:27:11 +02:00
newFileContent : jotai.PrimitiveAtom < string | null > ;
2024-09-03 01:48:10 +02:00
2024-08-31 20:57:45 +02:00
openFileModal : jotai.PrimitiveAtom < boolean > ;
2024-09-03 01:48:10 +02:00
openFileError : jotai.PrimitiveAtom < string > ;
openFileModalGiveFocusRef : React.MutableRefObject < ( ) = > boolean > ;
2024-09-05 06:15:39 +02:00
markdownShowToc : jotai.PrimitiveAtom < boolean > ;
2024-09-03 01:48:10 +02:00
monacoRef : React.MutableRefObject < MonacoTypes.editor.IStandaloneCodeEditor > ;
2024-07-09 00:04:48 +02:00
2024-07-09 01:36:30 +02:00
showHiddenFiles : jotai.PrimitiveAtom < boolean > ;
2024-07-09 08:13:12 +02:00
refreshVersion : jotai.PrimitiveAtom < number > ;
2024-07-09 01:36:30 +02:00
refreshCallback : ( ) = > void ;
2024-08-30 02:00:24 +02:00
directoryKeyDownHandler : ( waveEvent : WaveKeyboardEvent ) = > boolean ;
2024-09-03 01:48:10 +02:00
codeEditKeyDownHandler : ( waveEvent : WaveKeyboardEvent ) = > boolean ;
2024-07-09 01:36:30 +02:00
2024-07-09 00:04:48 +02:00
setPreviewFileName ( fileName : string ) {
services . ObjectService . UpdateObjectMeta ( ` block: ${ this . blockId } ` , { file : fileName } ) ;
}
2024-09-03 01:48:10 +02:00
constructor ( blockId : string , nodeModel : NodeModel ) {
2024-08-23 01:25:53 +02:00
this . viewType = "preview" ;
2024-07-09 00:04:48 +02:00
this . blockId = blockId ;
2024-09-03 01:48:10 +02:00
this . nodeModel = nodeModel ;
2024-07-09 01:36:30 +02:00
this . showHiddenFiles = jotai . atom ( true ) ;
2024-07-09 08:13:12 +02:00
this . refreshVersion = jotai . atom ( 0 ) ;
2024-08-29 08:47:45 +02:00
this . previewTextRef = createRef ( ) ;
2024-08-31 20:57:45 +02:00
this . openFileModal = jotai . atom ( false ) ;
2024-09-03 01:48:10 +02:00
this . openFileError = jotai . atom ( null ) as jotai . PrimitiveAtom < string > ;
this . openFileModalGiveFocusRef = createRef ( ) ;
2024-09-02 21:34:49 +02:00
this . manageConnection = jotai . atom ( true ) ;
2024-07-09 00:04:48 +02:00
this . blockAtom = WOS . getWaveObjectAtom < Block > ( ` block: ${ blockId } ` ) ;
2024-09-05 06:15:39 +02:00
this . markdownShowToc = jotai . atom ( false ) ;
2024-09-03 01:48:10 +02:00
this . monacoRef = createRef ( ) ;
2024-07-09 00:04:48 +02:00
this . viewIcon = jotai . atom ( ( get ) = > {
2024-09-04 03:27:41 +02:00
const blockData = get ( this . blockAtom ) ;
2024-07-09 00:04:48 +02:00
if ( blockData ? . meta ? . icon ) {
return blockData . meta . icon ;
}
2024-09-05 09:21:08 +02:00
const connStatus = get ( this . connStatus ) ;
if ( connStatus ? . status != "connected" ) {
return null ;
}
const fileName = get ( this . metaFilePath ) ;
const mimeTypeLoadable = get ( this . fileMimeTypeLoadable ) ;
2024-09-04 03:27:41 +02:00
const mimeType = util . jotaiLoadableValue ( mimeTypeLoadable , "" ) ;
2024-07-09 08:13:12 +02:00
if ( mimeType == "directory" ) {
return {
elemtype : "iconbutton" ,
icon : "folder-open" ,
longClick : ( e : React.MouseEvent < any > ) = > {
2024-09-04 03:27:41 +02:00
const menuItems : ContextMenuItem [ ] = [ ] ;
2024-08-20 07:07:35 +02:00
menuItems . push ( {
label : "Go to Home" ,
click : ( ) = > this . goHistory ( "~" ) ,
} ) ;
2024-07-09 08:13:12 +02:00
menuItems . push ( {
label : "Go to Desktop" ,
2024-08-20 07:07:35 +02:00
click : ( ) = > this . goHistory ( "~/Desktop" ) ,
2024-07-09 08:13:12 +02:00
} ) ;
menuItems . push ( {
label : "Go to Downloads" ,
2024-08-20 07:07:35 +02:00
click : ( ) = > this . goHistory ( "~/Downloads" ) ,
2024-07-09 08:13:12 +02:00
} ) ;
menuItems . push ( {
label : "Go to Documents" ,
2024-08-20 07:07:35 +02:00
click : ( ) = > this . goHistory ( "~/Documents" ) ,
} ) ;
menuItems . push ( {
label : "Go to Root" ,
click : ( ) = > this . goHistory ( "/" ) ,
2024-07-09 08:13:12 +02:00
} ) ;
ContextMenuModel . showContextMenu ( menuItems , e ) ;
} ,
} ;
}
2024-09-04 03:27:41 +02:00
return iconForFile ( mimeType ) ;
2024-07-09 00:04:48 +02:00
} ) ;
2024-08-30 22:56:53 +02:00
this . editMode = jotai . atom ( ( get ) = > {
const blockData = get ( this . blockAtom ) ;
return blockData ? . meta ? . edit ? ? false ;
} ) ;
2024-07-09 00:04:48 +02:00
this . viewName = jotai . atom ( "Preview" ) ;
this . viewText = jotai . atom ( ( get ) = > {
2024-09-05 09:21:08 +02:00
let headerPath = get ( this . metaFilePath ) ;
const connStatus = get ( this . connStatus ) ;
if ( connStatus ? . status != "connected" ) {
return [
{
elemtype : "text" ,
text : headerPath ,
className : "preview-filename" ,
} ,
] ;
}
2024-09-03 01:48:10 +02:00
const loadableSV = get ( this . loadableSpecializedView ) ;
const isCeView = loadableSV . state == "hasData" && loadableSV . data . specializedView == "codeedit" ;
2024-09-04 06:18:52 +02:00
const loadableFileInfo = get ( this . loadableFileInfo ) ;
if ( loadableFileInfo . state == "hasData" ) {
headerPath = loadableFileInfo . data ? . path ;
if ( headerPath == "~" ) {
headerPath = ` ~ ( ${ loadableFileInfo . data ? . dir } ) ` ;
}
2024-09-03 01:48:10 +02:00
}
2024-09-05 06:15:39 +02:00
2024-08-31 20:57:45 +02:00
const viewTextChildren : HeaderElem [ ] = [
{
elemtype : "text" ,
2024-09-03 01:48:10 +02:00
text : headerPath ,
2024-08-31 20:57:45 +02:00
ref : this.previewTextRef ,
className : "preview-filename" ,
2024-09-03 01:48:10 +02:00
onClick : ( ) = > this . updateOpenFileModalAndError ( true ) ,
2024-08-31 20:57:45 +02:00
} ,
] ;
let saveClassName = "secondary" ;
2024-09-10 23:27:11 +02:00
if ( get ( this . newFileContent ) !== null ) {
2024-08-31 20:57:45 +02:00
saveClassName = "primary" ;
}
2024-09-03 01:48:10 +02:00
if ( isCeView ) {
2024-08-31 20:57:45 +02:00
viewTextChildren . push ( {
elemtype : "textbutton" ,
text : "Save" ,
className : clsx (
` ${ saveClassName } warning border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500 `
) ,
onClick : this.handleFileSave.bind ( this ) ,
} ) ;
if ( get ( this . canPreview ) ) {
2024-07-18 08:41:33 +02:00
viewTextChildren . push ( {
elemtype : "textbutton" ,
2024-08-31 20:57:45 +02:00
text : "Preview" ,
className :
"secondary border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500" ,
2024-09-03 01:48:10 +02:00
onClick : ( ) = > this . setEditMode ( false ) ,
2024-07-18 08:41:33 +02:00
} ) ;
}
2024-08-31 20:57:45 +02:00
} else if ( get ( this . canPreview ) ) {
viewTextChildren . push ( {
elemtype : "textbutton" ,
text : "Edit" ,
className :
"secondary border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500" ,
2024-09-03 01:48:10 +02:00
onClick : ( ) = > this . setEditMode ( true ) ,
2024-08-31 20:57:45 +02:00
} ) ;
2024-07-18 08:41:33 +02:00
}
2024-08-31 20:57:45 +02:00
return [
{
elemtype : "div" ,
children : viewTextChildren ,
} ,
] as HeaderElem [ ] ;
2024-07-09 00:04:48 +02:00
} ) ;
2024-07-09 01:36:30 +02:00
this . preIconButton = jotai . atom ( ( get ) = > {
2024-09-05 09:21:08 +02:00
const connStatus = get ( this . connStatus ) ;
if ( connStatus ? . status != "connected" ) {
return null ;
}
2024-07-09 01:36:30 +02:00
const mimeType = util . jotaiLoadableValue ( get ( this . fileMimeTypeLoadable ) , "" ) ;
if ( mimeType == "directory" ) {
return null ;
}
return {
2024-07-09 08:13:12 +02:00
elemtype : "iconbutton" ,
2024-07-09 01:36:30 +02:00
icon : "chevron-left" ,
2024-08-20 07:07:35 +02:00
click : this.goParentDirectory.bind ( this ) ,
2024-07-09 01:36:30 +02:00
} ;
} ) ;
this . endIconButtons = jotai . atom ( ( get ) = > {
2024-09-05 09:21:08 +02:00
const connStatus = get ( this . connStatus ) ;
if ( connStatus ? . status != "connected" ) {
return null ;
}
2024-07-09 00:04:48 +02:00
const mimeType = util . jotaiLoadableValue ( get ( this . fileMimeTypeLoadable ) , "" ) ;
2024-09-05 06:15:39 +02:00
const loadableSV = get ( this . loadableSpecializedView ) ;
const isCeView = loadableSV . state == "hasData" && loadableSV . data . specializedView == "codeedit" ;
2024-07-09 00:04:48 +02:00
if ( mimeType == "directory" ) {
2024-09-04 03:27:41 +02:00
const showHiddenFiles = get ( this . showHiddenFiles ) ;
2024-09-13 12:36:15 +02:00
const settings = get ( atoms . settingsAtom ) ;
let tipIcon : IconButtonDecl [ ] ;
if ( settings [ "tips:show" ] ) {
tipIcon = [
{
elemtype : "iconbutton" ,
2024-09-13 19:24:16 +02:00
icon : "lightbulb" ,
2024-09-13 12:36:15 +02:00
iconColor : "var(--warning-color)" ,
2024-09-13 19:24:16 +02:00
className : "bulb" ,
2024-09-13 12:36:15 +02:00
click : ( ) = > {
const tips : UserInputRequest = {
requestid : "" ,
querytext : previewTipText ,
responsetype : "confirm" ,
title : "Preview Tips" ,
markdown : true ,
timeoutms : 0 ,
checkboxmsg : "" ,
publictext : true ,
} ;
modalsModel . pushModal ( "TipsModal" , tips ) ;
} ,
} ,
] ;
} else {
tipIcon = [ ] ;
}
2024-07-09 01:36:30 +02:00
return [
2024-09-13 12:36:15 +02:00
. . . tipIcon ,
2024-07-09 01:36:30 +02:00
{
2024-07-09 08:13:12 +02:00
elemtype : "iconbutton" ,
2024-07-09 01:36:30 +02:00
icon : showHiddenFiles ? "eye" : "eye-slash" ,
click : ( ) = > {
globalStore . set ( this . showHiddenFiles , ( prev ) = > ! prev ) ;
} ,
} ,
{
2024-07-09 08:13:12 +02:00
elemtype : "iconbutton" ,
2024-07-09 01:36:30 +02:00
icon : "arrows-rotate" ,
click : ( ) = > this . refreshCallback ? . ( ) ,
} ,
2024-09-09 21:35:53 +02:00
] as IconButtonDecl [ ] ;
2024-09-10 01:44:26 +02:00
} else if ( ! isCeView && mimeType ? . startsWith ( "text/markdown" ) ) {
2024-09-05 06:15:39 +02:00
return [
{
elemtype : "iconbutton" ,
icon : "book" ,
title : "Table of Contents" ,
click : ( ) = > this . markdownShowTocToggle ( ) ,
} ,
2024-09-09 21:35:53 +02:00
] as IconButtonDecl [ ] ;
2024-07-09 00:04:48 +02:00
}
2024-07-09 01:36:30 +02:00
return null ;
2024-07-09 00:04:48 +02:00
} ) ;
2024-09-03 01:48:10 +02:00
this . metaFilePath = jotai . atom < string > ( ( get ) = > {
2024-08-20 07:07:35 +02:00
const file = get ( this . blockAtom ) ? . meta ? . file ;
if ( util . isBlank ( file ) ) {
return "~" ;
2024-07-09 00:04:48 +02:00
}
2024-08-20 07:07:35 +02:00
return file ;
} ) ;
2024-09-03 01:48:10 +02:00
this . statFilePath = jotai . atom < Promise < string > > ( async ( get ) = > {
const fileInfo = await get ( this . statFile ) ;
return fileInfo ? . path ;
} ) ;
this . normFilePath = jotai . atom < Promise < string > > ( async ( get ) = > {
const fileInfo = await get ( this . statFile ) ;
if ( fileInfo == null ) {
return null ;
}
if ( fileInfo . isdir ) {
return fileInfo . dir + "/" ;
}
return fileInfo . dir + "/" + fileInfo . name ;
} ) ;
this . loadableStatFilePath = loadable ( this . statFilePath ) ;
2024-08-17 03:45:45 +02:00
this . connection = jotai . atom < string > ( ( get ) = > {
return get ( this . blockAtom ) ? . meta ? . connection ;
} ) ;
2024-07-09 00:04:48 +02:00
this . statFile = jotai . atom < Promise < FileInfo > > ( async ( get ) = > {
2024-09-03 01:48:10 +02:00
const fileName = get ( this . metaFilePath ) ;
2024-07-09 00:04:48 +02:00
if ( fileName == null ) {
return null ;
}
2024-08-17 03:45:45 +02:00
const conn = get ( this . connection ) ? ? "" ;
const statFile = await services . FileService . StatFile ( conn , fileName ) ;
2024-07-09 00:04:48 +02:00
return statFile ;
} ) ;
this . fileMimeType = jotai . atom < Promise < string > > ( async ( get ) = > {
const fileInfo = await get ( this . statFile ) ;
return fileInfo ? . mimetype ;
} ) ;
this . fileMimeTypeLoadable = loadable ( this . fileMimeType ) ;
2024-09-10 23:27:11 +02:00
this . newFileContent = jotai . atom ( null ) as jotai . PrimitiveAtom < string | null > ;
2024-08-20 07:07:35 +02:00
this . goParentDirectory = this . goParentDirectory . bind ( this ) ;
2024-09-03 01:48:10 +02:00
const fullFileAtom = jotai . atom < Promise < FullFile > > ( async ( get ) = > {
const fileName = get ( this . metaFilePath ) ;
if ( fileName == null ) {
return null ;
}
const conn = get ( this . connection ) ? ? "" ;
const file = await services . FileService . ReadFile ( conn , fileName ) ;
return file ;
} ) ;
const fileContentAtom = jotai . atom < Promise < string > > ( async ( get ) = > {
const fullFile = await get ( fullFileAtom ) ;
return util . base64ToString ( fullFile ? . data64 ) ;
} ) ;
this . fullFile = fullFileAtom ;
this . fileContent = fileContentAtom ;
this . specializedView = jotai . atom < Promise < { specializedView ? : string ; errorStr ? : string } > > ( async ( get ) = > {
return this . getSpecializedView ( get ) ;
} ) ;
this . loadableSpecializedView = loadable ( this . specializedView ) ;
this . canPreview = jotai . atom ( false ) ;
2024-09-04 06:18:52 +02:00
this . loadableFileInfo = loadable ( this . statFile ) ;
2024-09-05 09:21:08 +02:00
this . connStatus = jotai . atom ( ( get ) = > {
const blockData = get ( this . blockAtom ) ;
const connName = blockData ? . meta ? . connection ;
const connAtom = getConnStatusAtom ( connName ) ;
return get ( connAtom ) ;
} ) ;
2024-08-20 07:07:35 +02:00
}
2024-09-05 06:15:39 +02:00
markdownShowTocToggle() {
globalStore . set ( this . markdownShowToc , ! globalStore . get ( this . markdownShowToc ) ) ;
}
2024-09-03 01:48:10 +02:00
async getSpecializedView ( getFn : jotai.Getter ) : Promise < { specializedView? : string ; errorStr? : string } > {
const mimeType = await getFn ( this . fileMimeType ) ;
const fileInfo = await getFn ( this . statFile ) ;
const fileName = await getFn ( this . statFilePath ) ;
const editMode = getFn ( this . editMode ) ;
if ( fileInfo ? . notfound ) {
return { errorStr : ` File Not Found: ${ fileInfo . path } ` } ;
}
if ( mimeType == null ) {
return { errorStr : ` Unable to determine mimetype for: ${ fileInfo . path } ` } ;
}
if ( isStreamingType ( mimeType ) ) {
return { specializedView : "streaming" } ;
}
if ( ! fileInfo ) {
2024-09-04 03:27:41 +02:00
const fileNameStr = fileName ? " " + JSON . stringify ( fileName ) : "" ;
2024-09-03 01:48:10 +02:00
return { errorStr : "File Not Found" + fileNameStr } ;
}
if ( fileInfo . size > MaxFileSize ) {
return { errorStr : "File Too Large to Preiview (10 MB Max)" } ;
}
if ( mimeType == "text/csv" && fileInfo . size > MaxCSVSize ) {
return { errorStr : "CSV File Too Large to Preiview (1 MB Max)" } ;
}
if ( mimeType == "directory" ) {
return { specializedView : "directory" } ;
}
if ( mimeType == "text/csv" ) {
if ( editMode ) {
return { specializedView : "codeedit" } ;
2024-08-29 08:47:45 +02:00
}
2024-09-03 01:48:10 +02:00
return { specializedView : "csv" } ;
2024-08-29 08:47:45 +02:00
}
2024-09-03 01:48:10 +02:00
if ( mimeType . startsWith ( "text/markdown" ) ) {
if ( editMode ) {
return { specializedView : "codeedit" } ;
2024-08-29 08:47:45 +02:00
}
2024-09-03 01:48:10 +02:00
return { specializedView : "markdown" } ;
}
if ( isTextFile ( mimeType ) ) {
return { specializedView : "codeedit" } ;
}
return { errorStr : ` Preview ( ${ mimeType } ) ` } ;
2024-08-29 08:47:45 +02:00
}
2024-09-03 01:48:10 +02:00
updateOpenFileModalAndError ( isOpen , errorMsg = null ) {
globalStore . set ( this . openFileModal , isOpen ) ;
globalStore . set ( this . openFileError , errorMsg ) ;
2024-08-29 08:47:45 +02:00
}
2024-09-03 01:48:10 +02:00
async goHistory ( newPath : string ) {
let fileName = globalStore . get ( this . metaFilePath ) ;
2024-08-20 07:07:35 +02:00
if ( fileName == null ) {
2024-09-03 01:48:10 +02:00
fileName = "" ;
2024-08-29 08:47:45 +02:00
}
const blockMeta = globalStore . get ( this . blockAtom ) ? . meta ;
2024-08-20 07:07:35 +02:00
const updateMeta = historyutil . goHistory ( "file" , fileName , newPath , blockMeta ) ;
if ( updateMeta == null ) {
return ;
}
const blockOref = WOS . makeORef ( "block" , this . blockId ) ;
services . ObjectService . UpdateObjectMeta ( blockOref , updateMeta ) ;
2024-07-09 00:04:48 +02:00
}
2024-09-03 01:48:10 +02:00
async goParentDirectory() {
2024-08-20 07:07:35 +02:00
const blockMeta = globalStore . get ( this . blockAtom ) ? . meta ;
2024-09-03 01:48:10 +02:00
const metaPath = globalStore . get ( this . metaFilePath ) ;
const fileInfo = await globalStore . get ( this . statFile ) ;
if ( fileInfo == null ) {
2024-07-09 00:04:48 +02:00
return ;
}
2024-09-03 01:48:10 +02:00
let newPath : string = null ;
if ( ! fileInfo . isdir ) {
newPath = fileInfo . dir ;
} else {
const lastSlash = fileInfo . dir . lastIndexOf ( "/" ) ;
newPath = fileInfo . dir . slice ( 0 , lastSlash ) ;
if ( newPath . indexOf ( "/" ) == - 1 ) {
return ;
}
}
const updateMeta = historyutil . goHistory ( "file" , metaPath , newPath , blockMeta ) ;
2024-08-20 07:07:35 +02:00
if ( updateMeta == null ) {
return ;
}
2024-08-31 20:57:45 +02:00
updateMeta . edit = false ;
2024-08-20 07:07:35 +02:00
const blockOref = WOS . makeORef ( "block" , this . blockId ) ;
services . ObjectService . UpdateObjectMeta ( blockOref , updateMeta ) ;
}
goHistoryBack() {
const blockMeta = globalStore . get ( this . blockAtom ) ? . meta ;
2024-09-03 01:48:10 +02:00
const curPath = globalStore . get ( this . metaFilePath ) ;
2024-08-20 07:07:35 +02:00
const updateMeta = historyutil . goHistoryBack ( "file" , curPath , blockMeta , true ) ;
if ( updateMeta == null ) {
return ;
}
2024-08-31 20:57:45 +02:00
updateMeta . edit = false ;
2024-08-20 07:07:35 +02:00
const blockOref = WOS . makeORef ( "block" , this . blockId ) ;
services . ObjectService . UpdateObjectMeta ( blockOref , updateMeta ) ;
}
goHistoryForward() {
const blockMeta = globalStore . get ( this . blockAtom ) ? . meta ;
2024-09-03 01:48:10 +02:00
const curPath = globalStore . get ( this . metaFilePath ) ;
2024-08-20 07:07:35 +02:00
const updateMeta = historyutil . goHistoryForward ( "file" , curPath , blockMeta ) ;
if ( updateMeta == null ) {
return ;
}
2024-08-31 20:57:45 +02:00
updateMeta . edit = false ;
2024-08-20 07:07:35 +02:00
const blockOref = WOS . makeORef ( "block" , this . blockId ) ;
services . ObjectService . UpdateObjectMeta ( blockOref , updateMeta ) ;
2024-07-09 00:04:48 +02:00
}
2024-07-09 00:32:10 +02:00
2024-09-03 01:48:10 +02:00
setEditMode ( edit : boolean ) {
2024-08-31 20:57:45 +02:00
const blockMeta = globalStore . get ( this . blockAtom ) ? . meta ;
const blockOref = WOS . makeORef ( "block" , this . blockId ) ;
services . ObjectService . UpdateObjectMeta ( blockOref , { . . . blockMeta , edit } ) ;
2024-07-18 08:41:33 +02:00
}
async handleFileSave() {
2024-09-03 01:48:10 +02:00
const filePath = await globalStore . get ( this . statFilePath ) ;
if ( filePath == null ) {
return ;
}
2024-09-10 23:27:11 +02:00
const newFileContent = globalStore . get ( this . newFileContent ) ;
2024-09-03 01:48:10 +02:00
if ( newFileContent == null ) {
console . log ( "not saving file, newFileContent is null" ) ;
return ;
}
2024-08-19 20:02:40 +02:00
const conn = globalStore . get ( this . connection ) ? ? "" ;
2024-07-18 08:41:33 +02:00
try {
2024-09-03 01:48:10 +02:00
services . FileService . SaveFile ( conn , filePath , util . stringToBase64 ( newFileContent ) ) ;
2024-09-10 23:27:11 +02:00
globalStore . set ( this . newFileContent , null ) ;
2024-09-03 01:48:10 +02:00
console . log ( "saved file" , filePath ) ;
2024-07-18 08:41:33 +02:00
} catch ( error ) {
console . error ( "Error saving file:" , error ) ;
}
}
2024-09-03 01:48:10 +02:00
async handleFileRevert() {
2024-09-10 23:27:11 +02:00
const fileContent = await globalStore . get ( this . fileContent ) ;
this . monacoRef . current ? . setValue ( fileContent ) ;
globalStore . set ( this . newFileContent , null ) ;
2024-09-03 01:48:10 +02:00
}
async handleOpenFile ( filePath : string ) {
const fileInfo = await globalStore . get ( this . statFile ) ;
if ( fileInfo == null ) {
this . updateOpenFileModalAndError ( false ) ;
return true ;
}
2024-09-04 06:08:51 +02:00
const conn = globalStore . get ( this . connection ) ;
try {
const newFileInfo = await WshServer . RemoteFileJoinCommand ( [ fileInfo . dir , filePath ] , {
route : makeConnRoute ( conn ) ,
} ) ;
this . updateOpenFileModalAndError ( false ) ;
this . goHistory ( newFileInfo . path ) ;
refocusNode ( this . blockId ) ;
} catch ( e ) {
globalStore . set ( this . openFileError , e . message ) ;
console . error ( "Error opening file" , fileInfo . dir , filePath , e ) ;
}
2024-09-03 01:48:10 +02:00
}
isSpecializedView ( sv : string ) : boolean {
const loadableSV = globalStore . get ( this . loadableSpecializedView ) ;
return loadableSV . state == "hasData" && loadableSV . data . specializedView == sv ;
}
2024-07-09 00:32:10 +02:00
getSettingsMenuItems ( ) : ContextMenuItem [ ] {
const menuItems : ContextMenuItem [ ] = [ ] ;
menuItems . push ( {
label : "Copy Full Path" ,
2024-09-03 01:48:10 +02:00
click : async ( ) = > {
const filePath = await globalStore . get ( this . normFilePath ) ;
if ( filePath == null ) {
2024-07-09 00:32:10 +02:00
return ;
}
2024-09-03 01:48:10 +02:00
navigator . clipboard . writeText ( filePath ) ;
2024-07-09 00:32:10 +02:00
} ,
} ) ;
menuItems . push ( {
label : "Copy File Name" ,
2024-09-03 01:48:10 +02:00
click : async ( ) = > {
const fileInfo = await globalStore . get ( this . statFile ) ;
if ( fileInfo == null || fileInfo . name == null ) {
2024-07-09 00:32:10 +02:00
return ;
}
2024-09-03 01:48:10 +02:00
navigator . clipboard . writeText ( fileInfo . name ) ;
2024-07-09 00:32:10 +02:00
} ,
} ) ;
2024-07-26 09:48:12 +02:00
const mimeType = util . jotaiLoadableValue ( globalStore . get ( this . fileMimeTypeLoadable ) , "" ) ;
if ( mimeType == "directory" ) {
menuItems . push ( {
label : "Open Terminal in New Block" ,
click : async ( ) = > {
2024-09-03 01:48:10 +02:00
const fileInfo = await globalStore . get ( this . statFile ) ;
2024-07-26 09:48:12 +02:00
const termBlockDef : BlockDef = {
meta : {
2024-07-30 21:33:28 +02:00
view : "term" ,
controller : "shell" ,
2024-09-03 01:48:10 +02:00
"cmd:cwd" : fileInfo . dir ,
2024-07-26 09:48:12 +02:00
} ,
} ;
await createBlock ( termBlockDef ) ;
} ,
} ) ;
}
2024-09-03 01:48:10 +02:00
const loadableSV = globalStore . get ( this . loadableSpecializedView ) ;
if ( loadableSV . state == "hasData" ) {
if ( loadableSV . data . specializedView == "codeedit" ) {
2024-09-10 23:27:11 +02:00
if ( globalStore . get ( this . newFileContent ) != null ) {
2024-09-03 01:48:10 +02:00
menuItems . push ( { type : "separator" } ) ;
menuItems . push ( {
label : "Save File" ,
click : this.handleFileSave.bind ( this ) ,
} ) ;
menuItems . push ( {
label : "Revert File" ,
click : this.handleFileRevert.bind ( this ) ,
} ) ;
}
}
}
2024-07-09 00:32:10 +02:00
return menuItems ;
}
2024-07-23 01:41:18 +02:00
giveFocus ( ) : boolean {
2024-09-03 01:48:10 +02:00
const openModalOpen = globalStore . get ( this . openFileModal ) ;
if ( openModalOpen ) {
this . openFileModalGiveFocusRef . current ? . ( ) ;
return true ;
}
if ( this . monacoRef . current ) {
this . monacoRef . current . focus ( ) ;
return true ;
}
2024-07-23 01:41:18 +02:00
return false ;
}
2024-08-20 07:07:35 +02:00
keyDownHandler ( e : WaveKeyboardEvent ) : boolean {
if ( keyutil . checkKeyPressed ( e , "Cmd:ArrowLeft" ) ) {
this . goHistoryBack ( ) ;
return true ;
}
if ( keyutil . checkKeyPressed ( e , "Cmd:ArrowRight" ) ) {
this . goHistoryForward ( ) ;
return true ;
}
if ( keyutil . checkKeyPressed ( e , "Cmd:ArrowUp" ) ) {
// handle up directory
this . goParentDirectory ( ) ;
return true ;
}
2024-09-03 01:48:10 +02:00
const openModalOpen = globalStore . get ( this . openFileModal ) ;
if ( ! openModalOpen ) {
if ( keyutil . checkKeyPressed ( e , "Cmd:o" ) ) {
this . updateOpenFileModalAndError ( true ) ;
return true ;
}
}
const canPreview = globalStore . get ( this . canPreview ) ;
if ( canPreview ) {
if ( keyutil . checkKeyPressed ( e , "Cmd:e" ) ) {
const editMode = globalStore . get ( this . editMode ) ;
this . setEditMode ( ! editMode ) ;
return true ;
}
}
2024-08-30 02:00:24 +02:00
if ( this . directoryKeyDownHandler ) {
const handled = this . directoryKeyDownHandler ( e ) ;
if ( handled ) {
return true ;
}
}
2024-09-03 01:48:10 +02:00
if ( this . codeEditKeyDownHandler ) {
const handled = this . codeEditKeyDownHandler ( e ) ;
if ( handled ) {
return true ;
}
}
2024-08-20 07:07:35 +02:00
return false ;
}
2024-07-09 00:04:48 +02:00
}
2024-09-03 01:48:10 +02:00
function makePreviewModel ( blockId : string , nodeModel : NodeModel ) : PreviewModel {
const previewModel = new PreviewModel ( blockId , nodeModel ) ;
2024-07-09 00:04:48 +02:00
return previewModel ;
}
2024-05-17 07:48:23 +02:00
2024-09-03 01:48:10 +02:00
function MarkdownPreview ( { model } : SpecializedViewProps ) {
2024-09-06 21:59:28 +02:00
const connName = jotai . useAtomValue ( model . connection ) ;
const fileInfo = jotai . useAtomValue ( model . statFile ) ;
const resolveOpts : MarkdownResolveOpts = React . useMemo < MarkdownResolveOpts > ( ( ) = > {
return {
connName : connName ,
baseDir : fileInfo.dir ,
} ;
} , [ connName , fileInfo . dir ] ) ;
2024-05-14 18:37:41 +02:00
return (
< div className = "view-preview view-preview-markdown" >
2024-09-10 23:27:11 +02:00
< Markdown textAtom = { model . fileContent } showTocAtom = { model . markdownShowToc } resolveOpts = { resolveOpts } / >
2024-05-14 18:37:41 +02:00
< / div >
) ;
2024-05-17 07:48:23 +02:00
}
2024-09-03 01:48:10 +02:00
function StreamingPreview ( { model } : SpecializedViewProps ) {
const conn = jotai . useAtomValue ( model . connection ) ;
const fileInfo = jotai . useAtomValue ( model . statFile ) ;
2024-05-17 07:48:23 +02:00
const filePath = fileInfo . path ;
2024-08-19 23:37:52 +02:00
const usp = new URLSearchParams ( ) ;
usp . set ( "path" , filePath ) ;
2024-09-03 01:48:10 +02:00
if ( conn != null ) {
usp . set ( "connection" , conn ) ;
2024-08-19 23:37:52 +02:00
}
const streamingUrl = getWebServerEndpoint ( ) + "/wave/stream-file?" + usp . toString ( ) ;
2024-06-03 22:13:41 +02:00
if ( fileInfo . mimetype == "application/pdf" ) {
return (
< div className = "view-preview view-preview-pdf" >
2024-06-14 20:10:54 +02:00
< iframe src = { streamingUrl } width = "95%" height = "95%" name = "pdfview" / >
2024-06-03 22:13:41 +02:00
< / div >
) ;
}
2024-05-17 07:48:23 +02:00
if ( fileInfo . mimetype . startsWith ( "video/" ) ) {
return (
< div className = "view-preview view-preview-video" >
< video controls >
< source src = { streamingUrl } / >
< / video >
< / div >
) ;
}
if ( fileInfo . mimetype . startsWith ( "audio/" ) ) {
return (
< div className = "view-preview view-preview-audio" >
< audio controls >
< source src = { streamingUrl } / >
< / audio >
< / div >
) ;
}
if ( fileInfo . mimetype . startsWith ( "image/" ) ) {
return (
< div className = "view-preview view-preview-image" >
< img src = { streamingUrl } / >
< / div >
) ;
}
return < CenteredDiv > Preview Not Supported < / CenteredDiv > ;
}
2024-09-04 20:45:26 +02:00
function CodeEditPreview ( { model } : SpecializedViewProps ) {
2024-09-10 23:27:11 +02:00
const fileContent = jotai . useAtomValue ( model . fileContent ) ;
const setNewFileContent = jotai . useSetAtom ( model . newFileContent ) ;
2024-09-03 01:48:10 +02:00
const fileName = jotai . useAtomValue ( model . statFilePath ) ;
function codeEditKeyDownHandler ( e : WaveKeyboardEvent ) : boolean {
if ( keyutil . checkKeyPressed ( e , "Cmd:e" ) ) {
model . setEditMode ( false ) ;
return true ;
}
if ( keyutil . checkKeyPressed ( e , "Cmd:s" ) ) {
model . handleFileSave ( ) ;
return true ;
}
if ( keyutil . checkKeyPressed ( e , "Cmd:r" ) ) {
model . handleFileRevert ( ) ;
return true ;
}
return false ;
}
React . useEffect ( ( ) = > {
model . codeEditKeyDownHandler = codeEditKeyDownHandler ;
return ( ) = > {
model . codeEditKeyDownHandler = null ;
model . monacoRef . current = null ;
} ;
} , [ ] ) ;
function onMount ( editor : MonacoTypes.editor.IStandaloneCodeEditor , monaco : Monaco ) : ( ) = > void {
model . monacoRef . current = editor ;
editor . onKeyDown ( ( e : MonacoTypes.IKeyboardEvent ) = > {
const waveEvent = keyutil . adaptFromReactOrNativeKeyEvent ( e . browserEvent ) ;
const handled = tryReinjectKey ( waveEvent ) ;
if ( handled ) {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
}
} ) ;
const isFocused = globalStore . get ( model . nodeModel . isFocused ) ;
if ( isFocused ) {
editor . focus ( ) ;
}
return null ;
}
2024-07-18 08:41:33 +02:00
return (
< CodeEditor
2024-09-10 23:27:11 +02:00
text = { fileContent }
2024-09-03 01:48:10 +02:00
filename = { fileName }
2024-09-10 23:27:11 +02:00
onChange = { ( text ) = > setNewFileContent ( text ) }
2024-09-03 01:48:10 +02:00
onMount = { onMount }
2024-07-18 08:41:33 +02:00
/ >
) ;
2024-06-22 01:22:59 +02:00
}
2024-09-03 01:48:10 +02:00
function CSVViewPreview ( { model , parentRef } : SpecializedViewProps ) {
2024-09-10 23:27:11 +02:00
const fileContent = jotai . useAtomValue ( model . fileContent ) ;
2024-09-03 01:48:10 +02:00
const fileName = jotai . useAtomValue ( model . statFilePath ) ;
return < CSVView parentRef = { parentRef } readonly = { true } content = { fileContent } filename = { fileName } / > ;
2024-06-24 19:17:35 +02:00
}
2024-09-04 03:27:41 +02:00
function iconForFile ( mimeType : string ) : string {
2024-07-09 00:04:48 +02:00
if ( mimeType == null ) {
mimeType = "unknown" ;
}
2024-06-22 01:40:24 +02:00
if ( mimeType == "application/pdf" ) {
return "file-pdf" ;
} else if ( mimeType . startsWith ( "image/" ) ) {
return "image" ;
} else if ( mimeType . startsWith ( "video/" ) ) {
return "film" ;
} else if ( mimeType . startsWith ( "audio/" ) ) {
return "headphones" ;
} else if ( mimeType . startsWith ( "text/markdown" ) ) {
return "file-lines" ;
2024-06-26 18:39:41 +02:00
} else if ( mimeType == "text/csv" ) {
return "file-csv" ;
2024-06-22 01:40:24 +02:00
} else if (
mimeType . startsWith ( "text/" ) ||
2024-07-09 00:04:48 +02:00
mimeType == "application/sql" ||
2024-06-22 01:40:24 +02:00
( mimeType . startsWith ( "application/" ) &&
( mimeType . includes ( "json" ) || mimeType . includes ( "yaml" ) || mimeType . includes ( "toml" ) ) )
) {
return "file-code" ;
} else {
return "file" ;
}
}
2024-09-03 01:48:10 +02:00
function SpecializedView ( { parentRef , model } : SpecializedViewProps ) {
const specializedView = jotai . useAtomValue ( model . specializedView ) ;
const mimeType = jotai . useAtomValue ( model . fileMimeType ) ;
const setCanPreview = jotai . useSetAtom ( model . canPreview ) ;
React . 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 Specialzied View Component ( { specializedView . specializedView } ) < / CenteredDiv > ;
}
return < SpecializedViewComponent model = { model } parentRef = { parentRef } / > ;
}
2024-08-29 08:47:45 +02:00
function PreviewView ( {
blockId ,
blockRef ,
contentRef ,
model ,
} : {
blockId : string ;
blockRef : React.RefObject < HTMLDivElement > ;
contentRef : React.RefObject < HTMLDivElement > ;
model : PreviewModel ;
} ) {
2024-09-05 09:21:08 +02:00
const connStatus = jotai . useAtomValue ( model . connStatus ) ;
if ( connStatus ? . status != "connected" ) {
return null ;
}
2024-05-14 08:45:41 +02:00
return (
2024-08-23 09:18:49 +02:00
< >
2024-09-03 01:48:10 +02:00
< OpenFileModal blockId = { blockId } model = { model } blockRef = { blockRef } / >
< div className = "full-preview scrollbar-hide-until-hover" >
2024-08-23 09:18:49 +02:00
< div ref = { contentRef } className = "full-preview-content" >
2024-09-03 01:48:10 +02:00
< SpecializedView parentRef = { contentRef } model = { model } / >
2024-08-23 09:18:49 +02:00
< / div >
2024-07-11 00:06:19 +02:00
< / div >
2024-08-23 09:18:49 +02:00
< / >
2024-05-14 08:45:41 +02:00
) ;
2024-05-17 07:48:23 +02:00
}
2024-05-14 08:45:41 +02:00
2024-09-03 01:48:10 +02:00
const OpenFileModal = React . memo (
( {
model ,
blockRef ,
blockId ,
} : {
model : PreviewModel ;
blockRef : React.RefObject < HTMLDivElement > ;
blockId : string ;
} ) = > {
const openFileModal = jotai . useAtomValue ( model . openFileModal ) ;
const curFileName = jotai . useAtomValue ( model . metaFilePath ) ;
const [ filePath , setFilePath ] = useState ( "" ) ;
const isNodeFocused = jotai . useAtomValue ( model . nodeModel . isFocused ) ;
const handleKeyDown = useCallback (
keyutil . keydownWrapper ( ( waveEvent : WaveKeyboardEvent ) : boolean = > {
if ( keyutil . checkKeyPressed ( waveEvent , "Escape" ) ) {
model . updateOpenFileModalAndError ( false ) ;
return true ;
}
const handleCommandOperations = async ( ) = > {
if ( keyutil . checkKeyPressed ( waveEvent , "Enter" ) ) {
model . handleOpenFile ( filePath ) ;
return true ;
}
return false ;
} ;
handleCommandOperations ( ) . catch ( ( error ) = > {
console . error ( "Error handling key down:" , error ) ;
model . updateOpenFileModalAndError ( true , "An error occurred during operation." ) ;
return false ;
} ) ;
return false ;
} ) ,
[ model , blockId , filePath , curFileName ]
) ;
const handleFileSuggestionSelect = ( value ) = > {
globalStore . set ( model . openFileModal , false ) ;
} ;
const handleFileSuggestionChange = ( value ) = > {
setFilePath ( value ) ;
} ;
const handleBackDropClick = ( ) = > {
globalStore . set ( model . openFileModal , false ) ;
} ;
if ( ! openFileModal ) {
return null ;
}
return (
< TypeAheadModal
2024-09-04 06:18:52 +02:00
label = "Open path"
2024-09-03 01:48:10 +02:00
blockRef = { blockRef }
anchorRef = { model . previewTextRef }
onKeyDown = { handleKeyDown }
onSelect = { handleFileSuggestionSelect }
onChange = { handleFileSuggestionChange }
onClickBackdrop = { handleBackDropClick }
autoFocus = { isNodeFocused }
giveFocusRef = { model . openFileModalGiveFocusRef }
/ >
) ;
}
) ;
2024-07-30 21:33:28 +02:00
export { makePreviewModel , PreviewView } ;