local Ansi = require('opus.ansi') local Config = require('opus.config') local Packages = require('opus.packages') local UI = require('opus.ui') local Util = require('opus.util') local colors = _G.colors local term = _G.term UI:configure('PackageManager', ...) local config = Config.load('package') local page = UI.Page { grid = UI.ScrollingGrid { x = 2, ex = 14, y = 2, ey = -6, values = { }, columns = { { heading = ' Package', key = 'displayName' }, }, sortColumn = 'name', autospace = true, help = 'Space to select, Enter to toggle', }, installSelected = UI.Button { x = 2, y = -3, text = ' + ', event = 'batch_action', operation = 'install', operationText = 'Install', help = 'Install or update selected', }, removeSelected = UI.Button { x = 8, y = -3, text = ' - ', event = 'batch_action', operation = 'uninstall', operationText = 'Remove', help = 'Remove selected', }, updateall = UI.Button { ex = -2, y = -3, width = 12, text = 'Update All', event = 'updateall', help = 'Update all installed packages', }, description = UI.TextArea { x = 16, y = 3, ey = -5, marginRight = 2, marginLeft = 0, }, UI.Checkbox { x = 3, y = -5, label = 'Compress', textColor = 'yellow', backgroundColor = 'primary', value = config.compression, help = 'Compress packages (experimental)', }, action = UI.SlideOut { titleBar = UI.TitleBar { event = 'hide-action', }, button = UI.Button { x = -10, y = 3, text = ' Begin ', event = 'begin', }, output = UI.Embedded { y = 5, ey = -2, x = 2, ex = -2, visible = true, }, }, statusBar = UI.StatusBar { }, accelerators = { [ 'control-q' ] = 'quit', }, } function page:loadPackages() self.grid.values = { } self.statusBar:setStatus('Downloading...') self:sync() for k in pairs(Packages:list()) do local manifest = Packages:getManifest(k) if not manifest then manifest = { invalid = true, description = 'Unable to download manifest', title = '', } end table.insert(self.grid.values, { installed = not not Packages:isInstalled(k), selected = false, name = k, displayName = k, manifest = manifest, }) end self.grid:update() self.grid:setIndex(1) self.grid:emit({ type = 'grid_focus_row', selected = self.grid:getSelected(), element = self.grid, }) self.statusBar:setStatus('Updated packages') end function page.grid:getRowTextColor(row, selected) if row.selected then return colors.cyan end if row.installed then return colors.yellow end return UI.Grid.getRowTextColor(self, row, selected) end function page:getSelectedPackages() local selected = { } for _, row in pairs(self.grid.values) do if row.selected then table.insert(selected, row) end end return selected end function page:getSelectedCount() local count = 0 for _, row in pairs(self.grid.values) do if row.selected then count = count + 1 end end return count end function page:toggleSelection(row) row.selected = not row.selected row.displayName = row.selected and ('\187 ' .. row.name) or row.name self.grid:draw() self:updateStatus() end function page:clearSelection() for _, row in pairs(self.grid.values) do row.selected = false row.displayName = row.name end self.grid:draw() end function page:updateStatus() local count = self:getSelectedCount() if count > 0 then self.statusBar:setStatus(count .. ' package(s) selected') else local focused = self.grid:getSelected() if focused then self.statusBar:setStatus(focused.installed and 'Installed' or '') end end end function page.action:show() self.output.win:clear() UI.SlideOut.show(self) end function page:run(operation, name) local oterm = term.redirect(self.action.output.win) self.action.output:clear() local cmd = string.format('package %s %s', operation, name) term.setCursorPos(1, 1) term.clear() term.setTextColor(colors.yellow) print(cmd .. '\n') term.setTextColor(colors.white) local s, m = Util.run(_ENV, '/sys/apps/package.lua', operation, name) if not s and m then _G.printError(m) end term.redirect(oterm) self.action.output:draw() end function page:updateSelection(selected) -- no-op: buttons are always active for batch operations end function page:eventHandler(event) if event.type == 'focus_change' then self.statusBar:setStatus(event.focused.help) elseif event.type == 'grid_focus_row' then local manifest = event.selected.manifest self.description:setValue(string.format('%s%s\n\n%s%s', Ansi.yellow, manifest.title, Ansi.white, manifest.description)) self.description:draw() self:updateStatus() elseif event.type == 'grid_select' then -- Space or Enter toggles selection local row = self.grid:getSelected() if row then self:toggleSelection(row) end elseif event.type == 'checkbox_change' then config.compression = not config.compression Config.update('package', config) elseif event.type == 'updateall' then self.operation = 'updateall' self.operationTargets = { } self.action.button.text = ' Begin ' self.action.button.event = 'begin' self.action.titleBar.title = 'Update All' self.action:show() elseif event.type == 'batch_action' then local targets = self:getSelectedPackages() local operation = event.button.operation -- fall back to focused row if nothing selected if #targets == 0 then local focused = self.grid:getSelected() if focused then targets = { focused } end end if #targets == 0 then return end -- for install: update already-installed packages, install new ones if operation == 'install' then self.operation = 'install' else -- filter to only installed packages for uninstall local installed = { } for _, t in ipairs(targets) do if t.installed then table.insert(installed, t) end end targets = installed if #targets == 0 then return end self.operation = 'uninstall' end self.operationTargets = targets local title = #targets == 1 and targets[1].manifest.title or (#targets .. ' packages') self.action.button.text = ' Begin ' self.action.button.event = 'begin' self.action.titleBar.title = string.format('%s %s', operation == 'install' and 'Install/Update' or 'Remove', title) self.action:show() elseif event.type == 'hide-action' then self.action:hide() elseif event.type == 'begin' then if self.operation == 'updateall' then self:run('updateall', '') else for _, target in ipairs(self.operationTargets) do local op = self.operation if op == 'install' and target.installed then op = 'update' end self:run(op, target.name) target.installed = Packages:isInstalled(target.name) end self:clearSelection() self:updateStatus() end self.action.button.text = ' Done ' self.action.button.event = 'hide-action' self.action.button:draw() elseif event.type == 'quit' then UI:quit() end UI.Page.eventHandler(self, event) end UI:setPage(page) page.statusBar:setStatus('Downloading...') page:sync() Packages:downloadList() page:loadPackages() page:sync() UI:start()