custom help files + redo System UI

This commit is contained in:
kepler155c@gmail.com 2018-12-22 22:11:16 -05:00
parent 3de03bef22
commit 26564cbcc1
15 changed files with 541 additions and 292 deletions

View File

@ -7,6 +7,7 @@ local Config = { }
function Config.load(fname, data)
local filename = 'usr/config/' .. fname
data = data or { }
if not fs.exists('usr/config') then
fs.makeDir('usr/config')

42
sys/apis/turtle/home.lua Normal file
View File

@ -0,0 +1,42 @@
local Config = require('config')
local GPS = require('gps')
local turtle = _G.turtle
local Home = { }
function Home.go()
local config = { }
Config.load('gps', config)
if config.home then
if turtle.enableGPS() then
return turtle.pathfind(config.home)
end
end
end
function Home.set()
local config = { }
Config.load('gps', config)
local pt = GPS.getPoint()
if pt then
local originalHeading = turtle.point.heading
local heading = GPS.getHeading()
if heading then
local turns = (turtle.point.heading - originalHeading) % 4
pt.heading = (heading - turns) % 4
config.home = pt
Config.update('gps', config)
pt = GPS.getPoint()
pt.heading = heading
turtle.setPoint(pt, true)
turtle._goto(config.home)
return config.home
end
end
end
return Home

View File

@ -2250,6 +2250,13 @@ function UI.Tabs:add(children)
end
end
function UI.Tabs:selectTab(tab)
local menuItem = Util.find(self.tabBar:getFocusables(), 'tabUid', tab.uid)
if menuItem then
self.tabBar:emit({ type = 'tab_select', button = { uid = menuItem.uid } })
end
end
function UI.Tabs:enable()
self.enabled = true
self.tabBar:enable()
@ -2999,6 +3006,7 @@ function UI.Chooser:eventHandler(event)
if event.key == 'right' or event.key == 'space' then
local _,k = Util.find(self.choices, 'value', self.value)
local choice
if not k then k = 1 end
if k and k < #self.choices then
choice = self.choices[k+1]
else
@ -3021,7 +3029,7 @@ function UI.Chooser:eventHandler(event)
self:draw()
return true
end
elseif event.type == 'mouse_click' then
elseif event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then
if event.x == 1 then
self:emit({ type = 'key', key = 'left' })
return true

View File

