1
0
mirror of https://github.com/kepler155c/opus synced 2024-12-26 16:40:27 +00:00
opus/sys/apps/Overview.lua

553 lines
13 KiB
Lua
Raw Normal View History

2017-10-08 21:45:01 +00:00
_G.requireInjector()
2017-09-27 19:42:40 +00:00
local class = require('class')
local Config = require('config')
2017-09-27 19:42:40 +00:00
local Event = require('event')
2017-09-15 05:08:04 +00:00
local FileUI = require('ui.fileui')
2017-09-27 19:42:40 +00:00
local NFT = require('nft')
local SHA1 = require('sha1')
local Tween = require('ui.tween')
local UI = require('ui')
local Util = require('util')
2017-05-21 05:42:41 +00:00
2017-10-08 21:45:01 +00:00
local fs = _G.fs
local multishell = _ENV.multishell
local pocket = _G.pocket
local term = _G.term
local turtle = _G.turtle
2017-09-15 05:08:04 +00:00
2017-10-08 21:45:01 +00:00
local REGISTRY_DIR = 'usr/.registry'
2016-12-11 19:24:52 +00:00
multishell.setTitle(multishell.getCurrent(), 'Overview')
UI:configure('Overview', ...)
local config = {
Recent = { },
currentCategory = 'Apps',
}
Config.load('Overview', config)
2017-05-19 23:00:23 +00:00
2017-05-21 03:46:38 +00:00
local applications = { }
2017-05-19 23:00:23 +00:00
local function loadApplications()
2017-09-16 00:27:56 +00:00
local requirements = {
turtle = function() return turtle end,
advancedTurtle = function() return turtle and term.isColor() end,
pocket = function() return pocket end,
advancedPocket = function() return pocket and term.isColor() end,
advancedComputer = function() return not turtle and not pocket and term.isColor() end,
}
2017-05-21 05:42:41 +00:00
applications = Util.readTable('sys/etc/app.db')
if fs.exists(REGISTRY_DIR) then
local files = fs.list(REGISTRY_DIR)
for _,file in pairs(files) do
local app = Util.readTable(fs.combine(REGISTRY_DIR, file))
if app and app.key then
app.filename = fs.combine(REGISTRY_DIR, file)
applications[app.key] = app
end
2017-05-19 23:00:23 +00:00
end
end
2017-05-21 07:30:25 +00:00
Util.each(applications, function(v, k) v.key = k end)
2017-09-16 00:27:56 +00:00
applications = Util.filter(applications, function(_, a)
if a.disabled then
return false
end
2017-09-15 05:08:04 +00:00
2017-09-16 00:27:56 +00:00
if a.requires then
local fn = requirements[a.requires]
if fn and not fn() then
return false
end
end
2017-09-15 05:08:04 +00:00
2017-09-27 19:42:40 +00:00
return true -- Util.startsWidth(a.run, 'http') or shell.resolveProgram(a.run)
2017-09-16 00:27:56 +00:00
end)
2017-05-19 23:00:23 +00:00
end
loadApplications()
2016-12-11 19:24:52 +00:00
2017-09-27 19:42:40 +00:00
local defaultIcon = NFT.parse("\03180\031711\03180\
\031800\03171\03180\
\03171\031800\03171")
2016-12-11 19:24:52 +00:00
local sx, sy = term.current().getSize()
local maxRecent = math.ceil(sx * sy / 62)
local function elipse(s, len)
if #s > len then
s = s:sub(1, len - 2) .. '..'
end
return s
end
local buttons = { }
local categories = { }
for _,f in pairs(applications) do
if not categories[f.category] then
categories[f.category] = true
2017-10-18 23:51:55 +00:00
table.insert(buttons, { text = f.category })
2016-12-11 19:24:52 +00:00
end
end
2017-10-08 21:45:01 +00:00
table.sort(buttons, function(a, b) return a.text < b.text end)
2017-10-18 23:51:55 +00:00
table.insert(buttons, 1, { text = 'Recent' })
2016-12-11 19:24:52 +00:00
table.insert(buttons, { text = '+', event = 'new' })
local function parseIcon(iconText)
local icon
local s, m = pcall(function()
icon = NFT.parse(iconText)
if icon then
if icon.height > 3 or icon.width > 8 then
error('Invalid size')
end
end
return icon
end)
if s then
return icon
end
return s, m
end
2017-09-27 19:42:40 +00:00
UI.VerticalTabBar = class(UI.TabBar)
2017-10-11 15:37:52 +00:00
function UI.VerticalTabBar:setParent()
2017-09-27 19:42:40 +00:00
self.x = 1
self.width = 8
2017-09-30 02:30:01 +00:00
self.height = nil
self.ey = -1
2017-10-11 15:37:52 +00:00
UI.TabBar.setParent(self)
2017-09-27 19:42:40 +00:00
for k,c in pairs(self.children) do
c.x = 1
c.y = k + 1
2017-10-12 02:39:04 +00:00
c.ox, c.oy = c.x, c.y
2017-09-27 19:42:40 +00:00
c.width = 8
end
end
local cx = 9
local cy = 1
if sx < 30 then
UI.VerticalTabBar = UI.TabBar
cx = 1
cy = 2
end
2016-12-11 19:24:52 +00:00
local page = UI.Page {
2017-09-27 19:42:40 +00:00
tabBar = UI.VerticalTabBar {
2016-12-11 19:24:52 +00:00
buttons = buttons,
},
2017-10-08 03:03:18 +00:00
container = UI.Viewport {
2017-09-27 19:42:40 +00:00
x = cx,
y = cy,
2016-12-11 19:24:52 +00:00
},
notification = UI.Notification(),
accelerators = {
r = 'refresh',
e = 'edit',
2017-09-26 05:40:02 +00:00
f = 'files',
2016-12-11 19:24:52 +00:00
s = 'shell',
l = 'lua',
[ 'control-n' ] = 'new',
delete = 'delete',
},
}
UI.Icon = class(UI.Window)
2017-10-11 15:37:52 +00:00
UI.Icon.defaults = {
UIElement = 'Icon',
width = 14,
height = 4,
}
2016-12-11 19:24:52 +00:00
function UI.Icon:eventHandler(event)
if event.type == 'mouse_click' then
self:setFocus(self.button)
return true
elseif event.type == 'mouse_doubleclick' then
self:emit({ type = self.button.event, button = self.button })
elseif event.type == 'mouse_rightclick' then
self:setFocus(self.button)
self:emit({ type = 'edit', button = self.button })
end
return UI.Window.eventHandler(self, event)
end
2017-10-08 21:45:01 +00:00
function page.container:setCategory(categoryName, animate)
2016-12-11 19:24:52 +00:00
-- reset the viewport window
self.children = { }
self.offy = 0
local function filter(it, f)
local ot = { }
for _,v in pairs(it) do
if f(v) then
table.insert(ot, v)
end
end
return ot
end
local filtered
if categoryName == 'Recent' then
filtered = { }
for _,v in ipairs(config.Recent) do
2017-05-22 02:19:01 +00:00
local app = Util.find(applications, 'key', v)
if app then -- and fs.exists(app.run) then
2016-12-11 19:24:52 +00:00
table.insert(filtered, app)
end
end
else
2017-10-08 21:45:01 +00:00
filtered = filter(applications, function(a)
2016-12-11 19:24:52 +00:00
return a.category == categoryName -- and fs.exists(a.run)
end)
table.sort(filtered, function(a, b) return a.title < b.title end)
end
for _,program in ipairs(filtered) do
local icon
if program.icon then
icon = parseIcon(program.icon)
end
if not icon then
icon = defaultIcon
end
local title = elipse(program.title, 8)
local width = math.max(icon.width + 2, #title + 2)
table.insert(self.children, UI.Icon({
width = width,
image = UI.NftImage({
x = math.floor((width - icon.width) / 2) + 1,
image = icon,
width = 5,
height = 3,
}),
button = UI.Button({
x = math.floor((width - #title - 2) / 2) + 1,
y = 4,
text = title,
backgroundColor = self.backgroundColor,
2017-11-15 05:08:42 +00:00
backgroundFocusColor = colors.gray,
textColor = colors.white,
textFocusColor = colors.white,
2016-12-11 19:24:52 +00:00
width = #title + 2,
event = 'button',
app = program,
}),
}))
end
local gutter = 2
if UI.term.width <= 26 then
gutter = 1
end
local col, row = gutter, 2
local count = #self.children
local r = math.random(1, 5)
2016-12-11 19:24:52 +00:00
-- reposition all children
for k,child in ipairs(self.children) do
2017-10-03 04:50:54 +00:00
if r == 1 then
child.x = math.random(1, self.width)
child.y = math.random(1, self.height)
elseif r == 2 then
child.x = self.width
child.y = self.height
elseif r == 3 then
child.x = math.floor(self.width / 2)
child.y = math.floor(self.height / 2)
elseif r == 4 then
child.x = self.width - col
child.y = row
elseif r == 5 then
child.x = col
child.y = row
if k == #self.children then
child.x = self.width
child.y = self.height
end
2017-10-03 04:50:54 +00:00
end
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
2016-12-11 19:24:52 +00:00
2017-10-08 21:45:01 +00:00
if not animate then
child.x = col
child.y = row
end
2016-12-11 19:24:52 +00:00
if k < count then
col = col + child.width
2017-09-27 19:42:40 +00:00
if col + self.children[k + 1].width + gutter - 2 > self.width then
2016-12-11 19:24:52 +00:00
col = gutter
row = row + 5
end
end
end
self:initChildren()
2017-10-08 21:45:01 +00:00
if animate then -- need to fix transitions under layers
local function transition(args)
local i = 1
return function(device)
self:clear()
for _,child in pairs(self.children) do
child.tween:update(1)
child.x = math.floor(child.x)
child.y = math.floor(child.y)
child:draw()
end
args.canvas:blit(device, args, args)
i = i + 1
return i < 7
end
2016-12-15 18:32:33 +00:00
end
2017-10-08 21:45:01 +00:00
self:addTransition(transition)
2016-12-15 18:32:33 +00:00
end
2016-12-11 19:24:52 +00:00
end
function page:refresh()
local pos = self.container.offy
self:focusFirst(self)
2017-10-08 21:45:01 +00:00
self.container:setCategory(config.currentCategory)
2016-12-11 19:24:52 +00:00
self.container:setScrollPosition(pos)
end
function page:resize()
UI.Page.resize(self)
2017-10-05 17:07:48 +00:00
self:refresh()
2016-12-11 19:24:52 +00:00
end
function page:eventHandler(event)
2017-10-18 23:51:55 +00:00
if event.type == 'tab_select' then
2017-10-08 21:45:01 +00:00
self.container:setCategory(event.button.text, true)
2016-12-11 19:24:52 +00:00
self.container:draw()
2016-12-15 18:32:33 +00:00
2016-12-11 19:24:52 +00:00
config.currentCategory = event.button.text
Config.update('Overview', config)
elseif event.type == 'button' then
for k,v in ipairs(config.Recent) do
2017-05-22 02:19:01 +00:00
if v == event.button.app.key then
2016-12-11 19:24:52 +00:00
table.remove(config.Recent, k)
break
end
end
2017-05-22 02:19:01 +00:00
table.insert(config.Recent, 1, event.button.app.key)
2016-12-11 19:24:52 +00:00
if #config.Recent > maxRecent then
table.remove(config.Recent, maxRecent + 1)
end
Config.update('Overview', config)
multishell.openTab({
2017-09-15 05:08:04 +00:00
title = event.button.app.title,
2017-05-20 22:27:26 +00:00
path = 'sys/apps/shell',
2016-12-11 19:24:52 +00:00
args = { event.button.app.run },
focused = true,
})
elseif event.type == 'shell' then
multishell.openTab({
2017-05-20 22:27:26 +00:00
path = 'sys/apps/shell',
2016-12-11 19:24:52 +00:00
focused = true,
})
elseif event.type == 'lua' then
multishell.openTab({
2017-05-20 22:27:26 +00:00
path = 'sys/apps/Lua.lua',
2016-12-11 19:24:52 +00:00
focused = true,
})
2017-09-26 05:40:02 +00:00
elseif event.type == 'files' then
multishell.openTab({
path = 'sys/apps/Files.lua',
focused = true,
})
2016-12-11 19:24:52 +00:00
elseif event.type == 'focus_change' then
if event.focused.parent.UIElement == 'Icon' then
event.focused.parent:scrollIntoView()
end
2017-10-08 21:45:01 +00:00
elseif event.type == 'refresh' then -- remove this after fixing notification
2017-05-19 23:00:23 +00:00
loadApplications()
2016-12-11 19:24:52 +00:00
self:refresh()
self:draw()
self.notification:success('Refreshed')
elseif event.type == 'delete' then
local focused = page:getFocused()
if focused.app then
2017-05-21 07:30:25 +00:00
focused.app.disabled = true
local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key)
Util.writeTable(filename, focused.app)
2017-05-19 23:00:23 +00:00
loadApplications()
page:refresh()
page:draw()
self.notification:success('Removed')
2016-12-11 19:24:52 +00:00
end
elseif event.type == 'new' then
local category = 'Apps'
if config.currentCategory ~= 'Recent' then
category = config.currentCategory or 'Apps'
end
UI:setPage('editor', { category = category })
elseif event.type == 'edit' then
local focused = page:getFocused()
if focused.app then
UI:setPage('editor', focused.app)
end
else
UI.Page.eventHandler(self, event)
end
return true
end
2017-09-28 22:52:57 +00:00
local formWidth = math.max(UI.term.width - 8, 26)
2016-12-23 04:22:04 +00:00
2016-12-27 03:26:43 +00:00
local editor = UI.Dialog {
2016-12-23 04:22:04 +00:00
height = 11,
width = formWidth,
2017-10-06 07:07:24 +00:00
title = 'Edit Application',
2016-12-27 03:26:43 +00:00
form = UI.Form {
y = 2,
height = 9,
title = UI.TextEntry {
formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
required = true,
},
run = UI.TextEntry {
formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application',
required = true,
},
category = UI.TextEntry {
formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
required = true,
},
loadIcon = UI.Button {
2017-10-08 21:45:01 +00:00
x = 11, y = 6,
2016-12-27 03:26:43 +00:00
text = 'Icon', event = 'loadIcon', help = 'Select icon'
},
image = UI.NftImage {
2017-10-03 04:50:54 +00:00
y = 6, x = 2, height = 3, width = 8,
2016-12-11 19:24:52 +00:00
},
2016-12-23 04:22:04 +00:00
},
2016-12-11 19:24:52 +00:00
statusBar = UI.StatusBar(),
iconFile = '',
2016-12-23 04:22:04 +00:00
}
2016-12-11 19:24:52 +00:00
function editor:enable(app)
if app then
2016-12-27 03:26:43 +00:00
self.form:setValues(app)
2016-12-11 19:24:52 +00:00
local icon
if app.icon then
icon = parseIcon(app.icon)
end
2016-12-27 03:26:43 +00:00
self.form.image:setImage(icon)
2016-12-11 19:24:52 +00:00
end
2017-10-03 04:50:54 +00:00
UI.Dialog.enable(self)
2016-12-23 04:22:04 +00:00
self:focusFirst()
2016-12-11 19:24:52 +00:00
end
2016-12-27 03:26:43 +00:00
function editor.form.image:draw()
2016-12-11 19:24:52 +00:00
self:clear()
UI.NftImage.draw(self)
end
2016-12-27 03:26:43 +00:00
function editor:updateApplications(app)
2017-05-21 07:30:25 +00:00
if not app.key then
app.key = SHA1.sha1(app.title)
2016-12-11 19:24:52 +00:00
end
2017-05-21 07:30:25 +00:00
local filename = app.filename or fs.combine(REGISTRY_DIR, app.key)
Util.writeTable(filename, app)
2017-05-19 23:00:23 +00:00
loadApplications()
2016-12-11 19:24:52 +00:00
end
function editor:eventHandler(event)
2016-12-27 03:26:43 +00:00
if event.type == 'form_cancel' or event.type == 'cancel' then
2016-12-11 19:24:52 +00:00
UI:setPreviousPage()
elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help or '')
self.statusBar:draw()
elseif event.type == 'loadIcon' then
2016-12-23 04:22:04 +00:00
local fileui = FileUI({
x = self.x,
y = self.y,
z = 2,
width = self.width,
height = self.height,
})
2016-12-15 18:32:33 +00:00
UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName)
2016-12-11 19:24:52 +00:00
if fileName then
self.iconFile = fileName
local s, m = pcall(function()
local iconLines = Util.readFile(fileName)
if not iconLines then
error('Unable to load file')
end
local icon, m = parseIcon(iconLines)
if not icon then
error(m)
end
2016-12-27 03:26:43 +00:00
self.form.values.icon = iconLines
self.form.image:setImage(icon)
self.form.image:draw()
2016-12-11 19:24:52 +00:00
end)
if not s and m then
2016-12-23 04:22:04 +00:00
local msg = m:gsub('.*: (.*)', '%1')
2016-12-27 03:26:43 +00:00
page.notification:error(msg)
2016-12-11 19:24:52 +00:00
end
end
end)
2016-12-27 03:26:43 +00:00
elseif event.type == 'form_invalid' then
page.notification:error(event.message)
elseif event.type == 'form_complete' then
local values = self.form.values
UI:setPreviousPage()
self:updateApplications(values)
page:refresh()
page:draw()
2016-12-11 19:24:52 +00:00
else
2017-10-03 04:50:54 +00:00
return UI.Dialog.eventHandler(self, event)
2016-12-11 19:24:52 +00:00
end
return true
end
UI:setPages({
editor = editor,
main = page,
})
2017-07-28 23:01:59 +00:00
Event.on('os_register_app', function()
2017-05-19 23:00:23 +00:00
loadApplications()
2016-12-11 19:24:52 +00:00
page:refresh()
page:draw()
page:sync()
end)
page.tabBar:selectTab(config.currentCategory or 'Apps')
page.container:setCategory(config.currentCategory or 'Apps')
UI:setPage(page)
2017-10-08 21:45:01 +00:00
UI:pullEvents()