diff --git a/frontend/app/view/preview.tsx b/frontend/app/view/preview.tsx index 6533fafc..0544c7c6 100644 --- a/frontend/app/view/preview.tsx +++ b/frontend/app/view/preview.tsx @@ -53,6 +53,18 @@ function StreamingPreview({ fileInfo }: { fileInfo: FileInfo }) { return Preview Not Supported; } +function DirectoryPreview({ contentAtom }: { contentAtom: jotai.Atom> }) { + const contentText = jotai.useAtomValue(contentAtom); + let content: FileInfo[] = JSON.parse(contentText); + return ( +
+ {content.map((finfo) => ( + {finfo.path} + ))} +
+ ); +} + function PreviewView({ blockId }: { blockId: string }) { const blockDataAtom: jotai.Atom = blockDataMap.get(blockId); const fileNameAtom = useBlockAtom(blockId, "preview:filename", () => @@ -118,6 +130,9 @@ function PreviewView({ blockId }: { blockId: string }) { ); } + if (mimeType === "directory") { + return ; + } return (
Preview ({mimeType})
diff --git a/frontend/app/view/view.less b/frontend/app/view/view.less index 3700d347..3a1fcb2c 100644 --- a/frontend/app/view/view.less +++ b/frontend/app/view/view.less @@ -63,4 +63,8 @@ object-fit: contain; } } + &.view-preview-directory { + flex-direction: column; + align-items: start; + } } diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx index 2d03b794..4b5384de 100644 --- a/frontend/app/workspace/workspace.tsx +++ b/frontend/app/workspace/workspace.tsx @@ -92,6 +92,9 @@ function Widgets() {
clickPreview("build/appicon.png")}>
+
clickPreview("~")}> + +
clickPlot()}>
diff --git a/pkg/service/fileservice/fileservice.go b/pkg/service/fileservice/fileservice.go index a1c486f1..c66c81d0 100644 --- a/pkg/service/fileservice/fileservice.go +++ b/pkg/service/fileservice/fileservice.go @@ -5,6 +5,7 @@ package fileservice import ( "encoding/base64" + "encoding/json" "fmt" "os" "path/filepath" @@ -41,9 +42,9 @@ func (fs *FileService) StatFile(path string) (*FileInfo, error) { if err != nil { return nil, fmt.Errorf("cannot stat file %q: %w", path, err) } - mimeType := utilfn.DetectMimeType(path) + mimeType := utilfn.DetectMimeType(cleanedPath) return &FileInfo{ - Path: wavebase.ReplaceHomeDir(path), + Path: cleanedPath, Size: finfo.Size(), Mode: finfo.Mode(), ModTime: finfo.ModTime().UnixMilli(), @@ -63,6 +64,35 @@ func (fs *FileService) ReadFile(path string) (*FullFile, error) { if finfo.Size > MaxFileSize { return nil, fmt.Errorf("file %q is too large to read, use /wave/stream-file", path) } + if finfo.IsDir { + innerFilesEntries, err := os.ReadDir(finfo.Path) + if err != nil { + return nil, fmt.Errorf("unable to parse directory %s", finfo.Path) + } + + var innerFilesInfo []FileInfo + for _, innerFileEntry := range innerFilesEntries { + innerFileInfoInt, _ := innerFileEntry.Info() + innerFileInfo := FileInfo{ + Path: innerFileInfoInt.Name(), + Size: innerFileInfoInt.Size(), + Mode: innerFileInfoInt.Mode(), + ModTime: innerFileInfoInt.ModTime().UnixMilli(), + IsDir: innerFileInfoInt.IsDir(), + MimeType: "", + } + innerFilesInfo = append(innerFilesInfo, innerFileInfo) + } + + filesSerialized, err := json.Marshal(innerFilesInfo) + if err != nil { + return nil, fmt.Errorf("unable to serialize files %s", finfo.Path) + } + return &FullFile{ + Info: finfo, + Data64: base64.StdEncoding.EncodeToString(filesSerialized), + }, nil + } cleanedPath := filepath.Clean(wavebase.ExpandHomeDir(path)) barr, err := os.ReadFile(cleanedPath) if err != nil { diff --git a/pkg/util/utilfn/utilfn.go b/pkg/util/utilfn/utilfn.go index ae568d63..f18b98ef 100644 --- a/pkg/util/utilfn/utilfn.go +++ b/pkg/util/utilfn/utilfn.go @@ -634,6 +634,13 @@ func DetectMimeType(path string) string { if mimeType := mime.TypeByExtension(ext); mimeType != "" { return mimeType } + stats, err := os.Stat(path) + if err != nil { + return "" + } + if stats.IsDir() { + return "directory" + } fd, err := os.Open(path) if err != nil { return ""