411 lines
12 KiB
Plaintext
411 lines
12 KiB
Plaintext
DeclareModule AppManager
|
|
Declare Open()
|
|
EndDeclareModule
|
|
|
|
Module AppManager
|
|
EnableExplicit
|
|
|
|
;- Layout
|
|
#Win_W = 520
|
|
#Win_H = 380
|
|
#Toolbar_H = 40
|
|
#Statusbar_H = 26
|
|
#List_Row_H = 48
|
|
|
|
;- Colors
|
|
Global Color_Bg = RGB( 22, 22, 34)
|
|
Global Color_Row_Odd = RGB( 28, 28, 42)
|
|
Global Color_Row_Even = RGB( 22, 22, 34)
|
|
Global Color_Sel = RGB( 52, 52, 72)
|
|
Global Color_Accent = RGB(100, 120, 255)
|
|
Global Color_Text = RGB(200, 200, 215)
|
|
Global Color_Dim = RGB(110, 110, 135)
|
|
Global Color_Sep = RGB( 48, 48, 65)
|
|
|
|
Global Font_UI = LoadFont(#PB_Any, "sans-serif", 13)
|
|
Global Font_Small = LoadFont(#PB_Any, "sans-serif", 11)
|
|
|
|
;- App list
|
|
Structure InstalledApp
|
|
ID.s
|
|
Name.s
|
|
Manifest.s
|
|
Permissions.s
|
|
EndStructure
|
|
|
|
Global NewList _Apps.InstalledApp()
|
|
|
|
;- State
|
|
Global Window = 0
|
|
Global _Canvas
|
|
Global _StatusLabel
|
|
Global _InstallBtn
|
|
Global _LaunchBtn
|
|
Global _UninstallBtn
|
|
Global _Hover = -1
|
|
Global _Selected = -1
|
|
Global _PendingUninstall.s = ""
|
|
|
|
;- Install dialog
|
|
Global _InputWin = 0
|
|
Global _URLField
|
|
Global _OKBtn
|
|
Global _CancelBtn
|
|
|
|
;- Private declarations
|
|
Declare _Refresh()
|
|
Declare _DrawList()
|
|
Declare _UpdateButtons()
|
|
Declare _Status(Msg.s)
|
|
Declare _OpenInstallDialog()
|
|
Declare _CloseInstallDialog()
|
|
Declare _ListCallback(Success, DataString.s)
|
|
Declare _InstallCallback(Success, DataString.s)
|
|
Declare _UninstallCallback(Success, DataString.s)
|
|
Declare _DoUninstall(Result)
|
|
Declare Handler_Close()
|
|
Declare Handler_Canvas()
|
|
Declare Handler_Install()
|
|
Declare Handler_Launch()
|
|
Declare Handler_Uninstall()
|
|
Declare Handler_InputOK()
|
|
Declare Handler_InputCancel()
|
|
Declare Handler_Resize()
|
|
|
|
;- Public
|
|
Procedure Open()
|
|
If IsWindow(Window)
|
|
SetActiveWindow(Window) : ProcedureReturn
|
|
EndIf
|
|
|
|
Window = OpenWindow(#PB_Any, 160, 100, #Win_W, #Win_H, "App Manager",
|
|
#PB_Window_TitleBar | #PB_Window_SizeGadget | #PB_Window_SystemMenu)
|
|
SetWindowColor(Window, Color_Bg)
|
|
|
|
_InstallBtn = ButtonGadget(#PB_Any, 4, 6, 100, #Toolbar_H - 12, "+ Install")
|
|
_LaunchBtn = ButtonGadget(#PB_Any, 112, 6, 80, #Toolbar_H - 12, "Launch")
|
|
_UninstallBtn = ButtonGadget(#PB_Any, 200, 6, 90, #Toolbar_H - 12, "Uninstall")
|
|
|
|
_Canvas = CanvasGadget(#PB_Any, 0, #Toolbar_H,
|
|
#Win_W, #Win_H - #Toolbar_H - #Statusbar_H)
|
|
|
|
_StatusLabel = TextGadget(#PB_Any, 4, #Win_H - #Statusbar_H + 4,
|
|
#Win_W - 8, #Statusbar_H - 8, "")
|
|
SetGadgetColor(_StatusLabel, #PB_Gadget_FrontColor, Color_Dim)
|
|
SetGadgetColor(_StatusLabel, #PB_Gadget_BackColor, Color_Bg)
|
|
|
|
BindGadgetEvent(_InstallBtn, @Handler_Install())
|
|
BindGadgetEvent(_LaunchBtn, @Handler_Launch())
|
|
BindGadgetEvent(_UninstallBtn, @Handler_Uninstall())
|
|
BindGadgetEvent(_Canvas, @Handler_Canvas())
|
|
BindEvent(#PB_Event_SizeWindow, @Handler_Resize(), Window)
|
|
BindEvent(#PB_Event_CloseWindow, @Handler_Close(), Window)
|
|
|
|
Desktop::Register("App Manager", Window, "📦")
|
|
_UpdateButtons()
|
|
_Refresh()
|
|
EndProcedure
|
|
|
|
;- Private
|
|
Procedure _Refresh()
|
|
_Status("Loading...")
|
|
ClearList(_Apps())
|
|
_Selected = -1
|
|
_DrawList()
|
|
HTTPRequest(#PB_HTTP_Get, "/api/apps/list", "", @_ListCallback())
|
|
EndProcedure
|
|
|
|
Procedure _DrawList()
|
|
If Not IsGadget(_Canvas) : ProcedureReturn : EndIf
|
|
If Not StartDrawing(CanvasOutput(_Canvas)) : ProcedureReturn : EndIf
|
|
|
|
Protected W = OutputWidth()
|
|
Protected H = OutputHeight()
|
|
|
|
Box(0, 0, W, H, Color_Bg)
|
|
|
|
If IsFont(Font_UI) : DrawingFont(FontID(Font_UI)) : EndIf
|
|
Protected TH = TextHeight("A")
|
|
|
|
ForEach _Apps()
|
|
Protected i = ListIndex(_Apps())
|
|
Protected Y = i * #List_Row_H
|
|
If Y + #List_Row_H > H : Break : EndIf
|
|
|
|
Protected RowBg
|
|
If i = _Selected
|
|
RowBg = Color_Sel
|
|
ElseIf i = _Hover
|
|
RowBg = RGB(40, 40, 58)
|
|
ElseIf i % 2 = 0
|
|
RowBg = Color_Row_Even
|
|
Else
|
|
RowBg = Color_Row_Odd
|
|
EndIf
|
|
|
|
Box(0, Y, W, #List_Row_H, RowBg)
|
|
If i = _Selected : Box(0, Y, 3, #List_Row_H, Color_Accent) : EndIf
|
|
|
|
DrawingMode(#PB_2DDrawing_Transparent)
|
|
DrawText(14, Y + (#List_Row_H / 2) - TH - 1, _Apps()\Name, Color_Text)
|
|
|
|
If IsFont(Font_Small) : DrawingFont(FontID(Font_Small)) : EndIf
|
|
DrawText(14, Y + (#List_Row_H / 2) + 2, _Apps()\ID, Color_Dim)
|
|
If IsFont(Font_UI) : DrawingFont(FontID(Font_UI)) : EndIf
|
|
|
|
Box(0, Y + #List_Row_H - 1, W, 1, Color_Sep)
|
|
Next
|
|
|
|
If ListSize(_Apps()) = 0
|
|
DrawingMode(#PB_2DDrawing_Transparent)
|
|
If IsFont(Font_UI) : DrawingFont(FontID(Font_UI)) : EndIf
|
|
DrawText((W - TextWidth("No apps installed")) / 2,
|
|
(H - TH) / 2, "No apps installed", Color_Dim)
|
|
EndIf
|
|
|
|
StopDrawing()
|
|
EndProcedure
|
|
|
|
Procedure _UpdateButtons()
|
|
DisableGadget(_LaunchBtn, Bool(_Selected < 0))
|
|
DisableGadget(_UninstallBtn, Bool(_Selected < 0))
|
|
EndProcedure
|
|
|
|
Procedure _Status(Msg.s)
|
|
If IsGadget(_StatusLabel) : SetGadgetText(_StatusLabel, Msg) : EndIf
|
|
EndProcedure
|
|
|
|
Procedure _OpenInstallDialog()
|
|
If IsWindow(_InputWin) : ProcedureReturn : EndIf
|
|
|
|
Protected X = WindowX(Window) + (#Win_W / 2) - 170
|
|
Protected Y = WindowY(Window) + (#Win_H / 2) - 50
|
|
|
|
_InputWin = OpenWindow(#PB_Any, X, Y, 340, 100, "Install App from URL",
|
|
#PB_Window_TitleBar | #PB_Window_SystemMenu)
|
|
TextGadget(#PB_Any, 10, 14, 320, 18, "Package URL (.zip):")
|
|
_URLField = StringGadget(#PB_Any, 10, 34, 320, 26, "")
|
|
_OKBtn = ButtonGadget(#PB_Any, 10, 68, 150, 24, "Install")
|
|
_CancelBtn = ButtonGadget(#PB_Any, 170, 68, 160, 24, "Cancel")
|
|
SetActiveGadget(_URLField)
|
|
|
|
BindGadgetEvent(_OKBtn, @Handler_InputOK())
|
|
BindGadgetEvent(_CancelBtn, @Handler_InputCancel())
|
|
BindEvent(#PB_Event_CloseWindow, @Handler_InputCancel(), _InputWin)
|
|
EndProcedure
|
|
|
|
Procedure _CloseInstallDialog()
|
|
If Not IsWindow(_InputWin) : ProcedureReturn : EndIf
|
|
UnbindGadgetEvent(_OKBtn, @Handler_InputOK())
|
|
UnbindGadgetEvent(_CancelBtn, @Handler_InputCancel())
|
|
UnbindEvent(#PB_Event_CloseWindow, @Handler_InputCancel(), _InputWin)
|
|
CloseWindow(_InputWin)
|
|
_InputWin = 0
|
|
EndProcedure
|
|
|
|
;- HTTP callbacks
|
|
Procedure _ListCallback(Success, DataString.s)
|
|
If Not IsWindow(Window) : ProcedureReturn : EndIf
|
|
|
|
ClearList(_Apps())
|
|
_Selected = -1
|
|
|
|
If Success And ParseJSON(0, DataString)
|
|
Protected Root = JSONValue(0)
|
|
Protected Total = JSONArraySize(Root)
|
|
Protected i
|
|
|
|
For i = 0 To Total - 1
|
|
Protected Item = GetJSONElement(Root, i)
|
|
Protected ManiNode = GetJSONMember(Item, "manifest")
|
|
|
|
AddElement(_Apps())
|
|
_Apps()\ID = GetJSONString(GetJSONMember(Item, "app_id"))
|
|
|
|
Protected N.s = GetJSONString(GetJSONMember(ManiNode, "name"))
|
|
If N <> ""
|
|
_Apps()\Name = N
|
|
Else
|
|
_Apps()\Name = _Apps()\ID
|
|
EndIf
|
|
|
|
; Re-serialise manifest to a JSON string
|
|
Protected Icon.s = GetJSONString(GetJSONMember(ManiNode, "icon"))
|
|
_Apps()\Manifest = "{" +
|
|
~"\"id\":\"" + GetJSONString(GetJSONMember(ManiNode, "id")) + ~"\"," +
|
|
~"\"name\":\"" + GetJSONString(GetJSONMember(ManiNode, "name")) + ~"\"," +
|
|
~"\"version\":\"" + GetJSONString(GetJSONMember(ManiNode, "version")) + ~"\"," +
|
|
~"\"icon\":\"" + ReplaceString(Icon, ~"\"", ~"\\\"") + ~"\"," +
|
|
~"\"entry\":\"" + GetJSONString(GetJSONMember(ManiNode, "entry")) + ~"\"}"
|
|
|
|
; Re-serialise permissions array to a JSON string
|
|
Protected PermNode = GetJSONMember(Item, "permissions")
|
|
Protected PermCount = JSONArraySize(PermNode)
|
|
Protected j, Perms.s = "["
|
|
For j = 0 To PermCount - 1
|
|
If j > 0 : Perms + "," : EndIf
|
|
Perms + ~"\"" + GetJSONString(GetJSONElement(PermNode, j)) + ~"\""
|
|
Next
|
|
_Apps()\Permissions = Perms + "]"
|
|
|
|
; Sync with the desktop start menu
|
|
Desktop::InstallThirdPartyApp(_Apps()\ID, _Apps()\Manifest,
|
|
_Apps()\Permissions, Icon)
|
|
Next
|
|
FreeJSON(0)
|
|
|
|
Protected Count = ListSize(_Apps())
|
|
If Count = 1
|
|
_Status("1 app installed")
|
|
Else
|
|
_Status(Str(Count) + " apps installed")
|
|
EndIf
|
|
Else
|
|
_Status("Failed to load app list.")
|
|
EndIf
|
|
|
|
_DrawList()
|
|
_UpdateButtons()
|
|
EndProcedure
|
|
|
|
Procedure _InstallCallback(Success, DataString.s)
|
|
If Not IsWindow(Window) : ProcedureReturn : EndIf
|
|
DisableGadget(_InstallBtn, #False)
|
|
|
|
If Success And ParseJSON(0, DataString)
|
|
Protected Root = JSONValue(0)
|
|
If GetJSONBoolean(GetJSONMember(Root, "success"))
|
|
Protected AppID.s = GetJSONString(GetJSONMember(Root, "app_id"))
|
|
FreeJSON(0)
|
|
Notify::Toast("Installed: " + AppID, Notify::#Success)
|
|
_Refresh()
|
|
ProcedureReturn
|
|
EndIf
|
|
Protected Err.s = GetJSONString(GetJSONMember(Root, "error"))
|
|
FreeJSON(0)
|
|
Notify::Toast(Err, Notify::#Error)
|
|
Else
|
|
Notify::Toast("Install failed.", Notify::#Error)
|
|
EndIf
|
|
_Status("Ready.")
|
|
EndProcedure
|
|
|
|
Procedure _UninstallCallback(Success, DataString.s)
|
|
If Not IsWindow(Window) : ProcedureReturn : EndIf
|
|
If Success And ParseJSON(0, DataString)
|
|
If GetJSONBoolean(GetJSONMember(JSONValue(0), "success"))
|
|
FreeJSON(0)
|
|
Notify::Toast("App uninstalled.", Notify::#Success)
|
|
Desktop::UninstallThirdPartyApp(_PendingUninstall)
|
|
_PendingUninstall = ""
|
|
_Refresh()
|
|
ProcedureReturn
|
|
EndIf
|
|
FreeJSON(0)
|
|
EndIf
|
|
Notify::Toast("Uninstall failed.", Notify::#Error)
|
|
_PendingUninstall = ""
|
|
_Status("Ready.")
|
|
EndProcedure
|
|
|
|
;- Event handlers
|
|
Procedure Handler_Canvas()
|
|
Protected EType = EventType()
|
|
Protected MY = GetGadgetAttribute(_Canvas, #PB_Canvas_MouseY)
|
|
Protected Row = MY / #List_Row_H
|
|
|
|
Select EType
|
|
Case #PB_EventType_MouseMove
|
|
If Row >= ListSize(_Apps()) : Row = -1 : EndIf
|
|
If Row <> _Hover
|
|
_Hover = Row : _DrawList()
|
|
EndIf
|
|
|
|
Case #PB_EventType_MouseLeave
|
|
If _Hover <> -1 : _Hover = -1 : _DrawList() : EndIf
|
|
|
|
Case #PB_EventType_LeftButtonUp
|
|
If Row >= 0 And Row < ListSize(_Apps())
|
|
_Selected = Row : _DrawList() : _UpdateButtons()
|
|
EndIf
|
|
|
|
Case #PB_EventType_LeftDoubleClick
|
|
If Row >= 0 And Row < ListSize(_Apps())
|
|
_Selected = Row
|
|
Handler_Launch()
|
|
EndIf
|
|
EndSelect
|
|
EndProcedure
|
|
|
|
Procedure Handler_Install()
|
|
_OpenInstallDialog()
|
|
EndProcedure
|
|
|
|
Procedure Handler_Launch()
|
|
If _Selected < 0 Or Not SelectElement(_Apps(), _Selected) : ProcedureReturn : EndIf
|
|
AppRuntime::Launch(_Apps()\ID, _Apps()\Manifest, _Apps()\Permissions)
|
|
EndProcedure
|
|
|
|
Procedure Handler_Uninstall()
|
|
If _Selected < 0 Or Not SelectElement(_Apps(), _Selected) : ProcedureReturn : EndIf
|
|
Notify::Confirm("Uninstall " + _Apps()\Name,
|
|
"Remove this app? Private storage will also be deleted.",
|
|
@_DoUninstall())
|
|
EndProcedure
|
|
|
|
Procedure _DoUninstall(Result)
|
|
If Not Result Or _Selected < 0 : ProcedureReturn : EndIf
|
|
If Not SelectElement(_Apps(), _Selected) : ProcedureReturn : EndIf
|
|
_PendingUninstall = _Apps()\ID
|
|
_Status("Uninstalling " + _Apps()\ID + "...")
|
|
HTTPRequest(#PB_HTTP_Post, "/api/apps/uninstall",
|
|
"app_id=" + URLEncoder(_Apps()\ID, #PB_UTF8) + "&keep_data=0",
|
|
@_UninstallCallback())
|
|
EndProcedure
|
|
|
|
Procedure Handler_InputOK()
|
|
Protected URL.s = Trim(GetGadgetText(_URLField))
|
|
If URL = "" : ProcedureReturn : EndIf
|
|
_CloseInstallDialog()
|
|
_Status("Installing...")
|
|
DisableGadget(_InstallBtn, #True)
|
|
HTTPRequest(#PB_HTTP_Post, "/api/apps/install",
|
|
"url=" + URLEncoder(URL, #PB_UTF8), @_InstallCallback())
|
|
EndProcedure
|
|
|
|
Procedure Handler_InputCancel()
|
|
_CloseInstallDialog()
|
|
EndProcedure
|
|
|
|
Procedure Handler_Resize()
|
|
Protected W = WindowWidth(Window)
|
|
Protected H = WindowHeight(Window)
|
|
ResizeGadget(_Canvas, 0, #Toolbar_H, W, H - #Toolbar_H - #Statusbar_H)
|
|
ResizeGadget(_StatusLabel, 4, H - #Statusbar_H + 4, W - 8, #Statusbar_H - 8)
|
|
_DrawList()
|
|
EndProcedure
|
|
|
|
Procedure Handler_Close()
|
|
_CloseInstallDialog()
|
|
UnbindGadgetEvent(_InstallBtn, @Handler_Install())
|
|
UnbindGadgetEvent(_LaunchBtn, @Handler_Launch())
|
|
UnbindGadgetEvent(_UninstallBtn, @Handler_Uninstall())
|
|
UnbindGadgetEvent(_Canvas, @Handler_Canvas())
|
|
UnbindEvent(#PB_Event_SizeWindow, @Handler_Resize(), Window)
|
|
UnbindEvent(#PB_Event_CloseWindow, @Handler_Close(), Window)
|
|
Desktop::Unregister(Window)
|
|
Window = 0
|
|
EndProcedure
|
|
|
|
EndModule
|
|
|
|
; IDE Options = SpiderBasic 3.10 (Windows - x86)
|
|
; CursorPosition = 4
|
|
; FirstLine = 30
|
|
; Folding = DAA5
|
|
; iOSAppOrientation = 0
|
|
; AndroidAppCode = 0
|
|
; AndroidAppOrientation = 0
|
|
; EnableXP
|
|
; DPIAware
|
|
; CompileSourceDirectory |