@ -1,16 +1,9 @@
_G.requireInjector(_ENV)
local Config = require('config')
local UI = require('ui')
local Util = require('util')
local Config = require('config')
local Security = require('security')
local SHA1 = require('sha1')
local UI = require('ui')
local Util = require('util')
local fs = _G.fs
local os = _G.os
local settings = _G.settings
local shell = _ENV.shell
local turtle = _G.turtle
local fs = _G.fs
local shell = _ENV.shell
UI:configure('System', ...)
@ -23,116 +16,18 @@ Config.load('shell', env)
local systemPage = UI.Page {
tabs = UI.Tabs {
pathTab = UI.Window {
tabTitle = 'Path',
entry = UI.TextEntry {
x = 2, y = 2, ex = -2,
limit = 256,
value = shell.path(),
shadowText = 'enter system path',
accelerators = {
enter = 'update_path',
},
},
settings = UI.Window {
tabTitle = 'Category',
grid = UI.Grid {
y = 4,
disableHeader = true,
columns = { { key = 'value' } },
y = 2,
columns = {
{ heading = 'Name', key = 'name' },
{ heading = 'Description', key = 'description' },
},
sortColumn = 'name',
autospace = true,
},
},
aliasTab = UI.Window {
tabTitle = 'Alias',
alias = UI.TextEntry {
x = 2, y = 2, ex = -2,
limit = 32,
shadowText = 'Alias',
},
path = UI.TextEntry {
y = 3, x = 2, ex = -2,
limit = 256,
shadowText = 'Program path',
accelerators = {
enter = 'new_alias',
},
},
grid = UI.Grid {
y = 5,
sortColumn = 'alias',
columns = {
{ heading = 'Alias', key = 'alias' },
{ heading = 'Program', key = 'path' },
},
accelerators = {
delete = 'delete_alias',
},
},
},
passwordTab = UI.Window {
tabTitle = 'Password',
oldPass = UI.TextEntry {
x = 2, y = 2, ex = -2,
limit = 32,
mask = true,
shadowText = 'old password',
inactive = not Security.getPassword(),
},
newPass = UI.TextEntry {
y = 3, x = 2, ex = -2,
limit = 32,
mask = true,
shadowText = 'new password',
accelerators = {
enter = 'new_password',
},
},
button = UI.Button {
x = 2, y = 5,
text = 'Update',
event = 'update_password',
},
info = UI.TextArea {
x = 2, ex = -2,
y = 7,
inactive = true,
value = 'Add a password to enable other computers to connect to this one.',
}
},
infoTab = UI.Window {
tabTitle = 'Info',
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, ex = -4,
limit = 32,
value = os.getComputerLabel(),
accelerators = {
enter = 'update_label',
},
},
grid = UI.ScrollingGrid {
y = 3,
values = {
{ name = '', value = '' },
{ name = 'CC version', value = Util.getVersion() },
{ name = 'Lua version', value = _VERSION },
{ name = 'MC version', value = Util.getMinecraftVersion() },
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
{ name = 'Day', value = tostring(os.day()) },
},
inactive = true,
columns = {
{ key = 'name', width = 12 },
{ key = 'value' },
},
},
},
},
notification = UI.Notification(),
accelerators = {
@ -140,177 +35,15 @@ local systemPage = UI.Page {
},
}
if turtle then
pcall(function()
local Home = require('turtle.home')
-- TODO: dont rely on turtle.home
local values = { }
Config.load('gps', values.home and { values.home } or { })
systemPage.tabs:add({
gpsTab = UI.Window {
tabTitle = 'GPS',
labelText = UI.Text {
x = 3, y = 2,
value = 'On restart, return to this location'
},
grid = UI.Grid {
x = 3, ex = -3, y = 4,
height = 2,
values = values,
inactive = true,
columns = {
{ heading = 'x', key = 'x' },
{ heading = 'y', key = 'y' },
{ heading = 'z', key = 'z' },
},
},
button1 = UI.Button {
x = 3, y = 7,
text = 'Set home',
event = 'gps_set',
},
button2 = UI.Button {
ex = -3, y = 7, width = 7,
text = 'Clear',
event = 'gps_clear',
},
},
})
function systemPage.tabs.gpsTab:eventHandler(event)
if event.type == 'gps_set' then
systemPage.notification:info('Determining location', 10)
systemPage:sync()
if Home.set() then
Config.load('gps', values)
self.grid:setValues(values.home and { values.home } or { })
self.grid:draw()
systemPage.notification:success('Location set')
else
systemPage.notification:error('Unable to determine location')
end
return true
elseif event.type == 'gps_clear' then
fs.delete('usr/config/gps')
self.grid:setValues({ })
self.grid:draw()
return true
end
function systemPage.tabs.settings:eventHandler(event)
if event.type == 'grid_select' then
local tab = event.selected.tab
if not systemPage.tabs[tab.tabTitle] then
systemPage.tabs:add({ [ tab.tabTitle ] = tab })
tab:disable()
end
end)
end
if settings then
local values = { }
for _,v in pairs(settings.getNames()) do
local value = settings.get(v)
if not value then
value = false
end
table.insert(values, {
name = v,
value = value,
})
end
systemPage.tabs:add({
settingsTab = UI.Window {
tabTitle = 'Settings',
grid = UI.Grid {
y = 1,
values = values,
autospace = true,
sortColumn = 'name',
columns = {
{ heading = 'Setting', key = 'name' },
{ heading = 'Value', key = 'value' },
},
},
}
})
function systemPage.tabs.settingsTab:eventHandler(event)
if event.type == 'grid_select' then
if not event.selected.value or type(event.selected.value) == 'boolean' then
event.selected.value = not event.selected.value
end
settings.set(event.selected.name, event.selected.value)
settings.save('.settings')
self.grid:draw()
return true
end
end
end
function systemPage.tabs.pathTab.grid:draw()
self.values = { }
for _,v in ipairs(Util.split(env.path, '(.-):')) do
table.insert(self.values, { value = v })
end
self:update()
UI.Grid.draw(self)
end
function systemPage.tabs.pathTab:eventHandler(event)
if event.type == 'update_path' then
env.path = self.entry.value
self.grid:setIndex(self.grid:getIndex())
self.grid:draw()
Config.update('shell', env)
systemPage.notification:success('reboot to take effect')
return true
end
end
function systemPage.tabs.aliasTab.grid:draw()
self.values = { }
for k,v in pairs(env.aliases) do
table.insert(self.values, { alias = k, path = v })
end
self:update()
UI.Grid.draw(self)
end
function systemPage.tabs.aliasTab:eventHandler(event)
if event.type == 'delete_alias' then
env.aliases[self.grid:getSelected().alias] = nil
self.grid:setIndex(self.grid:getIndex())
self.grid:draw()
Config.update('shell', env)
systemPage.notification:success('reboot to take effect')
return true
elseif event.type == 'new_alias' then
env.aliases[self.alias.value] = self.path.value
self.alias:reset()
self.path:reset()
self:draw()
self:setFocus(self.alias)
Config.update('shell', env)
systemPage.notification:success('reboot to take effect')
return true
end
end
function systemPage.tabs.passwordTab:eventHandler(event)
if event.type == 'update_password' then
if #self.newPass.value == 0 then
systemPage.notification:error('Invalid password')
elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then
systemPage.notification:error('Passwords do not match')
else
Security.updatePassword(SHA1.sha1(self.newPass.value))
self.oldPass.inactive = false
systemPage.notification:success('Password updated')
end
return true
end
end
function systemPage.tabs.infoTab:eventHandler(event)
if event.type == 'update_label' then
os.setComputerLabel(self.label.value)
systemPage.notification:success('Label updated')
systemPage.tabs:selectTab(tab)
self.parent:draw()
return true
end
end
@ -318,13 +51,43 @@ end
function systemPage:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
elseif event.type == 'success_message' then
self.notification:success(event.message)
elseif event.type == 'info_message' then
self.notification:info(event.message)
elseif event.type == 'error_message' then
self.notification:error(event.message)
elseif event.type == 'tab_activate' then
event.activated:focusFirst()
else
return UI.Page.eventHandler(self, event)
end
return true
end
local function loadDirectory(dir)
local plugins = { }
for _, file in pairs(fs.list(dir)) do
local s, m = Util.run(_ENV, fs.combine(dir, file))
if not s and m then
_G.printError('Error loading: ' .. file)
error(m or 'Unknown error')
elseif s and m then
table.insert(plugins, { tab = m, name = m.tabTitle, description = m.description })
end
end
return plugins
end
local programDir = fs.getDir(shell.getRunningProgram())
local plugins = loadDirectory(fs.combine(programDir, 'system'), { })
systemPage.tabs.settings.grid:setValues(plugins)
UI:setPage(systemPage)
UI:pullEvents()

View File

@ -0,0 +1,66 @@
local Config = require('config')
local UI = require('ui')
local aliasTab = UI.Window {
tabTitle = 'Aliases',
description = 'Shell aliases',
alias = UI.TextEntry {
x = 2, y = 2, ex = -2,
limit = 32,
shadowText = 'Alias',
},
path = UI.TextEntry {
y = 3, x = 2, ex = -2,
limit = 256,
shadowText = 'Program path',
accelerators = {
enter = 'new_alias',
},
},
grid = UI.Grid {
y = 5,
sortColumn = 'alias',
columns = {
{ heading = 'Alias', key = 'alias' },
{ heading = 'Program', key = 'path' },
},
accelerators = {
delete = 'delete_alias',
},
},
}
function aliasTab.grid:draw()
self.values = { }
local env = Config.load('shell')
for k,v in pairs(env.aliases) do
table.insert(self.values, { alias = k, path = v })
end
self:update()
UI.Grid.draw(self)
end
function aliasTab:eventHandler(event)
if event.type == 'delete_alias' then
local env = Config.load('shell')
env.aliases[self.grid:getSelected().alias] = nil
self.grid:setIndex(self.grid:getIndex())
self.grid:draw()
Config.update('shell', env)
self:emit({ type = 'success_message', message = 'reboot to take effect' })
return true
elseif event.type == 'new_alias' then
local env = Config.load('shell')
env.aliases[self.alias.value] = self.path.value
self.alias:reset()
self.path:reset()
self:draw()
self:setFocus(self.alias)
Config.update('shell', env)
self:emit({ type = 'success_message', message = 'reboot to take effect' })
return true
end
end
return aliasTab

49
sys/apps/system/label.lua Normal file
View File

@ -0,0 +1,49 @@
local UI = require('ui')
local Util = require('util')
local fs = _G.fs
local os = _G.os
local labelTab = UI.Window {
tabTitle = 'Label',
description = 'Set the computer label',
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, ex = -4,
limit = 32,
value = os.getComputerLabel(),
accelerators = {
enter = 'update_label',
},
},
grid = UI.ScrollingGrid {
y = 3,
values = {
{ name = '', value = '' },
{ name = 'CC version', value = Util.getVersion() },
{ name = 'Lua version', value = _VERSION },
{ name = 'MC version', value = Util.getMinecraftVersion() },
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
{ name = 'Day', value = tostring(os.day()) },
},
inactive = true,
columns = {
{ key = 'name', width = 12 },
{ key = 'value' },
},
},
}
function labelTab:eventHandler(event)
if event.type == 'update_label' then
os.setComputerLabel(self.label.value)
self:emit({ type = 'success_message', message = 'Label updated' })
return true
end
end
return labelTab

