KUMOS/Client/Default Apps/FileExplorer.sbi
2026-05-02 15:49:06 +02:00

301 lines
8.7 KiB
Plaintext

DeclareModule FileExplorer
Declare Open()
EndDeclareModule
Module FileExplorer
EnableExplicit
; Layout constants
#Toolbar_Height = 35
#Statusbar_Height = 24
#Window_W = 480
#Window_H = 400
#Max_Entries = 512
; Gadgets
Global Window
Global UpButton
Global PathLabel
Global NewFolderButton
Global NewFileButton
Global ListView
Global StatusLabel
; Navigation state
Global CurrentPath.s = "/"
Global Dim PathStack.s(64)
Global StackDepth = 0
; Entry cache (populated on each listing)
Global Dim EntryIDs.s(#Max_Entries)
Global Dim EntryNames.s(#Max_Entries)
Global Dim EntryIsDir(#Max_Entries)
Global EntryCount = 0
; Input window state
Global InputWindow = 0
Global InputField
Global InputMode ; 0 = new folder, 1 = new file
Global ButtonNew
Global CancelBtn
; Private declarations
Declare _Load(Path.s)
Declare _ListCallback(Success.i, DataString.s)
Declare _MkdirCallback(Success.i, DataString.s)
Declare _CreateFileCallback(Success.i, DataString.s)
Declare _OpenInputWindow(Mode.i)
Declare _CloseInputWindow()
Declare Handler_Resize()
Declare Handler_Up()
Declare Handler_NewFolder()
Declare Handler_NewFile()
Declare Handler_ListView()
Declare Handler_Close()
Declare Handler_InputConfirm()
Declare Handler_InputCancel()
; Public procedures
Procedure Open()
If IsWindow(Window)
SetActiveWindow(Window)
ProcedureReturn
EndIf
Window = OpenWindow(#PB_Any, 150, 80, #Window_W, #Window_H, "File Explorer",
#PB_Window_TitleBar | #PB_Window_SizeGadget | #PB_Window_SystemMenu)
; Toolbar
UpButton = ButtonGadget(#PB_Any, 2, 4, 30, #Toolbar_Height - 8, "↑")
PathLabel = TextGadget (#PB_Any, 36, 4, #Window_W - 200, #Toolbar_Height - 8, "/")
NewFolderButton= ButtonGadget(#PB_Any, #Window_W - 162, 4, 80, #Toolbar_Height - 8, "+ Folder")
NewFileButton = ButtonGadget(#PB_Any, #Window_W - 78, 4, 74, #Toolbar_Height - 8, "+ File")
; File list
ListView = ListViewGadget(#PB_Any, 0, #Toolbar_Height,
#Window_W, #Window_H - #Toolbar_Height - #Statusbar_Height)
; Status bar
StatusLabel = TextGadget(#PB_Any, 4, #Window_H - #Statusbar_Height + 4,
#Window_W - 8, #Statusbar_Height - 8, "")
BindGadgetEvent(UpButton, @Handler_Up())
BindGadgetEvent(NewFolderButton, @Handler_NewFolder())
BindGadgetEvent(NewFileButton, @Handler_NewFile())
BindGadgetEvent(ListView, @Handler_ListView())
BindEvent(#PB_Event_SizeWindow, @Handler_Resize(), Window)
BindEvent(#PB_Event_CloseWindow, @Handler_Close(), Window)
Desktop::Register("File Explorer", Window, "📁")
CurrentPath = "/"
StackDepth = 0
_Load("/")
EndProcedure
; Private procedures
Procedure _Load(Path.s)
CurrentPath = Path
SetGadgetText(PathLabel, Path)
SetGadgetText(StatusLabel, "Loading...")
ClearGadgetItems(ListView)
EntryCount = 0
FS::List(Path, @_ListCallback())
EndProcedure
Procedure _ListCallback(Success.i, DataString.s)
If Not Success Or Not IsWindow(Window)
Notify::Toast("Failed to load directory.", Notify::#Error)
ProcedureReturn
EndIf
If Not ParseJSON(0, DataString)
Notify::Toast("Could not read directory.", Notify::#Error)
ProcedureReturn
EndIf
Protected Root.i = JSONValue(0)
Protected Count.i = JSONArraySize(Root)
Protected i.i, Item.i
ClearGadgetItems(ListView)
EntryCount = 0
For i = 0 To Count - 1
If EntryCount >= #Max_Entries : Break : EndIf
Item = GetJSONElement(Root, i)
EntryIDs(EntryCount) = Str(GetJSONInteger(GetJSONMember(Item, "id")))
EntryNames(EntryCount) = GetJSONString(GetJSONMember(Item, "name"))
EntryIsDir(EntryCount) = GetJSONInteger(GetJSONMember(Item, "is_dir"))
Protected Prefix.s = "[F] "
If EntryIsDir(EntryCount) : Prefix = "[D] " : EndIf
AddGadgetItem(ListView, -1, Prefix + EntryNames(EntryCount))
EntryCount + 1
Next
FreeJSON(0)
Protected Status.s = Str(EntryCount) + " item"
If EntryCount <> 1 : Status + "s" : EndIf
SetGadgetText(StatusLabel, Status)
EndProcedure
Procedure _MkdirCallback(Success.i, DataString.s)
If Not IsWindow(Window) : ProcedureReturn : EndIf
_Load(CurrentPath) ; refresh regardless — server may have created it
EndProcedure
Procedure _CreateFileCallback(Success.i, DataString.s)
If Not IsWindow(Window) : ProcedureReturn : EndIf
If Success And ParseJSON(0, DataString)
Protected Root.i = JSONValue(0)
If GetJSONBoolean(GetJSONMember(Root, "success"))
Protected ID.s = Str(GetJSONInteger(GetJSONMember(Root, "id")))
Protected Path.s = CurrentPath
If Right(Path, 1) <> "/" : Path + "/" : EndIf
Path + EntryNames(0) ; name was stored in EntryNames(0) temporarily
FreeJSON(0)
_Load(CurrentPath)
TextEditor::Open(ID, Path)
ProcedureReturn
EndIf
FreeJSON(0)
EndIf
_Load(CurrentPath)
EndProcedure
Procedure _OpenInputWindow(Mode.i)
If IsWindow(InputWindow) : ProcedureReturn : EndIf
InputMode = Mode
Protected Title.s = "New Folder"
If Mode = 1 : Title = "New File" : EndIf
Protected X.i = WindowX(Window) + (#Window_W / 2) - 150
Protected Y.i = WindowY(Window) + (#Window_H / 2) - 45
InputWindow = OpenWindow(#PB_Any, X, Y, 300, 90, Title,
#PB_Window_TitleBar | #PB_Window_SystemMenu)
TextGadget (#PB_Any, 10, 12, 280, 20, "Name:")
InputField = StringGadget(#PB_Any, 10, 32, 280, 24, "")
ButtonNew = ButtonGadget(#PB_Any, 10, 62, 130, 24, "OK") ; EventGadget index 2
CancelBtn = ButtonGadget(#PB_Any, 150, 62, 140, 24, "Cancel")
SetActiveGadget(InputField)
BindGadgetEvent(ButtonNew, @Handler_InputConfirm())
BindGadgetEvent(CancelBtn, @Handler_InputCancel())
BindEvent(#PB_Event_CloseWindow, @Handler_InputCancel(), InputWindow)
EndProcedure
Procedure _CloseInputWindow()
If IsWindow(InputWindow)
CloseWindow(InputWindow)
EndIf
EndProcedure
; Event handlers
Procedure Handler_Resize()
Protected W.i = WindowWidth(Window)
Protected H.i = WindowHeight(Window)
ResizeGadget(PathLabel, 36, 4, W - 200, #Toolbar_Height - 8)
ResizeGadget(NewFolderButton, W - 162, 4, 80, #Toolbar_Height - 8)
ResizeGadget(NewFileButton, W - 78, 4, 74, #Toolbar_Height - 8)
ResizeGadget(ListView, 0, #Toolbar_Height, W, H - #Toolbar_Height - #Statusbar_Height)
ResizeGadget(StatusLabel, 4, H - #Statusbar_Height + 4, W - 8, #Statusbar_Height - 8)
EndProcedure
Procedure Handler_Up()
If StackDepth > 0
StackDepth - 1
_Load(PathStack(StackDepth))
EndIf
EndProcedure
Procedure Handler_NewFolder()
_OpenInputWindow(0)
EndProcedure
Procedure Handler_NewFile()
_OpenInputWindow(1)
EndProcedure
Procedure Handler_ListView()
If EventType() <> #PB_EventType_LeftDoubleClick : ProcedureReturn : EndIf
Protected Index.i = GetGadgetState(ListView)
If Index < 0 Or Index >= EntryCount : ProcedureReturn : EndIf
If EntryIsDir(Index)
; Push current path, navigate in
PathStack(StackDepth) = CurrentPath
StackDepth + 1
Protected NewPath.s = CurrentPath
If Right(NewPath, 1) <> "/" : NewPath + "/" : EndIf
NewPath + EntryNames(Index)
_Load(NewPath)
Else
; Open in text editor
Protected Path.s = CurrentPath
If Right(Path, 1) <> "/" : Path + "/" : EndIf
Path + EntryNames(Index)
TextEditor::Open(EntryIDs(Index), Path)
EndIf
EndProcedure
Procedure Handler_Close()
If IsWindow(InputWindow)
UnbindEvent(#PB_Event_CloseWindow, @Handler_InputCancel(), InputWindow)
UnbindGadgetEvent(ButtonNew, @Handler_InputConfirm())
UnbindGadgetEvent(CancelBtn, @Handler_InputCancel())
_CloseInputWindow()
EndIf
UnbindGadgetEvent(UpButton, @Handler_Up())
UnbindGadgetEvent(NewFolderButton, @Handler_NewFolder())
UnbindGadgetEvent(NewFileButton, @Handler_NewFile())
UnbindGadgetEvent(ListView, @Handler_ListView())
UnbindEvent(#PB_Event_SizeWindow, @Handler_Resize(), Window)
UnbindEvent(#PB_Event_CloseWindow, @Handler_Close(), Window)
Desktop::Unregister(Window)
InputWindow = 0
Window = 0
EndProcedure
Procedure Handler_InputConfirm()
Protected Name.s = Trim(GetGadgetText(InputField))
If Name = "" : ProcedureReturn : EndIf
Protected Path.s = CurrentPath
If Right(Path, 1) <> "/" : Path + "/" : EndIf
Path + Name
_CloseInputWindow()
If InputMode = 0
FS::Mkdir(Path, @_MkdirCallback())
Else
; Stash name temporarily for the callback to build the full path
EntryNames(0) = Name
FS::Write("", Path, "", @_CreateFileCallback())
EndIf
EndProcedure
Procedure Handler_InputCancel()
_CloseInputWindow()
EndProcedure
EndModule
; IDE Options = SpiderBasic 3.10 (Windows - x86)
; CursorPosition = 96
; Folding = CAg
; iOSAppOrientation = 0
; AndroidAppCode = 0
; AndroidAppOrientation = 0
; EnableXP
; DPIAware
; CompileSourceDirectory