301 lines
8.7 KiB
Plaintext
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 |