View File

@ -0,0 +1,56 @@
local Config = require('config')
local UI = require('ui')
local device = _G.device
local tab = UI.Window {
tabTitle = 'Network',
description = 'Networking options',
form = UI.Form {
x = 2,
manualControls = true,
modem = UI.Chooser {
formLabel = 'Modem', formKey = 'modem',
nochoice = 'auto',
},
update = UI.Button {
x = 9, y = 4,
text = 'Update', event = 'form_complete',
},
},
}
function tab:enable()
local width = 7
local choices = {
{ name = 'auto', value = 'auto' },
{ name = 'disable', value = 'none' },
}
for k,v in pairs(device) do
if v.isWireless and v.isWireless() and k ~= 'wireless_modem' then
table.insert(choices, { name = k, value = v.name })
width = math.max(width, #k)
end
end
self.form.modem.choices = choices
self.form.modem.width = width + 4
local config = Config.load('os')
self.form.modem.value = config.wirelessModem or 'auto'
UI.Window.enable(self)
end
function tab:eventHandler(event)
if event.type == 'form_complete' then
local config = Config.load('os')
config.wirelessModem = self.form.modem.value
Config.update('os', config)
self:emit({ type = 'success_message', message = 'reboot to take effect' })
return true
end
end
return tab

View File

@ -0,0 +1,53 @@
local Security = require('security')
local SHA1 = require('sha1')
local UI = require('ui')
local passwordTab = UI.Window {
tabTitle = 'Password',
description = 'Wireless network password',
oldPass = UI.TextEntry {
x = 2, y = 2, ex = -2,
limit = 32,
mask = true,
shadowText = 'old password',
inactive = not Security.getPassword(),
},
newPass = UI.TextEntry {
y = 3, x = 2, ex = -2,
limit = 32,
mask = true,
shadowText = 'new password',
accelerators = {
enter = 'new_password',
},
},
button = UI.Button {
x = 2, y = 5,
text = 'Update',
event = 'update_password',
},
info = UI.TextArea {
x = 2, ex = -2,
y = 7,
inactive = true,
value = 'Add a password to enable other computers to connect to this one.',
}
}
function passwordTab:eventHandler(event)
if event.type == 'update_password' then
if #self.newPass.value == 0 then
self:emit({ type = 'error_message', message = 'Invalid password' })
elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then
self:emit({ type = 'error_message', message = 'Passwords do not match' })
else
Security.updatePassword(SHA1.sha1(self.newPass.value))
self.oldPass.inactive = false
self:emit({ type = 'success_message', message = 'Password updated' })
end
return true
end
end
return passwordTab

50
sys/apps/system/path.lua Normal file
View File

@ -0,0 +1,50 @@
local Config = require('config')
local UI = require('ui')
local Util = require('util')
local shell = _ENV.shell
local pathTab = UI.Window {
tabTitle = 'Path',
description = 'Set the shell path',
tabClose = true,
entry = UI.TextEntry {
x = 2, y = 2, ex = -2,
limit = 256,
value = shell.path(),
shadowText = 'enter system path',
accelerators = {
enter = 'update_path',
},
},
grid = UI.Grid {
y = 4,
disableHeader = true,
columns = { { key = 'value' } },
autospace = true,
},
}
function pathTab.grid:draw()
self.values = { }
local env = Config.load('shell')
for _,v in ipairs(Util.split(env.path, '(.-):')) do
table.insert(self.values, { value = v })
end
self:update()
UI.Grid.draw(self)
end
function pathTab:eventHandler(event)
if event.type == 'update_path' then
local env = Config.load('shell')
env.path = self.entry.value
self.grid:setIndex(self.grid:getIndex())
self.grid:draw()
Config.update('shell', env)
self:emit({ type = 'success_message', message = 'reboot to take effect' })
return true
end
end
return pathTab

View File

@ -0,0 +1,47 @@
local UI = require('ui')
local settings = _G.settings
if settings then
local values = { }
for _,v in pairs(settings.getNames()) do
local value = settings.get(v)
if not value then
value = false
end
table.insert(values, {
name = v,
value = value,
})
end
local settingsTab = UI.Window {
tabTitle = 'Settings',
description = 'Computercraft configurable settings',
grid = UI.Grid {
y = 1,
values = values,
autospace = true,
sortColumn = 'name',
columns = {
{ heading = 'Setting', key = 'name' },
{ heading = 'Value', key = 'value' },
},
},
}
function settingsTab:eventHandler(event)
if event.type == 'grid_select' then
if not event.selected.value or type(event.selected.value) == 'boolean' then
event.selected.value = not event.selected.value
end
settings.set(event.selected.name, event.selected.value)
settings.save('.settings')
self.grid:draw()
return true
end
end
return settingsTab
end

View File

@ -0,0 +1,62 @@
local Config = require('config')
local UI = require('ui')
local fs = _G.fs
local turtle = _G.turtle
if turtle then
local Home = require('turtle.home')
local values = { }
Config.load('gps', values.home and { values.home } or { })
local gpsTab = UI.Window {
tabTitle = 'GPS',
labelText = UI.Text {
x = 3, y = 2,
value = 'On restart, return to this location'
},
grid = UI.Grid {
x = 3, ex = -3, y = 4,
height = 2,
values = values,
inactive = true,
columns = {
{ heading = 'x', key = 'x' },
{ heading = 'y', key = 'y' },
{ heading = 'z', key = 'z' },
},
},
button1 = UI.Button {
x = 3, y = 7,
text = 'Set home',
event = 'gps_set',
},
button2 = UI.Button {
ex = -3, y = 7, width = 7,
text = 'Clear',
event = 'gps_clear',
},
}
function gpsTab:eventHandler(event)
if event.type == 'gps_set' then
self:emit({ type = 'info_message', message = 'Determining location' })
self:sync()
if Home.set() then
Config.load('gps', values)
self.grid:setValues(values.home and { values.home } or { })
self.grid:draw()
self:emit({ type = 'success_message', message = 'Location set' })
else
self:emit({ type = 'error_message', message = 'Unable to determine location' })
end
return true
elseif event.type == 'gps_clear' then
fs.delete('usr/config/gps')
self.grid:setValues({ })
self.grid:draw()
return true
end
end
return gpsTab
end

View File

@ -18,8 +18,12 @@ end
local function setModem(dev)
if not device.wireless_modem and dev.isWireless() then
local config = Config.load('os', { })
if not config.wirelessModem or dev.name == config.wirelessModem then
local config = Config.load('os')
if not config.wirelessModem or
config.wirelessModem == 'auto' or
dev.name == config.wirelessModem then
device.wireless_modem = dev
os.queueEvent('device_attach', 'wireless_modem')
return dev

View File

@ -3,8 +3,9 @@ _G.requireInjector(_ENV)
local Packages = require('packages')
local Util = require('util')
local fs = _G.fs
local help = _G.help
local shell = _ENV.shell
local fs = _G.fs
local appPaths = Util.split(shell.path(), '(.-);')
local luaPaths = Util.split(_G.LUA_PATH, '(.-);')
@ -26,6 +27,9 @@ end
-- dependency graph
-- https://github.com/mpeterv/depgraph/blob/master/src/depgraph/init.lua
local helpPaths = Util.split(help.path(), '(.-):')
table.insert(helpPaths, '/sys/help')
for name in pairs(Packages:installed()) do
local packageDir = fs.combine('packages', name)
if fs.exists(fs.combine(packageDir, '.install')) then
@ -40,7 +44,14 @@ for name in pairs(Packages:installed()) do
if fs.exists(apiPath) then
addPath(luaPaths, apiPath)
end
local helpPath = fs.combine(fs.combine('packages', name), 'help')
if fs.exists(helpPath) then
table.insert(helpPaths, helpPath)
end
end
help.setPath(table.concat(helpPaths, ':'))
shell.setPath(table.concat(appPaths, ':'))
_G.LUA_PATH = table.concat(luaPaths, ';')

View File

@ -0,0 +1,30 @@
Opus applications are grouped into packages with a common theme.
To install a package, use either the System -> Packages program or the package command line program.
Shell usage:
> package list
> package install <name>
> package update <name>
> package uninstall <name>
Package definitions are located in usr/apps/packages. This file can be modified to add custom packages.
Current stable packages
=======================
* core
Programming and miscellaneous applications. Also contains drivers needed for other packages.
* builder
A program for creating structures from schematic files using a turtle (requires core).
* farms
Various programs for farming resources (wood, crops, animals).
* milo
An A/E like storage implementation (requires core).
* miners
Mining programs.

View File

@ -0,0 +1,7 @@
To establish one-way trust between two computers with modems, run the following in a shell prompt:
On the target computer, run:
> password
On the source computer, run:
> trust <target computer ID>