Compare commits
69 Commits
ui-enhance
...
develop-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2461d060e0 | ||
|
|
aa3cd6d08d | ||
|
|
cba2f7013f | ||
|
|
b3052fe57b | ||
|
|
af570c7769 | ||
|
|
1d2187b957 | ||
|
|
c85bb53062 | ||
|
|
0272d2a303 | ||
|
|
5e275e36ad | ||
|
|
13601c97a9 | ||
|
|
c7a347f723 | ||
|
|
88cacb4823 | ||
|
|
64a218810a | ||
|
|
7f880b1563 | ||
|
|
86db60ba97 | ||
|
|
db007af3ea | ||
|
|
153c1ac4a2 | ||
|
|
287b362765 | ||
|
|
dcdd126bdf | ||
|
|
31a5d6c2d0 | ||
|
|
d6f61acace | ||
|
|
cb1126e216 | ||
|
|
cd0e6af55f | ||
|
|
27c7d2dd18 | ||
|
|
1a166bdb22 | ||
|
|
e9f9999f41 | ||
|
|
fae3b6a654 | ||
|
|
0f7534d12c | ||
|
|
de3d73de70 | ||
|
|
4f74ab2840 | ||
|
|
b289594c7f | ||
|
|
fb5e69f703 | ||
|
|
d203510527 | ||
|
|
6394a48766 | ||
|
|
8fef5d3580 | ||
|
|
70001196cb | ||
|
|
26b693d609 | ||
|
|
f07302588c | ||
|
|
cb58a553f5 | ||
|
|
2c27787f27 | ||
|
|
3d5f665b59 | ||
|
|
503a340035 | ||
|
|
72a85f1a4a | ||
|
|
46c1e3f7e5 | ||
|
|
249414e027 | ||
|
|
0619eee41c | ||
|
|
bc0cf883b4 | ||
|
|
ad32dcc2df | ||
|
|
759e4e2b95 | ||
|
|
d902acacf4 | ||
|
|
ad4cc5884f | ||
|
|
1fc2d08c18 | ||
|
|
94b743bfc0 | ||
|
|
9eff14f3ba | ||
|
|
428477bdec | ||
|
|
bc3a48f30f | ||
|
|
59de8e7f63 | ||
|
|
8db9a89f68 | ||
|
|
87a3f9fa96 | ||
|
|
8f13a0932e | ||
|
|
caa525e31d | ||
|
|
39fb43d6a3 | ||
|
|
38b5c4a5ed | ||
|
|
9bf017fe27 | ||
|
|
8a9878b8e5 | ||
|
|
ef0886ec85 | ||
|
|
8d014c0098 | ||
|
|
4576969739 | ||
|
|
47e0a90116 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/etc/fstab
|
/etc/fstab
|
||||||
/etc/recipes2.db
|
/etc/recipes2.db
|
||||||
|
/.vscode
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -1,2 +1,18 @@
|
|||||||
# opus-apps
|
# opus-apps
|
||||||
Applications for Opus OS
|
Applications for Opus OS
|
||||||
|
|
||||||
|
## Installing an application
|
||||||
|
To install an application, follow these steps
|
||||||
|
1. Start your OpusOS Computer
|
||||||
|
2. Go to the System tab on the main menu
|
||||||
|
3. Find the packages app and open it
|
||||||
|
4. Select the package you want
|
||||||
|
5. Select the package and press the `+` button
|
||||||
|
6. Your application should get installed!
|
||||||
|
|
||||||
|
## Updating your applications
|
||||||
|
To update your applications, follow these steps
|
||||||
|
1. Start your OpusOS Computer
|
||||||
|
2. Go to the System tab on the main menu
|
||||||
|
3. Find the packages app and open it
|
||||||
|
4. Press the `Update All` button
|
||||||
|
|||||||
@@ -236,10 +236,7 @@ function substitutionPage.info:draw()
|
|||||||
end
|
end
|
||||||
|
|
||||||
self:clear()
|
self:clear()
|
||||||
self:setCursorPos(1, 1)
|
self:print(' Replace ' .. inName .. '\n' .. ' With ' .. outName)
|
||||||
self:print(' Replace ' .. inName .. '\n')
|
|
||||||
--self:print(' ' .. sub.id .. ':' .. sub.dmg .. '\n', nil, colors.yellow)
|
|
||||||
self:print(' With ' .. outName)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function substitutionPage:enable()
|
function substitutionPage:enable()
|
||||||
@@ -536,7 +533,7 @@ local startPage = UI.Page {
|
|||||||
event = 'setStartLevel',
|
event = 'setStartLevel',
|
||||||
cancelEvent = 'slide_hide',
|
cancelEvent = 'slide_hide',
|
||||||
text = UI.Text {
|
text = UI.Text {
|
||||||
x = 5, y = 1, width = 20,
|
x = 5, y = 1, width = 10,
|
||||||
textColor = colors.gray,
|
textColor = colors.gray,
|
||||||
},
|
},
|
||||||
textEntry = UI.TextEntry {
|
textEntry = UI.TextEntry {
|
||||||
@@ -554,7 +551,7 @@ local startPage = UI.Page {
|
|||||||
event = 'setStartBlock',
|
event = 'setStartBlock',
|
||||||
cancelEvent = 'slide_hide',
|
cancelEvent = 'slide_hide',
|
||||||
text = UI.Text {
|
text = UI.Text {
|
||||||
x = 2, y = 1, width = 20,
|
x = 2, y = 1, width = 13,
|
||||||
textColor = colors.gray,
|
textColor = colors.gray,
|
||||||
},
|
},
|
||||||
textEntry = UI.TextEntry {
|
textEntry = UI.TextEntry {
|
||||||
@@ -727,8 +724,7 @@ function startPage:eventHandler(event)
|
|||||||
Builder:begin()
|
Builder:begin()
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
elseif event.type == 'quit' then
|
||||||
UI.term:reset()
|
UI:quit()
|
||||||
Event.exitPullEvents()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
@@ -772,5 +768,4 @@ UI:setPages({
|
|||||||
})
|
})
|
||||||
|
|
||||||
UI:setPage('start')
|
UI:setPage('start')
|
||||||
|
UI:start()
|
||||||
UI:pullEvents()
|
|
||||||
|
|||||||
9
busted/.package
Normal file
9
busted/.package
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
title = 'busted',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/moonscript',
|
||||||
|
description = [[WIP]],
|
||||||
|
license = 'MIT',
|
||||||
|
required = {
|
||||||
|
'penlight',
|
||||||
|
},
|
||||||
|
}
|
||||||
16
busted/depend/system.lua
Normal file
16
busted/depend/system.lua
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
return {
|
||||||
|
-- Returns the monotonic time the system has been up, in secconds.
|
||||||
|
monotime = function()
|
||||||
|
return os.clock()
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Sleep for n seconds.
|
||||||
|
sleep = function(n)
|
||||||
|
os.sleep(n)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Returns the current system time, 1970 (UTC), in secconds.
|
||||||
|
gettime = function()
|
||||||
|
return os.epoch('utc') / 1000
|
||||||
|
end,
|
||||||
|
}
|
||||||
5
busted/depend/term.lua
Normal file
5
busted/depend/term.lua
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
return {
|
||||||
|
isatty = function()
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
}
|
||||||
8
busted/etc/fstab
Normal file
8
busted/etc/fstab
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
packages/busted/busted urlfs https://raw.githubusercontent.com/Olivine-Labs/busted/master/bin/busted
|
||||||
|
rom/modules/main/busted gitfs Olivine-Labs/busted/master/busted
|
||||||
|
rom/modules/main/mediator.lua urlfs https://raw.githubusercontent.com/Olivine-Labs/mediator_lua/master/src/mediator.lua
|
||||||
|
rom/modules/main/cliargs gitfs amireh/lua_cliargs/master/src/cliargs
|
||||||
|
rom/modules/main/luassert gitfs Olivine-Labs/luassert/master/src
|
||||||
|
rom/modules/main/say.lua urlfs https://raw.githubusercontent.com/Olivine-Labs/say/master/src/init.lua
|
||||||
|
rom/modules/main/term.lua linkfs packages/busted/depend/term.lua
|
||||||
|
rom/modules/main/system.lua linkfs packages/busted/depend/system.lua
|
||||||
@@ -21,11 +21,4 @@ Restorable history
|
|||||||
Partial CCKernel2 support
|
Partial CCKernel2 support
|
||||||
Full compatibility with CraftOS shell.lua]],
|
Full compatibility with CraftOS shell.lua]],
|
||||||
license = 'MIT',
|
license = 'MIT',
|
||||||
install = [[
|
|
||||||
require('opus.alternate').set('shell', 'packages/cash/cash.lua')
|
|
||||||
require('opus.util').writeFile('.cashrc', 'set TERMINATE_QUIT=yes')
|
|
||||||
]],
|
|
||||||
uninstall = [[
|
|
||||||
require('opus.alternate').remove('shell', 'packages/cash/cash.lua')
|
|
||||||
]],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
local ccemux = _G.ccemux
|
local ccemux = _G.ccemux
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
local peripheral = _G.peripheral
|
local textutils = _G.textutils
|
||||||
|
|
||||||
if ccemux then
|
if ccemux then
|
||||||
-- add a System setup tab
|
-- add a System setup tab
|
||||||
fs.mount('sys/apps/system/ccemux.lua', 'linkfs', 'packages/ccemux/system/ccemux.lua')
|
fs.mount('sys/apps/system/ccemux.lua', 'linkfs', 'packages/ccemux/system/ccemux.lua')
|
||||||
|
|
||||||
local Config = require('opus.config')
|
_G.kernel.hook('clipboard_copy', function(_, args)
|
||||||
|
local data = args[1]
|
||||||
for k,v in pairs(Config.load('ccemux')) do
|
if type(data) == 'table' then
|
||||||
if not peripheral.getType(k) then
|
local s, m = pcall(textutils.serialize, data)
|
||||||
ccemux.attach(k, v.type, v.args)
|
data = s and m or tostring(data)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
if data then
|
||||||
|
ccemux.setClipboard(data)
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
20
ccemux/bin/emustartup.lua
Normal file
20
ccemux/bin/emustartup.lua
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
local ccemux
|
||||||
|
ccemux = _G.ccemux
|
||||||
|
local fs
|
||||||
|
fs = _G.fs
|
||||||
|
local peripheral
|
||||||
|
peripheral = _G.peripheral
|
||||||
|
local unserialize
|
||||||
|
unserialize = _G.textutils.unserialize
|
||||||
|
local CONFIG = 'usr/config/ccemux'
|
||||||
|
if ccemux and fs.exists(CONFIG) then
|
||||||
|
local f = fs.open(CONFIG, 'r')
|
||||||
|
local c = unserialize(f.readAll())
|
||||||
|
f.close()
|
||||||
|
for k, v in pairs(c) do
|
||||||
|
if not peripheral.getType(k) then
|
||||||
|
ccemux.attach(k, v.type, v.args)
|
||||||
|
print(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
ccemux/bin/emustartup.moon
Normal file
16
ccemux/bin/emustartup.moon
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import ccemux from _G
|
||||||
|
import fs from _G
|
||||||
|
import peripheral from _G
|
||||||
|
import unserialize from _G.textutils
|
||||||
|
|
||||||
|
CONFIG = 'usr/config/ccemux'
|
||||||
|
|
||||||
|
if ccemux and fs.exists CONFIG
|
||||||
|
f = fs.open(CONFIG, 'r')
|
||||||
|
c = unserialize(f.readAll())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
for k,v in pairs c
|
||||||
|
if not peripheral.getType(k)
|
||||||
|
ccemux.attach(k, v.type, v.args)
|
||||||
|
print k
|
||||||
12
ccemux/etc/apps.db
Normal file
12
ccemux/etc/apps.db
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
[ "87e89abb4c1c551fe08d355d097f18b8de78edca5f556997085681662fce8eed" ] = {
|
||||||
|
title = "Config",
|
||||||
|
category = "CCEmuX",
|
||||||
|
run = "emu config",
|
||||||
|
},
|
||||||
|
[ "cec3a9b89b2e391393d0f68e4bc12a9fa6cf358b3cdf79496dc442d52b8dd528" ] = {
|
||||||
|
title = "Data",
|
||||||
|
category = "CCEmuX",
|
||||||
|
run = "emu data",
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local ccemux = _G.ccemux
|
local ccemux = _G.ccemux
|
||||||
|
|
||||||
local sides = { 'bottom', 'top', 'back', 'front', 'right', 'left' }
|
local sides = { 'bottom', 'top', 'back', 'front', 'right', 'left' }
|
||||||
|
|
||||||
local tab = UI.Tab {
|
local tab = UI.Tab {
|
||||||
tabTitle = 'CCEmuX',
|
title = 'CCEmuX',
|
||||||
description = 'CCEmuX peripherals',
|
description = 'CCEmuX peripherals',
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
x = 2, ex = -2, y = 1, ey = 4,
|
x = 2, ex = -2, y = 2, ey = 5,
|
||||||
values = {
|
values = {
|
||||||
side = 'bottom',
|
side = 'bottom',
|
||||||
type = 'wireless_modem',
|
type = 'wireless_modem',
|
||||||
@@ -28,7 +29,7 @@ local tab = UI.Tab {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
drive_id = UI.TextEntry {
|
drive_id = UI.TextEntry {
|
||||||
x = 20, y = 3,
|
x = 19, y = 3,
|
||||||
formKey = 'drive_id',
|
formKey = 'drive_id',
|
||||||
shadowText = 'id',
|
shadowText = 'id',
|
||||||
width = 5,
|
width = 5,
|
||||||
@@ -36,16 +37,16 @@ local tab = UI.Tab {
|
|||||||
transform = 'number',
|
transform = 'number',
|
||||||
},
|
},
|
||||||
add = UI.Button {
|
add = UI.Button {
|
||||||
x = 28, y = 3,
|
x = -6, y = 3, width = 5,
|
||||||
text = 'Add', event = 'form_ok',
|
text = 'Add', event = 'form_ok',
|
||||||
help = 'Add items to turtle to add to filter',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.Grid {
|
grid = UI.Grid {
|
||||||
x = 3, ex = -3, y = 6, ey = -2,
|
x = 2, ex = -2, y = 7, ey = -2,
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Side', key = 'side', width = 8 },
|
{ heading = 'Side', key = 'side', width = 8 },
|
||||||
{ heading = 'Type', key = 'type' },
|
{ heading = 'Type', key = 'type' },
|
||||||
|
{ heading = 'ID', key = 'args', width = 4 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -56,13 +57,29 @@ function tab:updatePeripherals(config)
|
|||||||
table.insert(self.grid.values, {
|
table.insert(self.grid.values, {
|
||||||
side = k,
|
side = k,
|
||||||
type = v.type,
|
type = v.type,
|
||||||
args = v.args,
|
args = v.args and v.args.id,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
self.grid:update()
|
self.grid:update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function tab.bootCheck()
|
||||||
|
local startupFile = 'packages/ccemux/bin/emustartup.lua'
|
||||||
|
|
||||||
|
local c = Util.readTable('.startup.boot')
|
||||||
|
if c then
|
||||||
|
for _,v in pairs(c.preload) do
|
||||||
|
if v == startupFile then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(c.preload, startupFile)
|
||||||
|
Util.writeTable('.startup.boot', c)
|
||||||
|
end
|
||||||
|
|
||||||
function tab:enable()
|
function tab:enable()
|
||||||
|
self:bootCheck()
|
||||||
local config = Config.load('ccemux')
|
local config = Config.load('ccemux')
|
||||||
|
|
||||||
local choices = { }
|
local choices = { }
|
||||||
@@ -83,7 +100,6 @@ function tab:eventHandler(event)
|
|||||||
self:emit({ type = 'error_message', message = 'Invalid drive ID' })
|
self:emit({ type = 'error_message', message = 'Invalid drive ID' })
|
||||||
else
|
else
|
||||||
ccemux.detach(event.values.side)
|
ccemux.detach(event.values.side)
|
||||||
ccemux.attach(event.values.side, event.values.type)
|
|
||||||
|
|
||||||
local config = Config.load('ccemux')
|
local config = Config.load('ccemux')
|
||||||
config[event.values.side] = {
|
config[event.values.side] = {
|
||||||
@@ -93,6 +109,9 @@ function tab:eventHandler(event)
|
|||||||
config[event.values.side].args = {
|
config[event.values.side].args = {
|
||||||
id = event.values.drive_id
|
id = event.values.drive_id
|
||||||
}
|
}
|
||||||
|
ccemux.attach(event.values.side, event.values.type, { id = event.values.drive_id })
|
||||||
|
else
|
||||||
|
ccemux.attach(event.values.side, event.values.type)
|
||||||
end
|
end
|
||||||
Config.update('ccemux', config)
|
Config.update('ccemux', config)
|
||||||
self:updatePeripherals(config)
|
self:updatePeripherals(config)
|
||||||
|
|||||||
9
collections/.package
Normal file
9
collections/.package
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
title = 'Collections',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/collections',
|
||||||
|
description = [[Collections (rework)
|
||||||
|
See: https://github.com/imliam/Lua-Collections
|
||||||
|
|
||||||
|
Collections are like tables on steroids. They are designed to act as a fluent wrapper when working with structured data, offering the developer convenience for common tasks.]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
1257
collections/apis/init.lua
Normal file
1257
collections/apis/init.lua
Normal file
File diff suppressed because it is too large
Load Diff
1
collections/etc/fstab
Normal file
1
collections/etc/fstab
Normal file
@@ -0,0 +1 @@
|
|||||||
|
packages/collections/tests/tests.lua urlfs https://raw.githubusercontent.com/imliam/Lua-Collections/master/tests.lua
|
||||||
1030
collections/tests/tests.lua
Normal file
1030
collections/tests/tests.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,4 +13,7 @@
|
|||||||
* and more...
|
* and more...
|
||||||
]],
|
]],
|
||||||
license = 'MIT',
|
license = 'MIT',
|
||||||
|
required = {
|
||||||
|
'core',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ local http = _G.http
|
|||||||
local multishell = _ENV.multishell
|
local multishell = _ENV.multishell
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local shell = _ENV.shell
|
local shell = _ENV.shell
|
||||||
|
local colors = _G.colors
|
||||||
|
|
||||||
local REGISTRY_DIR = 'usr/.registry'
|
local REGISTRY_DIR = 'usr/.registry'
|
||||||
|
|
||||||
|
|
||||||
-- FIX SOMEDAY
|
-- FIX SOMEDAY
|
||||||
local function registerApp(app, key)
|
local function registerApp(app, key)
|
||||||
app.key = SHA.compute(key)
|
app.key = SHA.compute(key)
|
||||||
@@ -27,30 +27,15 @@ local function unregisterApp(key)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local sandboxEnv = Util.shallowCopy(_ENV)
|
|
||||||
setmetatable(sandboxEnv, { __index = _G })
|
|
||||||
|
|
||||||
multishell.setTitle(multishell.getCurrent(), 'App Store')
|
multishell.setTitle(multishell.getCurrent(), 'App Store')
|
||||||
UI:configure('Appstore', ...)
|
UI:configure('Appstore', ...)
|
||||||
|
|
||||||
local APP_DIR = 'usr/apps'
|
local APP_DIR = 'usr/apps'
|
||||||
|
|
||||||
local sources = {
|
local source = {
|
||||||
|
text = "STD Default",
|
||||||
{ text = "STD Default",
|
event = 'source',
|
||||||
event = 'source',
|
url = "https://github.com/LDDestroier/STD-GUI/raw/master/list.lua",
|
||||||
url = "https://github.com/LDDestroier/STD-GUI/raw/master/list.lua" }, --stock
|
|
||||||
--[[
|
|
||||||
{ text = "Discover",
|
|
||||||
event = 'source',
|
|
||||||
generateName = true,
|
|
||||||
url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95
|
|
||||||
|
|
||||||
{ text = "Opus",
|
|
||||||
event = 'source',
|
|
||||||
url = "http://pastebin.com/raw/ajQ91Rmn" },
|
|
||||||
]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.setDir(APP_DIR)
|
shell.setDir(APP_DIR)
|
||||||
@@ -72,7 +57,7 @@ local function downloadApp(app)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function runApp(app, checkExists, ...)
|
local function runApp(app, checkExists, ...)
|
||||||
|
local env = shell.makeEnv(_ENV)
|
||||||
local path, fn
|
local path, fn
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
|
|
||||||
@@ -87,20 +72,19 @@ local function runApp(app, checkExists, ...)
|
|||||||
error('Failed to download')
|
error('Failed to download')
|
||||||
end
|
end
|
||||||
|
|
||||||
local fn = loadstring(program, app.name)
|
fn = _G.loadstring(program, app.name)
|
||||||
|
|
||||||
if not fn then
|
if not fn then
|
||||||
error('Failed to download')
|
error('Failed to download')
|
||||||
end
|
end
|
||||||
|
|
||||||
setfenv(fn, sandboxEnv)
|
_G.setfenv(fn, env)
|
||||||
fn(unpack(args))
|
fn(table.unpack(args))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
multishell.openTab({
|
multishell.openTab(_ENV, {
|
||||||
title = app.name,
|
title = app.name,
|
||||||
env = sandboxEnv,
|
|
||||||
path = path,
|
path = path,
|
||||||
fn = fn,
|
fn = fn,
|
||||||
focused = true,
|
focused = true,
|
||||||
@@ -134,16 +118,16 @@ local viewApp = function(app)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local getSourceListing = function(source)
|
local getSourceListing = function()
|
||||||
local contents = http.get(source.url)
|
local contents = http.get(source.url)
|
||||||
if contents then
|
if contents then
|
||||||
|
|
||||||
local fn = loadstring(contents.readAll(), source.text)
|
local fn = _G.loadstring(contents.readAll(), source.text)
|
||||||
contents.close()
|
contents.close()
|
||||||
|
|
||||||
local env = { std = { } }
|
local env = { std = { } }
|
||||||
setmetatable(env, { __index = _G })
|
setmetatable(env, { __index = _G })
|
||||||
setfenv(fn, env)
|
_G.setfenv(fn, env)
|
||||||
fn()
|
fn()
|
||||||
|
|
||||||
if env.contextualGet then
|
if env.contextualGet then
|
||||||
@@ -172,9 +156,28 @@ local getSourceListing = function(source)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
getSourceListing()
|
||||||
|
|
||||||
|
if not source.storeURLs then
|
||||||
|
error('Unable to download application list')
|
||||||
|
end
|
||||||
|
|
||||||
|
local buttons = { }
|
||||||
|
for k,v in Util.spairs(source.storeCatagoryNames,
|
||||||
|
function(a, b) return a:lower() < b:lower() end) do
|
||||||
|
|
||||||
|
if v ~= 'Operating System' then
|
||||||
|
table.insert(buttons, {
|
||||||
|
text = v,
|
||||||
|
event = 'category',
|
||||||
|
index = k,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
source.index, source.name = Util.first(source.storeCatagoryNames)
|
||||||
|
|
||||||
local appPage = UI.Page {
|
local appPage = UI.Page {
|
||||||
menuBar = UI.MenuBar {
|
menuBar = UI.MenuBar {
|
||||||
-- showBackButton = not pocket,
|
|
||||||
buttons = {
|
buttons = {
|
||||||
{ text = '\027', event = 'back' },
|
{ text = '\027', event = 'back' },
|
||||||
{ text = 'Install', event = 'install' },
|
{ text = 'Install', event = 'install' },
|
||||||
@@ -204,16 +207,14 @@ function appPage.container.viewport:draw()
|
|||||||
Ansi.yellow .. app.description .. Ansi.reset)
|
Ansi.yellow .. app.description .. Ansi.reset)
|
||||||
|
|
||||||
self:clear()
|
self:clear()
|
||||||
self:setCursorPos(1, 1)
|
|
||||||
self:print(str)
|
self:print(str)
|
||||||
self.ymax = self.cursorY
|
|
||||||
|
|
||||||
if appPage.notification.enabled then
|
if appPage.notification.enabled then
|
||||||
appPage.notification:draw()
|
appPage.notification:draw()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function appPage:enable(source, app)
|
function appPage:enable(app)
|
||||||
self.source = source
|
self.source = source
|
||||||
self.app = app
|
self.app = app
|
||||||
UI.Page.enable(self)
|
UI.Page.enable(self)
|
||||||
@@ -293,8 +294,7 @@ end
|
|||||||
local categoryPage = UI.Page {
|
local categoryPage = UI.Page {
|
||||||
menuBar = UI.MenuBar {
|
menuBar = UI.MenuBar {
|
||||||
buttons = {
|
buttons = {
|
||||||
{ text = 'Catalog', dropdown = sources },
|
{ text = 'Category', name = 'categoryButton', dropdown = buttons },
|
||||||
{ text = 'Category', name = 'categoryButton', dropdown = { } },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
@@ -309,62 +309,21 @@ local categoryPage = UI.Page {
|
|||||||
l = 'lua',
|
l = 'lua',
|
||||||
[ 'control-q' ] = 'quit',
|
[ 'control-q' ] = 'quit',
|
||||||
},
|
},
|
||||||
|
source = source,
|
||||||
}
|
}
|
||||||
|
|
||||||
function categoryPage:setCategory(source, name, index)
|
function categoryPage:setCategory(name, index)
|
||||||
self.grid.values = { }
|
self.grid.values = { }
|
||||||
for _,v in pairs(source.storeURLs) do
|
for _,v in pairs(source.storeURLs) do
|
||||||
if index == 0 or index == v.catagory then
|
if index == 0 or index == v.catagory then
|
||||||
table.insert(self.grid.values, v)
|
table.insert(self.grid.values, v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.statusBar:setStatus(string.format('%s: %s', source.text, name))
|
self.statusBar:setStatus(string.format('%s: %s', self.source.text or '', name))
|
||||||
self.grid:update()
|
self.grid:update()
|
||||||
self.grid:setIndex(1)
|
self.grid:setIndex(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function categoryPage:setSource(source)
|
|
||||||
|
|
||||||
if not source.categoryMenu then
|
|
||||||
|
|
||||||
self.statusBar:setStatus('Loading...')
|
|
||||||
self.statusBar:draw()
|
|
||||||
self:sync()
|
|
||||||
|
|
||||||
getSourceListing(source)
|
|
||||||
|
|
||||||
if not source.storeURLs then
|
|
||||||
error('Unable to download application list')
|
|
||||||
end
|
|
||||||
|
|
||||||
local buttons = { }
|
|
||||||
for k,v in Util.spairs(source.storeCatagoryNames,
|
|
||||||
function(a, b) return a:lower() < b:lower() end) do
|
|
||||||
|
|
||||||
if v ~= 'Operating System' then
|
|
||||||
table.insert(buttons, {
|
|
||||||
text = v,
|
|
||||||
event = 'category',
|
|
||||||
index = k,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
source.categoryMenu = UI.DropMenu({
|
|
||||||
buttons = buttons,
|
|
||||||
})
|
|
||||||
source.index, source.name = Util.first(source.storeCatagoryNames)
|
|
||||||
|
|
||||||
categoryPage.menuBar.categoryButton:add({
|
|
||||||
categoryMenu = source.categoryMenu
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
self.source = source
|
|
||||||
self.menuBar.categoryButton.dropmenu = source.categoryMenu
|
|
||||||
categoryPage:setCategory(source, source.name, source.index)
|
|
||||||
end
|
|
||||||
|
|
||||||
function categoryPage.grid:sortCompare(a, b)
|
function categoryPage.grid:sortCompare(a, b)
|
||||||
return a.ltitle < b.ltitle
|
return a.ltitle < b.ltitle
|
||||||
end
|
end
|
||||||
@@ -377,12 +336,11 @@ function categoryPage.grid:getRowTextColor(row, selected)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function categoryPage:eventHandler(event)
|
function categoryPage:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'grid_select' or event.type == 'select' then
|
if event.type == 'grid_select' or event.type == 'select' then
|
||||||
UI:setPage(appPage, self.source, self.grid:getSelected())
|
UI:setPage(appPage, self.grid:getSelected())
|
||||||
|
|
||||||
elseif event.type == 'category' then
|
elseif event.type == 'category' then
|
||||||
self:setCategory(self.source, event.button.text, event.button.index)
|
self:setCategory(event.button.text, event.button.index)
|
||||||
self:setFocus(self.grid)
|
self:setFocus(self.grid)
|
||||||
self:draw()
|
self:draw()
|
||||||
|
|
||||||
@@ -392,7 +350,7 @@ function categoryPage:eventHandler(event)
|
|||||||
self:draw()
|
self:draw()
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
elseif event.type == 'quit' then
|
||||||
UI:exitPullEvents()
|
UI:quit()
|
||||||
|
|
||||||
else
|
else
|
||||||
return UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
@@ -401,8 +359,7 @@ function categoryPage:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
print("Retrieving catalog list")
|
print("Retrieving catalog list")
|
||||||
categoryPage:setSource(sources[1])
|
categoryPage:setCategory(source.name, source.index)
|
||||||
|
|
||||||
UI:setPage(categoryPage)
|
UI:setPage(categoryPage)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
UI.term:reset()
|
|
||||||
|
|||||||
@@ -3,19 +3,32 @@ local Event = require('opus.event')
|
|||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local colors = _G.colors
|
local device = _G.device
|
||||||
local peripheral = _G.peripheral
|
|
||||||
|
|
||||||
--[[ -- PeripheralsPage -- ]] --
|
--[[ -- PeripheralsPage -- ]] --
|
||||||
local peripheralsPage = UI.Page {
|
local peripheralsPage = UI.Page {
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
ey = -2,
|
ey = -2,
|
||||||
columns = {
|
columns = {
|
||||||
|
--{ heading = 'Name', key = 'name' },
|
||||||
{ heading = 'Type', key = 'type' },
|
{ heading = 'Type', key = 'type' },
|
||||||
{ heading = 'Side', key = 'side' },
|
{ heading = 'Side', key = 'side' },
|
||||||
},
|
},
|
||||||
sortColumn = 'type',
|
sortColumn = 'type',
|
||||||
autospace = true,
|
autospace = true,
|
||||||
|
enable = function(self)
|
||||||
|
Util.clear(self.values)
|
||||||
|
for _,v in pairs(device) do
|
||||||
|
table.insert(self.values, {
|
||||||
|
type = v.type,
|
||||||
|
side = v.side,
|
||||||
|
name = v.name,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self:update()
|
||||||
|
self:adjustWidth()
|
||||||
|
UI.Grid.enable(self)
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
statusBar = UI.StatusBar {
|
statusBar = UI.StatusBar {
|
||||||
values = 'Select peripheral',
|
values = 'Select peripheral',
|
||||||
@@ -23,52 +36,35 @@ local peripheralsPage = UI.Page {
|
|||||||
accelerators = {
|
accelerators = {
|
||||||
[ 'control-q' ] = 'quit',
|
[ 'control-q' ] = 'quit',
|
||||||
},
|
},
|
||||||
|
updatePeripherals = function(self)
|
||||||
|
if UI:getCurrentPage() == self then
|
||||||
|
self.grid:draw()
|
||||||
|
self:sync()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
UI:setPage('methods', event.selected)
|
||||||
|
|
||||||
|
end
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
function peripheralsPage.grid:enable()
|
|
||||||
local sides = peripheral.getNames()
|
|
||||||
|
|
||||||
Util.clear(self.values)
|
|
||||||
for _,side in pairs(sides) do
|
|
||||||
table.insert(self.values, {
|
|
||||||
type = peripheral.getType(side),
|
|
||||||
side = side
|
|
||||||
})
|
|
||||||
end
|
|
||||||
self:update()
|
|
||||||
self:adjustWidth()
|
|
||||||
UI.Grid.enable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function peripheralsPage:updatePeripherals()
|
|
||||||
if UI:getCurrentPage() == self then
|
|
||||||
self.grid:draw()
|
|
||||||
self:sync()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function peripheralsPage:eventHandler(event)
|
|
||||||
if event.type == 'quit' then
|
|
||||||
Event.exitPullEvents()
|
|
||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
|
||||||
UI:setPage('methods', event.selected)
|
|
||||||
|
|
||||||
end
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ -- MethodsPage -- ]] --
|
--[[ -- MethodsPage -- ]] --
|
||||||
local methodsPage = UI.Page {
|
local methodsPage = UI.Page {
|
||||||
backgroundColor = colors.black,
|
|
||||||
doc = UI.TextArea {
|
doc = UI.TextArea {
|
||||||
backgroundColor = colors.black,
|
backgroundColor = 'black',
|
||||||
x = 2, y = 2, ex = -1, ey = -7,
|
ey = -7,
|
||||||
|
marginLeft = 1, marginTop = 1,
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = -6, ey = -2,
|
y = -6, ey = -2,
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Name', key = 'name', width = UI.term.width }
|
{ heading = 'Name', key = 'name' }
|
||||||
},
|
},
|
||||||
sortColumn = 'name',
|
sortColumn = 'name',
|
||||||
},
|
},
|
||||||
@@ -79,49 +75,50 @@ local methodsPage = UI.Page {
|
|||||||
[ 'control-q' ] = 'back',
|
[ 'control-q' ] = 'back',
|
||||||
backspace = 'back',
|
backspace = 'back',
|
||||||
},
|
},
|
||||||
|
enable = function(self, p)
|
||||||
|
self.peripheral = p or self.peripheral
|
||||||
|
|
||||||
|
p = device[self.peripheral.name]
|
||||||
|
if p.getDocs then
|
||||||
|
-- plethora
|
||||||
|
self.grid.values = { }
|
||||||
|
for k,v in pairs(p.getDocs()) do
|
||||||
|
table.insert(self.grid.values, {
|
||||||
|
name = k,
|
||||||
|
doc = v,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
elseif not p.getAdvancedMethodsData then
|
||||||
|
-- computercraft
|
||||||
|
self.grid.values = { }
|
||||||
|
for k,v in pairs(p) do
|
||||||
|
if type(v) == 'function' then
|
||||||
|
table.insert(self.grid.values, {
|
||||||
|
name = k,
|
||||||
|
noext = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- open peripherals
|
||||||
|
self.grid.values = p.getAdvancedMethodsData()
|
||||||
|
for name,f in pairs(self.grid.values) do
|
||||||
|
f.name = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
|
||||||
|
self.doc:setText(self:getDocumentation())
|
||||||
|
|
||||||
|
self.statusBar:setStatus(self.peripheral.type)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
|
||||||
|
self:setFocus(self.grid)
|
||||||
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
function methodsPage:enable(p)
|
|
||||||
self.peripheral = p or self.peripheral
|
|
||||||
|
|
||||||
p = peripheral.wrap(self.peripheral.side)
|
|
||||||
if p.getDocs then
|
|
||||||
-- plethora
|
|
||||||
self.grid.values = { }
|
|
||||||
for k,v in pairs(p.getDocs()) do
|
|
||||||
table.insert(self.grid.values, {
|
|
||||||
name = k,
|
|
||||||
doc = v,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
elseif not p.getAdvancedMethodsData then
|
|
||||||
-- computercraft
|
|
||||||
self.grid.values = { }
|
|
||||||
for name in pairs(p) do
|
|
||||||
table.insert(self.grid.values, {
|
|
||||||
name = name,
|
|
||||||
noext = true,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- open peripherals
|
|
||||||
self.grid.values = p.getAdvancedMethodsData()
|
|
||||||
for name,f in pairs(self.grid.values) do
|
|
||||||
f.name = name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.grid:update()
|
|
||||||
self.grid:setIndex(1)
|
|
||||||
|
|
||||||
self.doc:setText(self:getDocumentation())
|
|
||||||
|
|
||||||
self.statusBar:setStatus(self.peripheral.type)
|
|
||||||
UI.Page.enable(self)
|
|
||||||
|
|
||||||
self:setFocus(self.grid)
|
|
||||||
end
|
|
||||||
|
|
||||||
function methodsPage:eventHandler(event)
|
function methodsPage:eventHandler(event)
|
||||||
if event.type == 'back' then
|
if event.type == 'back' then
|
||||||
UI:setPage(peripheralsPage)
|
UI:setPage(peripheralsPage)
|
||||||
@@ -135,7 +132,7 @@ end
|
|||||||
function methodsPage:getDocumentation()
|
function methodsPage:getDocumentation()
|
||||||
local method = self.grid:getSelected()
|
local method = self.grid:getSelected()
|
||||||
|
|
||||||
if method.noext then -- computercraft docs
|
if not method or method.noext then -- computercraft docs
|
||||||
return 'No documentation'
|
return 'No documentation'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -198,4 +195,4 @@ UI:setPages({
|
|||||||
methods = methodsPage,
|
methods = methodsPage,
|
||||||
})
|
})
|
||||||
|
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ function page:enable()
|
|||||||
self.eject.value = config.eject
|
self.eject.value = config.eject
|
||||||
self.automatic.value = config.automatic
|
self.automatic.value = config.automatic
|
||||||
|
|
||||||
self.dir.x = math.floor((self.width / 2) - 3) + 1
|
self.dir:move(math.floor((self.width / 2) - 3) + 1, self.dir.y)
|
||||||
|
|
||||||
UI.Page.enable(self)
|
UI.Page.enable(self)
|
||||||
end
|
end
|
||||||
@@ -122,7 +122,6 @@ function page:drawInfo(drive, textArea)
|
|||||||
return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0
|
return isValid(drive) and fs.getFreeSpace(drive.getMountPath()) or 0
|
||||||
end
|
end
|
||||||
|
|
||||||
textArea:setCursorPos(1, 1)
|
|
||||||
textArea:print(string.format('Drive: %s%s%s\nLabel: %s%s%s\nUsed: %s%s%s\nFree: %s%s%s',
|
textArea:print(string.format('Drive: %s%s%s\nLabel: %s%s%s\nUsed: %s%s%s\nFree: %s%s%s',
|
||||||
Ansi.yellow, drive.name, Ansi.reset,
|
Ansi.yellow, drive.name, Ansi.reset,
|
||||||
isValid(drive) and Ansi.yellow or Ansi.orange, getLabel():sub(1, 10), Ansi.reset,
|
isValid(drive) and Ansi.yellow or Ansi.orange, getLabel():sub(1, 10), Ansi.reset,
|
||||||
@@ -138,6 +137,7 @@ function page:scan()
|
|||||||
self.copyButton.inactive = not valid
|
self.copyButton.inactive = not valid
|
||||||
|
|
||||||
self:draw()
|
self:draw()
|
||||||
|
self.progress:clear()
|
||||||
self.progress:centeredWrite(1, 'Analyzing Disks..')
|
self.progress:centeredWrite(1, 'Analyzing Disks..')
|
||||||
self.progress:sync()
|
self.progress:sync()
|
||||||
|
|
||||||
@@ -167,6 +167,7 @@ function page:copy()
|
|||||||
throttle()
|
throttle()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.progress:clear()
|
||||||
self.progress:centeredWrite(1, 'Computing..')
|
self.progress:centeredWrite(1, 'Computing..')
|
||||||
self.progress:sync()
|
self.progress:sync()
|
||||||
|
|
||||||
@@ -211,11 +212,13 @@ function page:copy()
|
|||||||
self.progress:clear()
|
self.progress:clear()
|
||||||
rawCopy(sdrive.getMountPath(), tdrive.getMountPath())
|
rawCopy(sdrive.getMountPath(), tdrive.getMountPath())
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|
||||||
|
self.progress:clear()
|
||||||
self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black)
|
self.progress:centeredWrite(1, 'Copy Complete', colors.lime, colors.black)
|
||||||
self.progress:sync()
|
self.progress:sync()
|
||||||
|
|
||||||
self.progress.value = 0
|
self.progress.value = 0
|
||||||
self.progress:clear()
|
-- self.progress:clear()
|
||||||
|
|
||||||
self:scan()
|
self:scan()
|
||||||
|
|
||||||
@@ -270,4 +273,4 @@ Event.onTimeout(.2, function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
autospace = true,
|
autospace = true,
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
|
||||||
|
for k,v in pairs(row) do
|
||||||
|
row[k] = type(v) == 'table' and 'table' or v
|
||||||
|
end
|
||||||
|
|
||||||
|
return row
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
accelerators = {
|
accelerators = {
|
||||||
f = 'filter',
|
f = 'filter',
|
||||||
@@ -36,106 +45,68 @@ local page = UI.Page {
|
|||||||
[ 'control-q' ] = 'quit',
|
[ 'control-q' ] = 'quit',
|
||||||
},
|
},
|
||||||
filtered = { },
|
filtered = { },
|
||||||
}
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'filter' then
|
||||||
|
local entry = self.grid:getSelected()
|
||||||
|
self.filtered[entry.event] = true
|
||||||
|
|
||||||
function page:eventHandler(event)
|
elseif event.type == 'toggle' then
|
||||||
|
self.paused = not self.paused
|
||||||
|
if self.paused then
|
||||||
|
self.menuBar.pauseButton.text = 'Resume'
|
||||||
|
else
|
||||||
|
self.menuBar.pauseButton.text = 'Pause '
|
||||||
|
end
|
||||||
|
self.menuBar:draw()
|
||||||
|
|
||||||
if event.type == 'filter' then
|
elseif event.type == 'grid_select' then
|
||||||
local entry = self.grid:getSelected()
|
multishell.openTab(_ENV, {
|
||||||
self.filtered[entry.event] = true
|
path = 'sys/apps/Lua.lua',
|
||||||
|
args = { event.selected },
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
|
|
||||||
elseif event.type == 'toggle' then
|
elseif event.type == 'reset' then
|
||||||
self.paused = not self.paused
|
self.filtered = { }
|
||||||
if self.paused then
|
self.grid:setValues({ })
|
||||||
self.menuBar.pauseButton.text = 'Resume'
|
self.grid:draw()
|
||||||
else
|
if self.paused then
|
||||||
self.menuBar.pauseButton.text = 'Pause '
|
|
||||||
end
|
|
||||||
self.menuBar:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
|
||||||
multishell.openTab({
|
|
||||||
path = 'sys/apps/Lua.lua',
|
|
||||||
args = { event.selected },
|
|
||||||
focused = true,
|
|
||||||
})
|
|
||||||
|
|
||||||
elseif event.type == 'reset' then
|
|
||||||
self.filtered = { }
|
|
||||||
self.grid:setValues({ })
|
|
||||||
self.grid:draw()
|
|
||||||
if self.paused then
|
|
||||||
self:emit({ type = 'toggle' })
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event.type == 'clear' then
|
|
||||||
self.grid:setValues({ })
|
|
||||||
self.grid:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
|
||||||
UI:exitPullEvents()
|
|
||||||
|
|
||||||
--[[
|
|
||||||
elseif event.type == 'focus_change' then
|
|
||||||
if event.focused == self.grid then
|
|
||||||
if not self.paused then
|
|
||||||
self:emit({ type = 'toggle' })
|
self:emit({ type = 'toggle' })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'clear' then
|
||||||
|
self.grid:setValues({ })
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
--]]
|
return true
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.grid:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
|
|
||||||
local function tovalue(s)
|
|
||||||
if type(s) == 'table' then
|
|
||||||
return 'table'
|
|
||||||
end
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
for k,v in pairs(row) do
|
|
||||||
row[k] = tovalue(v)
|
|
||||||
end
|
|
||||||
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.grid:draw()
|
|
||||||
self:adjustWidth()
|
|
||||||
UI.Grid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
local updated = false
|
|
||||||
local timerId = os.startTimer(1)
|
local timerId = os.startTimer(1)
|
||||||
|
|
||||||
Event.addRoutine(function()
|
Event.addRoutine(function()
|
||||||
while true do
|
while true do
|
||||||
local _, id = os.pullEvent('timer')
|
local _, id = os.pullEvent('timer')
|
||||||
if id == timerId then
|
if id == timerId then
|
||||||
if updated then
|
while #page.grid.values > 100 do
|
||||||
while #page.grid.values > 100 do -- page.grid.height do
|
table.remove(page.grid.values)
|
||||||
table.remove(page.grid.values, 100) -- #page.grid.values)
|
|
||||||
end
|
|
||||||
updated = false
|
|
||||||
page.grid:update()
|
|
||||||
page.grid:draw()
|
|
||||||
page:sync()
|
|
||||||
end
|
end
|
||||||
timerId = os.startTimer(1)
|
timerId = nil
|
||||||
|
page.grid:update()
|
||||||
|
page.grid:draw()
|
||||||
|
page:sync()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local hookFunction = function(event, e)
|
local hookFunction = function(event, e)
|
||||||
if not page.filtered[event] and not page.paused and not (event == 'timer' and e[1] == timerId) then
|
if not page.filtered[event] and not page.paused and not (event == 'timer' and e[1] == timerId) then
|
||||||
updated = true
|
|
||||||
table.insert(page.grid.values, 1, {
|
table.insert(page.grid.values, 1, {
|
||||||
event = event,
|
event = event,
|
||||||
p1 = e[1],
|
p1 = e[1],
|
||||||
@@ -144,12 +115,13 @@ local hookFunction = function(event, e)
|
|||||||
p4 = e[4],
|
p4 = e[4],
|
||||||
p5 = e[5],
|
p5 = e[5],
|
||||||
})
|
})
|
||||||
|
timerId = timerId or os.startTimer(.1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
kernel.hook('*', hookFunction)
|
kernel.hook('*', hookFunction)
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|
||||||
kernel.unhook('*', hookFunction)
|
kernel.unhook('*', hookFunction)
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
range = UI.SlideOut {
|
range = UI.SlideOut {
|
||||||
y = -7, height = 7,
|
y = -7, height = 7,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
titleBar = UI.TitleBar {
|
titleBar = UI.TitleBar {
|
||||||
event = 'cancel',
|
event = 'cancel',
|
||||||
title = 'Enter range',
|
title = 'Enter range',
|
||||||
@@ -239,6 +238,6 @@ Event.addRoutine(function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|
||||||
swarm:stop()
|
swarm:stop()
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ function page:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -2,19 +2,14 @@ local Config = require('opus.config')
|
|||||||
local Event = require('opus.event')
|
local Event = require('opus.event')
|
||||||
local itemDB = require('core.itemDB')
|
local itemDB = require('core.itemDB')
|
||||||
local Socket = require('opus.socket')
|
local Socket = require('opus.socket')
|
||||||
local Terminal = require('opus.terminal')
|
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
local multishell = _ENV.multishell
|
local multishell = _ENV.multishell
|
||||||
local network = _G.network
|
local network = _G.network
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local shell = _ENV.shell
|
|
||||||
local term = _G.term
|
|
||||||
|
|
||||||
--UI.Button.defaults.focusIndicator = ' '
|
|
||||||
UI:configure('Turtles', ...)
|
UI:configure('Turtles', ...)
|
||||||
|
|
||||||
local config = { }
|
local config = { }
|
||||||
@@ -31,66 +26,172 @@ local options = {
|
|||||||
|
|
||||||
local SCRIPTS_PATH = 'packages/common/etc/scripts'
|
local SCRIPTS_PATH = 'packages/common/etc/scripts'
|
||||||
|
|
||||||
local nullTerm = Terminal.getNullTerm(term.current())
|
local socket, turtle, page
|
||||||
local socket
|
|
||||||
|
|
||||||
local page = UI.Page {
|
page = UI.Page {
|
||||||
coords = UI.Window {
|
coords = UI.Window {
|
||||||
backgroundColor = colors.black,
|
backgroundColor = 'black',
|
||||||
height = 3,
|
height = 3,
|
||||||
|
marginTop = 1, marginLeft = 1,
|
||||||
|
draw = function(self)
|
||||||
|
local t = turtle
|
||||||
|
self:clear()
|
||||||
|
if t then
|
||||||
|
self:setCursorPos(2, 2)
|
||||||
|
local ind = 'GPS'
|
||||||
|
if not t.point.gps then
|
||||||
|
ind = 'REL'
|
||||||
|
end
|
||||||
|
self:print(string.format('%s : %d,%d,%d',
|
||||||
|
ind, t.point.x, t.point.y, t.point.z))
|
||||||
|
end
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
tabs = UI.Tabs {
|
tabs = UI.Tabs {
|
||||||
x = 1, y = 4, ey = -2,
|
x = 1, y = 4, ey = -2,
|
||||||
scripts = UI.ScrollingGrid {
|
UI.Tab {
|
||||||
tabTitle = 'Run',
|
title = 'Run',
|
||||||
backgroundColor = colors.cyan,
|
scripts = UI.ScrollingGrid {
|
||||||
columns = {
|
backgroundColor = 'primary',
|
||||||
{ heading = '', key = 'label' },
|
columns = {
|
||||||
|
{ heading = '', key = 'label' },
|
||||||
|
},
|
||||||
|
disableHeader = true,
|
||||||
|
sortColumn = 'label',
|
||||||
|
autospace = true,
|
||||||
|
draw = function(self)
|
||||||
|
Util.clear(self.values)
|
||||||
|
local files = fs.list(SCRIPTS_PATH)
|
||||||
|
for _,path in pairs(files) do
|
||||||
|
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
|
||||||
|
end
|
||||||
|
self:update()
|
||||||
|
UI.ScrollingGrid.draw(self)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
page:runScript(event.selected.label)
|
||||||
|
else
|
||||||
|
return UI.ScrollingGrid.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
disableHeader = true,
|
|
||||||
sortColumn = 'label',
|
|
||||||
autospace = true,
|
|
||||||
},
|
},
|
||||||
turtles = UI.ScrollingGrid {
|
UI.Tab {
|
||||||
tabTitle = 'Select',
|
title = 'Select',
|
||||||
backgroundColor = colors.cyan,
|
turtles = UI.ScrollingGrid {
|
||||||
columns = {
|
backgroundColor = 'primary',
|
||||||
{ heading = 'label', key = 'label' },
|
columns = {
|
||||||
{ heading = 'Dist', key = 'distance' },
|
{ heading = 'label', key = 'label' },
|
||||||
{ heading = 'Status', key = 'status' },
|
{ heading = 'Dist', key = 'distance' },
|
||||||
{ heading = 'Fuel', key = 'fuel' },
|
{ heading = 'Status', key = 'status' },
|
||||||
|
{ heading = 'Fuel', key = 'fuel' },
|
||||||
|
},
|
||||||
|
disableHeader = true,
|
||||||
|
sortColumn = 'label',
|
||||||
|
autospace = true,
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
if row.fuel then
|
||||||
|
row.fuel = Util.toBytes(row.fuel)
|
||||||
|
end
|
||||||
|
if row.distance then
|
||||||
|
row.distance = Util.round(row.distance, 1)
|
||||||
|
end
|
||||||
|
return row
|
||||||
|
end,
|
||||||
|
draw = function(self)
|
||||||
|
Util.clear(self.values)
|
||||||
|
for _,v in pairs(network) do
|
||||||
|
if v.fuel then
|
||||||
|
table.insert(self.values, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:update()
|
||||||
|
UI.ScrollingGrid.draw(self)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
turtle = event.selected
|
||||||
|
config.id = event.selected.id
|
||||||
|
Config.update('Turtles', config)
|
||||||
|
multishell.setTitle(multishell.getCurrent(), turtle.label)
|
||||||
|
if socket then
|
||||||
|
socket:close()
|
||||||
|
socket = nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return UI.ScrollingGrid.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
disableHeader = true,
|
|
||||||
sortColumn = 'label',
|
|
||||||
autospace = true,
|
|
||||||
},
|
},
|
||||||
inventory = UI.ScrollingGrid {
|
UI.Tab {
|
||||||
backgroundColor = colors.cyan,
|
title = 'Inv',
|
||||||
tabTitle = 'Inv',
|
inventory = UI.ScrollingGrid {
|
||||||
columns = {
|
backgroundColor = 'primary',
|
||||||
{ heading = '', key = 'index', width = 2 },
|
columns = {
|
||||||
{ heading = '', key = 'count', width = 2 },
|
{ heading = '', key = 'index', width = 2 },
|
||||||
{ heading = 'Inventory', key = 'key', width = UI.term.width - 7 },
|
{ heading = '', key = 'count', width = 2 },
|
||||||
|
{ heading = 'Inventory', key = 'key' },
|
||||||
|
},
|
||||||
|
disableHeader = true,
|
||||||
|
sortColumn = 'index',
|
||||||
|
getRowTextColor = function(self, row, selected)
|
||||||
|
if turtle and row.selected then
|
||||||
|
return 'yellow'
|
||||||
|
end
|
||||||
|
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
||||||
|
end,
|
||||||
|
draw = function(self)
|
||||||
|
local t = turtle
|
||||||
|
Util.clear(self.values)
|
||||||
|
if t then
|
||||||
|
for k,v in pairs(t.inv or { }) do -- new method (less data)
|
||||||
|
local index, count = k:match('(%d+),(%d+)')
|
||||||
|
v = {
|
||||||
|
index = tonumber(index),
|
||||||
|
key = v,
|
||||||
|
count = tonumber(count),
|
||||||
|
}
|
||||||
|
table.insert(self.values, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,v in pairs(t.inventory or { }) do
|
||||||
|
if v.count > 0 then
|
||||||
|
table.insert(self.values, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,v in pairs(self.values) do
|
||||||
|
if v.index == t.slotIndex then
|
||||||
|
v.selected = true
|
||||||
|
end
|
||||||
|
if v.key then
|
||||||
|
v.key = itemDB:getName(v.key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:adjustWidth()
|
||||||
|
self:update()
|
||||||
|
UI.ScrollingGrid.draw(self)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
local fn = string.format('turtle.select(%d)', event.selected.index)
|
||||||
|
page:runFunction(fn)
|
||||||
|
else
|
||||||
|
return UI.ScrollingGrid.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
disableHeader = true,
|
|
||||||
sortColumn = 'index',
|
|
||||||
},
|
},
|
||||||
--[[
|
UI.Tab {
|
||||||
policy = UI.ScrollingGrid {
|
title = 'Action',
|
||||||
tabTitle = 'Mod',
|
backgroundColor = 'primary',
|
||||||
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
|
|
||||||
columns = {
|
|
||||||
{ heading = 'label', key = 'label' },
|
|
||||||
},
|
|
||||||
values = policies,
|
|
||||||
disableHeader = true,
|
|
||||||
sortColumn = 'label',
|
|
||||||
autospace = true,
|
|
||||||
},
|
|
||||||
]]
|
|
||||||
action = UI.Window {
|
|
||||||
tabTitle = 'Action',
|
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
moveUp = UI.Button {
|
moveUp = UI.Button {
|
||||||
x = 5, y = 2,
|
x = 5, y = 2,
|
||||||
text = 'up',
|
text = 'up',
|
||||||
@@ -122,10 +223,43 @@ local page = UI.Page {
|
|||||||
fn = 'turtle.turnRight',
|
fn = 'turtle.turnRight',
|
||||||
},
|
},
|
||||||
info = UI.TextArea {
|
info = UI.TextArea {
|
||||||
x = 15, y = 2,
|
x = 15, y = 1,
|
||||||
inactive = true,
|
inactive = true,
|
||||||
}
|
},
|
||||||
|
showBlocks = function(self)
|
||||||
|
local script = [[
|
||||||
|
local function inspect(direction)
|
||||||
|
local s,b = turtle['inspect' .. (direction or '')]()
|
||||||
|
if not s then
|
||||||
|
return 'minecraft:air:0'
|
||||||
|
end
|
||||||
|
return string.format('%s:%d', b.name, b.metadata)
|
||||||
|
end
|
||||||
|
|
||||||
|
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
|
||||||
|
return string.format('%s\n%s\n%s', bu, bf, bd)
|
||||||
|
]]
|
||||||
|
|
||||||
|
local s, m = page:runFunction(script, true)
|
||||||
|
self.info:setText(s or m)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'button_press' then
|
||||||
|
if event.button.fn then
|
||||||
|
page:runFunction(event.button.fn, event.button.nowrap)
|
||||||
|
self:showBlocks()
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return UI.Tab.eventHandler(self, event)
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
|
enable = function(self)
|
||||||
|
if config.tab then
|
||||||
|
self:selectTab(Util.find(self, 'title', config.tab))
|
||||||
|
end
|
||||||
|
UI.Tabs.enable(self)
|
||||||
|
end
|
||||||
},
|
},
|
||||||
statusBar = UI.StatusBar {
|
statusBar = UI.StatusBar {
|
||||||
values = { },
|
values = { },
|
||||||
@@ -134,6 +268,15 @@ local page = UI.Page {
|
|||||||
{ key = 'distance', width = 6 },
|
{ key = 'distance', width = 6 },
|
||||||
{ key = 'fuel', width = 6 },
|
{ key = 'fuel', width = 6 },
|
||||||
},
|
},
|
||||||
|
draw = function(self)
|
||||||
|
local t = turtle
|
||||||
|
if t then
|
||||||
|
self.values.status = t.status
|
||||||
|
self.values.distance = t.distance and Util.round(t.distance, 2)
|
||||||
|
self.values.fuel = Util.toBytes(t.fuel)
|
||||||
|
end
|
||||||
|
UI.StatusBar.draw(self)
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
notification = UI.Notification(),
|
notification = UI.Notification(),
|
||||||
accelerators = {
|
accelerators = {
|
||||||
@@ -141,15 +284,10 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function page:enable(turtle)
|
|
||||||
self.turtle = turtle
|
|
||||||
UI.Page.enable(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:runFunction(script, nowrap)
|
function page:runFunction(script, nowrap)
|
||||||
for _ = 1, 2 do
|
for _ = 1, 2 do
|
||||||
if not socket then
|
if not socket then
|
||||||
socket = Socket.connect(self.turtle.id, 161)
|
socket = Socket.connect(turtle.id, 161)
|
||||||
end
|
end
|
||||||
|
|
||||||
if socket then
|
if socket then
|
||||||
@@ -170,203 +308,76 @@ function page:runFunction(script, nowrap)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function page:runScript(scriptName)
|
function page:runScript(scriptName)
|
||||||
if self.turtle then
|
if turtle then
|
||||||
self.notification:info('Connecting')
|
self.notification:info('Connecting')
|
||||||
self:sync()
|
self:sync()
|
||||||
|
|
||||||
local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
|
local script = Util.readFile(fs.combine(SCRIPTS_PATH, scriptName))
|
||||||
local ot = term.redirect(nullTerm)
|
if not script then
|
||||||
pcall(function() shell.run(cmd) end)
|
print('Unable to read script file')
|
||||||
term.redirect(ot)
|
|
||||||
self.notification:success('Sent')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.coords:draw()
|
|
||||||
local t = self.parent.turtle
|
|
||||||
self:clear()
|
|
||||||
if t then
|
|
||||||
self:setCursorPos(2, 2)
|
|
||||||
local ind = 'GPS'
|
|
||||||
if not t.point.gps then
|
|
||||||
ind = 'REL'
|
|
||||||
end
|
end
|
||||||
self:print(string.format('%s : %d,%d,%d',
|
|
||||||
ind, t.point.x, t.point.y, t.point.z))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ Inventory Tab ]]--
|
local function processVariables()
|
||||||
function page.tabs.inventory:getRowTextColor(row, selected)
|
local variables = {
|
||||||
if page.turtle and row.selected then
|
COMPUTER_ID = os.getComputerID,
|
||||||
return colors.yellow
|
GPS = function()
|
||||||
end
|
local pt = require('opus.gps').getPoint()
|
||||||
return UI.ScrollingGrid.getRowTextColor(self, row, selected)
|
if not pt then
|
||||||
end
|
error('Unable to determine location')
|
||||||
|
end
|
||||||
function page.tabs.inventory:draw()
|
return _G.textutils.serialize(pt)
|
||||||
local t = page.turtle
|
end,
|
||||||
Util.clear(self.values)
|
|
||||||
if t then
|
|
||||||
for k,v in pairs(t.inv or { }) do -- new method (less data)
|
|
||||||
local index, count = k:match('(%d+),(%d+)')
|
|
||||||
v = {
|
|
||||||
index = tonumber(index),
|
|
||||||
key = v,
|
|
||||||
count = tonumber(count),
|
|
||||||
}
|
}
|
||||||
table.insert(self.values, v)
|
for k,v in pairs(variables) do
|
||||||
end
|
local token = string.format('{%s}', k)
|
||||||
|
if script:find(token, 1, true) then
|
||||||
for _,v in pairs(t.inventory or { }) do
|
local s, m = pcall(v)
|
||||||
if v.count > 0 then
|
if not s then
|
||||||
table.insert(self.values, v)
|
self.notification:error(m)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
script = script:gsub(token, m)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
for _,v in pairs(self.values) do
|
if processVariables(script) then
|
||||||
if v.index == t.slotIndex then
|
local socket = Socket.connect(turtle.id, 161)
|
||||||
v.selected = true
|
if not socket then
|
||||||
|
self.notification:error('Unable to connect')
|
||||||
|
return
|
||||||
end
|
end
|
||||||
if v.key then
|
socket:write({ type = 'script', args = script })
|
||||||
v.key = itemDB:getName(v.key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:adjustWidth()
|
|
||||||
self:update()
|
|
||||||
UI.ScrollingGrid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.inventory:eventHandler(event)
|
|
||||||
if event.type == 'grid_select' then
|
|
||||||
local fn = string.format('turtle.select(%d)', event.selected.index)
|
|
||||||
page:runFunction(fn)
|
|
||||||
else
|
|
||||||
return UI.ScrollingGrid.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.scripts:draw()
|
|
||||||
Util.clear(self.values)
|
|
||||||
local files = fs.list(SCRIPTS_PATH)
|
|
||||||
for _,path in pairs(files) do
|
|
||||||
table.insert(self.values, { label = path, path = fs.combine(SCRIPTS_PATH, path) })
|
|
||||||
end
|
|
||||||
self:update()
|
|
||||||
UI.ScrollingGrid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.scripts:eventHandler(event)
|
|
||||||
if event.type == 'grid_select' then
|
|
||||||
page:runScript(event.selected.label)
|
|
||||||
else
|
|
||||||
return UI.ScrollingGrid.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.turtles:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
if row.fuel then
|
|
||||||
row.fuel = Util.toBytes(row.fuel)
|
|
||||||
end
|
|
||||||
if row.distance then
|
|
||||||
row.distance = Util.round(row.distance, 1)
|
|
||||||
end
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.turtles:draw()
|
|
||||||
Util.clear(self.values)
|
|
||||||
for _,v in pairs(network) do
|
|
||||||
if v.fuel then
|
|
||||||
table.insert(self.values, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self:update()
|
|
||||||
UI.ScrollingGrid.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.tabs.turtles:eventHandler(event)
|
|
||||||
if event.type == 'grid_select' then
|
|
||||||
page.turtle = event.selected
|
|
||||||
config.id = event.selected.id
|
|
||||||
Config.update('Turtles', config)
|
|
||||||
multishell.setTitle(multishell.getCurrent(), page.turtle.label)
|
|
||||||
if socket then
|
|
||||||
socket:close()
|
socket:close()
|
||||||
socket = nil
|
|
||||||
|
self.notification:success('Sent')
|
||||||
end
|
end
|
||||||
else
|
|
||||||
return UI.ScrollingGrid.eventHandler(self, event)
|
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.statusBar:draw()
|
|
||||||
local t = self.parent.turtle
|
|
||||||
if t then
|
|
||||||
self.values.status = t.status
|
|
||||||
self.values.distance = t.distance and Util.round(t.distance, 2)
|
|
||||||
self.values.fuel = Util.toBytes(t.fuel)
|
|
||||||
end
|
|
||||||
UI.StatusBar.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:showBlocks()
|
|
||||||
local script = [[
|
|
||||||
local function inspect(direction)
|
|
||||||
local s,b = turtle['inspect' .. (direction or '')]()
|
|
||||||
if not s then
|
|
||||||
return 'minecraft:air:0'
|
|
||||||
end
|
|
||||||
return string.format('%s:%d', b.name, b.metadata)
|
|
||||||
end
|
|
||||||
|
|
||||||
local bu, bf, bd = inspect('Up'), inspect(), inspect('Down')
|
|
||||||
return string.format('%s\n%s\n%s', bu, bf, bd)
|
|
||||||
]]
|
|
||||||
|
|
||||||
local s, m = self:runFunction(script, true)
|
|
||||||
self.tabs.action.info:setText(s or m)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
if event.type == 'quit' then
|
if event.type == 'quit' then
|
||||||
UI:exitPullEvents()
|
UI:quit()
|
||||||
|
|
||||||
elseif event.type == 'tab_select' then
|
elseif event.type == 'tab_select' then
|
||||||
config.tab = event.button.text
|
config.tab = event.button.text
|
||||||
Config.update('Turtles', config)
|
Config.update('Turtles', config)
|
||||||
|
|
||||||
elseif event.type == 'button_press' then
|
|
||||||
if event.button.fn then
|
|
||||||
self:runFunction(event.button.fn, event.button.nowrap)
|
|
||||||
self:showBlocks()
|
|
||||||
elseif event.button.script then
|
|
||||||
self:runScript(event.button.script)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
return UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function page:enable()
|
|
||||||
UI.Page.enable(self)
|
|
||||||
-- self.tabs:activateTab(page.tabs.turtles)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not Util.getOptions(options, { ... }, true) then
|
if not Util.getOptions(options, { ... }, true) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if options.turtle.value >= 0 then
|
if options.turtle.value >= 0 then
|
||||||
for _ = 1, 10 do
|
for _ = 1, 10 do
|
||||||
page.turtle = _G.network[options.turtle.value]
|
turtle = _G.network[options.turtle.value]
|
||||||
if page.turtle then
|
if turtle then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
os.sleep(1)
|
os.sleep(1)
|
||||||
@@ -374,18 +385,13 @@ if options.turtle.value >= 0 then
|
|||||||
end
|
end
|
||||||
|
|
||||||
Event.onInterval(1, function()
|
Event.onInterval(1, function()
|
||||||
if page.turtle then
|
if turtle then
|
||||||
local t = _G.network[page.turtle.id]
|
--local t = _G.network[turtle.id]
|
||||||
page.turtle = t
|
--turtle = t
|
||||||
page:draw()
|
page:draw()
|
||||||
page:sync()
|
page:sync()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if config.tab then
|
|
||||||
page.tabs.tabBar:selectTab(config.tab)
|
|
||||||
end
|
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
UI:pullEvents()
|
|
||||||
|
|||||||
@@ -6,3 +6,49 @@ end
|
|||||||
|
|
||||||
_ENV.shell.setCompletionFunction("packages/common/edit.lua", c)
|
_ENV.shell.setCompletionFunction("packages/common/edit.lua", c)
|
||||||
_ENV.shell.setCompletionFunction("packages/common/hexedit.lua", c)
|
_ENV.shell.setCompletionFunction("packages/common/hexedit.lua", c)
|
||||||
|
|
||||||
|
_ENV.shell.registerHandler(function(env, command, args)
|
||||||
|
if command:match('^!') then
|
||||||
|
return {
|
||||||
|
title = 'lua',
|
||||||
|
path = table.concat({ command:match('^!(.+)'), table.unpack(args) }, ' '),
|
||||||
|
args = args,
|
||||||
|
load = function(s)
|
||||||
|
return function()
|
||||||
|
local fn, m
|
||||||
|
local wrapped
|
||||||
|
|
||||||
|
fn = load('return (' ..s.. ')', 'lua', nil, env)
|
||||||
|
|
||||||
|
if fn then
|
||||||
|
fn = load('return {' ..s.. '}', 'lua', nil, env)
|
||||||
|
wrapped = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if fn then
|
||||||
|
fn, m = pcall(fn)
|
||||||
|
if #m <= 1 and wrapped then
|
||||||
|
m = m[1]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fn, m = load(s, 'lua', nil, env)
|
||||||
|
if fn then
|
||||||
|
fn, m = pcall(fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if fn then
|
||||||
|
if m or wrapped then
|
||||||
|
require('opus.util').print(m or 'nil')
|
||||||
|
else
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
_G.printError(m)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
env = env,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local peripheral = _G.peripheral
|
local peripheral = _G.peripheral
|
||||||
local term = _G.term
|
local term = _G.term
|
||||||
|
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
local mon = args[1] and peripheral.wrap(args[1]) or
|
local mon = not args[1] and term.current() or
|
||||||
|
device[args[1]] or
|
||||||
|
peripheral.wrap(args[1]) or
|
||||||
peripheral.find('monitor') or
|
peripheral.find('monitor') or
|
||||||
error('Syntax: debug <monitor>')
|
error('Syntax: debug <monitor>')
|
||||||
|
|
||||||
mon.clear()
|
mon.clear()
|
||||||
mon.setTextScale(.5)
|
if mon.setTextScale then
|
||||||
|
mon.setTextScale(.5)
|
||||||
|
end
|
||||||
mon.setCursorPos(1, 1)
|
mon.setCursorPos(1, 1)
|
||||||
|
|
||||||
local oldDebug = _G._syslog
|
local oldDebug = _G._syslog
|
||||||
@@ -19,13 +24,16 @@ _G._syslog = function(...)
|
|||||||
local oldTerm = term.redirect(mon)
|
local oldTerm = term.redirect(mon)
|
||||||
Util.print(...)
|
Util.print(...)
|
||||||
term.redirect(oldTerm)
|
term.redirect(oldTerm)
|
||||||
|
oldDebug(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
repeat
|
repeat
|
||||||
local e, side = os.pullEventRaw('monitor_touch')
|
local e, side = os.pullEventRaw('monitor_touch')
|
||||||
if e == 'monitor_touch' and side == mon.side then
|
if e == 'monitor_touch' and side == mon.side then
|
||||||
mon.clear()
|
mon.clear()
|
||||||
mon.setTextScale(.5)
|
if mon.setTextScale then
|
||||||
|
mon.setTextScale(.5)
|
||||||
|
end
|
||||||
mon.setCursorPos(1, 1)
|
mon.setCursorPos(1, 1)
|
||||||
end
|
end
|
||||||
until e == 'terminate'
|
until e == 'terminate'
|
||||||
|
|||||||
1344
common/edit.lua
1344
common/edit.lua
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,7 @@
|
|||||||
category = "Apps",
|
category = "Apps",
|
||||||
requires = "advancedComputer",
|
requires = "advancedComputer",
|
||||||
iconExt = "\030 \031 \128\030d\159\030 \031d\140\030d\031 \155\030 \0315\140\0305\031 \155\030 \128\010\030 \031d\136\145\0315\136\145\031d\153\031 \128\0315\153\010\030 \031 \128\031d\130\140\134\0315\140\134\031 \128",
|
iconExt = "\030 \031 \128\030d\159\030 \031d\140\030d\031 \155\030 \0315\140\0305\031 \155\030 \128\010\030 \031d\136\145\0315\136\145\031d\153\031 \128\0315\153\010\030 \031 \128\031d\130\140\134\0315\140\134\031 \128",
|
||||||
run = "packages/common/hexedit.lua",
|
run = "fileui --exec=hexedit.lua --title=hexedit",
|
||||||
},
|
},
|
||||||
[ "fb1c39e9f4f3c2628ad173ab401a6e4e4baf783d" ] = {
|
[ "fb1c39e9f4f3c2628ad173ab401a6e4e4baf783d" ] = {
|
||||||
title = "Sounds",
|
title = "Sounds",
|
||||||
@@ -65,4 +65,22 @@
|
|||||||
run = "SoundPlayer",
|
run = "SoundPlayer",
|
||||||
iconExt = "\030 \031 \128\0307\159\129\030 \0317\149\0310\144\0300\031 \155\030 \0310\137\144\010\0307\0317\128\128\128\030 \149\0300\031 \149\030 \128\0310\149\0300\031 \149\010\030 \031 \128\0317\130\0307\031 \144\030 \0317\149\0310\129\134\152\129",
|
iconExt = "\030 \031 \128\0307\159\129\030 \0317\149\0310\144\0300\031 \155\030 \0310\137\144\010\0307\0317\128\128\128\030 \149\0300\031 \149\030 \128\0310\149\0300\031 \149\010\030 \031 \128\0317\130\0307\031 \144\030 \0317\149\0310\129\134\152\129",
|
||||||
},
|
},
|
||||||
|
[ "464c4ffd019e1e9691dcf0537c797353ef2b1c1d4833d3d463e5b74ae4547344" ] = {
|
||||||
|
title = "Editor",
|
||||||
|
category = "Apps",
|
||||||
|
run = "edit",
|
||||||
|
iconExt = "7\
|
||||||
|
¨¨¨¨f0¨\
|
||||||
|
¨¨f0¨¨f",
|
||||||
|
},
|
||||||
|
[ "3f00927a719345edd4a8316599d3b328857987547f8884306861161ffa09647e" ] = {
|
||||||
|
title = "Write",
|
||||||
|
category = "Apps",
|
||||||
|
run = "write",
|
||||||
|
},
|
||||||
|
[ "c543ece81605c7d202121c62080a0db4020fc2c75bfac35d101d7f3e93c93949" ] = {
|
||||||
|
category = "Apps",
|
||||||
|
run = "ascii",
|
||||||
|
title = "Ascii",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
packages/common/ascii.lua urlfs http://pastebin.com/raw/u3kcnyjd
|
packages/common/ascii.lua urlfs https://pastebin.com/raw/U3KcNyJd
|
||||||
packages/common/hexedit.lua urlfs https://pastebin.com/raw/Ds9ajsp4
|
packages/common/hexedit.lua urlfs https://pastebin.com/raw/Ds9ajsp4
|
||||||
packages/common/colors.lua urlfs https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/ignore/colors.lua
|
packages/common/colors.lua urlfs https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/ignore/colors.lua
|
||||||
packages/common/cowsay.lua urlfs https://pastebin.com/raw/n00VQJsw
|
packages/common/cowsay.lua urlfs https://pastebin.com/raw/n00VQJsw
|
||||||
packages/common/calc.lua urlfs https://pastebin.com/raw/nAinUn1h
|
packages/common/calc.lua urlfs https://pastebin.com/raw/nAinUn1h
|
||||||
packages/common/write.lua urlfs https://pastebin.com/raw/RSyhCjqv
|
packages/common/write.lua urlfs https://pastebin.com/raw/RSyhCjqv
|
||||||
|
# pretty output
|
||||||
|
rom/modules/main/inspect.lua urlfs https://raw.githubusercontent.com/kikito/inspect.lua/master/inspect.lua
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
_G.requireInjector(_ENV)
|
local config = require('opus.config').load('gps')
|
||||||
local config = require('config').load('gps')
|
|
||||||
if config.home then
|
if config.home then
|
||||||
if turtle.enableGPS() then
|
if turtle.enableGPS() then
|
||||||
return turtle.pathfind(config.home)
|
return turtle.pathfind(config.home)
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
turtle.run(function()
|
|
||||||
|
|
||||||
_G.requireInjector(_ENV)
|
|
||||||
|
|
||||||
local GPS = require('gps')
|
|
||||||
local Socket = require('socket')
|
|
||||||
|
|
||||||
local id = {COMPUTER_ID}
|
|
||||||
|
|
||||||
if not turtle.enableGPS() then
|
|
||||||
error('turtle: No GPS found')
|
|
||||||
end
|
|
||||||
|
|
||||||
local socket = Socket.connect(id, 161)
|
|
||||||
if not socket then
|
|
||||||
error('turtle: Unable to connect to ' .. id)
|
|
||||||
end
|
|
||||||
|
|
||||||
socket:write({ type = 'gps' })
|
|
||||||
|
|
||||||
local pt = socket:read(3)
|
|
||||||
if not pt then
|
|
||||||
error('turtle: No GPS response')
|
|
||||||
end
|
|
||||||
|
|
||||||
if not turtle.pathfind(pt) then
|
|
||||||
error('Unable to go to location')
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
15
common/etc/scripts/moveTo.lua
Normal file
15
common/etc/scripts/moveTo.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
local turtle = _G.turtle
|
||||||
|
|
||||||
|
turtle.run(function()
|
||||||
|
local GPS = require('opus.gps')
|
||||||
|
|
||||||
|
if not turtle.enableGPS() then
|
||||||
|
error('turtle: No GPS found')
|
||||||
|
end
|
||||||
|
|
||||||
|
local pt = {GPS}
|
||||||
|
|
||||||
|
if not turtle.pathfind(pt) then
|
||||||
|
error('Unable to go to location')
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
_G.requireInjector(_ENV)
|
local Config = require('opus.config')
|
||||||
local Config = require('config')
|
|
||||||
local pt = turtle.enableGPS()
|
local pt = turtle.enableGPS()
|
||||||
if pt then
|
if pt then
|
||||||
local config = Config.load('gps', { })
|
local config = Config.load('gps', { })
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
local function summon(id)
|
local function summon(id)
|
||||||
|
local GPS = require('opus.gps')
|
||||||
_G.requireInjector(_ENV)
|
local Point = require('opus.point')
|
||||||
|
local Socket = require('opus.socket')
|
||||||
local GPS = require('gps')
|
|
||||||
local Point = require('point')
|
|
||||||
local Socket = require('socket')
|
|
||||||
|
|
||||||
turtle.setStatus('GPSing')
|
turtle.setStatus('GPSing')
|
||||||
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })
|
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ local function hijackTurtle(remoteId)
|
|||||||
local socket, msg = Socket.connect(remoteId, 188)
|
local socket, msg = Socket.connect(remoteId, 188)
|
||||||
|
|
||||||
if not socket then
|
if not socket then
|
||||||
error(msg)
|
error(msg, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
socket:write('turtle')
|
socket:write('turtle')
|
||||||
@@ -101,7 +101,7 @@ local function hijackTurtle(remoteId)
|
|||||||
socket:write({ method, ... })
|
socket:write({ method, ... })
|
||||||
local resp = socket:read()
|
local resp = socket:read()
|
||||||
if not resp then
|
if not resp then
|
||||||
error('timed out: ' .. method)
|
error('T/O: ' .. method, 0)
|
||||||
end
|
end
|
||||||
return table.unpack(resp)
|
return table.unpack(resp)
|
||||||
end
|
end
|
||||||
@@ -308,7 +308,7 @@ local containerText = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local containTab = UI.Tab {
|
local containTab = UI.Tab {
|
||||||
tabTitle = 'Contain',
|
title = 'Contain',
|
||||||
button = UI.Button {
|
button = UI.Button {
|
||||||
x = 2, y = 2,
|
x = 2, y = 2,
|
||||||
text = 'Set corner',
|
text = 'Set corner',
|
||||||
@@ -321,7 +321,7 @@ local containTab = UI.Tab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local blocksTab = UI.Tab {
|
local blocksTab = UI.Tab {
|
||||||
tabTitle = 'Blocks',
|
title = 'Blocks',
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 1,
|
y = 1,
|
||||||
columns = {
|
columns = {
|
||||||
@@ -333,7 +333,7 @@ local blocksTab = UI.Tab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local turtlesTab = UI.Tab {
|
local turtlesTab = UI.Tab {
|
||||||
tabTitle = 'Turtles',
|
title = 'Turtles',
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 1,
|
y = 1,
|
||||||
values = pool,
|
values = pool,
|
||||||
|
|||||||
@@ -19,10 +19,14 @@ local Util = require('opus.util')
|
|||||||
local multishell = _ENV.multishell
|
local multishell = _ENV.multishell
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
|
|
||||||
local recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput = {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, ""
|
local colours = _G.colors
|
||||||
|
|
||||||
|
local args, options = Util.parse(...)
|
||||||
|
|
||||||
|
local oldTerm, showInput, skipLast, lastDelay, curInput = Util.shallowCopy(_G.device.terminal), false, false, 2, ""
|
||||||
local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()
|
local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()
|
||||||
local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
|
local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
|
||||||
local charW, charH, chars, resp
|
local charW, charH, chars
|
||||||
|
|
||||||
local calls = { }
|
local calls = { }
|
||||||
local curCalls = { delay = 0 }
|
local curCalls = { delay = 0 }
|
||||||
@@ -32,38 +36,44 @@ local callCount = 0
|
|||||||
local function showSyntax()
|
local function showSyntax()
|
||||||
print('Gif Recorder by Bomb Bloke\n')
|
print('Gif Recorder by Bomb Bloke\n')
|
||||||
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
|
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
|
||||||
print(' -i : show input')
|
print(' --showInput : show input')
|
||||||
print(' -s : skip last')
|
print(' --skipLast : skip last')
|
||||||
print(' -ld : last delay')
|
print(' --lastDelay : last delay')
|
||||||
|
print(' --noResize : dont resize')
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = #arg, 1, -1 do
|
if options.showInput then
|
||||||
local curArg = arg[i]:lower()
|
showInput, ySize = true, ySize + 1
|
||||||
|
|
||||||
if curArg == "-i" then
|
|
||||||
showInput, ySize = true, ySize + 1
|
|
||||||
table.remove(arg, i)
|
|
||||||
elseif curArg == "-s" then
|
|
||||||
skipLast = true
|
|
||||||
table.remove(arg, i)
|
|
||||||
elseif curArg:sub(1, 4) == "-ld:" then
|
|
||||||
curArg = tonumber(curArg:sub(5))
|
|
||||||
if curArg then lastDelay = curArg end
|
|
||||||
table.remove(arg, i)
|
|
||||||
elseif curArg == '-?' then
|
|
||||||
showSyntax()
|
|
||||||
return
|
|
||||||
elseif i ~= #arg then
|
|
||||||
showSyntax()
|
|
||||||
printError('\nInvalid argument')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
print('Gif Recorder by Bomb Bloke\n')
|
if options.skipLast then
|
||||||
print('Press control-p to stop recording')
|
skipLast = true
|
||||||
|
end
|
||||||
|
|
||||||
local filename = arg[#arg]
|
if options.lastDelay then
|
||||||
|
lastDelay = options.lastDelay
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
showSyntax()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.daemon then
|
||||||
|
_G.device.keyboard.addHotkey('control-P', function()
|
||||||
|
multishell.openTab(_ENV, {
|
||||||
|
path = 'sys/apps/shell.lua',
|
||||||
|
args = { arg[0], '--noResize', '--rawOutput', 'recorder.gif' },
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
print('Gif Recorder by Bomb Bloke')
|
||||||
|
print(version)
|
||||||
|
print('\nPress control-p to stop recording')
|
||||||
|
|
||||||
|
local filename = args[1]
|
||||||
if not filename then
|
if not filename then
|
||||||
print('Enter file name:')
|
print('Enter file name:')
|
||||||
filename = read()
|
filename = read()
|
||||||
@@ -131,37 +141,34 @@ end
|
|||||||
|
|
||||||
-- Build a terminal that records stuff:
|
-- Build a terminal that records stuff:
|
||||||
|
|
||||||
recTerm = multishell.term
|
local recTerm = _G.device.terminal
|
||||||
|
|
||||||
for key, func in pairs(oldTerm) do
|
for key, func in pairs(oldTerm) do
|
||||||
recTerm[key] = function(...)
|
if type(func) == 'function' then
|
||||||
local result = { func(...) }
|
recTerm[key] = function(...)
|
||||||
|
local result = { func(...) }
|
||||||
|
|
||||||
if callCount == 0 then
|
if callCount == 0 then
|
||||||
os.queueEvent('capture_frame')
|
os.queueEvent('capture_frame')
|
||||||
|
end
|
||||||
|
callCount = callCount + 1
|
||||||
|
curCalls[callCount] = { key, ... }
|
||||||
|
return table.unpack(result)
|
||||||
end
|
end
|
||||||
callCount = callCount + 1
|
|
||||||
curCalls[callCount] = { key, ... }
|
|
||||||
return unpack(result)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local tabId = multishell.getCurrent()
|
local tabId = multishell.getCurrent()
|
||||||
|
multishell.hideTab(tabId)
|
||||||
|
|
||||||
|
if not options.noResize then
|
||||||
|
os.queueEvent('term_resize')
|
||||||
|
end
|
||||||
|
|
||||||
_G.device.keyboard.addHotkey('control-p', function()
|
_G.device.keyboard.addHotkey('control-p', function()
|
||||||
os.queueEvent('recorder_stop')
|
os.queueEvent('recorder_stop')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local tabs = multishell.getTabs()
|
|
||||||
for _,tab in pairs(tabs) do
|
|
||||||
if tab.isOverview then
|
|
||||||
multishell.hideTab(tabId)
|
|
||||||
multishell.setFocus(tab.tabId)
|
|
||||||
os.queueEvent('term_resize')
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local curTime = os.clock() - 1
|
local curTime = os.clock() - 1
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
@@ -189,7 +196,7 @@ end
|
|||||||
_G.device.keyboard.removeHotkey('control-p')
|
_G.device.keyboard.removeHotkey('control-p')
|
||||||
|
|
||||||
for k,fn in pairs(oldTerm) do
|
for k,fn in pairs(oldTerm) do
|
||||||
multishell.term[k] = fn
|
_G.device.terminal[k] = fn
|
||||||
end
|
end
|
||||||
|
|
||||||
multishell.unhideTab(tabId)
|
multishell.unhideTab(tabId)
|
||||||
@@ -200,8 +207,12 @@ if skipLast and #calls > 1 then calls[#calls] = nil end
|
|||||||
|
|
||||||
calls[#calls].delay = lastDelay
|
calls[#calls].delay = lastDelay
|
||||||
|
|
||||||
|
if options.rawOutput then
|
||||||
|
Util.writeTable('tmp/raw.txt', calls)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
print(string.format("Encoding %d frames...", #calls))
|
print(string.format("Encoding %d frames...", #calls))
|
||||||
--Util.writeTable('tmp/raw.txt', calls)
|
|
||||||
|
|
||||||
-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
|
-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
|
||||||
|
|
||||||
|
|||||||
6
compress/.package
Normal file
6
compress/.package
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
title = 'Compress',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/compress',
|
||||||
|
description = [[untar / gunzip]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
530
compress/apis/deflatelua.lua
Normal file
530
compress/apis/deflatelua.lua
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
--[[
|
||||||
|
see: https://github.com/davidm/lua-compress-deflatelua/
|
||||||
|
for licensing / details
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local M = {_TYPE='module', _NAME='compress.deflatelua', _VERSION='0.3.20111128'}
|
||||||
|
|
||||||
|
local assert = assert
|
||||||
|
local error = error
|
||||||
|
local ipairs = ipairs
|
||||||
|
local pairs = pairs
|
||||||
|
local tostring = tostring
|
||||||
|
local type = type
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local io = io
|
||||||
|
local math = math
|
||||||
|
local table_sort = table.sort
|
||||||
|
local math_max = math.max
|
||||||
|
local string_char = string.char
|
||||||
|
local band = bit32.band
|
||||||
|
local lshift = bit32.lshift
|
||||||
|
local rshift = bit32.rshift
|
||||||
|
|
||||||
|
local function runtime_error(s, level)
|
||||||
|
level = level or 1
|
||||||
|
error(s, level+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function make_outstate(outbs)
|
||||||
|
local outstate = {}
|
||||||
|
outstate.outbs = outbs
|
||||||
|
outstate.window = {}
|
||||||
|
outstate.window_pos = 1
|
||||||
|
return outstate
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function output(outstate, byte)
|
||||||
|
local window_pos = outstate.window_pos
|
||||||
|
outstate.outbs(byte)
|
||||||
|
outstate.window[window_pos] = byte
|
||||||
|
outstate.window_pos = window_pos % 32768 + 1 -- 32K
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function noeof(val)
|
||||||
|
return assert(val, 'unexpected end of file')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function hasbit(bits, bit)
|
||||||
|
return bits % (bit + bit) >= bit
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function memoize(f)
|
||||||
|
local mt = {}
|
||||||
|
local t = setmetatable({}, mt)
|
||||||
|
function mt:__index(k)
|
||||||
|
local v = f(k)
|
||||||
|
t[k] = v
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- small optimization (lookup table for powers of 2)
|
||||||
|
local pow2 = memoize(function(n) return 2^n end)
|
||||||
|
|
||||||
|
--local tbits = memoize(
|
||||||
|
-- function(bits)
|
||||||
|
-- return memoize( function(bit) return getbit(bits, bit) end )
|
||||||
|
-- end )
|
||||||
|
|
||||||
|
|
||||||
|
-- weak metatable marking objects as bitstream type
|
||||||
|
local is_bitstream = setmetatable({}, {__mode='k'})
|
||||||
|
|
||||||
|
local function bytestream_from_file(fh)
|
||||||
|
local o = {}
|
||||||
|
function o.read()
|
||||||
|
local sb = fh:read(1)
|
||||||
|
if sb then return sb:byte() end
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bytestream_from_string(s)
|
||||||
|
local i = 1
|
||||||
|
local o = {}
|
||||||
|
function o.read()
|
||||||
|
local by
|
||||||
|
if i <= #s then
|
||||||
|
by = s:byte(i)
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return by
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bytestream_from_function(f)
|
||||||
|
local o = {}
|
||||||
|
function o.read()
|
||||||
|
return f()
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function bitstream_from_bytestream(bys)
|
||||||
|
local buf_byte = 0
|
||||||
|
local buf_nbit = 0
|
||||||
|
local o = {}
|
||||||
|
|
||||||
|
function o.nbits_left_in_byte()
|
||||||
|
return buf_nbit
|
||||||
|
end
|
||||||
|
|
||||||
|
function o:read(nbits)
|
||||||
|
nbits = nbits or 1
|
||||||
|
while buf_nbit < nbits do
|
||||||
|
local byte = bys:read()
|
||||||
|
if not byte then return end -- note: more calls also return nil
|
||||||
|
buf_byte = buf_byte + lshift(byte, buf_nbit)
|
||||||
|
buf_nbit = buf_nbit + 8
|
||||||
|
end
|
||||||
|
local bits
|
||||||
|
if nbits == 0 then
|
||||||
|
bits = 0
|
||||||
|
elseif nbits == 32 then
|
||||||
|
bits = buf_byte
|
||||||
|
buf_byte = 0
|
||||||
|
else
|
||||||
|
bits = band(buf_byte, rshift(0xffffffff, 32 - nbits))
|
||||||
|
buf_byte = rshift(buf_byte, nbits)
|
||||||
|
end
|
||||||
|
buf_nbit = buf_nbit - nbits
|
||||||
|
return bits
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
is_bitstream[o] = true
|
||||||
|
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_bitstream(o)
|
||||||
|
local bs
|
||||||
|
if is_bitstream[o] then
|
||||||
|
return o
|
||||||
|
elseif io.type(o) == 'file' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_file(o))
|
||||||
|
elseif type(o) == 'string' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_string(o))
|
||||||
|
elseif type(o) == 'function' then
|
||||||
|
bs = bitstream_from_bytestream(bytestream_from_function(o))
|
||||||
|
else
|
||||||
|
runtime_error 'unrecognized type'
|
||||||
|
end
|
||||||
|
return bs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_obytestream(o)
|
||||||
|
local bs
|
||||||
|
if io.type(o) == 'file' then
|
||||||
|
bs = function(sbyte) o:write(string_char(sbyte)) end
|
||||||
|
elseif type(o) == 'function' then
|
||||||
|
bs = o
|
||||||
|
else
|
||||||
|
runtime_error('unrecognized type: ' .. tostring(o))
|
||||||
|
end
|
||||||
|
return bs
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function HuffmanTable(init, is_full)
|
||||||
|
local t = {}
|
||||||
|
if is_full then
|
||||||
|
for val,nbits in pairs(init) do
|
||||||
|
if nbits ~= 0 then
|
||||||
|
t[#t+1] = {val=val, nbits=nbits}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i=1,#init-2,2 do
|
||||||
|
local firstval, nbits, nextval = init[i], init[i+1], init[i+2]
|
||||||
|
if nbits ~= 0 then
|
||||||
|
for val=firstval,nextval-1 do
|
||||||
|
t[#t+1] = {val=val, nbits=nbits}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table_sort(t, function(a,b)
|
||||||
|
return a.nbits == b.nbits and a.val < b.val or a.nbits < b.nbits
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- assign codes
|
||||||
|
local code = 1 -- leading 1 marker
|
||||||
|
local nbits = 0
|
||||||
|
for _,s in ipairs(t) do
|
||||||
|
if s.nbits ~= nbits then
|
||||||
|
code = code * pow2[s.nbits - nbits]
|
||||||
|
nbits = s.nbits
|
||||||
|
end
|
||||||
|
s.code = code
|
||||||
|
code = code + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local minbits = math.huge
|
||||||
|
local look = {}
|
||||||
|
for _,s in ipairs(t) do
|
||||||
|
minbits = math.min(minbits, s.nbits)
|
||||||
|
look[s.code] = s.val
|
||||||
|
end
|
||||||
|
|
||||||
|
local msb = function(bits, nbits)
|
||||||
|
local res = 0
|
||||||
|
for _=1,nbits do
|
||||||
|
res = lshift(res, 1) + band(bits, 1)
|
||||||
|
bits = rshift(bits, 1)
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local tfirstcode = memoize(
|
||||||
|
function(bits) return pow2[minbits] + msb(bits, minbits) end)
|
||||||
|
|
||||||
|
function t:read(bs)
|
||||||
|
local code = 1 -- leading 1 marker
|
||||||
|
local nbits = 0
|
||||||
|
while 1 do
|
||||||
|
if nbits == 0 then -- small optimization (optional)
|
||||||
|
code = tfirstcode[noeof(bs:read(minbits))]
|
||||||
|
nbits = nbits + minbits
|
||||||
|
else
|
||||||
|
local b = noeof(bs:read())
|
||||||
|
nbits = nbits + 1
|
||||||
|
code = code * 2 + b -- MSB first
|
||||||
|
end
|
||||||
|
local val = look[code]
|
||||||
|
if val then
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_gzip_header(bs)
|
||||||
|
-- local FLG_FTEXT = 2^0
|
||||||
|
local FLG_FHCRC = 2^1
|
||||||
|
local FLG_FEXTRA = 2^2
|
||||||
|
local FLG_FNAME = 2^3
|
||||||
|
local FLG_FCOMMENT = 2^4
|
||||||
|
|
||||||
|
local id1 = bs:read(8)
|
||||||
|
local id2 = bs:read(8)
|
||||||
|
if id1 ~= 31 or id2 ~= 139 then
|
||||||
|
runtime_error 'not in gzip format'
|
||||||
|
end
|
||||||
|
bs:read(8) -- compression method
|
||||||
|
local flg = bs:read(8) -- FLaGs
|
||||||
|
local mtime = bs:read(32) -- Modification TIME
|
||||||
|
local xfl = bs:read(8) -- eXtra FLags
|
||||||
|
local os = bs:read(8) -- Operating System
|
||||||
|
|
||||||
|
if not os then runtime_error 'invalid header' end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FEXTRA) then
|
||||||
|
local xlen = bs:read(16)
|
||||||
|
local extra = 0
|
||||||
|
for i=1,xlen do
|
||||||
|
extra = bs:read(8)
|
||||||
|
end
|
||||||
|
if not extra then runtime_error 'invalid header' end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_zstring(bs)
|
||||||
|
repeat
|
||||||
|
local by = bs:read(8)
|
||||||
|
if not by then runtime_error 'invalid header' end
|
||||||
|
until by == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FNAME) then
|
||||||
|
parse_zstring(bs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FCOMMENT) then
|
||||||
|
parse_zstring(bs)
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasbit(flg, FLG_FHCRC) then
|
||||||
|
local crc16 = bs:read(16)
|
||||||
|
if not crc16 then runtime_error 'invalid header' end
|
||||||
|
-- IMPROVE: check CRC. where is an example .gz file that
|
||||||
|
-- has this set?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_zlib_header(bs)
|
||||||
|
local cm = bs:read(4) -- Compression Method
|
||||||
|
local cinfo = bs:read(4) -- Compression info
|
||||||
|
local fcheck = bs:read(5) -- FLaGs: FCHECK (check bits for CMF and FLG)
|
||||||
|
local fdict = bs:read(1) -- FLaGs: FDICT (present dictionary)
|
||||||
|
local flevel = bs:read(2) -- FLaGs: FLEVEL (compression level)
|
||||||
|
local cmf = cinfo * 16 + cm -- CMF (Compresion Method and flags)
|
||||||
|
local flg = fcheck + fdict * 32 + flevel * 64 -- FLaGs
|
||||||
|
|
||||||
|
if cm ~= 8 then -- not "deflate"
|
||||||
|
runtime_error("unrecognized zlib compression method: " + cm)
|
||||||
|
end
|
||||||
|
if cinfo > 7 then
|
||||||
|
runtime_error("invalid zlib window size: cinfo=" + cinfo)
|
||||||
|
end
|
||||||
|
local window_size = 2^(cinfo + 8)
|
||||||
|
|
||||||
|
if (cmf*256 + flg) % 31 ~= 0 then
|
||||||
|
runtime_error("invalid zlib header (bad fcheck sum)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if fdict == 1 then
|
||||||
|
runtime_error("FIX:TODO - FDICT not currently implemented")
|
||||||
|
local dictid_ = bs:read(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
return window_size
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parse_huffmantables(bs)
|
||||||
|
local hlit = bs:read(5) -- # of literal/length codes - 257
|
||||||
|
local hdist = bs:read(5) -- # of distance codes - 1
|
||||||
|
local hclen = noeof(bs:read(4)) -- # of code length codes - 4
|
||||||
|
|
||||||
|
local ncodelen_codes = hclen + 4
|
||||||
|
local codelen_init = {}
|
||||||
|
local codelen_vals = {
|
||||||
|
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
|
||||||
|
for i=1,ncodelen_codes do
|
||||||
|
local nbits = bs:read(3)
|
||||||
|
local val = codelen_vals[i]
|
||||||
|
codelen_init[val] = nbits
|
||||||
|
end
|
||||||
|
local codelentable = HuffmanTable(codelen_init, true)
|
||||||
|
|
||||||
|
local function decode(ncodes)
|
||||||
|
local init = {}
|
||||||
|
local nbits
|
||||||
|
local val = 0
|
||||||
|
while val < ncodes do
|
||||||
|
local codelen = codelentable:read(bs)
|
||||||
|
--FIX:check nil?
|
||||||
|
local nrepeat
|
||||||
|
if codelen <= 15 then
|
||||||
|
nrepeat = 1
|
||||||
|
nbits = codelen
|
||||||
|
elseif codelen == 16 then
|
||||||
|
nrepeat = 3 + noeof(bs:read(2))
|
||||||
|
-- nbits unchanged
|
||||||
|
elseif codelen == 17 then
|
||||||
|
nrepeat = 3 + noeof(bs:read(3))
|
||||||
|
nbits = 0
|
||||||
|
elseif codelen == 18 then
|
||||||
|
nrepeat = 11 + noeof(bs:read(7))
|
||||||
|
nbits = 0
|
||||||
|
else
|
||||||
|
error 'ASSERT'
|
||||||
|
end
|
||||||
|
for i=1,nrepeat do
|
||||||
|
init[val] = nbits
|
||||||
|
val = val + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local huffmantable = HuffmanTable(init, true)
|
||||||
|
return huffmantable
|
||||||
|
end
|
||||||
|
|
||||||
|
local nlit_codes = hlit + 257
|
||||||
|
local ndist_codes = hdist + 1
|
||||||
|
|
||||||
|
local littable = decode(nlit_codes)
|
||||||
|
local disttable = decode(ndist_codes)
|
||||||
|
|
||||||
|
return littable, disttable
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local tdecode_len_base
|
||||||
|
local tdecode_len_nextrabits
|
||||||
|
local tdecode_dist_base
|
||||||
|
local tdecode_dist_nextrabits
|
||||||
|
local function parse_compressed_item(bs, outstate, littable, disttable)
|
||||||
|
local val = littable:read(bs)
|
||||||
|
if val < 256 then -- literal
|
||||||
|
output(outstate, val)
|
||||||
|
elseif val == 256 then -- end of block
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
if not tdecode_len_base then
|
||||||
|
local t = {[257]=3}
|
||||||
|
local skip = 1
|
||||||
|
for i=258,285,4 do
|
||||||
|
for j=i,i+3 do t[j] = t[j-1] + skip end
|
||||||
|
if i ~= 258 then skip = skip * 2 end
|
||||||
|
end
|
||||||
|
t[285] = 258
|
||||||
|
tdecode_len_base = t
|
||||||
|
end
|
||||||
|
if not tdecode_len_nextrabits then
|
||||||
|
local t = {}
|
||||||
|
|
||||||
|
for i=257,285 do
|
||||||
|
local j = math_max(i - 261, 0)
|
||||||
|
t[i] = rshift(j, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
t[285] = 0
|
||||||
|
tdecode_len_nextrabits = t
|
||||||
|
end
|
||||||
|
local len_base = tdecode_len_base[val]
|
||||||
|
local nextrabits = tdecode_len_nextrabits[val]
|
||||||
|
local extrabits = bs:read(nextrabits)
|
||||||
|
local len = len_base + extrabits
|
||||||
|
|
||||||
|
if not tdecode_dist_base then
|
||||||
|
local t = {[0]=1}
|
||||||
|
local skip = 1
|
||||||
|
for i=1,29,2 do
|
||||||
|
for j=i,i+1 do t[j] = t[j-1] + skip end
|
||||||
|
if i ~= 1 then skip = skip * 2 end
|
||||||
|
end
|
||||||
|
tdecode_dist_base = t
|
||||||
|
end
|
||||||
|
if not tdecode_dist_nextrabits then
|
||||||
|
local t = {}
|
||||||
|
|
||||||
|
for i=0,29 do
|
||||||
|
local j = math_max(i - 2, 0)
|
||||||
|
t[i] = rshift(j, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
tdecode_dist_nextrabits = t
|
||||||
|
end
|
||||||
|
local dist_val = disttable:read(bs)
|
||||||
|
local dist_base = tdecode_dist_base[dist_val]
|
||||||
|
local dist_nextrabits = tdecode_dist_nextrabits[dist_val]
|
||||||
|
local dist_extrabits = bs:read(dist_nextrabits)
|
||||||
|
local dist = dist_base + dist_extrabits
|
||||||
|
|
||||||
|
for i=1,len do
|
||||||
|
local pos = (outstate.window_pos - 1 - dist) % 32768 + 1 -- 32K
|
||||||
|
output(outstate, assert(outstate.window[pos], 'invalid distance'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function parse_block(bs, outstate, throttle)
|
||||||
|
local bfinal = bs:read(1)
|
||||||
|
local btype = bs:read(2)
|
||||||
|
|
||||||
|
local BTYPE_NO_COMPRESSION = 0
|
||||||
|
local BTYPE_FIXED_HUFFMAN = 1
|
||||||
|
local BTYPE_DYNAMIC_HUFFMAN = 2
|
||||||
|
local BTYPE_RESERVED_ = 3
|
||||||
|
|
||||||
|
if btype == BTYPE_NO_COMPRESSION then
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
local len = bs:read(16)
|
||||||
|
local nlen_ = noeof(bs:read(16))
|
||||||
|
|
||||||
|
for _=1,len do
|
||||||
|
local by = noeof(bs:read(8))
|
||||||
|
output(outstate, by)
|
||||||
|
end
|
||||||
|
elseif btype == BTYPE_FIXED_HUFFMAN or btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||||
|
local littable, disttable
|
||||||
|
if btype == BTYPE_DYNAMIC_HUFFMAN then
|
||||||
|
littable, disttable = parse_huffmantables(bs)
|
||||||
|
else
|
||||||
|
littable = HuffmanTable {0,8, 144,9, 256,7, 280,8, 288,nil}
|
||||||
|
disttable = HuffmanTable {0,5, 32,nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local is_done = parse_compressed_item(
|
||||||
|
bs, outstate, littable, disttable)
|
||||||
|
throttle()
|
||||||
|
until is_done
|
||||||
|
else
|
||||||
|
runtime_error 'unrecognized compression type'
|
||||||
|
end
|
||||||
|
|
||||||
|
return bfinal ~= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.inflate(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
local outstate = make_outstate(outbs)
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local is_final = parse_block(bs, outstate, t.throttle)
|
||||||
|
until is_final
|
||||||
|
end
|
||||||
|
local inflate = M.inflate
|
||||||
|
|
||||||
|
function M.gunzip(t)
|
||||||
|
local bs = get_bitstream(t.input)
|
||||||
|
local outbs = get_obytestream(t.output)
|
||||||
|
|
||||||
|
parse_gzip_header(bs)
|
||||||
|
|
||||||
|
inflate{input=bs, output=outbs, throttle=t.throttle or function() end}
|
||||||
|
|
||||||
|
bs:read(bs:nbits_left_in_byte())
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
3525
compress/apis/libdeflate.lua
Normal file
3525
compress/apis/libdeflate.lua
Normal file
File diff suppressed because it is too large
Load Diff
29
compress/compress.lua
Normal file
29
compress/compress.lua
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
local LZW = require('opus.compress.lzw')
|
||||||
|
local Tar = require('opus.compress.tar')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
|
||||||
|
if not args[2] then
|
||||||
|
error('Syntax: tar OUTFILE DIR')
|
||||||
|
end
|
||||||
|
|
||||||
|
local file = shell.resolve(args[1])
|
||||||
|
local dir = shell.resolve(args[2])
|
||||||
|
|
||||||
|
local filetype = 'tar'
|
||||||
|
if file:match('(.+)%.tar$') then
|
||||||
|
filetype = 'tar'
|
||||||
|
elseif file:match('(.+)%.lzw$') then
|
||||||
|
filetype = 'lzw'
|
||||||
|
end
|
||||||
|
|
||||||
|
if filetype == 'tar' then
|
||||||
|
Tar.tar(file, dir)
|
||||||
|
|
||||||
|
elseif filetype == 'lzw' then
|
||||||
|
local c = Tar.tar_string(dir)
|
||||||
|
Util.writeFile(file, LZW.compress(c), 'wb')
|
||||||
|
end
|
||||||
54
compress/uncompress.lua
Normal file
54
compress/uncompress.lua
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
local DEFLATE = require('compress.deflatelua')
|
||||||
|
local LZW = require('opus.compress.lzw')
|
||||||
|
local Tar = require('opus.compress.tar')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local io = _G.io
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
|
||||||
|
if not args[2] then
|
||||||
|
error('Syntax: tar FILE DESTDIR')
|
||||||
|
end
|
||||||
|
|
||||||
|
local inFile = shell.resolve(args[1])
|
||||||
|
local outDir = shell.resolve(args[2])
|
||||||
|
|
||||||
|
if inFile:match('(.+)%.[gG][zZ]$') then
|
||||||
|
-- uncompress a file created with: tar czf ...
|
||||||
|
local fh = io.open(inFile, 'rb') or error('Error opening ' .. inFile)
|
||||||
|
|
||||||
|
local t = { }
|
||||||
|
local function writer(b)
|
||||||
|
table.insert(t, b)
|
||||||
|
end
|
||||||
|
|
||||||
|
DEFLATE.gunzip {input=fh, output=writer, disable_crc=true}
|
||||||
|
|
||||||
|
fh:close()
|
||||||
|
|
||||||
|
local s, m = Tar.untar_string(string.char(table.unpack(t)), outDir, true)
|
||||||
|
|
||||||
|
if not s then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif inFile:match('(.+)%.tar%.lzw$') then
|
||||||
|
local c = Util.readFile(inFile, 'rb')
|
||||||
|
if not c then
|
||||||
|
error('Unable to open ' .. inFile)
|
||||||
|
end
|
||||||
|
|
||||||
|
local s, m = Tar.untar_string(LZW.decompress(c), outDir, true)
|
||||||
|
|
||||||
|
if not s then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
local s, m = Tar.untar(inFile, outDir, true)
|
||||||
|
if not s then
|
||||||
|
error(m)
|
||||||
|
end
|
||||||
|
end
|
||||||
6
debugger/.package
Normal file
6
debugger/.package
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
title = 'Lua Debugger',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/debugger',
|
||||||
|
description = [[Lua interactive debugger]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
229
debugger/apis/init.lua
Normal file
229
debugger/apis/init.lua
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
-- this code is loaded into the code being debugged
|
||||||
|
-- some portions from https://github.com/slembcke/debugger.lua
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
|
local dbg = {
|
||||||
|
hooks = { },
|
||||||
|
waits = { },
|
||||||
|
breakpoints = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function breakpointHook(depth, lineNo)
|
||||||
|
if dbg.breakpoints then
|
||||||
|
local info
|
||||||
|
for _,v in ipairs(dbg.breakpoints) do
|
||||||
|
if v.line == lineNo then
|
||||||
|
if not info then
|
||||||
|
info = debug.getinfo(depth)
|
||||||
|
end
|
||||||
|
if (v.file == info.short_src or v.bfile == info.short_src) then
|
||||||
|
return not v.disabled
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function functionHook(fn)
|
||||||
|
return function()
|
||||||
|
return debug.getinfo(3).func == fn
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stepHook()
|
||||||
|
return function()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stackSizeHook(n)
|
||||||
|
local i = 2
|
||||||
|
while true do
|
||||||
|
local info = debug.getinfo(i)
|
||||||
|
if not info then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
return function(depth, lineNo)
|
||||||
|
return not debug.getinfo(i - n)
|
||||||
|
or breakpointHook(depth + 1, lineNo)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stepOutHook()
|
||||||
|
return stackSizeHook(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stepOverHook()
|
||||||
|
return stackSizeHook(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a table of all the locally accessible variables.
|
||||||
|
local function local_bindings(offset, stack_inspect_offset)
|
||||||
|
offset = offset + 1 + stack_inspect_offset -- add this function to the offset
|
||||||
|
local func = debug.getinfo(offset).func
|
||||||
|
local bindings = { }
|
||||||
|
|
||||||
|
-- Retrieve the upvalues
|
||||||
|
do local i = 1; while true do
|
||||||
|
local name, value = debug.getupvalue(func, i)
|
||||||
|
if not name then break end
|
||||||
|
bindings[name] = { type = 'U', raw = value }
|
||||||
|
i = i + 1
|
||||||
|
end end
|
||||||
|
|
||||||
|
-- Retrieve the locals (overwriting any upvalues)
|
||||||
|
do local i = 1; while true do
|
||||||
|
local name, value = debug.getlocal(offset, i)
|
||||||
|
if not name then break end
|
||||||
|
bindings[name] = { type = 'L', raw = value }
|
||||||
|
i = i + 1
|
||||||
|
end end
|
||||||
|
|
||||||
|
-- Retrieve the varargs (works in Lua 5.2 and LuaJIT)
|
||||||
|
local varargs = { }
|
||||||
|
do local i = 1; while true do
|
||||||
|
local name, value = debug.getlocal(offset, -i)
|
||||||
|
if not name then break end
|
||||||
|
varargs[i] = value
|
||||||
|
i = i + 1
|
||||||
|
end end
|
||||||
|
if #varargs > 0 then
|
||||||
|
bindings["..."] = { type = 'V', value = varargs }
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = { }
|
||||||
|
for k,v in pairs(bindings) do
|
||||||
|
if k ~= '(*temporary)' then
|
||||||
|
v.name = k
|
||||||
|
v.value = tostring(v.raw)
|
||||||
|
--if type(v.raw) == 'table' and not next(v.raw) then
|
||||||
|
-- v.value = 'table: (empty)'
|
||||||
|
--end
|
||||||
|
table.insert(t, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_trace(offset, stack_inspect_offset)
|
||||||
|
local function format_loc(file, line) return file..":"..line end
|
||||||
|
local function format_stack_frame_info(info)
|
||||||
|
local filename = info.source:match("@(.*)")
|
||||||
|
local source = filename and fs.getName(filename) or info.short_src
|
||||||
|
local namewhat = (info.namewhat == "" and "chunk at" or info.namewhat)
|
||||||
|
local name = (info.name and "'"..info.name.."'" or format_loc(source, info.linedefined))
|
||||||
|
return format_loc(source, info.currentline).." in "..namewhat.." "..name
|
||||||
|
end
|
||||||
|
|
||||||
|
offset = offset + 1 -- add this function to the offset
|
||||||
|
local t = { }
|
||||||
|
local i = 0
|
||||||
|
while true do
|
||||||
|
local info = debug.getinfo(offset + i)
|
||||||
|
if not info then break end
|
||||||
|
t[i] = {
|
||||||
|
index = i,
|
||||||
|
current = (i == stack_inspect_offset),
|
||||||
|
desc = format_stack_frame_info(info),
|
||||||
|
info = info,
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hook(_, lineNo)
|
||||||
|
local h = dbg.hooks[coroutine.running()]
|
||||||
|
|
||||||
|
if h and h.eval(3, lineNo) then
|
||||||
|
local inspectOffset = 0
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local done = true
|
||||||
|
|
||||||
|
local snapshot = {
|
||||||
|
info = debug.getinfo(2 + inspectOffset),
|
||||||
|
locals = local_bindings(2, inspectOffset),
|
||||||
|
stack = get_trace(2, inspectOffset),
|
||||||
|
}
|
||||||
|
inspectOffset = 0 -- reset
|
||||||
|
|
||||||
|
table.insert(dbg.waits, h)
|
||||||
|
while dbg.waits[1] ~= h do
|
||||||
|
os.sleep(.1)
|
||||||
|
end
|
||||||
|
local cmd, param = dbg.read(snapshot)
|
||||||
|
|
||||||
|
table.remove(dbg.waits, 1)
|
||||||
|
|
||||||
|
if cmd == 's' then
|
||||||
|
h.eval = stepHook()
|
||||||
|
elseif cmd == 'n' then
|
||||||
|
h.eval = stepOverHook()
|
||||||
|
elseif cmd == 'f' then
|
||||||
|
h.eval = stepOutHook()
|
||||||
|
elseif cmd == 'c' then
|
||||||
|
h.eval = breakpointHook
|
||||||
|
elseif cmd == 'i' then
|
||||||
|
-- get snapshot of stack at this offset
|
||||||
|
inspectOffset = param
|
||||||
|
done = false
|
||||||
|
else
|
||||||
|
done = false
|
||||||
|
end
|
||||||
|
until done
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function dbg.call(fn, ...)
|
||||||
|
local args = { ... }
|
||||||
|
return xpcall(
|
||||||
|
function()
|
||||||
|
return fn(table.unpack(args))
|
||||||
|
end,
|
||||||
|
function(err)
|
||||||
|
dbg.hooks[coroutine.running()].eval = stepHook()
|
||||||
|
|
||||||
|
-- An error has occurred
|
||||||
|
return err
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
dbg.stopIn = function(fn)
|
||||||
|
dbg.hooks[coroutine.running()].eval = functionHook(fn)
|
||||||
|
end
|
||||||
|
|
||||||
|
_ENV.coroutine = setmetatable({
|
||||||
|
create = function(fn)
|
||||||
|
local co = _G.coroutine.create(function(...)
|
||||||
|
local r = { dbg.call(fn, ...) }
|
||||||
|
|
||||||
|
dbg.hooks[coroutine.running()] = nil
|
||||||
|
if not r[1] then
|
||||||
|
error(r[2], -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.unpack(r, 2)
|
||||||
|
end)
|
||||||
|
|
||||||
|
dbg.hooks[co] = {
|
||||||
|
co = co,
|
||||||
|
eval = breakpointHook,
|
||||||
|
}
|
||||||
|
debug.sethook(co, hook, 'l')
|
||||||
|
return co
|
||||||
|
end
|
||||||
|
}, { __index = coroutine })
|
||||||
|
|
||||||
|
dbg.hooks[coroutine.running()] = {
|
||||||
|
co = coroutine.running(),
|
||||||
|
eval = function() return false end,
|
||||||
|
}
|
||||||
|
|
||||||
|
debug.sethook(hook, 'l')
|
||||||
|
return dbg
|
||||||
4
debugger/autorun/startup.lua
Normal file
4
debugger/autorun/startup.lua
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
local completion = require('cc.shell.completion')
|
||||||
|
|
||||||
|
_ENV.shell.setCompletionFunction("packages/debugger/debug.lua",
|
||||||
|
completion.build(completion.program))
|
||||||
590
debugger/debug.lua
Normal file
590
debugger/debug.lua
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
local class = require('opus.class')
|
||||||
|
local Config = require('opus.config')
|
||||||
|
local Event = require('opus.event')
|
||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local getfenv = _G.getfenv
|
||||||
|
local kernel = _G.kernel
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
local filename = shell.resolveProgram(table.remove(args, 1))
|
||||||
|
|
||||||
|
if not filename then
|
||||||
|
error('file not found')
|
||||||
|
end
|
||||||
|
|
||||||
|
UI:disableEffects()
|
||||||
|
|
||||||
|
local config = Config.load('debugger')
|
||||||
|
if not config[filename] then
|
||||||
|
config[filename] = { }
|
||||||
|
end
|
||||||
|
|
||||||
|
local breakpoints = config[filename]
|
||||||
|
local currentFile
|
||||||
|
local debugFile, debugLine
|
||||||
|
|
||||||
|
local debugger = kernel.getCurrent()
|
||||||
|
local client
|
||||||
|
|
||||||
|
local function startClient()
|
||||||
|
local env = kernel.makeEnv(_ENV, fs.getDir(filename))
|
||||||
|
currentFile = nil
|
||||||
|
|
||||||
|
local clientId = multishell.openTab(nil, {
|
||||||
|
env = env,
|
||||||
|
title = fs.getName(filename):match('([^%.]+)'),
|
||||||
|
args = args,
|
||||||
|
fn = function()
|
||||||
|
local dbg = require('debugger')
|
||||||
|
local fn, msg = loadfile(filename, env)
|
||||||
|
|
||||||
|
if not fn then
|
||||||
|
error(msg, -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
dbg.read = function(snapshot)
|
||||||
|
os.sleep(0) -- not sure why, but we need a sleep before :resume
|
||||||
|
-- directly resuming debugger routine to prevent
|
||||||
|
-- serialization of the snapshot
|
||||||
|
dbg.debugger:resume('debuggerX', 'break', snapshot)
|
||||||
|
local e, cmd, param
|
||||||
|
repeat
|
||||||
|
e, cmd, param = os.pullEvent('debugger')
|
||||||
|
until e == 'debugger'
|
||||||
|
return cmd, param
|
||||||
|
end
|
||||||
|
|
||||||
|
_ENV.arg = { table.unpack(args) }
|
||||||
|
_ENV.arg[0] = filename
|
||||||
|
-- breakpoint table is shared across processes
|
||||||
|
dbg.breakpoints = breakpoints
|
||||||
|
dbg.debugger = debugger
|
||||||
|
dbg.stopIn(fn)
|
||||||
|
local s, m = dbg.call(fn, table.unpack(args))
|
||||||
|
|
||||||
|
dbg.debugger:resume('debuggerX', 'disconnect')
|
||||||
|
|
||||||
|
if not s then
|
||||||
|
error(m, -1)
|
||||||
|
end
|
||||||
|
print('Process ended normally')
|
||||||
|
print('Press enter to exit')
|
||||||
|
while true do
|
||||||
|
local e, code = os.pullEventRaw('key')
|
||||||
|
if e == 'terminate' or e == 'key' and code == _G.keys.enter then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
client = kernel.find(clientId)
|
||||||
|
end
|
||||||
|
|
||||||
|
local romFiles = {
|
||||||
|
files = { },
|
||||||
|
load = function(self)
|
||||||
|
local function recurse(dir)
|
||||||
|
local files = fs.list(dir)
|
||||||
|
for _,f in ipairs(files) do
|
||||||
|
local fullName = fs.combine(dir, f)
|
||||||
|
if fs.isDir(fullName) then
|
||||||
|
recurse(fullName)
|
||||||
|
else
|
||||||
|
self.files[f] = fullName
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
recurse('rom/apis')
|
||||||
|
self.reversed = Util.transpose(self.files)
|
||||||
|
end,
|
||||||
|
get = function(self, file)
|
||||||
|
return self.files[file]
|
||||||
|
end,
|
||||||
|
lookup = function(self, file)
|
||||||
|
return self.reversed[file]
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
romFiles:load()
|
||||||
|
|
||||||
|
local function loadSource(file)
|
||||||
|
currentFile = romFiles:get(file) or file:match('@?(.*)')
|
||||||
|
local src = { }
|
||||||
|
local lines = Util.readLines(currentFile) or type(file) == 'string' and Util.split(file)
|
||||||
|
|
||||||
|
if lines then
|
||||||
|
for i = 1, #lines do
|
||||||
|
table.insert(src, { line = i, source = lines[i] })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return src
|
||||||
|
end
|
||||||
|
|
||||||
|
local function message(...)
|
||||||
|
client:resume('debugger', ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
UI.InverseButton = class(UI.Button)
|
||||||
|
UI.InverseButton.defaults = {
|
||||||
|
UIElement = 'InverseButton',
|
||||||
|
backgroundColor = 'primary',
|
||||||
|
backgroundFocusColor = 'gray',
|
||||||
|
textFocusColor = 'primary',
|
||||||
|
textColor = 'gray',
|
||||||
|
}
|
||||||
|
|
||||||
|
local page = UI.Page {
|
||||||
|
backgroundColor = 'gray',
|
||||||
|
|
||||||
|
container = UI.Window {
|
||||||
|
y = 1, ey = '50%',
|
||||||
|
tabs = UI.Tabs {
|
||||||
|
ey = -2,
|
||||||
|
barBackgroundColor = 'tertiary',
|
||||||
|
locals = UI.Tab {
|
||||||
|
title = 'Locals',
|
||||||
|
index = 1,
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
disableHeader = true,
|
||||||
|
unfocusedBackgroundSelectedColor = 'black',
|
||||||
|
columns = {
|
||||||
|
{ heading = 'localname', key = 'name' },
|
||||||
|
{ heading = 'Value', key = 'value', textColor = 'yellow' },
|
||||||
|
},
|
||||||
|
autospace = true,
|
||||||
|
accelerators = {
|
||||||
|
grid_select = 'show_variable',
|
||||||
|
},
|
||||||
|
getRowTextColor = function(self, row, selected)
|
||||||
|
return row.type == 'U' and 'cyan'
|
||||||
|
or row.type == 'V' and 'lime'
|
||||||
|
or UI.Grid.getRowTextColor(self, row, selected)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
stack = UI.Tab {
|
||||||
|
title = 'Stack',
|
||||||
|
index = 3,
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
disableHeader = true,
|
||||||
|
sortColumn = 'index',
|
||||||
|
unfocusedBackgroundSelectedColor = 'black',
|
||||||
|
columns = {
|
||||||
|
{ key = 'index', width = 2 },
|
||||||
|
{ heading = 'heading', key = 'desc' },
|
||||||
|
},
|
||||||
|
getRowTextColor = function(self, row, selected)
|
||||||
|
return row.current and 'yellow'
|
||||||
|
or UI.Grid.getRowTextColor(self, row, selected)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
message('i', event.selected.index)
|
||||||
|
else
|
||||||
|
return UI.Grid.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
env = UI.Tab {
|
||||||
|
title = 'Env',
|
||||||
|
index = 4,
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
disableHeader = true,
|
||||||
|
autospace = true,
|
||||||
|
unfocusedBackgroundSelectedColor = 'black',
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Key', key = 'name' },
|
||||||
|
{ heading = 'Value', key = 'value', textColor = 'yellow' },
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
grid_select = 'show_variable',
|
||||||
|
},
|
||||||
|
sortCompare = function() end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
breaks = UI.Tab {
|
||||||
|
title = 'Breakpoints',
|
||||||
|
index = 2,
|
||||||
|
UI.MenuBar {
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Toggle', event = 'toggle' },
|
||||||
|
{ text = 'Remove', event = 'remove' },
|
||||||
|
{ text = 'Clear', event = 'clear' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = 2,
|
||||||
|
values = breakpoints,
|
||||||
|
autospace = true,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Line', key = 'line', width = 5 },
|
||||||
|
{ heading = 'Name', key = 'short' },
|
||||||
|
{ heading = 'Path', key = 'path', textColor = 'lightGray' },
|
||||||
|
},
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
return {
|
||||||
|
line = row.line,
|
||||||
|
short = fs.getName(row.file),
|
||||||
|
path = fs.getDir(row.file),
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
getRowTextColor = function(self, row, selected)
|
||||||
|
return row.disabled and 'lightGray'
|
||||||
|
or UI.Grid.getRowTextColor(self, row, selected)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'clear' then
|
||||||
|
Util.clear(self.grid.values)
|
||||||
|
self:emit({ type = 'update_breakpoints' })
|
||||||
|
|
||||||
|
elseif event.type == 'toggle' then
|
||||||
|
local bp = self.grid:getSelected()
|
||||||
|
if bp then
|
||||||
|
bp.disabled = not bp.disabled
|
||||||
|
self:emit({ type = 'update_breakpoints' })
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
self:emit({
|
||||||
|
type = 'open_file',
|
||||||
|
file = event.selected.file,
|
||||||
|
line = event.selected.line,
|
||||||
|
})
|
||||||
|
|
||||||
|
elseif event.type == 'remove' then
|
||||||
|
local bp = self.grid:getSelected()
|
||||||
|
if bp then
|
||||||
|
Util.removeByValue(self.grid.values, bp)
|
||||||
|
self:emit({ type = 'update_breakpoints' })
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
return UI.Tab.eventHandler(self, event)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
UI.MenuBar {
|
||||||
|
y = -1,
|
||||||
|
backgroundColor = 'primary',
|
||||||
|
buttonClass = 'InverseButton',
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Continue', event = 'cmd', cmd = 'c' },
|
||||||
|
{ text = 'Step', event = 'cmd', cmd = 's' },
|
||||||
|
{ text = 'Over', event = 'cmd', cmd = 'n' },
|
||||||
|
{ text = 'Out', event = 'cmd', cmd = 'f' },
|
||||||
|
{ text = 'Restart', event = 'restart', width = 9, ex = -1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
source = UI.ScrollingGrid {
|
||||||
|
y = '50%', ey = -2,
|
||||||
|
disableHeader = true,
|
||||||
|
backgroundColor = 'gray',
|
||||||
|
backgroundSelectedColor = 'lightGray',
|
||||||
|
unfocusedBackgroundSelectedColor = 'lightGray',
|
||||||
|
columns = {
|
||||||
|
{ key = 'marker', width = 1 },
|
||||||
|
{ key = 'line', textColor = 'cyan', width = 4 },
|
||||||
|
{ heading = 'heading', key = 'source' },
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
t = 'toggle_enabled'
|
||||||
|
},
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
for _,v in pairs(breakpoints) do
|
||||||
|
if v.file == currentFile and v.line == row.line then
|
||||||
|
return {
|
||||||
|
marker = v.disabled and 'x' or '!',
|
||||||
|
line = row.line,
|
||||||
|
source = row.source,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return row
|
||||||
|
end,
|
||||||
|
getRowTextColor = function(self, row, selected)
|
||||||
|
return row.line == debugLine and currentFile == debugFile and 'yellow'
|
||||||
|
or UI.Grid.getRowTextColor(self, row, selected)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'grid_select' then
|
||||||
|
self:emit({
|
||||||
|
type = 'toggle_breakpoint',
|
||||||
|
file = currentFile,
|
||||||
|
line = event.selected.line,
|
||||||
|
})
|
||||||
|
elseif event.type == 'toggle_enabled' then
|
||||||
|
local line = self:getSelected() and self:getSelected().line
|
||||||
|
if line then
|
||||||
|
for _,v in pairs(breakpoints) do
|
||||||
|
if v.file == currentFile and v.line == line then
|
||||||
|
v.disabled = not v.disabled
|
||||||
|
self:emit({ type = 'update_breakpoints' })
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return UI.Grid.eventHandler(self, event)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
ex = -12, y = -1,
|
||||||
|
backgroundColor = 'gray',
|
||||||
|
textColor = 'orange',
|
||||||
|
},
|
||||||
|
UI.FlatButton {
|
||||||
|
y = -1, x = -5,
|
||||||
|
textColor = 'orange',
|
||||||
|
event = 'open',
|
||||||
|
text = 'Open',
|
||||||
|
},
|
||||||
|
UI.FlatButton {
|
||||||
|
y = -1, x = -10,
|
||||||
|
textColor = 'orange',
|
||||||
|
event = 'edit_file',
|
||||||
|
text = 'Edit',
|
||||||
|
},
|
||||||
|
|
||||||
|
quick_open = UI.QuickSelect {
|
||||||
|
y = '50%',
|
||||||
|
modal = true,
|
||||||
|
enable = function() end,
|
||||||
|
getFiles = function()
|
||||||
|
local paths = { }
|
||||||
|
for _,v in pairs(Util.split(client.env.package.path, '(.-);')) do
|
||||||
|
v = v:match('(.+)%?') or ''
|
||||||
|
if v:sub(1, 1) ~= '/' then
|
||||||
|
v = fs.combine(fs.getDir(filename), v)
|
||||||
|
end
|
||||||
|
if fs.exists(v) and fs.isDir(v) then
|
||||||
|
paths[fs.combine(v, '')] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = { }
|
||||||
|
for k in pairs(paths) do
|
||||||
|
local function recurse(dir)
|
||||||
|
local files = fs.list(dir)
|
||||||
|
for _,f in ipairs(files) do
|
||||||
|
local fullName = fs.combine(dir, f)
|
||||||
|
if fs.isDir(fullName) then
|
||||||
|
-- skip virtual dirs
|
||||||
|
if f ~= '.git' and fs.native.isDir(fullName) then
|
||||||
|
recurse(fullName)
|
||||||
|
end
|
||||||
|
elseif fullName:match('(.+)%.lua$') then
|
||||||
|
table.insert(t, {
|
||||||
|
name = f,
|
||||||
|
dir = dir,
|
||||||
|
lname = f:lower(),
|
||||||
|
fullName = fullName,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
recurse(k)
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end,
|
||||||
|
show = function(self)
|
||||||
|
UI.QuickSelect.enable(self)
|
||||||
|
self:focusFirst()
|
||||||
|
self:draw()
|
||||||
|
self:addTransition('expandUp', { easing = 'outBounce', ticks = 12 })
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'select_cancel' then
|
||||||
|
self:disable()
|
||||||
|
elseif event.type == 'select_file' then
|
||||||
|
self.parent:openFile(event.file)
|
||||||
|
self:disable()
|
||||||
|
end
|
||||||
|
return UI.QuickSelect.eventHandler(self, event)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
textDisplay = UI.SlideOut {
|
||||||
|
ey = '50%',
|
||||||
|
textArea = UI.TextArea {
|
||||||
|
ey = -2,
|
||||||
|
},
|
||||||
|
UI.Button {
|
||||||
|
x = '50%', y = -1,
|
||||||
|
text = 'Ok',
|
||||||
|
event = 'slide_hide',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openFile = function(self, file, line)
|
||||||
|
if file ~= currentFile then
|
||||||
|
local src = loadSource(file)
|
||||||
|
self.source:setValues(src)
|
||||||
|
end
|
||||||
|
if line then
|
||||||
|
self.source:setIndex(#self.source.values)
|
||||||
|
self.source:setIndex(math.max(1, line - 4))
|
||||||
|
end
|
||||||
|
self.source:setIndex(line or 1)
|
||||||
|
|
||||||
|
if currentFile == debugFile then
|
||||||
|
self.statusBar:setStatus(
|
||||||
|
string.format('%s : %d', fs.getName(file), debugLine))
|
||||||
|
else
|
||||||
|
self.statusBar:setStatus(fs.getName(file))
|
||||||
|
end
|
||||||
|
self:draw()
|
||||||
|
end,
|
||||||
|
|
||||||
|
editFile = function(self, file)
|
||||||
|
if fs.exists(file) then
|
||||||
|
local line = self.source:getSelected().line
|
||||||
|
multishell.openTab(_ENV, {
|
||||||
|
path = 'sys/apps/shell.lua',
|
||||||
|
args = { ('edit --line=%d %s'):format(line , '/' .. file) },
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'cmd' then
|
||||||
|
self.statusBar:setStatus('Running...')
|
||||||
|
self:sync()
|
||||||
|
message(event.element.cmd)
|
||||||
|
|
||||||
|
elseif event.type == 'restart' then
|
||||||
|
if kernel.find(client.uid) then
|
||||||
|
client:resume('terminate')
|
||||||
|
end
|
||||||
|
startClient()
|
||||||
|
|
||||||
|
elseif event.type == 'open' then
|
||||||
|
self.quick_open:show()
|
||||||
|
|
||||||
|
elseif event.type == 'edit_file' then
|
||||||
|
self:editFile(currentFile)
|
||||||
|
|
||||||
|
elseif event.type == 'open_file' then
|
||||||
|
self:openFile(event.file, event.line)
|
||||||
|
|
||||||
|
elseif event.type == 'update_breakpoints' then
|
||||||
|
self.container.tabs.breaks.grid:update()
|
||||||
|
self.container.tabs.breaks.grid:draw()
|
||||||
|
self.source:draw()
|
||||||
|
Config.update('debugger', config)
|
||||||
|
|
||||||
|
elseif event.type == 'toggle_breakpoint' then
|
||||||
|
for k,v in pairs(breakpoints) do
|
||||||
|
if v.file == event.file and v.line == event.line then
|
||||||
|
table.remove(breakpoints, k)
|
||||||
|
self:emit({ type = 'update_breakpoints' })
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(breakpoints, {
|
||||||
|
file = event.file,
|
||||||
|
line = event.line,
|
||||||
|
bfile = romFiles:lookup(event.file),
|
||||||
|
})
|
||||||
|
|
||||||
|
self:emit({ type = 'update_breakpoints' })
|
||||||
|
|
||||||
|
elseif event.type == 'show_variable' then
|
||||||
|
if type(event.selected.raw) == 'table' then
|
||||||
|
if event.selected.children then
|
||||||
|
event.selected.children = nil
|
||||||
|
else
|
||||||
|
event.selected.children = { }
|
||||||
|
local t = event.selected.raw
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
local depth = event.selected.depth or 0
|
||||||
|
table.insert(event.selected.children, {
|
||||||
|
name = (' '):rep(depth + 2) .. tostring(k),
|
||||||
|
value = tostring(v),
|
||||||
|
raw = v,
|
||||||
|
depth = depth + 2
|
||||||
|
})
|
||||||
|
end
|
||||||
|
table.sort(event.selected.children, function(a, b) return a.name < b.name end)
|
||||||
|
end
|
||||||
|
local t = { }
|
||||||
|
local function insert(values)
|
||||||
|
for _,v in pairs(values) do
|
||||||
|
table.insert(t, v)
|
||||||
|
if v.children then
|
||||||
|
insert(v.children)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
insert(event.element.orig)
|
||||||
|
event.element:setValues(t)
|
||||||
|
event.element:draw()
|
||||||
|
else
|
||||||
|
self.textDisplay.textArea:setValue(event.selected.value)
|
||||||
|
self.textDisplay:show()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end,
|
||||||
|
enable = function(self)
|
||||||
|
UI.Page.enable(self)
|
||||||
|
startClient()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
Event.on('debuggerX', function(_, cmd, data)
|
||||||
|
if cmd == 'disconnect' then
|
||||||
|
page.statusBar:setStatus('Finished')
|
||||||
|
page:sync()
|
||||||
|
|
||||||
|
elseif cmd == 'break' then
|
||||||
|
kernel.raise(debugger.uid)
|
||||||
|
|
||||||
|
-- local tab
|
||||||
|
table.sort(data.locals, function(a, b) return a.name < b.name end)
|
||||||
|
page.container.tabs.locals.grid:setValues(data.locals)
|
||||||
|
page.container.tabs.locals.grid.orig = Util.shallowCopy(data.locals)
|
||||||
|
|
||||||
|
-- env tab
|
||||||
|
local t = { }
|
||||||
|
for k,v in pairs(getfenv(data.info.func)) do
|
||||||
|
table.insert(t, { name = k, value = tostring(v), raw = v })
|
||||||
|
end
|
||||||
|
table.sort(t, function(a, b) return a.name < b.name end)
|
||||||
|
page.container.tabs.env.grid:setValues(t)
|
||||||
|
page.container.tabs.env.grid.orig = Util.shallowCopy(t)
|
||||||
|
|
||||||
|
debugLine = data.info.currentline
|
||||||
|
debugFile = data.info.source:match('@?(.*)')
|
||||||
|
|
||||||
|
-- source tab
|
||||||
|
page:openFile(debugFile, debugLine)
|
||||||
|
|
||||||
|
-- stack
|
||||||
|
page.container.tabs.stack.grid:setValues(data.stack)
|
||||||
|
|
||||||
|
page:draw()
|
||||||
|
page:sync()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
|
|
||||||
|
if kernel.find(client.uid) then
|
||||||
|
client:resume('terminate')
|
||||||
|
end
|
||||||
8
debugger/etc/apps.db
Normal file
8
debugger/etc/apps.db
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
[ "1a03bd2fd107c453f3183e30b9716f82200671e8270fbbefbe602f5a48705527" ] = {
|
||||||
|
run = "fileui --exec=debug.lua --title=debug",
|
||||||
|
title = "Debug",
|
||||||
|
category = "Apps",
|
||||||
|
iconExt = "\30\50\31\50\128\31\102\152\144\30\102\31\50\159\155\30\50\128\10\30\50\31\102\130\152\30\101\31\53\143\143\30\102\31\50\155\30\50\31\102\129\10\30\50\31\102\130\31\50\128\31\101\134\137\31\50\128\31\102\129",
|
||||||
|
},
|
||||||
|
}
|
||||||
6
debugger/help/debug.txt
Normal file
6
debugger/help/debug.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
An interactive debugger for lua
|
||||||
|
|
||||||
|
debug must be enabled!
|
||||||
|
|
||||||
|
Run from a shell prompt
|
||||||
|
> debug FILE [ARGS]
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
title = 'Example programs for coding in Opus',
|
|
||||||
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/examples',
|
|
||||||
description = [[Starter/example programs for creating Opus programs.]],
|
|
||||||
license = 'MIT',
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
local UI = require('opus.ui')
|
|
||||||
|
|
||||||
local page = UI.Page {
|
|
||||||
menuBar = UI.MenuBar {
|
|
||||||
buttons = {
|
|
||||||
{ text = 'Shuffle', event = 'shuffle' },
|
|
||||||
{ text = 'Clear', event = 'clear', },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid = UI.ScrollingGrid {
|
|
||||||
y = 2, ey = -2,
|
|
||||||
values = {
|
|
||||||
{ col = 'column1', value = 'value1' },
|
|
||||||
{ col = 'column2', value = 'value2' },
|
|
||||||
{ col = 'column3', value = 'value3' },
|
|
||||||
},
|
|
||||||
columns = {
|
|
||||||
{ heading = 'HDR1', key = 'col' },
|
|
||||||
{ heading = 'HDR2', key = 'value' },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
statusBar = UI.StatusBar { },
|
|
||||||
}
|
|
||||||
|
|
||||||
function page:eventHandler(event)
|
|
||||||
if event.type == 'grid_select' then
|
|
||||||
self.statusBar:setStatus('Selected: ' .. event.selected.value)
|
|
||||||
|
|
||||||
elseif event.type == 'shuffle' then
|
|
||||||
for _,v in pairs(self.grid.values) do
|
|
||||||
v.col = 'column' .. math.random(1,3)
|
|
||||||
end
|
|
||||||
self.grid:update()
|
|
||||||
self.grid:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'clear' then
|
|
||||||
self.grid:setValues({ })
|
|
||||||
self.grid:draw()
|
|
||||||
end
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
UI:setPage(page)
|
|
||||||
UI:pullEvents()
|
|
||||||
@@ -246,43 +246,35 @@ local function server(mode)
|
|||||||
computers[computerId] = nil
|
computers[computerId] = nil
|
||||||
page.grid.values = positions
|
page.grid.values = positions
|
||||||
page.grid:update()
|
page.grid:update()
|
||||||
page.grid:draw()
|
|
||||||
page.grid:sync()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Event.on('modem_message', function(_, side, channel, computerId, message, distance)
|
Event.on('modem_message', function(_, side, channel, computerId, message, distance)
|
||||||
if distance and modems[side] then
|
local modem = modems[side]
|
||||||
|
if distance and modem then
|
||||||
if mode == 'gps' and channel == GPS.CHANNEL_GPS and message == "PING" then
|
if mode == 'gps' and channel == GPS.CHANNEL_GPS and message == "PING" then
|
||||||
for _, modem in pairs(modems) do
|
modem.transmit(computerId, GPS.CHANNEL_GPS, { modem.x, modem.y, modem.z })
|
||||||
modem.transmit(computerId, GPS.CHANNEL_GPS, { modem.x, modem.y, modem.z })
|
getPosition(computerId, modem, distance)
|
||||||
end
|
|
||||||
getPosition(computerId, modems[side], distance)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if mode == 'snmp' and channel == 999 then
|
if mode == 'snmp' and channel == 999 then
|
||||||
getPosition(computerId, modems[side], distance, message)
|
getPosition(computerId, modem, distance, message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Event.onInterval(1, function()
|
Event.onInterval(1, function()
|
||||||
local resync = false
|
|
||||||
for _, detail in pairs(positions) do
|
for _, detail in pairs(positions) do
|
||||||
if os.clock() - detail.lastChanged > 10 then
|
if os.clock() - detail.lastChanged > 10 then
|
||||||
detail.changed = false
|
detail.changed = false
|
||||||
resync = true
|
|
||||||
end
|
end
|
||||||
if os.clock() - detail.timestamp > 60 and detail.alive then
|
if os.clock() - detail.timestamp > 60 and detail.alive then
|
||||||
detail.alive = false
|
detail.alive = false
|
||||||
detail.hbeat = false
|
detail.hbeat = false
|
||||||
resync = true
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if resync then
|
page:draw()
|
||||||
page:draw()
|
page:sync()
|
||||||
page:sync()
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -304,6 +296,7 @@ elseif args[1] == 'snmp' then
|
|||||||
table.insert(page.grid.columns,
|
table.insert(page.grid.columns,
|
||||||
{ heading = 'Label', key = 'label', textColor = colors.cyan }
|
{ heading = 'Label', key = 'label', textColor = colors.cyan }
|
||||||
)
|
)
|
||||||
|
page.grid.sortColumn = 'label'
|
||||||
page.grid:adjustWidth()
|
page.grid:adjustWidth()
|
||||||
server('snmp')
|
server('snmp')
|
||||||
|
|
||||||
@@ -313,4 +306,4 @@ else
|
|||||||
end
|
end
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -250,5 +250,3 @@ turtle.setStatus('Jamming')
|
|||||||
UI:pullEvents()
|
UI:pullEvents()
|
||||||
turtle.setStatus('idle')
|
turtle.setStatus('idle')
|
||||||
page:play(false)
|
page:play(false)
|
||||||
|
|
||||||
UI.term:reset()
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
local w, h = term.getSize()
|
|
||||||
|
|
||||||
term.clear()
|
|
||||||
term.setCursorPos(1, 1)
|
|
||||||
|
|
||||||
local t = { }
|
|
||||||
for i = 1, 8 do
|
|
||||||
table.insert(t, '---')
|
|
||||||
end
|
|
||||||
|
|
||||||
for i = 1, 255 do
|
|
||||||
table.insert(t, string.format('%d %c', i, i))
|
|
||||||
end
|
|
||||||
|
|
||||||
textutils.pagedTabulate(t)
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
local class = require('opus.class')
|
|
||||||
local UI = require('opus.ui')
|
|
||||||
local Event = require('opus.event')
|
|
||||||
local Peripheral = require('opus.peripheral')
|
|
||||||
|
|
||||||
--[[-- Glasses device --]]--
|
|
||||||
local Glasses = class()
|
|
||||||
function Glasses:init(args)
|
|
||||||
|
|
||||||
local defaults = {
|
|
||||||
backgroundColor = colors.black,
|
|
||||||
textColor = colors.white,
|
|
||||||
textScale = .5,
|
|
||||||
backgroundOpacity = .5,
|
|
||||||
multiplier = 2.6665,
|
|
||||||
-- multiplier = 2.333,
|
|
||||||
}
|
|
||||||
defaults.width, defaults.height = term.getSize()
|
|
||||||
|
|
||||||
UI:setProperties(defaults, args)
|
|
||||||
UI:setProperties(self, defaults)
|
|
||||||
|
|
||||||
self.bridge = Peripheral.get({
|
|
||||||
type = 'openperipheral_bridge',
|
|
||||||
method = 'addBox',
|
|
||||||
})
|
|
||||||
self.bridge.clear()
|
|
||||||
|
|
||||||
self.setBackgroundColor = function(...) end
|
|
||||||
self.setTextColor = function(...) end
|
|
||||||
|
|
||||||
self.t = { }
|
|
||||||
for i = 1, self.height do
|
|
||||||
self.t[i] = {
|
|
||||||
text = string.rep(' ', self.width+1),
|
|
||||||
--text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff),
|
|
||||||
bg = { },
|
|
||||||
textFields = { },
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor)
|
|
||||||
local colors = {
|
|
||||||
[ colors.black ] = 0x000000,
|
|
||||||
[ colors.brown ] = 0x7F664C,
|
|
||||||
[ colors.blue ] = 0x253192,
|
|
||||||
[ colors.red ] = 0xFF0000,
|
|
||||||
[ colors.gray ] = 0x272727,
|
|
||||||
[ colors.lime ] = 0x426A0D,
|
|
||||||
[ colors.green ] = 0x2D5628,
|
|
||||||
[ colors.white ] = 0xFFFFFF
|
|
||||||
}
|
|
||||||
|
|
||||||
local function overlap(box, ax, bx)
|
|
||||||
if bx < box.ax or ax > box.bx then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
for _,box in pairs(boxes) do
|
|
||||||
if overlap(box, ax, bx) then
|
|
||||||
if box.bgColor == bgColor then
|
|
||||||
ax = math.min(ax, box.ax)
|
|
||||||
bx = math.max(bx, box.bx)
|
|
||||||
box.ax = box.bx + 1
|
|
||||||
elseif ax == box.ax then
|
|
||||||
box.ax = bx + 1
|
|
||||||
elseif ax > box.ax then
|
|
||||||
if bx < box.bx then
|
|
||||||
table.insert(boxes, { -- split
|
|
||||||
ax = bx + 1,
|
|
||||||
bx = box.bx,
|
|
||||||
bgColor = box.bgColor
|
|
||||||
})
|
|
||||||
box.bx = ax - 1
|
|
||||||
break
|
|
||||||
else
|
|
||||||
box.ax = box.bx + 1
|
|
||||||
end
|
|
||||||
elseif ax < box.ax then
|
|
||||||
if bx > box.bx then
|
|
||||||
box.ax = box.bx + 1 -- delete
|
|
||||||
else
|
|
||||||
box.ax = bx + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if bgColor ~= colors.black then
|
|
||||||
table.insert(boxes, {
|
|
||||||
ax = ax,
|
|
||||||
bx = bx,
|
|
||||||
bgColor = bgColor
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local deleted
|
|
||||||
repeat
|
|
||||||
deleted = false
|
|
||||||
for k,box in pairs(boxes) do
|
|
||||||
if box.ax > box.bx then
|
|
||||||
if box.box then
|
|
||||||
box.box.delete()
|
|
||||||
end
|
|
||||||
table.remove(boxes, k)
|
|
||||||
deleted = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
if not box.box then
|
|
||||||
box.box = self.bridge.addBox(
|
|
||||||
math.floor(self.x + (box.ax - 1) * self.multiplier),
|
|
||||||
self.y + y * 4,
|
|
||||||
math.ceil((box.bx - box.ax + 1) * self.multiplier),
|
|
||||||
4,
|
|
||||||
colors[bgColor],
|
|
||||||
self.backgroundOpacity)
|
|
||||||
else
|
|
||||||
box.box.setX(self.x + math.floor((box.ax - 1) * self.multiplier))
|
|
||||||
box.box.setWidth(math.ceil((box.bx - box.ax + 1) * self.multiplier))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
until not deleted
|
|
||||||
end
|
|
||||||
|
|
||||||
function Glasses:write(x, y, text, bg)
|
|
||||||
|
|
||||||
if x < 1 then
|
|
||||||
error(' less ', 6)
|
|
||||||
end
|
|
||||||
if y <= #self.t then
|
|
||||||
local line = self.t[y]
|
|
||||||
local str = line.text
|
|
||||||
str = str:sub(1, x-1) .. text .. str:sub(x + #text)
|
|
||||||
self.t[y].text = str
|
|
||||||
|
|
||||||
for _,tf in pairs(line.textFields) do
|
|
||||||
tf.delete()
|
|
||||||
end
|
|
||||||
line.textFields = { }
|
|
||||||
|
|
||||||
local function split(st)
|
|
||||||
local words = { }
|
|
||||||
local offset = 0
|
|
||||||
while true do
|
|
||||||
local b,e,w = st:find('(%S+)')
|
|
||||||
if not b then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
table.insert(words, {
|
|
||||||
offset = b + offset - 1,
|
|
||||||
text = w,
|
|
||||||
})
|
|
||||||
offset = offset + e
|
|
||||||
st = st:sub(e + 1)
|
|
||||||
end
|
|
||||||
return words
|
|
||||||
end
|
|
||||||
|
|
||||||
local words = split(str)
|
|
||||||
for _,word in pairs(words) do
|
|
||||||
local tf = self.bridge.addText(self.x + word.offset * self.multiplier,
|
|
||||||
self.y+y*4, '', 0xffffff)
|
|
||||||
tf.setScale(self.textScale)
|
|
||||||
tf.setZ(1)
|
|
||||||
tf.setText(word.text)
|
|
||||||
table.insert(line.textFields, tf)
|
|
||||||
end
|
|
||||||
|
|
||||||
self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Glasses:clear(bg)
|
|
||||||
for _,line in pairs(self.t) do
|
|
||||||
for _,tf in pairs(line.textFields) do
|
|
||||||
tf.delete()
|
|
||||||
end
|
|
||||||
line.textFields = { }
|
|
||||||
line.text = string.rep(' ', self.width+1)
|
|
||||||
-- self.t[i].text.setText('')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Glasses:reset()
|
|
||||||
self:clear()
|
|
||||||
self.bridge.clear()
|
|
||||||
self.bridge.sync()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Glasses:sync()
|
|
||||||
self.bridge.sync()
|
|
||||||
end
|
|
||||||
|
|
||||||
return Glasses
|
|
||||||
77
ignore/pal.lua
Normal file
77
ignore/pal.lua
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
local pals = {
|
||||||
|
{ -- molokai
|
||||||
|
0x101010,
|
||||||
|
0x960050,
|
||||||
|
0x66aa11,
|
||||||
|
0xc47f2c,
|
||||||
|
0x30309b,
|
||||||
|
0x7e40a5,
|
||||||
|
0x3579a8,
|
||||||
|
0x9999aa,
|
||||||
|
0x303030,
|
||||||
|
0xff0090,
|
||||||
|
0x80ff00,
|
||||||
|
0xffba68,
|
||||||
|
0x5f5fee,
|
||||||
|
0xbb88dd,
|
||||||
|
0x4eb4fa,
|
||||||
|
0xd0d0d0,
|
||||||
|
},
|
||||||
|
{ -- solarized
|
||||||
|
0xffffd7,
|
||||||
|
0xd75f00, -- orange
|
||||||
|
0x585858,
|
||||||
|
0x0087ff, -- light blue
|
||||||
|
0x1c1c1c,
|
||||||
|
0x8a8a8a,
|
||||||
|
0xd70000, -- light red
|
||||||
|
0x808080, -- gray
|
||||||
|
0xe4e4e4, -- light gray
|
||||||
|
0x00afaf, -- cyan
|
||||||
|
0x626262,
|
||||||
|
0x5f5faf, -- blue
|
||||||
|
0xaf8700, -- brown
|
||||||
|
0x5f8700, -- green
|
||||||
|
0xaf005f, -- dark red
|
||||||
|
0x262626, -- black
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0xf7f7f7,
|
||||||
|
0xc4a500, -- mustard
|
||||||
|
0xf79aff, -- magenta
|
||||||
|
0x8dcff0, -- light blue
|
||||||
|
0xfee14d, -- yellow
|
||||||
|
0xc4f137, -- lime
|
||||||
|
0x207383, -- dark green
|
||||||
|
0x7a7a7a,
|
||||||
|
0xa1a1a1,
|
||||||
|
0x6ad9cf, -- greenish blue
|
||||||
|
0xba8acc, -- purple
|
||||||
|
0x62a3c4, -- blue gray
|
||||||
|
0xd6837c, -- orange/brown
|
||||||
|
0x7da900, -- green
|
||||||
|
0xb84131, -- redish brown
|
||||||
|
0x1b1b1b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
term.setPaletteColor(2^0,0xFFFFFF)
|
||||||
|
term.setPaletteColor(2^1,0xFF6300)
|
||||||
|
term.setPaletteColor(2^2,0xFF00DE)
|
||||||
|
term.setPaletteColor(2^3,0x00C3FF)
|
||||||
|
term.setPaletteColor(2^4,0xFFFF00)
|
||||||
|
term.setPaletteColor(2^5,0x91FF00)
|
||||||
|
term.setPaletteColor(2^6,0xFF6DA8)
|
||||||
|
term.setPaletteColor(2^7,0x585757)
|
||||||
|
term.setPaletteColor(2^8,0xA9A9A9)
|
||||||
|
term.setPaletteColor(2^9,0x00FFFF)
|
||||||
|
term.setPaletteColor(2^10,0x7700FF)
|
||||||
|
term.setPaletteColor(2^11,0x0000FF)
|
||||||
|
term.setPaletteColor(2^12,0x4C2700)
|
||||||
|
term.setPaletteColor(2^13,0x00FF00)
|
||||||
|
term.setPaletteColor(2^14,0xFF0000)
|
||||||
|
term.setPaletteColor(2^15,0x000000)
|
||||||
|
|
||||||
|
local pal = pals[tonumber(({...})[1])]
|
||||||
|
for k,v in pairs(pal) do
|
||||||
|
term.setPaletteColour(2^(k - 1), v)
|
||||||
|
end
|
||||||
174
ignore/passthrough.lua
Normal file
174
ignore/passthrough.lua
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
local _rep = string.rep
|
||||||
|
local _sub = string.sub
|
||||||
|
local colors = _G.colors
|
||||||
|
|
||||||
|
local palette = { }
|
||||||
|
|
||||||
|
for n = 1, 16 do
|
||||||
|
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||||
|
end
|
||||||
|
|
||||||
|
local swindow = { }
|
||||||
|
|
||||||
|
function swindow.createPassthrough(parent, wx, wy, width, height)
|
||||||
|
local window = { }
|
||||||
|
local cx, cy = 1, 1
|
||||||
|
local blink = false
|
||||||
|
local fg = colors.white
|
||||||
|
local bg = colors.black
|
||||||
|
|
||||||
|
local function crop(text, x)
|
||||||
|
local w = #text
|
||||||
|
|
||||||
|
if x < 1 then
|
||||||
|
text = _sub(text, 2 - x)
|
||||||
|
w = w + x - 1
|
||||||
|
x = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if x + w - 1 > width then
|
||||||
|
text = _sub(text, 1, width - x + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
local function blit(text, fg, bg)
|
||||||
|
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
|
||||||
|
parent.blit(text, fg, bg)
|
||||||
|
cx = cx + #text
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.write(text)
|
||||||
|
if cy > 0 and cy <= height then
|
||||||
|
text = crop(tostring(text), cx)
|
||||||
|
if #text > 0 then
|
||||||
|
--parent.setCursorPos(cx + wx - 1, cy + wy - 1)
|
||||||
|
blit(text, _rep(palette[fg], #text), _rep(palette[bg], #text))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.blit(text, fg, bg)
|
||||||
|
if cy > 0 and cy <= height then
|
||||||
|
text = crop(tostring(text), cx)
|
||||||
|
if #text > 0 then
|
||||||
|
blit(text, crop(tostring(fg), cx), crop(tostring(bg), cx))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.clear()
|
||||||
|
local filler = _rep(' ', width)
|
||||||
|
for i = 1, height do
|
||||||
|
parent.setCursorPos(wx, i + wy - 1)
|
||||||
|
parent.write(filler)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.clearLine()
|
||||||
|
if cy > 0 and cy <= height then
|
||||||
|
local filler = _rep(' ', width)
|
||||||
|
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
|
||||||
|
parent.write(filler)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.getCursorPos()
|
||||||
|
return cx, cy
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.setCursorPos(x, y)
|
||||||
|
cx = math.floor(x)
|
||||||
|
cy = math.floor(y)
|
||||||
|
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.setCursorBlink(b)
|
||||||
|
blink = b
|
||||||
|
parent.setCursorBlink(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.getCursorBlink()
|
||||||
|
return blink
|
||||||
|
end
|
||||||
|
|
||||||
|
window.isColor = parent.isColor
|
||||||
|
window.isColour = parent.isColour
|
||||||
|
window.setPaletteColour = parent.setPaletteColour
|
||||||
|
window.setPaletteColor = parent.setPaletteColor
|
||||||
|
window.getPaletteColour = parent.getPaletteColour
|
||||||
|
window.getPaletteColor = parent.getPaletteColour
|
||||||
|
window.setBackgroundColor = parent.setBackgroundColor
|
||||||
|
window.setBackgroundColour = parent.setBackgroundColor
|
||||||
|
window.getBackgroundColor = parent.getBackgroundColor
|
||||||
|
window.getBackgroundColour = parent.getBackgroundColor
|
||||||
|
window.setVisible = parent.setVisible
|
||||||
|
window.redraw = function() end --parent.redraw
|
||||||
|
|
||||||
|
function window.getTextColor()
|
||||||
|
return fg
|
||||||
|
end
|
||||||
|
window.getTextColour = window.getTextColor
|
||||||
|
|
||||||
|
function window.setTextColor(textColor)
|
||||||
|
fg = textColor
|
||||||
|
parent.setTextColor(fg)
|
||||||
|
end
|
||||||
|
window.setTextColour = window.setTextColor
|
||||||
|
|
||||||
|
function window.restoreCursor()
|
||||||
|
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
|
||||||
|
parent.setTextColor(fg)
|
||||||
|
parent.setCursorBlink(blink)
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.getSize()
|
||||||
|
return width, height
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.scroll( n )
|
||||||
|
if n ~= 0 then
|
||||||
|
local lines = { }
|
||||||
|
for i = 1, height do
|
||||||
|
lines[i] = { parent.getLine(wy + i - 1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
for newY = 1, height do
|
||||||
|
local y = newY + n
|
||||||
|
parent.setCursorPos(wx, wy + newY - 1)
|
||||||
|
if y >= 1 and y <= height then
|
||||||
|
parent.blit(table.unpack(lines[y]))
|
||||||
|
else
|
||||||
|
parent.blit(
|
||||||
|
_rep(' ', width),
|
||||||
|
_rep(palette[fg], width),
|
||||||
|
_rep(palette[bg], width))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
parent.setCursorPos(cx + wx - 1, cy + wy - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.getLine(y)
|
||||||
|
local t, tc, bc = parent.getLine(y + cy - 1)
|
||||||
|
return t:sub(1, width), tc:sub(1, width), bc:sub(1, width)
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.getPosition()
|
||||||
|
return wx, wy
|
||||||
|
end
|
||||||
|
|
||||||
|
function window.reposition(nNewX, nNewY, nNewWidth, nNewHeight, newParent)
|
||||||
|
wx = nNewX
|
||||||
|
wy = nNewY
|
||||||
|
width = nNewWidth
|
||||||
|
height = nNewHeight
|
||||||
|
|
||||||
|
window.restoreCursor()
|
||||||
|
end
|
||||||
|
|
||||||
|
return window
|
||||||
|
end
|
||||||
|
|
||||||
|
return swindow
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
--[[
|
|
||||||
Breed rabbits with a rabbit.
|
|
||||||
]]
|
|
||||||
|
|
||||||
local neural = require('neural.interface')
|
|
||||||
local Point = require('point')
|
|
||||||
local Sound = require('sound')
|
|
||||||
local Util = require('util')
|
|
||||||
|
|
||||||
local os = _G.os
|
|
||||||
|
|
||||||
local BREEDING = 'Rabbit'
|
|
||||||
local WALK_SPEED = 1.3
|
|
||||||
local MAX_GROWN = 100
|
|
||||||
local BREED_DELAY = 120
|
|
||||||
|
|
||||||
neural.assertModules({
|
|
||||||
'plethora:sensor',
|
|
||||||
'plethora:scanner',
|
|
||||||
'plethora:laser',
|
|
||||||
'plethora:kinetic',
|
|
||||||
'plethora:introspection',
|
|
||||||
})
|
|
||||||
|
|
||||||
local ID = neural.getID()
|
|
||||||
local fed = { }
|
|
||||||
|
|
||||||
local function resupply()
|
|
||||||
local slot = neural.getEquipment().list()[1]
|
|
||||||
if slot and slot.count > 32 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
print('resupplying')
|
|
||||||
|
|
||||||
local dispenser = Util.find(neural.scan(), 'name', 'minecraft:wooden_pressure_plate')
|
|
||||||
if dispenser then
|
|
||||||
if math.abs(dispenser.x) > 1 or math.abs(dispenser.z) > 1 then
|
|
||||||
neural.walkTo({ x = dispenser.x, y = 0, z = dispenser.z }, WALK_SPEED)
|
|
||||||
end
|
|
||||||
neural.lookAt(dispenser)
|
|
||||||
neural.getEquipment().suck(1, 64)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function breed(entity)
|
|
||||||
print('breeding')
|
|
||||||
entity.lastFed = os.clock()
|
|
||||||
fed[entity.id] = entity
|
|
||||||
|
|
||||||
neural.walkTo(entity, WALK_SPEED, .5)
|
|
||||||
entity = neural.getMetaByID(entity.id)
|
|
||||||
if entity and not entity.isChild then
|
|
||||||
neural.lookAt(entity)
|
|
||||||
neural.use(1)
|
|
||||||
os.sleep(.1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function kill(entity)
|
|
||||||
print('killing')
|
|
||||||
neural.walkTo(entity, WALK_SPEED, 2)
|
|
||||||
entity = neural.getMetaByID(entity.id)
|
|
||||||
if entity and not entity.isChild then
|
|
||||||
neural.lookAt(entity)
|
|
||||||
neural.fireAt({ x = entity.x, y = 0, z = entity.z })
|
|
||||||
Sound.play('entity.firework.launch')
|
|
||||||
os.sleep(.2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getEntities()
|
|
||||||
return Util.filter(neural.sense(), function(entity)
|
|
||||||
if entity.name == BREEDING and entity.y > -.5 and entity.id ~= ID then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function getHungry(entities)
|
|
||||||
for _,v in pairs(entities) do
|
|
||||||
if not fed[v.id] or
|
|
||||||
os.clock() - fed[v.id].lastFed > BREED_DELAY then
|
|
||||||
return v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function randomEntity(entities)
|
|
||||||
local r = math.random(1, Util.size(entities))
|
|
||||||
local i = 1
|
|
||||||
for _, v in pairs(entities) do
|
|
||||||
i = i + 1
|
|
||||||
if i > r then
|
|
||||||
return v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dropOff()
|
|
||||||
print('dropping')
|
|
||||||
|
|
||||||
if neural.getEquipment().list()[2] then
|
|
||||||
local b = Util.find(neural.scan(), 'name', 'minecraft:hopper')
|
|
||||||
if b then
|
|
||||||
neural.walkTo({ x = b.x, y = 0, z = b.z }, 2)
|
|
||||||
|
|
||||||
b = Util.find(neural.scan(), 'name', 'minecraft:hopper')
|
|
||||||
if b and math.abs(b.x) < 1 and math.abs(b.z) < 1 then
|
|
||||||
print('dropped')
|
|
||||||
neural.getEquipment().drop(2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function pickup(id)
|
|
||||||
local b = neural.getMetaByID(id)
|
|
||||||
if b then
|
|
||||||
neural.walkTo(b, 2)
|
|
||||||
|
|
||||||
local main = neural.getEquipment().list()[1]
|
|
||||||
local amount = neural.getEquipment().suck(not main and 2 or nil)
|
|
||||||
print('sucked: ' .. amount)
|
|
||||||
if amount > 0 then
|
|
||||||
Sound.play('entity.item.pickup')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function drops()
|
|
||||||
local sensed = Util.reduce(neural.sense(), function(acc, s)
|
|
||||||
if Util.round(s.y) == 0 and s.name == 'Item' then
|
|
||||||
acc[s.id] = s
|
|
||||||
end
|
|
||||||
return acc
|
|
||||||
end, { })
|
|
||||||
|
|
||||||
local pt = { x = 0, y = 0, z = 0 }
|
|
||||||
while true do
|
|
||||||
local b = Point.closest(pt, sensed)
|
|
||||||
if not b then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
sensed[b.id] = nil
|
|
||||||
|
|
||||||
if pickup(b.id) then
|
|
||||||
pt = b
|
|
||||||
else
|
|
||||||
dropOff()
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
while true do
|
|
||||||
resupply()
|
|
||||||
|
|
||||||
local entities = getEntities()
|
|
||||||
|
|
||||||
if Util.size(entities) > MAX_GROWN then
|
|
||||||
kill(randomEntity(entities))
|
|
||||||
else
|
|
||||||
local entity = getHungry(entities)
|
|
||||||
if entity then
|
|
||||||
breed(entity)
|
|
||||||
else
|
|
||||||
print('sleeping')
|
|
||||||
os.sleep(5)
|
|
||||||
end
|
|
||||||
drops()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -4,42 +4,21 @@ local Util = require('opus.util')
|
|||||||
|
|
||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local peripheral = _G.peripheral
|
|
||||||
local printError = _G.printError
|
local printError = _G.printError
|
||||||
local shell = _ENV.shell
|
|
||||||
local term = _G.term
|
local term = _G.term
|
||||||
local window = _G.window
|
local window = _G.window
|
||||||
|
|
||||||
local function syntax()
|
|
||||||
printError('Syntax:')
|
|
||||||
error('mwm [--config=filename] [monitor]')
|
|
||||||
end
|
|
||||||
|
|
||||||
local args = Util.parse(...)
|
|
||||||
local UID = 0
|
local UID = 0
|
||||||
local multishell = { }
|
local multishell = { }
|
||||||
local processes = { }
|
local processes = { }
|
||||||
local parentTerm = term.current()
|
local parentTerm = term.current()
|
||||||
local sessionFile = args.config or 'usr/config/mwm'
|
local sessionFile = 'usr/config/twm'
|
||||||
local monName = args[1]
|
|
||||||
local running
|
local running
|
||||||
local parentMon
|
local parentMon = term.current()
|
||||||
|
|
||||||
local defaultEnv = Util.shallowCopy(_ENV)
|
local defaultEnv = Util.shallowCopy(_ENV)
|
||||||
defaultEnv.multishell = multishell
|
defaultEnv.multishell = multishell
|
||||||
|
|
||||||
if monName == 'terminal' then
|
|
||||||
parentMon = term.current()
|
|
||||||
elseif monName then
|
|
||||||
parentMon = peripheral.wrap(monName) or syntax()
|
|
||||||
else
|
|
||||||
parentMon = peripheral.find('monitor') or syntax()
|
|
||||||
end
|
|
||||||
|
|
||||||
if parentMon.setTextScale then
|
|
||||||
parentMon.setTextScale(.5)
|
|
||||||
end
|
|
||||||
|
|
||||||
local monDim, termDim = { }, { }
|
local monDim, termDim = { }, { }
|
||||||
monDim.width, monDim.height = parentMon.getSize()
|
monDim.width, monDim.height = parentMon.getSize()
|
||||||
termDim.width, termDim.height = parentTerm.getSize()
|
termDim.width, termDim.height = parentTerm.getSize()
|
||||||
@@ -73,13 +52,10 @@ local function write(win, x, y, text)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function redraw()
|
local function redraw()
|
||||||
--monitor.clear()
|
|
||||||
monitor.canvas:dirty()
|
monitor.canvas:dirty()
|
||||||
--monitor.setBackgroundColor(colors.gray)
|
|
||||||
monitor.canvas:clear(colors.gray)
|
monitor.canvas:clear(colors.gray)
|
||||||
for k, process in ipairs(processes) do
|
for _, process in ipairs(processes) do
|
||||||
process.container.canvas:dirty()
|
process.container.canvas:dirty()
|
||||||
process:focus(k == #processes)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -112,7 +88,8 @@ function Process:new(args)
|
|||||||
height = args.height + 1,
|
height = args.height + 1,
|
||||||
path = args.path,
|
path = args.path,
|
||||||
args = args.args or { },
|
args = args.args or { },
|
||||||
title = args.title or 'shell',
|
title = args.title or 'shell',
|
||||||
|
timestamp = os.clock(),
|
||||||
isMoving = false,
|
isMoving = false,
|
||||||
isResizing = false,
|
isResizing = false,
|
||||||
}, { __index = Process })
|
}, { __index = Process })
|
||||||
@@ -155,20 +132,12 @@ function Process:new(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Process:focus(focused)
|
function Process:focus(focused)
|
||||||
if focused then
|
self.container.setBackgroundColor(focused and colors.yellow or colors.lightGray)
|
||||||
self.container.setBackgroundColor(colors.yellow)
|
|
||||||
else
|
|
||||||
self.container.setBackgroundColor(colors.lightGray)
|
|
||||||
end
|
|
||||||
self.container.setTextColor(colors.black)
|
self.container.setTextColor(colors.black)
|
||||||
write(self.container, 1, 1, string.rep(' ', self.width))
|
write(self.container, 1, 1, string.rep(' ', self.width))
|
||||||
write(self.container, 2, 1, self.title)
|
write(self.container, 2, 1, self.title)
|
||||||
write(self.container, self.width - 1, 1, '*')
|
write(self.container, self.width - 1, 1, '*')
|
||||||
write(self.container, self.width - 3, 1, '\029')
|
write(self.container, self.width - 3, 1, '\029')
|
||||||
|
|
||||||
if focused then
|
|
||||||
self.window.restoreCursor()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Process:drawSizers()
|
function Process:drawSizers()
|
||||||
@@ -188,17 +157,18 @@ function Process:adjustDimensions()
|
|||||||
self.y = math.min(self.y, monDim.height - self.height + 1)
|
self.y = math.min(self.y, monDim.height - self.height + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Process:reposition()
|
function Process:reposition(resizing)
|
||||||
self:adjustDimensions()
|
self:adjustDimensions()
|
||||||
self.container.reposition(self.x, self.y, self.width, self.height)
|
self.container.reposition(self.x, self.y, self.width, self.height)
|
||||||
self.container.setBackgroundColor(colors.black)
|
|
||||||
self.container.clear()
|
|
||||||
self.window.reposition(1, 2, self.width, self.height - 1)
|
self.window.reposition(1, 2, self.width, self.height - 1)
|
||||||
if self.window ~= self.terminal then
|
if self.window ~= self.terminal then
|
||||||
if self.terminal.reposition then -- ??
|
if self.terminal.reposition then -- ??
|
||||||
self.terminal.reposition(1, 1, self.width, self.height - 1)
|
self.terminal.reposition(1, 1, self.width, self.height - 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if resizing then
|
||||||
|
self:focus(self == processes[#processes])
|
||||||
|
end
|
||||||
redraw()
|
redraw()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -225,7 +195,7 @@ function Process:resize(x, y)
|
|||||||
self.height = y - self.isResizing.y + self.isResizing.h
|
self.height = y - self.isResizing.y + self.isResizing.h
|
||||||
self.width = x - self.isResizing.x + self.isResizing.w
|
self.width = x - self.isResizing.x + self.isResizing.w
|
||||||
|
|
||||||
self:reposition()
|
self:reposition(true)
|
||||||
self:resume('term_resize')
|
self:resume('term_resize')
|
||||||
self:drawSizers()
|
self:drawSizers()
|
||||||
multishell.saveSession(sessionFile)
|
multishell.saveSession(sessionFile)
|
||||||
@@ -278,7 +248,8 @@ function multishell.setFocus(uid)
|
|||||||
|
|
||||||
process.container.canvas:raise()
|
process.container.canvas:raise()
|
||||||
process:focus(true)
|
process:focus(true)
|
||||||
process.container.canvas:dirty()
|
process.container.canvas:dirty()
|
||||||
|
process.window.restoreCursor()
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -423,14 +394,15 @@ function multishell.start()
|
|||||||
elseif focused.isMoving then
|
elseif focused.isMoving then
|
||||||
focused.x = event[3] - focused.isMoving.x + focused.isMoving.ox
|
focused.x = event[3] - focused.isMoving.x + focused.isMoving.ox
|
||||||
focused.y = event[4] - focused.isMoving.y + focused.isMoving.oy
|
focused.y = event[4] - focused.isMoving.y + focused.isMoving.oy
|
||||||
focused:reposition()
|
focused:reposition(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif event[1] == 'char' or
|
elseif event[1] == 'char' or
|
||||||
event[1] == 'key' or
|
event[1] == 'key' or
|
||||||
event[1] == 'key_up' or
|
event[1] == 'key_up' or
|
||||||
event[1] == 'paste' then
|
event[1] == 'paste' or
|
||||||
|
event[1] == 'mouse_scroll' then
|
||||||
|
|
||||||
local focused = processes[#processes]
|
local focused = processes[#processes]
|
||||||
if focused then
|
if focused then
|
||||||
|
|||||||
11
lfs/.package
Normal file
11
lfs/.package
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
title = 'LuaFileSystem',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/lfs',
|
||||||
|
description = [[LuaFileSystem port for computercraft
|
||||||
|
See: https://github.com/keplerproject/luafilesystem
|
||||||
|
|
||||||
|
LuaFileSystem is a Lua library developed to complement the set of functions related to file systems offered by the standard Lua distribution.
|
||||||
|
|
||||||
|
LuaFileSystem offers a portable way to access the underlying directory structure and file attributes. LuaFileSystem is free software and uses the same license as Lua 5.x (MIT).]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
237
lfs/apis/init.lua
Normal file
237
lfs/apis/init.lua
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
-- a port of LuaFileSystem
|
||||||
|
-- https://keplerproject.github.io/luafilesystem/manual.html
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
local lfs = {
|
||||||
|
_VERSION = '1.8.0.computercraft'
|
||||||
|
}
|
||||||
|
|
||||||
|
-- lfs.attributes (filepath [, request_name | result_table])
|
||||||
|
-- Returns a table with the file attributes corresponding to filepath (or nil followed
|
||||||
|
-- by an error message and a system-dependent error code in case of error). If the second
|
||||||
|
-- optional argument is given and is a string, then only the value of the named attribute
|
||||||
|
-- is returned (this use is equivalent to lfs.attributes(filepath)[request_name], but the
|
||||||
|
-- table is not created and only one attribute is retrieved from the O.S.). if a table is
|
||||||
|
-- passed as the second argument, it (result_table) is filled with attributes and returned
|
||||||
|
-- instead of a new table. The attributes are described as follows; attribute mode is a
|
||||||
|
-- string, all the others are numbers, and the time related attributes use the same time
|
||||||
|
-- reference of os.time:
|
||||||
|
function lfs.attributes(path, request_name)
|
||||||
|
path = shell.resolve(path)
|
||||||
|
|
||||||
|
local s, fsattr = pcall(fs.attributes, path)
|
||||||
|
if not s then
|
||||||
|
return nil, fsattr, 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local attributes = type(request_name) == 'table' and request_name or { }
|
||||||
|
|
||||||
|
-- on Unix systems, this represents the device that the inode resides on.
|
||||||
|
-- On Windows systems, represents the drive number of the disk containing the file
|
||||||
|
attributes.dev = fs.getDrive(path)
|
||||||
|
|
||||||
|
-- on Unix systems, this represents the inode number.
|
||||||
|
-- On Windows systems this has no meaning
|
||||||
|
attributes.ino = nil
|
||||||
|
|
||||||
|
--string representing the associated protection mode
|
||||||
|
-- (the values could be file, directory, link, socket, named pipe,
|
||||||
|
-- char device, block device or other)
|
||||||
|
attributes.mode = fsattr.isDir and 'directory' or 'file'
|
||||||
|
|
||||||
|
-- number of hard links to the file
|
||||||
|
attributes.nlink = 0
|
||||||
|
|
||||||
|
-- user-id of owner (Unix only, always 0 on Windows)
|
||||||
|
attributes.uid = 0
|
||||||
|
|
||||||
|
-- group-id of owner (Unix only, always 0 on Windows)
|
||||||
|
attributes.gid = 0
|
||||||
|
|
||||||
|
-- on Unix systems, represents the device type, for special file inodes.
|
||||||
|
-- On Windows systems represents the same as dev
|
||||||
|
attributes.rdev = attributes.dev
|
||||||
|
|
||||||
|
-- time of last access
|
||||||
|
attributes.access = fsattr.modification
|
||||||
|
|
||||||
|
-- time of last data modification
|
||||||
|
attributes.modification = fsattr.modification
|
||||||
|
|
||||||
|
-- time of last file status change
|
||||||
|
attributes.change = fsattr.modification
|
||||||
|
|
||||||
|
-- file size, in bytes
|
||||||
|
attributes.size = fsattr.size
|
||||||
|
|
||||||
|
-- file permissions string
|
||||||
|
local perm = (fs.isDir or fs.isReadOnly(path)) and 'r-x' or 'rwx'
|
||||||
|
attributes.permissions = perm .. perm .. perm
|
||||||
|
|
||||||
|
-- block allocated for file; (Unix only)
|
||||||
|
attributes.blocks = nil
|
||||||
|
|
||||||
|
-- optimal file system I/O blocksize; (Unix only)
|
||||||
|
attributes.blksize = nil
|
||||||
|
|
||||||
|
return type(request_name) ~= 'string' and attributes or attributes[request_name]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- lfs.chdir (path)
|
||||||
|
-- Changes the current working directory to the given path.
|
||||||
|
-- Returns true in case of success or nil plus an error string.
|
||||||
|
function lfs.chdir(path)
|
||||||
|
path = shell.resolve(path)
|
||||||
|
if fs.isDir(path) then
|
||||||
|
shell.setDir(path)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return nil, path .. ': No such directory'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- lfs.currentdir ()
|
||||||
|
-- Returns a string with the current working directory or nil plus an error string.
|
||||||
|
function lfs.currentdir()
|
||||||
|
return '/' .. shell.dir()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- iter, dir_obj = lfs.dir (path)
|
||||||
|
-- Lua iterator over the entries of a given directory.
|
||||||
|
-- Each time the iterator is called with dir_obj it returns a directory
|
||||||
|
-- entry's name as a string, or nil if there are no more entries.
|
||||||
|
-- You can also iterate by calling dir_obj:next(), and explicitly close the
|
||||||
|
-- directory before the iteration finished with dir_obj:close().
|
||||||
|
-- Raises an error if path is not a directory.
|
||||||
|
function lfs.dir(path)
|
||||||
|
path = shell.resolve(path)
|
||||||
|
local set = fs.list(path)
|
||||||
|
local iter = function()
|
||||||
|
local key, value = next(set)
|
||||||
|
set[key or false] = nil
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
return iter, {
|
||||||
|
valid = true,
|
||||||
|
closed = false,
|
||||||
|
next = function(self)
|
||||||
|
if not self.valid then
|
||||||
|
error('file iterator invalid')
|
||||||
|
end
|
||||||
|
local n = iter()
|
||||||
|
if not n then
|
||||||
|
self.valid = false
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end,
|
||||||
|
close = function(self)
|
||||||
|
if self.closed then
|
||||||
|
error('file iterator invalid')
|
||||||
|
end
|
||||||
|
self.closed = true
|
||||||
|
self.valid = false
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- lfs.link (old, new[, symlink])
|
||||||
|
-- Creates a link. The first argument is the object to link to and the second is the
|
||||||
|
-- name of the link. If the optional third argument is true, the link will by a symbolic
|
||||||
|
-- link (by default, a hard link is created).
|
||||||
|
function lfs.link(old, new, symlink)
|
||||||
|
if not symlink then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- hard links are not supported in vfs :(
|
||||||
|
old = shell.resolve(old)
|
||||||
|
new = shell.resolve(new)
|
||||||
|
return not not fs.mount(new, 'linkfs', old)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- lfs.mkdir (dirname)
|
||||||
|
-- Creates a new directory. The argument is the name of the new directory.
|
||||||
|
-- Returns true in case of success or nil, an error message and a system-dependent
|
||||||
|
-- error code in case of error.
|
||||||
|
function lfs.mkdir(dirname)
|
||||||
|
dirname = shell.resolve(dirname)
|
||||||
|
if fs.exists(fs.getDir(dirname)) then
|
||||||
|
fs.makeDir(dirname)
|
||||||
|
if fs.isDir(dirname) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, dirname .. ': Unable to create directory', 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- lfs.rmdir (dirname)
|
||||||
|
-- Removes an existing directory. The argument is the name of the directory.
|
||||||
|
-- Returns true in case of success or nil, an error message and a system-dependent
|
||||||
|
-- error code in case of error.
|
||||||
|
function lfs.rmdir(dirname)
|
||||||
|
dirname = shell.resolve(dirname)
|
||||||
|
if not fs.exists(dirname) or not fs.isDir(dirname) then
|
||||||
|
return false, dirname .. ': Not a directory', 1
|
||||||
|
end
|
||||||
|
pcall(fs.delete, dirname)
|
||||||
|
return not fs.exists(dirname) or false, dirname .. ': Unable to remove directory', 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- lfs.setmode (file, mode)
|
||||||
|
-- Sets the writing mode for a file. The mode string can be either "binary" or "text".
|
||||||
|
-- Returns true followed the previous mode string for the file, or nil followed by an
|
||||||
|
-- error string in case of errors. On non-Windows platforms, where the two modes are
|
||||||
|
-- identical, setting the mode has no effect, and the mode is always returned as binary.
|
||||||
|
function lfs.setmode(file)
|
||||||
|
if tostring(file) == 'file (closed)' then
|
||||||
|
error('closed file')
|
||||||
|
end
|
||||||
|
return true, 'binary'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- lfs.symlinkattributes (filepath [, request_name])
|
||||||
|
-- Identical to lfs.attributes except that it obtains information about the link itself
|
||||||
|
-- (not the file it refers to). It also adds a target field, containing the file name that
|
||||||
|
-- the symlink points to. On Windows this function does not yet support links, and is
|
||||||
|
-- identical to lfs.attributes.
|
||||||
|
function lfs.symlinkattributes(filepath, request_name)
|
||||||
|
filepath = shell.resolve(filepath)
|
||||||
|
|
||||||
|
local target = fs.resolve(filepath)
|
||||||
|
local attribs = lfs.attributes('/' .. target)
|
||||||
|
if filepath ~= target then
|
||||||
|
attribs.target = '/' .. target
|
||||||
|
attribs.mode = 'link'
|
||||||
|
end
|
||||||
|
|
||||||
|
return request_name and attribs[request_name] or attribs
|
||||||
|
end
|
||||||
|
|
||||||
|
-- lfs.touch (filepath [, atime [, mtime]])
|
||||||
|
-- Set access and modification times of a file. This function is a bind to utime function.
|
||||||
|
-- The first argument is the filename, the second argument (atime) is the access time, and
|
||||||
|
-- the third argument (mtime) is the modification time. Both times are provided in seconds
|
||||||
|
-- (which should be generated with Lua standard function os.time). If the modification time
|
||||||
|
-- is omitted, the access time provided is used; if both times are omitted, the current time
|
||||||
|
-- is used.
|
||||||
|
-- Returns true in case of success or nil, an error message and a system-dependent error
|
||||||
|
-- code in case of error.
|
||||||
|
function lfs.touch(filename, atime, mtime)
|
||||||
|
mtime = mtime or atime
|
||||||
|
filename = shell.resolve(filename)
|
||||||
|
|
||||||
|
if atime or mtime then
|
||||||
|
error('setting access/modification time is not supported')
|
||||||
|
end
|
||||||
|
|
||||||
|
-- cc does not suport setting atime/mtime
|
||||||
|
-- error('lfs.touch not supported')
|
||||||
|
if not fs.exists(filename) then
|
||||||
|
local f = fs.open(filename, 'w')
|
||||||
|
if f then
|
||||||
|
f.close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return lfs
|
||||||
1
lfs/etc/fstab
Normal file
1
lfs/etc/fstab
Normal file
@@ -0,0 +1 @@
|
|||||||
|
packages/lfs/tests/test.lua urlfs https://raw.githubusercontent.com/keplerproject/luafilesystem/master/tests/test.lua
|
||||||
9
lpeg/.package
Normal file
9
lpeg/.package
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
title = 'LPeg',
|
||||||
|
repository = 'kepler155c/opus-apps/{{OPUS_BRANCH}}/lpeg',
|
||||||
|
description = [[A pure Lua port of LPeg, Roberto Ierusalimschy's Parsing Expression Grammars library.
|
||||||
|
|
||||||
|
This version of LuLPeg emulates LPeg v0.12.
|
||||||
|
see: https://github.com/pygy/LuLPeg/]],
|
||||||
|
license = 'MIT',
|
||||||
|
}
|
||||||
1
lpeg/etc/fstab
Normal file
1
lpeg/etc/fstab
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rom/modules/main/lpeg.lua urlfs https://raw.githubusercontent.com/pygy/LuLPeg/master/lulpeg.lua
|
||||||
354
lzwfs/lzwfs.lua
354
lzwfs/lzwfs.lua
@@ -19,9 +19,9 @@ local SIGC = 'LZWC'
|
|||||||
local basedictcompress = {}
|
local basedictcompress = {}
|
||||||
local basedictdecompress = {}
|
local basedictdecompress = {}
|
||||||
for i = 0, 255 do
|
for i = 0, 255 do
|
||||||
local ic, iic = char(i), char(i, 0)
|
local ic, iic = char(i), char(i, 0)
|
||||||
basedictcompress[ic] = iic
|
basedictcompress[ic] = iic
|
||||||
basedictdecompress[iic] = ic
|
basedictdecompress[iic] = ic
|
||||||
end
|
end
|
||||||
|
|
||||||
local native = { open = fs.open }
|
local native = { open = fs.open }
|
||||||
@@ -29,123 +29,123 @@ local enabled = false
|
|||||||
local filters = { }
|
local filters = { }
|
||||||
|
|
||||||
local function dictAddA(str, dict, a, b)
|
local function dictAddA(str, dict, a, b)
|
||||||
if a >= 256 then
|
if a >= 256 then
|
||||||
a, b = 0, b+1
|
a, b = 0, b+1
|
||||||
if b >= 256 then
|
if b >= 256 then
|
||||||
dict = {}
|
dict = {}
|
||||||
b = 1
|
b = 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
dict[str] = char(a,b)
|
dict[str] = char(a,b)
|
||||||
a = a+1
|
a = a+1
|
||||||
return dict, a, b
|
return dict, a, b
|
||||||
end
|
end
|
||||||
|
|
||||||
local function compress(input)
|
local function compress(input)
|
||||||
if type(input) ~= "string" then
|
if type(input) ~= "string" then
|
||||||
error ("string expected, got "..type(input))
|
error ("string expected, got "..type(input))
|
||||||
end
|
end
|
||||||
local len = #input
|
local len = #input
|
||||||
if len <= 1 then
|
if len <= 1 then
|
||||||
return input
|
return input
|
||||||
end
|
end
|
||||||
|
|
||||||
local dict = {}
|
local dict = {}
|
||||||
local a, b = 0, 1
|
local a, b = 0, 1
|
||||||
|
|
||||||
local result = { SIGC }
|
local result = { SIGC }
|
||||||
local resultlen = 1
|
local resultlen = 1
|
||||||
local n = 2
|
local n = 2
|
||||||
local word = ""
|
local word = ""
|
||||||
for i = 1, len do
|
for i = 1, len do
|
||||||
local c = sub(input, i, i)
|
local c = sub(input, i, i)
|
||||||
local wc = word..c
|
local wc = word..c
|
||||||
if not (basedictcompress[wc] or dict[wc]) then
|
if not (basedictcompress[wc] or dict[wc]) then
|
||||||
local write = basedictcompress[word] or dict[word]
|
local write = basedictcompress[word] or dict[word]
|
||||||
if not write then
|
if not write then
|
||||||
error "algorithm error, could not fetch word"
|
error "algorithm error, could not fetch word"
|
||||||
end
|
end
|
||||||
result[n] = write
|
result[n] = write
|
||||||
resultlen = resultlen + #write
|
resultlen = resultlen + #write
|
||||||
n = n+1
|
n = n+1
|
||||||
if len <= resultlen then
|
if len <= resultlen then
|
||||||
return input
|
return input
|
||||||
end
|
end
|
||||||
dict, a, b = dictAddA(wc, dict, a, b)
|
dict, a, b = dictAddA(wc, dict, a, b)
|
||||||
word = c
|
word = c
|
||||||
else
|
else
|
||||||
word = wc
|
word = wc
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
result[n] = basedictcompress[word] or dict[word]
|
result[n] = basedictcompress[word] or dict[word]
|
||||||
resultlen = resultlen+#result[n]
|
resultlen = resultlen+#result[n]
|
||||||
if len <= resultlen then
|
if len <= resultlen then
|
||||||
return input
|
return input
|
||||||
end
|
end
|
||||||
return tconcat(result)
|
return tconcat(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function dictAddB(str, dict, a, b)
|
local function dictAddB(str, dict, a, b)
|
||||||
if a >= 256 then
|
if a >= 256 then
|
||||||
a, b = 0, b+1
|
a, b = 0, b+1
|
||||||
if b >= 256 then
|
if b >= 256 then
|
||||||
dict = {}
|
dict = {}
|
||||||
b = 1
|
b = 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
dict[char(a,b)] = str
|
dict[char(a,b)] = str
|
||||||
a = a+1
|
a = a+1
|
||||||
return dict, a, b
|
return dict, a, b
|
||||||
end
|
end
|
||||||
|
|
||||||
local function decompress(input)
|
local function decompress(input)
|
||||||
if type(input) ~= "string" then
|
if type(input) ~= "string" then
|
||||||
error( "string expected, got "..type(input))
|
error( "string expected, got "..type(input))
|
||||||
end
|
end
|
||||||
|
|
||||||
if #input <= 1 then
|
if #input <= 1 then
|
||||||
return input
|
return input
|
||||||
end
|
end
|
||||||
|
|
||||||
local control = sub(input, 1, 4)
|
local control = sub(input, 1, 4)
|
||||||
if control ~= SIGC then
|
if control ~= SIGC then
|
||||||
return input
|
return input
|
||||||
end
|
end
|
||||||
input = sub(input, 5)
|
input = sub(input, 5)
|
||||||
local len = #input
|
local len = #input
|
||||||
|
|
||||||
if len < 2 then
|
if len < 2 then
|
||||||
error("invalid input - not a compressed string")
|
error("invalid input - not a compressed string")
|
||||||
end
|
end
|
||||||
|
|
||||||
local dict = {}
|
local dict = {}
|
||||||
local a, b = 0, 1
|
local a, b = 0, 1
|
||||||
|
|
||||||
local result = {}
|
local result = {}
|
||||||
local n = 1
|
local n = 1
|
||||||
local last = sub(input, 1, 2)
|
local last = sub(input, 1, 2)
|
||||||
result[n] = basedictdecompress[last] or dict[last]
|
result[n] = basedictdecompress[last] or dict[last]
|
||||||
n = n+1
|
n = n+1
|
||||||
for i = 3, len, 2 do
|
for i = 3, len, 2 do
|
||||||
local code = sub(input, i, i+1)
|
local code = sub(input, i, i+1)
|
||||||
local lastStr = basedictdecompress[last] or dict[last]
|
local lastStr = basedictdecompress[last] or dict[last]
|
||||||
if not lastStr then
|
if not lastStr then
|
||||||
error( "could not find last from dict. Invalid input?")
|
error( "could not find last from dict. Invalid input?")
|
||||||
end
|
end
|
||||||
local toAdd = basedictdecompress[code] or dict[code]
|
local toAdd = basedictdecompress[code] or dict[code]
|
||||||
if toAdd then
|
if toAdd then
|
||||||
result[n] = toAdd
|
result[n] = toAdd
|
||||||
n = n+1
|
n = n+1
|
||||||
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
|
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
|
||||||
else
|
else
|
||||||
local tmp = lastStr..sub(lastStr, 1, 1)
|
local tmp = lastStr..sub(lastStr, 1, 1)
|
||||||
result[n] = tmp
|
result[n] = tmp
|
||||||
n = n+1
|
n = n+1
|
||||||
dict, a, b = dictAddB(tmp, dict, a, b)
|
dict, a, b = dictAddB(tmp, dict, a, b)
|
||||||
end
|
end
|
||||||
last = code
|
last = code
|
||||||
end
|
end
|
||||||
return tconcat(result)
|
return tconcat(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function split(str, pattern)
|
local function split(str, pattern)
|
||||||
@@ -157,39 +157,39 @@ local function split(str, pattern)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function matchesFilter(fname)
|
local function matchesFilter(fname)
|
||||||
if not fname:find('lzwfs') then -- don't compress anything with lzwfs in name (sigh)
|
if not fname:find('lzwfs') then -- don't compress anything with lzwfs in name (sigh)
|
||||||
for _, filter in pairs(filters) do
|
for _, filter in pairs(filters) do
|
||||||
if fname:match(filter) then
|
if fname:match(filter) then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.open(fname, flags)
|
function fs.open(fname, flags)
|
||||||
if not enabled then
|
if not enabled then
|
||||||
return native.open(fname, flags)
|
return native.open(fname, flags)
|
||||||
end
|
end
|
||||||
|
|
||||||
if flags == 'r' then
|
if flags == 'r' then
|
||||||
local f, err = native.open(fname, 'rb')
|
local f, err = native.open(fname, 'rb')
|
||||||
if not f then
|
if not f then
|
||||||
return f, err
|
return f, err
|
||||||
end
|
end
|
||||||
|
|
||||||
local ctr = 0
|
local ctr = 0
|
||||||
local lines
|
local lines
|
||||||
return {
|
return {
|
||||||
read = function()
|
read = function()
|
||||||
if not lines then
|
|
||||||
lines = decompress(f.readAll())
|
|
||||||
end
|
|
||||||
ctr = ctr + 1
|
|
||||||
return lines:sub(ctr, ctr)
|
|
||||||
end,
|
|
||||||
readLine = function()
|
|
||||||
if not lines then
|
if not lines then
|
||||||
lines = split(decompress(f.readAll()))
|
lines = decompress(f.readAll())
|
||||||
|
end
|
||||||
|
ctr = ctr + 1
|
||||||
|
return lines:sub(ctr, ctr)
|
||||||
|
end,
|
||||||
|
readLine = function()
|
||||||
|
if not lines then
|
||||||
|
lines = split(decompress(f.readAll()))
|
||||||
end
|
end
|
||||||
ctr = ctr + 1
|
ctr = ctr + 1
|
||||||
return lines[ctr]
|
return lines[ctr]
|
||||||
@@ -197,61 +197,61 @@ function fs.open(fname, flags)
|
|||||||
readAll = function()
|
readAll = function()
|
||||||
return decompress(f.readAll())
|
return decompress(f.readAll())
|
||||||
end,
|
end,
|
||||||
close = function()
|
close = function()
|
||||||
f.close()
|
f.close()
|
||||||
end,
|
|
||||||
}
|
|
||||||
elseif flags == 'w' or flags == 'a' then
|
|
||||||
if not matchesFilter(fs.combine(fname, '')) then
|
|
||||||
return native.open(fname, flags)
|
|
||||||
end
|
|
||||||
|
|
||||||
local c = { }
|
|
||||||
|
|
||||||
if flags == 'a' then
|
|
||||||
local f = fs.open(fname, 'r')
|
|
||||||
if f then
|
|
||||||
tinsert(c, f.readAll())
|
|
||||||
f.close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local f, err = native.open(fname, 'wb')
|
|
||||||
if not f then
|
|
||||||
return f, err
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
write = function(str)
|
|
||||||
tinsert(c, str)
|
|
||||||
end,
|
|
||||||
writeLine = function(str)
|
|
||||||
tinsert(c, str)
|
|
||||||
tinsert(c, '\n')
|
|
||||||
end,
|
|
||||||
flush = function()
|
|
||||||
-- this isn't gonna work...
|
|
||||||
-- f.write(compress(tconcat(c)))
|
|
||||||
f.flush();
|
|
||||||
end,
|
|
||||||
close = function()
|
|
||||||
f.write(compress(tconcat(c)))
|
|
||||||
f.close()
|
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
end
|
elseif flags == 'w' or flags == 'a' then
|
||||||
|
if not matchesFilter(fs.combine(fname, '')) then
|
||||||
|
return native.open(fname, flags)
|
||||||
|
end
|
||||||
|
|
||||||
return native.open(fname, flags)
|
local c = { }
|
||||||
|
|
||||||
|
if flags == 'a' then
|
||||||
|
local f = fs.open(fname, 'r')
|
||||||
|
if f then
|
||||||
|
tinsert(c, f.readAll())
|
||||||
|
f.close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local f, err = native.open(fname, 'wb')
|
||||||
|
if not f then
|
||||||
|
return f, err
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
write = function(str)
|
||||||
|
tinsert(c, str)
|
||||||
|
end,
|
||||||
|
writeLine = function(str)
|
||||||
|
tinsert(c, str)
|
||||||
|
tinsert(c, '\n')
|
||||||
|
end,
|
||||||
|
flush = function()
|
||||||
|
-- this isn't gonna work...
|
||||||
|
-- f.write(compress(tconcat(c)))
|
||||||
|
f.flush();
|
||||||
|
end,
|
||||||
|
close = function()
|
||||||
|
f.write(compress(tconcat(c)))
|
||||||
|
f.close()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return native.open(fname, flags)
|
||||||
end
|
end
|
||||||
|
|
||||||
function fs.option(category, action, option)
|
function fs.option(category, action, option)
|
||||||
if category == 'compression' then
|
if category == 'compression' then
|
||||||
if action == 'enabled' then
|
if action == 'enabled' then
|
||||||
enabled = option
|
enabled = option
|
||||||
elseif action == 'filters' then
|
elseif action == 'filters' then
|
||||||
filters = option
|
filters = option
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
print('lzwfs started')
|
print('lzwfs started')
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ local CONFIG = 'usr/config/lzwfs'
|
|||||||
local config = { }
|
local config = { }
|
||||||
|
|
||||||
if fs.exists(CONFIG) then
|
if fs.exists(CONFIG) then
|
||||||
local f = fs.open(CONFIG, 'r')
|
local f = fs.open(CONFIG, 'r')
|
||||||
if f then
|
if f then
|
||||||
config = textutils.unserialize(f.readAll())
|
config = textutils.unserialize(f.readAll())
|
||||||
f.close()
|
f.close()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
os.run(_ENV, '/packages/lzwfs/lzwfs.lua')
|
os.run(_ENV, '/packages/lzwfs/lzwfs.lua')
|
||||||
|
|||||||
@@ -14,19 +14,21 @@ local config = Config.load('lzwfs', {
|
|||||||
})
|
})
|
||||||
|
|
||||||
local tab = UI.Tab {
|
local tab = UI.Tab {
|
||||||
tabTitle = 'Compression',
|
title = 'Compression',
|
||||||
description = 'Disk compression',
|
description = 'Disk compression',
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = 2, ex = -2, ey = 6,
|
||||||
|
},
|
||||||
label1 = UI.Text {
|
label1 = UI.Text {
|
||||||
x = 2, y = 2,
|
x = 3, y = 3,
|
||||||
value = 'Enable compression',
|
value = 'Enable compression',
|
||||||
},
|
},
|
||||||
checkbox = UI.Checkbox {
|
checkbox = UI.Checkbox {
|
||||||
x = 20, y = 2,
|
x = 21, y = 3,
|
||||||
value = config.enabled
|
value = config.enabled
|
||||||
},
|
},
|
||||||
entry = UI.TextEntry {
|
entry = UI.TextEntry {
|
||||||
x = 2, y = 4, ex = -2,
|
x = 3, y = 5 , ex = -3,
|
||||||
limit = 256,
|
|
||||||
shadowText = 'enter new path',
|
shadowText = 'enter new path',
|
||||||
accelerators = {
|
accelerators = {
|
||||||
enter = 'add_path',
|
enter = 'add_path',
|
||||||
@@ -34,7 +36,7 @@ local tab = UI.Tab {
|
|||||||
help = 'add a new path',
|
help = 'add a new path',
|
||||||
},
|
},
|
||||||
grid = UI.Grid {
|
grid = UI.Grid {
|
||||||
x = 2, ex = -2, y = 6, ey = -5,
|
x = 2, ex = -2, y = 8, ey = -5,
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
columns = { { key = 'value' } },
|
columns = { { key = 'value' } },
|
||||||
autospace = true,
|
autospace = true,
|
||||||
@@ -45,7 +47,7 @@ local tab = UI.Tab {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
button = UI.Button {
|
button = UI.Button {
|
||||||
x = -9, ex = -2, y = -3,
|
x = -8, ex = -2, y = -3,
|
||||||
text = 'Apply',
|
text = 'Apply',
|
||||||
event = 'apply',
|
event = 'apply',
|
||||||
},
|
},
|
||||||
|
|||||||
11
mbs/.package
11
mbs/.package
@@ -8,15 +8,4 @@ https://github.com/SquidDev-CC/mbs
|
|||||||
MBS is a series of utilities for improving the default CraftOS experience.
|
MBS is a series of utilities for improving the default CraftOS experience.
|
||||||
]],
|
]],
|
||||||
license = 'MIT',
|
license = 'MIT',
|
||||||
install = [[
|
|
||||||
local Alt = require('opus.alternate')
|
|
||||||
Alt.set('shell', '.mbs/bin/shell.lua')
|
|
||||||
Alt.add('lua', '.mbs/bin/lua.lua')
|
|
||||||
]],
|
|
||||||
uninstall = [[
|
|
||||||
local Alt = require('opus.alternate')
|
|
||||||
Alt.remove('shell', '.mbs/bin/shell.lua')
|
|
||||||
Alt.remove('lua', '.mbs/bin/lua.lua')
|
|
||||||
fs.delete('.mbs')
|
|
||||||
]],
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ local shell = _ENV.shell
|
|||||||
|
|
||||||
if not fs.exists('.mbs') then
|
if not fs.exists('.mbs') then
|
||||||
print('Installing MBS')
|
print('Installing MBS')
|
||||||
shell.run('mbs download')
|
--shell.run('mbs download')
|
||||||
end
|
end
|
||||||
print('Initializing MBS')
|
print('Initializing MBS')
|
||||||
shell.run('mbs startup')
|
--shell.run('mbs startup')
|
||||||
|
|||||||
@@ -216,10 +216,12 @@ _G._syslog = function(...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local s, m = pcall(function()
|
local s, m = pcall(function()
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
turtle.setStatus('idle')
|
if turtle.setStatus then
|
||||||
|
turtle.setStatus('idle')
|
||||||
|
end
|
||||||
|
|
||||||
_G._syslog = oldDebug
|
_G._syslog = oldDebug
|
||||||
if not s then error(m) end
|
if not s then error(m) end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
local Event = require('opus.event')
|
local Event = require('opus.event')
|
||||||
local fuzzy = require('milo.fuzzyMatch')
|
local fuzzy = require('opus.fuzzy')
|
||||||
local Sound = require('opus.sound')
|
local Sound = require('opus.sound')
|
||||||
local Socket = require('opus.socket')
|
local Socket = require('opus.socket')
|
||||||
local sync = require('opus.sync').sync
|
local sync = require('opus.sync').sync
|
||||||
@@ -12,8 +12,12 @@ local fs = _G.fs
|
|||||||
local peripheral = _G.peripheral
|
local peripheral = _G.peripheral
|
||||||
local shell = _ENV.shell
|
local shell = _ENV.shell
|
||||||
|
|
||||||
|
local configName = ({...})[1]
|
||||||
|
local configPath = 'miloRemote' .. (configName and "_"..configName or "")
|
||||||
|
|
||||||
local context = {
|
local context = {
|
||||||
state = Config.load('miloRemote', { displayMode = 0, deposit = true }),
|
state = Config.load(configPath, { displayMode = 0, deposit = true }),
|
||||||
|
configPath = configPath,
|
||||||
responseHandlers = { },
|
responseHandlers = { },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,8 +67,8 @@ local page = UI.Page {
|
|||||||
x = 1, ex = -13,
|
x = 1, ex = -13,
|
||||||
limit = 50,
|
limit = 50,
|
||||||
shadowText = 'filter',
|
shadowText = 'filter',
|
||||||
backgroundColor = colors.cyan,
|
backgroundColor = 'primary',
|
||||||
backgroundFocusColor = colors.cyan,
|
backgroundFocusColor = 'primary',
|
||||||
accelerators = {
|
accelerators = {
|
||||||
[ 'enter' ] = 'eject',
|
[ 'enter' ] = 'eject',
|
||||||
[ 'up' ] = 'grid_up',
|
[ 'up' ] = 'grid_up',
|
||||||
@@ -158,7 +162,7 @@ function page.grid:eventHandler(event)
|
|||||||
if event.type == 'grid_sort' then
|
if event.type == 'grid_sort' then
|
||||||
context.state.sortColumn = event.sortColumn
|
context.state.sortColumn = event.sortColumn
|
||||||
context.state.inverseSort = event.inverseSort
|
context.state.inverseSort = event.inverseSort
|
||||||
Config.update('miloRemote', context.state)
|
Config.update(configPath, context.state)
|
||||||
end
|
end
|
||||||
return UI.Grid.eventHandler(self, event)
|
return UI.Grid.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
@@ -169,7 +173,7 @@ end
|
|||||||
|
|
||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
if event.type == 'quit' then
|
if event.type == 'quit' then
|
||||||
UI:exitPullEvents()
|
UI:quit()
|
||||||
|
|
||||||
elseif event.type == 'setup' then
|
elseif event.type == 'setup' then
|
||||||
self.setup.form:setValues(context.state)
|
self.setup.form:setValues(context.state)
|
||||||
@@ -181,7 +185,7 @@ function page:eventHandler(event)
|
|||||||
self.statusBar:draw()
|
self.statusBar:draw()
|
||||||
context:setStatus(depositMode[context.state.deposit].help)
|
context:setStatus(depositMode[context.state.deposit].help)
|
||||||
context:notifyInfo(depositMode[context.state.deposit].help)
|
context:notifyInfo(depositMode[context.state.deposit].help)
|
||||||
Config.update('miloRemote', context.state)
|
Config.update(configPath, context.state)
|
||||||
|
|
||||||
elseif event.type == 'focus_change' then
|
elseif event.type == 'focus_change' then
|
||||||
context:setStatus(event.focused.help)
|
context:setStatus(event.focused.help)
|
||||||
@@ -243,7 +247,7 @@ function page:eventHandler(event)
|
|||||||
context:setStatus(event.button.help)
|
context:setStatus(event.button.help)
|
||||||
context:notifyInfo(event.button.help)
|
context:notifyInfo(event.button.help)
|
||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
Config.update('miloRemote', context.state)
|
Config.update(configPath, context.state)
|
||||||
|
|
||||||
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
|
elseif event.type == 'text_change' and event.element == self.statusBar.filter then
|
||||||
self.filter = event.text or ''
|
self.filter = event.text or ''
|
||||||
@@ -318,7 +322,7 @@ function page:applyFilter()
|
|||||||
v.score = fuzzy(v.lname, filter)
|
v.score = fuzzy(v.lname, filter)
|
||||||
if v.score then
|
if v.score then
|
||||||
if v.count > 0 then
|
if v.count > 0 then
|
||||||
v.score = v.score + 1
|
v.score = v.score + .2
|
||||||
end
|
end
|
||||||
table.insert(r, v)
|
table.insert(r, v)
|
||||||
end
|
end
|
||||||
@@ -445,7 +449,7 @@ end
|
|||||||
|
|
||||||
function context:setState(key, value)
|
function context:setState(key, value)
|
||||||
self.state[key] = value
|
self.state[key] = value
|
||||||
Config.update('miloRemote', self.state)
|
Config.update(configPath, self.state)
|
||||||
end
|
end
|
||||||
|
|
||||||
context.responseHandlers['received'] = function(response)
|
context.responseHandlers['received'] = function(response)
|
||||||
@@ -517,14 +521,14 @@ local function loadDirectory(dir)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
page.menuBar.config:add({ dropmenu = UI.DropMenu { buttons = dropdown } })
|
page.menuBar.config.dropdown = dropdown
|
||||||
end
|
end
|
||||||
|
|
||||||
local programDir = fs.getDir(shell.getRunningProgram())
|
local programDir = fs.getDir(shell.getRunningProgram())
|
||||||
loadDirectory(fs.combine(programDir, 'plugins/remote'))
|
loadDirectory(fs.combine(programDir, 'plugins/remote'))
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|
||||||
if context.socket then
|
if context.socket then
|
||||||
context.socket:close()
|
context.socket:close()
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
-- Based on Squid's fuzzy search
|
|
||||||
-- https://github.com/SquidDev-CC/artist/blob/vnext/artist/lib/match.lua
|
|
||||||
--
|
|
||||||
-- not very fuzzy anymore
|
|
||||||
|
|
||||||
local SCORE_WEIGHT = 1000
|
|
||||||
local LEADING_LETTER_PENALTY = -3
|
|
||||||
local LEADING_LETTER_PENALTY_MAX = -9
|
|
||||||
|
|
||||||
local _find = string.find
|
|
||||||
local _max = math.max
|
|
||||||
|
|
||||||
return function(str, pattern)
|
|
||||||
local start = _find(str, pattern, 1, true)
|
|
||||||
if start then
|
|
||||||
-- All letters before the current one are considered leading, so add them to our penalty
|
|
||||||
return SCORE_WEIGHT + _max(LEADING_LETTER_PENALTY * (start - 1), LEADING_LETTER_PENALTY_MAX)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
local class = require('opus.class')
|
|
||||||
local itemDB = require('core.itemDB')
|
|
||||||
local Mini = require('milo.miniAdapter')
|
|
||||||
|
|
||||||
local os = _G.os
|
|
||||||
|
|
||||||
local Adapter = class(Mini)
|
|
||||||
|
|
||||||
function Adapter:init(args)
|
|
||||||
Mini.init(self, args)
|
|
||||||
|
|
||||||
self._rawList = self.list
|
|
||||||
|
|
||||||
function self.list()
|
|
||||||
-- wait for up to 1 sec until any items that have been inserted
|
|
||||||
-- into interface are added to the system
|
|
||||||
for _ = 0, 20 do
|
|
||||||
if #self._rawList() == 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
os.sleep(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
local list = { }
|
|
||||||
for _, v in pairs(self.listAvailableItems()) do
|
|
||||||
list[itemDB:makeKey(v)] = v
|
|
||||||
end
|
|
||||||
return list
|
|
||||||
end
|
|
||||||
|
|
||||||
function self.getItemMeta(key)
|
|
||||||
local item = self.findItem(itemDB:splitKey(key))
|
|
||||||
if item and item.getMetadata then
|
|
||||||
return item.getMetadata()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function self.pushItems(target, key, amount, slot)
|
|
||||||
local item = self.findItem(itemDB:splitKey(key))
|
|
||||||
if item and item.export then
|
|
||||||
return item.export(target, amount, slot)
|
|
||||||
end
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function self.pullItems(target, key, amount, slot)
|
|
||||||
_G._syslog({target, key, amount, slot })
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return Adapter
|
|
||||||
@@ -9,6 +9,8 @@ local device = _G.device
|
|||||||
local os = _G.os
|
local os = _G.os
|
||||||
local parallel = _G.parallel
|
local parallel = _G.parallel
|
||||||
|
|
||||||
|
local SCAN_CHUNK_SIZE = 16
|
||||||
|
|
||||||
local Storage = class()
|
local Storage = class()
|
||||||
|
|
||||||
local function loadOld(storage)
|
local function loadOld(storage)
|
||||||
@@ -263,7 +265,22 @@ function Storage:listItems(throttle)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if #t > 0 then
|
if #t > 0 then
|
||||||
parallel.waitForAll(table.unpack(t))
|
local chunk = {}
|
||||||
|
|
||||||
|
-- Split the work into chunks to avoid spamming too many coroutines.
|
||||||
|
for i = 1, #t do
|
||||||
|
table.insert(chunk, t[i])
|
||||||
|
|
||||||
|
-- When we reach the chunk limit, execute them and start a new chunk
|
||||||
|
if #chunk >= SCAN_CHUNK_SIZE then
|
||||||
|
parallel.waitForAll(table.unpack(chunk))
|
||||||
|
chunk = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #chunk > 0 then
|
||||||
|
parallel.waitForAll(table.unpack(chunk))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, adapter in self:onlineAdapters() do
|
for _, adapter in self:onlineAdapters() do
|
||||||
@@ -291,6 +308,134 @@ function Storage:listItems(throttle)
|
|||||||
return self.cache
|
return self.cache
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- provide a raw list of all the items in all storage chests
|
||||||
|
-- it might be beneficial to move this to the adapter class at some point and cache the raw item list in this class at some point
|
||||||
|
function Storage:listItemsRaw(throttle)
|
||||||
|
local res = {}
|
||||||
|
|
||||||
|
throttle = throttle or Util.throttle()
|
||||||
|
|
||||||
|
for _, v in pairs(self.nodes) do
|
||||||
|
if v.category == "storage" then
|
||||||
|
local chest = device[v.name]
|
||||||
|
local items = chest.list()
|
||||||
|
|
||||||
|
for slot, item in pairs(items) do
|
||||||
|
items[slot] = itemDB:get(item, function() return chest.getItemMeta(slot) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
res[v.name] = items
|
||||||
|
throttle()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
-- provide a list of items and which chests provide them
|
||||||
|
function Storage:listProviders(throttle)
|
||||||
|
local res = {}
|
||||||
|
|
||||||
|
local rawItems = self:listItemsRaw(throttle)
|
||||||
|
|
||||||
|
for chest, items in pairs(rawItems) do
|
||||||
|
for slot, item in pairs(items) do
|
||||||
|
local key = table.concat({item.name, item.damage, item.nbtHash}, ":")
|
||||||
|
if not res[key] then
|
||||||
|
res[key] = {}
|
||||||
|
end
|
||||||
|
table.insert(res[key], {item = item, device = device[chest], lockedToThis = (self.nodes[chest].lock or {})[key] or false, slot = slot})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
-- defrags the storage system
|
||||||
|
function Storage:defrag(throttle)
|
||||||
|
local items = self:listProviders(throttle)
|
||||||
|
local slotsSaved = 0
|
||||||
|
|
||||||
|
-- This will make sure the table is sorted in the following order:
|
||||||
|
-- Unlocked stacks with less than maxCount items | Locked stacks with less than maxCount items | stacks with more than maxCount items
|
||||||
|
-- This way the locked stacks will be filled first
|
||||||
|
local function sortFunction(a, b)
|
||||||
|
local preferenceA, preferenceB
|
||||||
|
preferenceA = (a.item.count == a.item.maxCount and 3)
|
||||||
|
or (a.lockedToThis and 2)
|
||||||
|
or 1
|
||||||
|
preferenceB = (b.item.count == b.item.maxCount and 3)
|
||||||
|
or (b.lockedToThis and 2)
|
||||||
|
or 1
|
||||||
|
|
||||||
|
if preferenceA < preferenceB then
|
||||||
|
return true
|
||||||
|
elseif preferenceB > preferenceA then
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
return a.item.count < b.item.count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, providers in pairs(items) do
|
||||||
|
table.sort(providers, sortFunction)
|
||||||
|
|
||||||
|
-- We're done when we either compressed the stacks so far, that there's only one left (#providers == 1)
|
||||||
|
-- Or when we've compressed so far, that the there's only one stack which has a lower count than the maxCount
|
||||||
|
-- Because of the sorting, we know that this will be the stack in providers[1], so we check if providers[2] is at the maxCount
|
||||||
|
while #providers > 1 and providers[2].item.count ~= providers[2].item.maxCount do
|
||||||
|
local from = providers[1]
|
||||||
|
local to
|
||||||
|
|
||||||
|
-- We're pushing to the highest stack which is still below the maxCount, this way as many slots as possible will be filled
|
||||||
|
-- This loop is guarenteed to assign a value to "to", as the only cases where it wouldn't (#providers == 1 or no provider with less than maxCount)
|
||||||
|
-- are ruled out by the condition of the outer while loop
|
||||||
|
for i = 2, #providers do
|
||||||
|
-- Give preference to locked chests
|
||||||
|
if not (to and to.lockedToThis or false) and providers[i].lockedToThis then
|
||||||
|
to = providers[i]
|
||||||
|
elseif ((to and to.lockedToThis or false) == providers[i].lockedToThis) and providers[i].item.count < providers[i].item.maxCount then
|
||||||
|
to = providers[i]
|
||||||
|
elseif providers[i].item.count == providers[i].item.maxCount then
|
||||||
|
-- As this slot is already at maxCount, all the remaining ones will also be due to sorting
|
||||||
|
-- If any of the remaining providers is locked that doesn't matter. We wouldn't have been able to push there anyways
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local toMove = math.min(to.item.maxCount - to.item.count, from.item.count)
|
||||||
|
local s, m = pcall(function()
|
||||||
|
from.device.pushItems(to.device.name, from.slot, toMove, to.slot)
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not s and m then
|
||||||
|
_G._syslog(m)
|
||||||
|
end
|
||||||
|
|
||||||
|
if s then
|
||||||
|
to.item.count = to.item.count + toMove
|
||||||
|
from.item.count = from.item.count - toMove
|
||||||
|
else
|
||||||
|
-- Do not try to send to the target again after it failed
|
||||||
|
for i = 2, #providers do
|
||||||
|
if to == providers[i] then
|
||||||
|
table.remove(providers, i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if from.item.count <= 0 then
|
||||||
|
table.remove(providers, 1)
|
||||||
|
slotsSaved = slotsSaved + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(providers, sortFunction)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return slotsSaved
|
||||||
|
end
|
||||||
|
|
||||||
function Storage:updateCache(adapter, item, count)
|
function Storage:updateCache(adapter, item, count)
|
||||||
if not adapter.cache then
|
if not adapter.cache then
|
||||||
adapter.dirty = true
|
adapter.dirty = true
|
||||||
@@ -392,6 +537,8 @@ local function rawExport(source, target, item, qty, slot)
|
|||||||
if amount > 0 then
|
if amount > 0 then
|
||||||
source.lastUpdate = os.clock()
|
source.lastUpdate = os.clock()
|
||||||
target.lastUpdate = os.clock()
|
target.lastUpdate = os.clock()
|
||||||
|
else
|
||||||
|
-- break -- this should work ?? is cache out of sync ?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
qty = qty - amount
|
qty = qty - amount
|
||||||
@@ -423,6 +570,7 @@ function Storage:export(target, slot, count, item)
|
|||||||
|
|
||||||
if amount ~= pcount then
|
if amount ~= pcount then
|
||||||
-- this *should* only happen if cache is out of sync
|
-- this *should* only happen if cache is out of sync
|
||||||
|
-- or... the target is full
|
||||||
self:updateCache(adapter, item, pcount - amount)
|
self:updateCache(adapter, item, pcount - amount)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -433,28 +581,43 @@ function Storage:export(target, slot, count, item)
|
|||||||
end
|
end
|
||||||
count = count - amount
|
count = count - amount
|
||||||
total = total + amount
|
total = total + amount
|
||||||
|
|
||||||
|
return amount
|
||||||
end
|
end
|
||||||
|
|
||||||
-- request from adapters with this item
|
-- request from adapters with this item
|
||||||
for _, adapter in self:onlineAdapters() do
|
for _, adapter in self:onlineAdapters() do
|
||||||
local cache = adapter.cache and adapter.cache[key]
|
local cache = adapter.cache and adapter.cache[key]
|
||||||
if cache then
|
if cache then
|
||||||
provide(adapter, math.min(count, cache.count))
|
local request = math.min(count, cache.count)
|
||||||
|
|
||||||
|
local amount = provide(adapter, request)
|
||||||
|
|
||||||
|
-- couldn't provide the amount that was requested
|
||||||
|
-- either the target must be full - or the cache is invalid
|
||||||
|
if amount ~= request then
|
||||||
|
break
|
||||||
|
end
|
||||||
if count <= 0 then
|
if count <= 0 then
|
||||||
return total
|
return total
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
_G._syslog('STORAGE warning: %s(%d): %s%s %s failed to export',
|
if slot then -- ignore warning when exporting to all slots
|
||||||
item.displayName or item.name, count, self:_sn(target.name),
|
_G._syslog('STORAGE warning: %s(%d): %s%s %s failed to export',
|
||||||
slot and string.format('[%d]', slot) or '[*]', key)
|
item.displayName or item.name, count, self:_sn(target.name),
|
||||||
|
slot and string.format('[%d]', slot) or '[*]', key)
|
||||||
|
end
|
||||||
|
|
||||||
-- TODO: If there are misses when a slot is specified than something is wrong...
|
-- TODO: If there are misses when a slot is specified than something is wrong...
|
||||||
-- The caller should confirm the quantity beforehand
|
-- The caller should confirm the quantity beforehand
|
||||||
-- If no slot and full amount is not exported, then no need to check rest of adapters
|
-- If no slot and full amount is not exported, then no need to check rest of adapters
|
||||||
-- ... so should not reach here
|
-- ... so should not reach here
|
||||||
|
|
||||||
|
-- but... there is the case where exporting to all slots of the target
|
||||||
|
-- this is valid
|
||||||
|
|
||||||
return total
|
return total
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ fs.delete('packages/milo/Milo.lua')
|
|||||||
fs.delete('packages/milo/plugins/listing.lua')
|
fs.delete('packages/milo/plugins/listing.lua')
|
||||||
fs.delete('packages/milo/apis/milo.lua')
|
fs.delete('packages/milo/apis/milo.lua')
|
||||||
fs.delete('packages/milo/plugins/manipulator.lua')
|
fs.delete('packages/milo/plugins/manipulator.lua')
|
||||||
|
fs.delete('packages/milo/apps')
|
||||||
|
|
||||||
if peripheral.find('workbench') and shell.openForegroundTab then
|
if peripheral.find('workbench') and shell.openForegroundTab then
|
||||||
shell.openForegroundTab('MiloLocal')
|
shell.openForegroundTab('MiloLocal')
|
||||||
|
|||||||
@@ -1,33 +1,29 @@
|
|||||||
local Milo = require('milo')
|
local Milo = require('milo')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
local turtle = _G.turtle
|
|
||||||
|
|
||||||
local learnPage = UI.Page {
|
local learnPage = UI.Page {
|
||||||
titleBar = UI.TitleBar { title = 'Learn Recipe' },
|
titleBar = UI.TitleBar { title = 'Learn Recipe' },
|
||||||
wizard = UI.Wizard {
|
wizard = UI.Wizard {
|
||||||
y = 2, ey = -2,
|
y = 2, ey = -2,
|
||||||
pages = {
|
general = UI.WizardPage {
|
||||||
general = UI.WizardPage {
|
index = 1,
|
||||||
index = 1,
|
grid = UI.ScrollingGrid {
|
||||||
grid = UI.ScrollingGrid {
|
x = 2, ex = -2, y = 2, ey = -2,
|
||||||
x = 2, ex = -2, y = 2, ey = -2,
|
disableHeader = true,
|
||||||
disableHeader = true,
|
columns = {
|
||||||
columns = {
|
{ heading = 'Name', key = 'name'},
|
||||||
{ heading = 'Name', key = 'name'},
|
|
||||||
},
|
|
||||||
sortColumn = 'name',
|
|
||||||
},
|
|
||||||
accelerators = {
|
|
||||||
grid_select = 'nextView',
|
|
||||||
},
|
},
|
||||||
|
sortColumn = 'name',
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
grid_select = 'nextView',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
notification = UI.Notification { },
|
notification = UI.Notification { },
|
||||||
}
|
}
|
||||||
|
|
||||||
local general = learnPage.wizard.pages.general
|
local general = learnPage.wizard.general
|
||||||
|
|
||||||
function general:validate()
|
function general:validate()
|
||||||
Milo:setState('learnType', self.grid:getSelected().value)
|
Milo:setState('learnType', self.grid:getSelected().value)
|
||||||
@@ -37,7 +33,7 @@ end
|
|||||||
function learnPage:enable()
|
function learnPage:enable()
|
||||||
local t = { }
|
local t = { }
|
||||||
|
|
||||||
for _, page in pairs(self.wizard.pages) do
|
for _, page in pairs(self.wizard:getPages()) do
|
||||||
if page.validFor then
|
if page.validFor then
|
||||||
t[page.validFor] = {
|
t[page.validFor] = {
|
||||||
name = page.validFor,
|
name = page.validFor,
|
||||||
@@ -63,7 +59,7 @@ function learnPage.wizard:getPage(index)
|
|||||||
local pages = { }
|
local pages = { }
|
||||||
table.insert(pages, general)
|
table.insert(pages, general)
|
||||||
local selected = general.grid:getSelected()
|
local selected = general.grid:getSelected()
|
||||||
for _, page in pairs(self.pages) do
|
for _, page in pairs(self:getPages()) do
|
||||||
if page.validFor and (not selected or selected.value == page.validFor) then
|
if page.validFor and (not selected or selected.value == page.validFor) then
|
||||||
table.insert(pages, page)
|
table.insert(pages, page)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
local Craft = require('milo.craft2')
|
local Craft = require('milo.craft2')
|
||||||
local Event = require('opus.event')
|
local Event = require('opus.event')
|
||||||
local fuzzy = require('milo.fuzzyMatch')
|
local fuzzy = require('opus.fuzzy')
|
||||||
local Milo = require('milo')
|
local Milo = require('milo')
|
||||||
local Sound = require('opus.sound')
|
local Sound = require('opus.sound')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
@@ -33,6 +33,11 @@ local page = UI.Page {
|
|||||||
event = 'rescan',
|
event = 'rescan',
|
||||||
help = 'Rescan all inventories'
|
help = 'Rescan all inventories'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text = 'Defragment storage',
|
||||||
|
event = 'defrag',
|
||||||
|
help = 'Defragments the storage'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -54,8 +59,8 @@ local page = UI.Page {
|
|||||||
limit = 50,
|
limit = 50,
|
||||||
shadowText = 'filter',
|
shadowText = 'filter',
|
||||||
shadowTextColor = colors.gray,
|
shadowTextColor = colors.gray,
|
||||||
backgroundColor = colors.cyan,
|
backgroundColor = 'primary',
|
||||||
backgroundFocusColor = colors.cyan,
|
backgroundFocusColor = 'primary',
|
||||||
accelerators = {
|
accelerators = {
|
||||||
[ 'enter' ] = 'eject',
|
[ 'enter' ] = 'eject',
|
||||||
[ 'up' ] = 'grid_up',
|
[ 'up' ] = 'grid_up',
|
||||||
@@ -66,7 +71,7 @@ local page = UI.Page {
|
|||||||
storageStatus = UI.Text {
|
storageStatus = UI.Text {
|
||||||
x = -17, ex = -10,
|
x = -17, ex = -10,
|
||||||
textColor = colors.lime,
|
textColor = colors.lime,
|
||||||
backgroundColor = colors.cyan,
|
backgroundColor = 'primary',
|
||||||
value = '',
|
value = '',
|
||||||
},
|
},
|
||||||
amount = UI.TextEntry {
|
amount = UI.TextEntry {
|
||||||
@@ -208,7 +213,7 @@ end
|
|||||||
|
|
||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
if event.type == 'quit' then
|
if event.type == 'quit' then
|
||||||
UI:exitPullEvents()
|
UI:quit()
|
||||||
|
|
||||||
elseif event.type == 'eject' or event.type == 'grid_select' then
|
elseif event.type == 'eject' or event.type == 'grid_select' then
|
||||||
self:eject(1)
|
self:eject(1)
|
||||||
@@ -250,6 +255,10 @@ function page:eventHandler(event)
|
|||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
self:setFocus(self.statusBar.filter)
|
self:setFocus(self.statusBar.filter)
|
||||||
|
|
||||||
|
elseif event.type == 'defrag' then
|
||||||
|
self:defrag()
|
||||||
|
self:refresh(true)
|
||||||
|
|
||||||
elseif event.type == 'toggle_display' then
|
elseif event.type == 'toggle_display' then
|
||||||
displayMode = (displayMode + 1) % 2
|
displayMode = (displayMode + 1) % 2
|
||||||
Util.merge(event.button, displayModes[displayMode])
|
Util.merge(event.button, displayModes[displayMode])
|
||||||
@@ -357,6 +366,15 @@ function page:refresh(force)
|
|||||||
self.throttle:disable()
|
self.throttle:disable()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function page:defrag()
|
||||||
|
local throttle = function() self.throttle:update() end
|
||||||
|
|
||||||
|
self.throttle:enable()
|
||||||
|
local saved = context.storage:defrag(throttle)
|
||||||
|
self.throttle:disable()
|
||||||
|
self:notifyInfo(("Saved %d slots"):format(saved))
|
||||||
|
end
|
||||||
|
|
||||||
function page:applyFilter()
|
function page:applyFilter()
|
||||||
local function filterItems(t, filter)
|
local function filterItems(t, filter)
|
||||||
self.grid.sortColumn = Milo:getState('sortColumn') or 'count'
|
self.grid.sortColumn = Milo:getState('sortColumn') or 'count'
|
||||||
@@ -372,7 +390,7 @@ function page:applyFilter()
|
|||||||
v.score = fuzzy(v.lname, filter)
|
v.score = fuzzy(v.lname, filter)
|
||||||
if v.score then
|
if v.score then
|
||||||
if v.count > 0 then
|
if v.count > 0 then
|
||||||
v.score = v.score + 1
|
v.score = v.score + .2
|
||||||
end
|
end
|
||||||
table.insert(r, v)
|
table.insert(r, v)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ local Util = require('opus.util')
|
|||||||
|
|
||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local turtle = _G.turtle
|
|
||||||
|
|
||||||
local context = Milo:getContext()
|
local context = Milo:getContext()
|
||||||
|
|
||||||
@@ -22,20 +21,50 @@ local networkPage = UI.Page {
|
|||||||
y = -2, x = 1, ex = -9,
|
y = -2, x = 1, ex = -9,
|
||||||
limit = 50,
|
limit = 50,
|
||||||
shadowText = 'filter',
|
shadowText = 'filter',
|
||||||
backgroundColor = colors.cyan,
|
backgroundColor = 'primary',
|
||||||
backgroundFocusColor = colors.cyan,
|
backgroundFocusColor = 'primary',
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 2, ey = -3,
|
y = 2, ey = -3,
|
||||||
values = context.storage.nodes,
|
values = context.storage.nodes,
|
||||||
columns = {
|
columns = {
|
||||||
{ key = 'suffix', width = 4, align = 'right' },
|
{ key = 'suffix', width = 5, align = 'right' },
|
||||||
{ heading = 'Name', key = 'displayName' },
|
{ heading = 'Name', key = 'displayName' },
|
||||||
{ heading = 'Type', key = 'mtype', width = 4 },
|
{ heading = 'Type', key = 'mtype', width = 4 },
|
||||||
{ heading = 'Pri', key = 'priority', width = 3 },
|
{ heading = 'Pri', key = 'priority', width = 3 },
|
||||||
},
|
},
|
||||||
sortColumn = 'displayName',
|
sortColumn = 'displayName',
|
||||||
help = 'Select Node',
|
help = 'Select Node',
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
local t = { row.name:match(':(.+)_(%d+)$') }
|
||||||
|
if #t ~= 2 then
|
||||||
|
t = { row.name:match('(.+)_(%d+)$') }
|
||||||
|
end
|
||||||
|
if t and #t == 2 then
|
||||||
|
row.name, row.suffix = table.unpack(t)
|
||||||
|
row.name = row.name .. '_' .. row.suffix
|
||||||
|
end
|
||||||
|
row.displayName = row.displayName or row.name
|
||||||
|
return row
|
||||||
|
end,
|
||||||
|
getRowTextColor = function(self, row, selected)
|
||||||
|
if not device[row.name] then
|
||||||
|
return colors.red
|
||||||
|
end
|
||||||
|
if row.mtype == 'ignore' then
|
||||||
|
return colors.lightGray
|
||||||
|
end
|
||||||
|
return UI.Grid.getRowTextColor(self, row, selected)
|
||||||
|
end,
|
||||||
|
sortCompare = function(self, a, b)
|
||||||
|
if self.sortColumn == 'displayName' then
|
||||||
|
local an = a.displayName or a.name
|
||||||
|
local bn = b.displayName or b.name
|
||||||
|
return an:lower() < bn:lower()
|
||||||
|
end
|
||||||
|
return UI.Grid.sortCompare(self, a, b)
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
remove = UI.Button {
|
remove = UI.Button {
|
||||||
y = -2, x = -4,
|
y = -2, x = -4,
|
||||||
@@ -56,39 +85,6 @@ local networkPage = UI.Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function networkPage.grid:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
local t = { row.name:match(':(.+)_(%d+)$') }
|
|
||||||
if #t ~= 2 then
|
|
||||||
t = { row.name:match('(.+)_(%d+)$') }
|
|
||||||
end
|
|
||||||
if t and #t == 2 then
|
|
||||||
row.name, row.suffix = table.unpack(t)
|
|
||||||
row.name = row.name .. '_' .. row.suffix
|
|
||||||
end
|
|
||||||
row.displayName = row.displayName or row.name
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function networkPage.grid:getRowTextColor(row, selected)
|
|
||||||
if not device[row.name] then
|
|
||||||
return colors.red
|
|
||||||
end
|
|
||||||
if row.mtype == 'ignore' then
|
|
||||||
return colors.lightGray
|
|
||||||
end
|
|
||||||
return UI.Grid:getRowTextColor(row, selected)
|
|
||||||
end
|
|
||||||
|
|
||||||
function networkPage.grid:sortCompare(a, b)
|
|
||||||
if self.sortColumn == 'displayName' then
|
|
||||||
local an = a.displayName or a.name
|
|
||||||
local bn = b.displayName or b.name
|
|
||||||
return an:lower() < bn:lower()
|
|
||||||
end
|
|
||||||
return UI.Grid.sortCompare(self, a, b)
|
|
||||||
end
|
|
||||||
|
|
||||||
function networkPage:getList()
|
function networkPage:getList()
|
||||||
for _, v in pairs(device) do
|
for _, v in pairs(device) do
|
||||||
if not context.storage.nodes[v.name] then
|
if not context.storage.nodes[v.name] then
|
||||||
@@ -97,7 +93,7 @@ function networkPage:getList()
|
|||||||
mtype = 'ignore',
|
mtype = 'ignore',
|
||||||
category = 'ignore',
|
category = 'ignore',
|
||||||
}
|
}
|
||||||
for _, page in pairs(nodeWizard.wizard.pages) do
|
for _, page in pairs(nodeWizard.wizard:getPages()) do
|
||||||
if page.isValidType and page:isValidType(node) then
|
if page.isValidType and page:isValidType(node) then
|
||||||
context.storage.nodes[v.name] = node
|
context.storage.nodes[v.name] = node
|
||||||
break
|
break
|
||||||
@@ -192,55 +188,99 @@ nodeWizard = UI.Page {
|
|||||||
titleBar = UI.TitleBar { title = 'Configure' },
|
titleBar = UI.TitleBar { title = 'Configure' },
|
||||||
wizard = UI.Wizard {
|
wizard = UI.Wizard {
|
||||||
y = 2, ey = -2,
|
y = 2, ey = -2,
|
||||||
pages = {
|
general = UI.WizardPage {
|
||||||
general = UI.WizardPage {
|
index = 1,
|
||||||
index = 1,
|
form = UI.Form {
|
||||||
backgroundColor = colors.cyan,
|
x = 2, ex = -2, y = 1, ey = 3,
|
||||||
form = UI.Form {
|
manualControls = true,
|
||||||
x = 2, ex = -2, y = 1, ey = 3,
|
[1] = UI.TextEntry {
|
||||||
manualControls = true,
|
formLabel = 'Name', formKey = 'displayName',
|
||||||
[1] = UI.TextEntry {
|
help = 'Set a friendly name',
|
||||||
formLabel = 'Name', formKey = 'displayName',
|
limit = 64,
|
||||||
help = 'Set a friendly name',
|
|
||||||
limit = 64,
|
|
||||||
},
|
|
||||||
[2] = UI.Chooser {
|
|
||||||
width = 25,
|
|
||||||
formLabel = 'Type', formKey = 'mtype',
|
|
||||||
--nochoice = 'Storage',
|
|
||||||
help = 'Select type',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
[2] = UI.Chooser {
|
||||||
y = 5, ey = -2, x = 2, ex = -2,
|
width = 25,
|
||||||
columns = {
|
formLabel = 'Type', formKey = 'mtype',
|
||||||
{ heading = 'Slot', key = 'slot', width = 4 },
|
--nochoice = 'Storage',
|
||||||
{ heading = 'Name', key = 'displayName', },
|
help = 'Select type',
|
||||||
{ heading = 'Qty', key = 'count' , width = 3 },
|
|
||||||
},
|
|
||||||
sortColumn = 'slot',
|
|
||||||
help = 'Contents of inventory',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
confirmation = UI.WizardPage {
|
grid = UI.ScrollingGrid {
|
||||||
title = 'Confirm changes',
|
y = 5, ey = -2, x = 2, ex = -2,
|
||||||
index = 2,
|
columns = {
|
||||||
notice = UI.TextArea {
|
{ heading = 'Slot', key = 'slot', width = 4 },
|
||||||
x = 2, ex = -2, y = 2, ey = -2,
|
{ heading = 'Name', key = 'displayName', },
|
||||||
value =
|
{ heading = 'Qty', key = 'count' , width = 3 },
|
||||||
|
},
|
||||||
|
sortColumn = 'slot',
|
||||||
|
help = 'Contents of inventory',
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
row.displayName = itemDB:getName(row)
|
||||||
|
return row
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
enable = function(self)
|
||||||
|
UI.WizardPage.enable(self)
|
||||||
|
self:focusFirst()
|
||||||
|
end,
|
||||||
|
isValidFor = function()
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
showInventory = function(self, node)
|
||||||
|
local inventory
|
||||||
|
|
||||||
|
if device[node.name] and device[node.name].list then
|
||||||
|
pcall(function()
|
||||||
|
inventory = device[node.name].list()
|
||||||
|
for k,v in pairs(inventory) do
|
||||||
|
v.slot = k
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.grid:setValues(inventory or { })
|
||||||
|
end,
|
||||||
|
validate = function(self)
|
||||||
|
if self.form:save() then
|
||||||
|
nodeWizard.node.category = Util.find(nodeWizard.choices, 'value', nodeWizard.node.mtype).category
|
||||||
|
|
||||||
|
nodeWizard.nodePages = { }
|
||||||
|
table.insert(nodeWizard.nodePages, nodeWizard.wizard.general)
|
||||||
|
for _, page in pairs(nodeWizard.wizard:getPages()) do
|
||||||
|
if not page.isValidFor or page:isValidFor(nodeWizard.node) then
|
||||||
|
table.insert(nodeWizard.nodePages, page)
|
||||||
|
if page.setNode then
|
||||||
|
page:setNode(nodeWizard.node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(nodeWizard.nodePages, nodeWizard.wizard.confirmation)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
confirmation = UI.WizardPage {
|
||||||
|
title = 'Confirm changes',
|
||||||
|
index = 2,
|
||||||
|
notice = UI.TextArea {
|
||||||
|
x = 2, ex = -2, y = 2, ey = -2,
|
||||||
|
value =
|
||||||
[[Press accept to save the changes.
|
[[Press accept to save the changes.
|
||||||
|
|
||||||
The settings will take effect immediately!]],
|
The settings will take effect immediately!]],
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
isValidFor = function()
|
||||||
|
return false
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
statusBar = UI.StatusBar {
|
statusBar = UI.StatusBar {
|
||||||
backgroundColor = colors.cyan,
|
backgroundColor = 'primary',
|
||||||
},
|
},
|
||||||
notification = UI.Notification { },
|
notification = UI.Notification { },
|
||||||
filter = UI.SlideOut {
|
filter = UI.SlideOut {
|
||||||
backgroundColor = colors.cyan,
|
noFill = true,
|
||||||
menuBar = UI.MenuBar {
|
menuBar = UI.MenuBar {
|
||||||
buttons = {
|
buttons = {
|
||||||
{ text = 'Save', event = 'save' },
|
{ text = 'Save', event = 'save' },
|
||||||
@@ -248,7 +288,8 @@ The settings will take effect immediately!]],
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
x = 2, ex = -6, y = 2, ey = -6,
|
x = 2, ex = -6, y = 3, ey = -7,
|
||||||
|
disableHeader = true,
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Name', key = 'displayName' },
|
{ heading = 'Name', key = 'displayName' },
|
||||||
},
|
},
|
||||||
@@ -256,13 +297,18 @@ The settings will take effect immediately!]],
|
|||||||
accelerators = {
|
accelerators = {
|
||||||
delete = 'remove_entry',
|
delete = 'remove_entry',
|
||||||
},
|
},
|
||||||
|
getDisplayValues = function(_, row)
|
||||||
|
row = Util.shallowCopy(row)
|
||||||
|
row.displayName = itemDB:getName(row)
|
||||||
|
return row
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
remove = UI.Button {
|
remove = UI.Button {
|
||||||
x = -4, y = 4,
|
x = -4, y = 4,
|
||||||
text = '-', event = 'remove_entry', help = 'Remove',
|
text = '-', event = 'remove_entry', help = 'Remove',
|
||||||
},
|
},
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
x = 2, y = -4, height = 3,
|
x = 2, y = -5, height = 3,
|
||||||
margin = 1,
|
margin = 1,
|
||||||
manualControls = true,
|
manualControls = true,
|
||||||
[1] = UI.Checkbox {
|
[1] = UI.Checkbox {
|
||||||
@@ -290,145 +336,78 @@ The settings will take effect immediately!]],
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
statusBar = UI.StatusBar {
|
statusBar = UI.StatusBar {
|
||||||
backgroundColor = colors.cyan,
|
backgroundColor = 'primary',
|
||||||
},
|
},
|
||||||
|
show = function(self, entry, callback, whitelistOnly)
|
||||||
|
self.entry = entry
|
||||||
|
self.callback = callback
|
||||||
|
|
||||||
|
if not self.entry.filter then
|
||||||
|
self.entry.filter = { }
|
||||||
|
end
|
||||||
|
|
||||||
|
self.form:setValues(entry)
|
||||||
|
self:resetGrid()
|
||||||
|
|
||||||
|
self.form[3].inactive = whitelistOnly
|
||||||
|
|
||||||
|
UI.SlideOut.show(self)
|
||||||
|
self:setFocus(self.form.scan)
|
||||||
|
|
||||||
|
Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' })
|
||||||
|
end,
|
||||||
|
hide = function(self)
|
||||||
|
UI.SlideOut.hide(self)
|
||||||
|
Milo:resumeCrafting({ key = 'gridInUse' })
|
||||||
|
end,
|
||||||
|
resetGrid = function(self)
|
||||||
|
local t = { }
|
||||||
|
for k in pairs(self.entry.filter) do
|
||||||
|
table.insert(t, itemDB:splitKey(k))
|
||||||
|
end
|
||||||
|
self.grid:setValues(t)
|
||||||
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'focus_change' then
|
||||||
|
self.statusBar:setStatus(event.focused.help)
|
||||||
|
|
||||||
|
elseif event.type == 'scan_turtle' then
|
||||||
|
local inventory = Milo:getTurtleInventory()
|
||||||
|
for _,item in pairs(inventory) do
|
||||||
|
self.entry.filter[itemDB:makeKey(item)] = true
|
||||||
|
end
|
||||||
|
self:resetGrid()
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:draw()
|
||||||
|
Milo:emptyInventory()
|
||||||
|
|
||||||
|
elseif event.type == 'remove_entry' then
|
||||||
|
local row = self.grid:getSelected()
|
||||||
|
if row then
|
||||||
|
Util.removeByValue(self.grid.values, row)
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'save' then
|
||||||
|
self.form:save()
|
||||||
|
self.entry.filter = { }
|
||||||
|
for _,v in pairs(self.grid.values) do
|
||||||
|
self.entry.filter[itemDB:makeKey(v)] = true
|
||||||
|
end
|
||||||
|
self:hide()
|
||||||
|
self.callback()
|
||||||
|
|
||||||
|
elseif event.type == 'cancel' then
|
||||||
|
self:hide()
|
||||||
|
else
|
||||||
|
return UI.SlideOut.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
--[[ Filter slide out ]] --
|
|
||||||
function nodeWizard.filter:show(entry, callback, whitelistOnly)
|
|
||||||
self.entry = entry
|
|
||||||
self.callback = callback
|
|
||||||
|
|
||||||
if not self.entry.filter then
|
|
||||||
self.entry.filter = { }
|
|
||||||
end
|
|
||||||
|
|
||||||
self.form:setValues(entry)
|
|
||||||
self:resetGrid()
|
|
||||||
|
|
||||||
self.form[3].inactive = whitelistOnly
|
|
||||||
|
|
||||||
UI.SlideOut.show(self)
|
|
||||||
self:setFocus(self.form.scan)
|
|
||||||
|
|
||||||
Milo:pauseCrafting({ key = 'gridInUse', msg = 'Crafting paused' })
|
|
||||||
end
|
|
||||||
|
|
||||||
function nodeWizard.filter:hide()
|
|
||||||
UI.SlideOut.hide(self)
|
|
||||||
Milo:resumeCrafting({ key = 'gridInUse' })
|
|
||||||
end
|
|
||||||
|
|
||||||
function nodeWizard.filter:resetGrid()
|
|
||||||
local t = { }
|
|
||||||
for k in pairs(self.entry.filter) do
|
|
||||||
table.insert(t, itemDB:splitKey(k))
|
|
||||||
end
|
|
||||||
self.grid:setValues(t)
|
|
||||||
end
|
|
||||||
|
|
||||||
function nodeWizard.filter.grid:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
row.displayName = itemDB:getName(row)
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function nodeWizard.filter:eventHandler(event)
|
|
||||||
if event.type == 'focus_change' then
|
|
||||||
self.statusBar:setStatus(event.focused.help)
|
|
||||||
|
|
||||||
elseif event.type == 'scan_turtle' then
|
|
||||||
local inventory = Milo:getTurtleInventory()
|
|
||||||
for _,item in pairs(inventory) do
|
|
||||||
self.entry.filter[itemDB:makeKey(item)] = true
|
|
||||||
end
|
|
||||||
self:resetGrid()
|
|
||||||
self.grid:update()
|
|
||||||
self.grid:draw()
|
|
||||||
Milo:emptyInventory()
|
|
||||||
|
|
||||||
elseif event.type == 'remove_entry' then
|
|
||||||
local row = self.grid:getSelected()
|
|
||||||
if row then
|
|
||||||
Util.removeByValue(self.grid.values, row)
|
|
||||||
self.grid:update()
|
|
||||||
self.grid:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif event.type == 'save' then
|
|
||||||
self.form:save()
|
|
||||||
self.entry.filter = { }
|
|
||||||
for _,v in pairs(self.grid.values) do
|
|
||||||
self.entry.filter[itemDB:makeKey(v)] = true
|
|
||||||
end
|
|
||||||
self:hide()
|
|
||||||
self.callback()
|
|
||||||
|
|
||||||
elseif event.type == 'cancel' then
|
|
||||||
self:hide()
|
|
||||||
|
|
||||||
else
|
|
||||||
return UI.SlideOut.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ General Page ]] --
|
|
||||||
function nodeWizard.wizard.pages.general:enable()
|
|
||||||
UI.WizardPage.enable(self)
|
|
||||||
self:focusFirst()
|
|
||||||
end
|
|
||||||
|
|
||||||
function nodeWizard.wizard.pages.general:isValidFor()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function nodeWizard.wizard.pages.general:showInventory(node)
|
|
||||||
local inventory
|
|
||||||
|
|
||||||
if device[node.name] and device[node.name].list then
|
|
||||||
pcall(function()
|
|
||||||
inventory = device[node.name].list()
|
|
||||||
for k,v in pairs(inventory) do
|
|
||||||
v.slot = k
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.grid:setValues(inventory or { })
|
|
||||||
end
|
|
||||||
|
|
||||||
function nodeWizard.wizard.pages.general.grid:getDisplayValues(row)
|
|
||||||
row = Util.shallowCopy(row)
|
|
||||||
row.displayName = itemDB:getName(row)
|
|
||||||
return row
|
|
||||||
end
|
|
||||||
|
|
||||||
function nodeWizard.wizard.pages.general:validate()
|
|
||||||
if self.form:save() then
|
|
||||||
nodeWizard.node.category = Util.find(nodeWizard.choices, 'value', nodeWizard.node.mtype).category
|
|
||||||
|
|
||||||
nodeWizard.nodePages = { }
|
|
||||||
table.insert(nodeWizard.nodePages, nodeWizard.wizard.pages.general)
|
|
||||||
for _, page in pairs(nodeWizard.wizard.pages) do
|
|
||||||
if not page.isValidFor or page:isValidFor(nodeWizard.node) then
|
|
||||||
table.insert(nodeWizard.nodePages, page)
|
|
||||||
if page.setNode then
|
|
||||||
page:setNode(nodeWizard.node)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.insert(nodeWizard.nodePages, nodeWizard.wizard.pages.confirmation)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ Confirmation ]]--
|
|
||||||
function nodeWizard.wizard.pages.confirmation:isValidFor()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ Wizard ]] --
|
--[[ Wizard ]] --
|
||||||
function nodeWizard:enable(node)
|
function nodeWizard:enable(node)
|
||||||
local adapter = node.adapter
|
local adapter = node.adapter
|
||||||
@@ -441,7 +420,7 @@ function nodeWizard:enable(node)
|
|||||||
{ name = 'Ignore', value = 'ignore', category = 'ignore' },
|
{ name = 'Ignore', value = 'ignore', category = 'ignore' },
|
||||||
{ name = 'Hidden', value = 'hidden', category = 'ignore', help = 'Do not show in list' },
|
{ name = 'Hidden', value = 'hidden', category = 'ignore', help = 'Do not show in list' },
|
||||||
}
|
}
|
||||||
for _, page in pairs(self.wizard.pages) do
|
for _, page in pairs(self.wizard:getPages()) do
|
||||||
if page.isValidType then
|
if page.isValidType then
|
||||||
local choice = page:isValidType(self.node)
|
local choice = page:isValidType(self.node)
|
||||||
if choice and not Util.find(self.choices, 'value', choice.value) then
|
if choice and not Util.find(self.choices, 'value', choice.value) then
|
||||||
@@ -449,15 +428,15 @@ function nodeWizard:enable(node)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.wizard.pages.general.form[1].shadowText = self.node.name
|
self.wizard.general.form[1].shadowText = self.node.name
|
||||||
self.wizard.pages.general.form[2].choices = self.choices
|
self.wizard.general.form[2].choices = self.choices
|
||||||
self.wizard.pages.general.form:setValues(self.node)
|
self.wizard.general.form:setValues(self.node)
|
||||||
|
|
||||||
self.wizard.pages.general:showInventory(self.node)
|
self.wizard.general:showInventory(self.node)
|
||||||
|
|
||||||
self.nodePages = { }
|
self.nodePages = { }
|
||||||
table.insert(self.nodePages, self.wizard.pages.general)
|
table.insert(self.nodePages, self.wizard.general)
|
||||||
table.insert(self.nodePages, self.wizard.pages.confirmation)
|
table.insert(self.nodePages, self.wizard.confirmation)
|
||||||
|
|
||||||
UI.Page.enable(self)
|
UI.Page.enable(self)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ Right-clicking on the activity monitor will reset the totals.]]
|
|||||||
local wizardPage = UI.WizardPage {
|
local wizardPage = UI.WizardPage {
|
||||||
title = 'Activity Monitor',
|
title = 'Activity Monitor',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.TextArea {
|
[1] = UI.TextArea {
|
||||||
x = 2, ex = -2, y = 2, ey = 6,
|
x = 2, ex = -2, y = 2, ey = 6,
|
||||||
marginRight = 0,
|
marginRight = 0,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ local Event = require('opus.event')
|
|||||||
local Milo = require('milo')
|
local Milo = require('milo')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
@@ -23,7 +22,6 @@ Backup configuration files each minecraft day.
|
|||||||
local wizardPage = UI.WizardPage {
|
local wizardPage = UI.WizardPage {
|
||||||
title = 'Backup Drive',
|
title = 'Backup Drive',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.TextArea {
|
[1] = UI.TextArea {
|
||||||
x = 2, ex = -2, y = 2, ey = -2,
|
x = 2, ex = -2, y = 2, ey = -2,
|
||||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ Note that you do not need to import items from the brewing stand or export blaze
|
|||||||
local wizardPage = UI.WizardPage {
|
local wizardPage = UI.WizardPage {
|
||||||
title = 'Brewing Stand',
|
title = 'Brewing Stand',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.TextArea {
|
[1] = UI.TextArea {
|
||||||
x = 2, ex = -2, y = 2, ey = -2,
|
x = 2, ex = -2, y = 2, ey = -2,
|
||||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ local itemDB = require('core.itemDB')
|
|||||||
|
|
||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local context = Milo:getContext()
|
|
||||||
|
|
||||||
local wizardPage = UI.WizardPage {
|
local wizardPage = UI.WizardPage {
|
||||||
title = 'Level Emitter',
|
title = 'Level Emitter',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.TextArea {
|
[1] = UI.TextArea {
|
||||||
x = 2, y = 1,
|
x = 2, y = 1,
|
||||||
height = 2,
|
height = 2,
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ function ExportTask:cycle(context)
|
|||||||
|
|
||||||
for node in context.storage:filterActive('machine', filter) do
|
for node in context.storage:filterActive('machine', filter) do
|
||||||
tasks:add(function()
|
tasks:add(function()
|
||||||
|
|
||||||
|
local slots
|
||||||
|
|
||||||
for _, entry in pairs(node.exports) do
|
for _, entry in pairs(node.exports) do
|
||||||
|
|
||||||
if not entry.filter then
|
if not entry.filter then
|
||||||
@@ -66,13 +69,36 @@ function ExportTask:cycle(context)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function exportItems()
|
local function exportItems()
|
||||||
|
local function canExport(item)
|
||||||
|
if not node.adapter.__size then
|
||||||
|
node.adapter.__size = node.adapter.size()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not slots then
|
||||||
|
slots = node.adapter.list()
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, node.adapter.__size do
|
||||||
|
local slot = slots[i]
|
||||||
|
if (not slot or slot.name == item.name and
|
||||||
|
(entry.ignoreDamage or slot.damage == item.damage) and
|
||||||
|
(entry.ignoreNbtHash or slot.nbtHash == item.nbtHash) and
|
||||||
|
slot.count < item.maxCount) then
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for key in pairs(entry.filter) do
|
for key in pairs(entry.filter) do
|
||||||
local items = Milo:getMatches(itemDB:splitKey(key), entry)
|
local items = Milo:getMatches(itemDB:splitKey(key), entry)
|
||||||
for _,item in pairs(items) do
|
for _,item in pairs(items) do
|
||||||
if context.storage:export(node, nil, item.count, item) == 0 then
|
if canExport(item) then
|
||||||
-- TODO: really shouldn't break here as there may be room in other slots
|
if context.storage:export(node, nil, item.count, item) == 0 then
|
||||||
-- leaving for now for performance reasons
|
break
|
||||||
break
|
end
|
||||||
|
-- refresh the slots
|
||||||
|
slots = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
local Ansi = require('opus.ansi')
|
local Ansi = require('opus.ansi')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
|
|
||||||
--[[ Configuration Screen ]]
|
--[[ Configuration Screen ]]
|
||||||
@@ -14,7 +13,6 @@ Any items placed in this chest will be imported into storage.
|
|||||||
local inputChestWizardPage = UI.WizardPage {
|
local inputChestWizardPage = UI.WizardPage {
|
||||||
title = 'Input Chest',
|
title = 'Input Chest',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.TextArea {
|
[1] = UI.TextArea {
|
||||||
x = 2, ex = -2, y = 2, ey = -2,
|
x = 2, ex = -2, y = 2, ey = -2,
|
||||||
value = string.format(template, Ansi.yellow, Ansi.reset),
|
value = string.format(template, Ansi.yellow, Ansi.reset),
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ function page:eventHandler(event)
|
|||||||
|
|
||||||
elseif event.type == 'focus_change' then
|
elseif event.type == 'focus_change' then
|
||||||
self.statusBar:setStatus(event.focused.help)
|
self.statusBar:setStatus(event.focused.help)
|
||||||
self.statusBar:draw()
|
|
||||||
|
|
||||||
elseif event.type == 'success_message' then
|
elseif event.type == 'success_message' then
|
||||||
self.notification:success(event.message)
|
self.notification:success(event.message)
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
local Ansi = require('opus.ansi')
|
local Ansi = require('opus.ansi')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
|
|
||||||
local infoTab = UI.Tab {
|
local infoTab = UI.Tab {
|
||||||
tabTitle = 'Info',
|
title = 'Info',
|
||||||
index = 4,
|
index = 4,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
textArea = UI.TextArea {
|
textArea = UI.TextArea {
|
||||||
x = 2, ex = -2, y = 2,
|
x = 2, ex = -2, y = 2, ey = -2,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ local colors = _G.colors
|
|||||||
local context = Milo:getContext()
|
local context = Milo:getContext()
|
||||||
|
|
||||||
local machinesTab = UI.Tab {
|
local machinesTab = UI.Tab {
|
||||||
tabTitle = 'Machine',
|
title = 'Machine',
|
||||||
index = 3,
|
index = 3,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
x = 2, ex = -2, y = 2, ey = -2,
|
x = 2, ex = -2, y = 2, ey = -2,
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ local Util = require('opus.util')
|
|||||||
local context = Milo:getContext()
|
local context = Milo:getContext()
|
||||||
|
|
||||||
local manageTab = UI.Tab {
|
local manageTab = UI.Tab {
|
||||||
tabTitle = 'Manage',
|
title = 'Manage',
|
||||||
index = 1,
|
index = 1,
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
x = 1, ex = -1, ey = -1,
|
x = 1, ex = -1, ey = -1,
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ local itemDB = require('core.itemDB')
|
|||||||
local Milo = require('milo')
|
local Milo = require('milo')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
|
|
||||||
local recipeTab = UI.Tab {
|
local recipeTab = UI.Tab {
|
||||||
tabTitle = 'Recipe',
|
title = 'Recipe',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
x = 2, ex = -2, y = 2, ey = -4,
|
x = 2, ex = -2, y = 2, ey = -4,
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ local colors = _G.colors
|
|||||||
local context = Milo:getContext()
|
local context = Milo:getContext()
|
||||||
|
|
||||||
local resetTab = UI.Tab {
|
local resetTab = UI.Tab {
|
||||||
tabTitle = 'Reset',
|
title = 'Reset',
|
||||||
index = 5,
|
index = 5,
|
||||||
backgroundColor = colors.cyan,
|
noFill = true,
|
||||||
textArea = UI.TextArea {
|
textArea = UI.TextArea {
|
||||||
y = 2, ey = 6,
|
y = 2, ey = 6,
|
||||||
textColor = colors.yellow,
|
textColor = colors.yellow,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ local os = _G.os
|
|||||||
local wizardPage = UI.WizardPage {
|
local wizardPage = UI.WizardPage {
|
||||||
title = 'Crafting Monitor',
|
title = 'Crafting Monitor',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.TextArea {
|
[1] = UI.TextArea {
|
||||||
x = 2, ex = -2, y = 2, ey = 3,
|
x = 2, ex = -2, y = 2, ey = 3,
|
||||||
marginRight = 0,
|
marginRight = 0,
|
||||||
@@ -157,6 +156,7 @@ local function createPage(node)
|
|||||||
if row.requested then
|
if row.requested then
|
||||||
row.remaining = math.max(0, row.requested - row.crafted)
|
row.remaining = math.max(0, row.requested - row.crafted)
|
||||||
--_syslog('%d %d %d %d', row.remaining, row.requested, row.total, row.crafted)
|
--_syslog('%d %d %d %d', row.remaining, row.requested, row.total, row.crafted)
|
||||||
|
row.total = row.total or 0
|
||||||
row.status = (row.status or '') ..
|
row.status = (row.status or '') ..
|
||||||
string.format(' %d of %d', row.crafted + row.total, row.total + row.requested)
|
string.format(' %d of %d', row.crafted + row.total, row.total + row.requested)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
local Ansi = require('opus.ansi')
|
|
||||||
local UI = require('opus.ui')
|
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
local device = _G.device
|
|
||||||
|
|
||||||
--[[ Configuration Screen ]]
|
|
||||||
local template =
|
|
||||||
[[%sWarning%s
|
|
||||||
|
|
||||||
Must an interface for Refined Storage / Applied Energistics.
|
|
||||||
|
|
||||||
Add all speed upgrades possible.
|
|
||||||
]]
|
|
||||||
|
|
||||||
local wizardPage = UI.WizardPage {
|
|
||||||
title = 'Mass Storage',
|
|
||||||
index = 2,
|
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.TextArea {
|
|
||||||
x = 2, ex = -2, y = 2, ey = -2,
|
|
||||||
value = string.format(template, Ansi.red, Ansi.reset),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function wizardPage:isValidFor(node)
|
|
||||||
if node.mtype == 'storage' then
|
|
||||||
local m = device[node.name]
|
|
||||||
return m and m.listAvailableItems
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function wizardPage:setNode(node)
|
|
||||||
self.node = node
|
|
||||||
end
|
|
||||||
|
|
||||||
function wizardPage:validate()
|
|
||||||
self.node.adapterType = 'massAdapter'
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- disable until a way is found to transfer between 2 non-transferrable nodes
|
|
||||||
-- UI:getPage('nodeWizard').wizard:add({ inputChest = wizardPage })
|
|
||||||
@@ -23,7 +23,7 @@ local page = UI.Page {
|
|||||||
tabs = UI.Tabs {
|
tabs = UI.Tabs {
|
||||||
y = 2, ey = -2,
|
y = 2, ey = -2,
|
||||||
inventory = UI.Tab {
|
inventory = UI.Tab {
|
||||||
tabTitle = 'Inventory',
|
title = 'Inventory',
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 2, ey = -2,
|
y = 2, ey = -2,
|
||||||
columns = {
|
columns = {
|
||||||
@@ -33,7 +33,7 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
autostore = UI.Tab {
|
autostore = UI.Tab {
|
||||||
tabTitle = 'Deposit',
|
title = 'Deposit',
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 2, ey = -2,
|
y = 2, ey = -2,
|
||||||
columns = {
|
columns = {
|
||||||
|
|||||||
@@ -5,17 +5,16 @@ local Util = require('opus.util')
|
|||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
|
|
||||||
local STARTUP_FILE = 'usr/autorun/miloRemote.lua'
|
|
||||||
|
|
||||||
local context = ({ ... })[1]
|
local context = ({ ... })[1]
|
||||||
|
|
||||||
|
local STARTUP_FILE = 'usr/autorun/'..context.configPath..'.lua'
|
||||||
|
|
||||||
local setup = UI.SlideOut {
|
local setup = UI.SlideOut {
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
titleBar = UI.TitleBar {
|
titleBar = UI.TitleBar {
|
||||||
title = 'Remote Setup',
|
title = 'Remote Setup',
|
||||||
},
|
},
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
x = 2, ex = -2, y = 2, ey = -1,
|
y = 2, ey = -1,
|
||||||
[1] = UI.TextEntry {
|
[1] = UI.TextEntry {
|
||||||
formLabel = 'Server', formKey = 'server',
|
formLabel = 'Server', formKey = 'server',
|
||||||
help = 'ID for the server',
|
help = 'ID for the server',
|
||||||
@@ -40,18 +39,18 @@ local setup = UI.SlideOut {
|
|||||||
formLabel = 'Run on startup', formKey = 'runOnStartup',
|
formLabel = 'Run on startup', formKey = 'runOnStartup',
|
||||||
help = 'Run this program on startup'
|
help = 'Run this program on startup'
|
||||||
},
|
},
|
||||||
info = UI.TextArea {
|
},
|
||||||
x = 1, ex = -1, y = 6, ey = -4,
|
info = UI.TextArea {
|
||||||
textColor = colors.yellow,
|
x = 2, ex = -2, y = 8, ey = -4,
|
||||||
marginLeft = 0,
|
textColor = colors.yellow,
|
||||||
marginRight = 0,
|
marginLeft = 0,
|
||||||
value = [[The Milo turtle must connect to a manipulator with a ]] ..
|
marginRight = 0,
|
||||||
[[bound introspection module. The neural interface must ]] ..
|
value = [[The Milo turtle must connect to a manipulator with a ]] ..
|
||||||
[[also have an introspection module.]],
|
[[bound introspection module. The neural interface must ]] ..
|
||||||
},
|
[[also have an introspection module.]],
|
||||||
},
|
},
|
||||||
statusBar = UI.StatusBar {
|
statusBar = UI.StatusBar {
|
||||||
backgroundColor = colors.cyan,
|
backgroundColor = 'primary',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ function setup:eventHandler(event)
|
|||||||
self.statusBar:setStatus(event.focused.help)
|
self.statusBar:setStatus(event.focused.help)
|
||||||
|
|
||||||
elseif event.type == 'form_complete' then
|
elseif event.type == 'form_complete' then
|
||||||
Config.update('miloRemote', context.state)
|
Config.update(context.configPath, context.state)
|
||||||
self:hide()
|
self:hide()
|
||||||
context.page:refresh('list')
|
context.page:refresh('list')
|
||||||
context.page.grid:draw()
|
context.page.grid:draw()
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ end
|
|||||||
local wizardPage = UI.WizardPage {
|
local wizardPage = UI.WizardPage {
|
||||||
title = 'Speaker',
|
title = 'Speaker',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.Text {
|
[1] = UI.Text {
|
||||||
x = 2, y = 2,
|
x = 2, y = 2,
|
||||||
textColor = colors.yellow,
|
textColor = colors.yellow,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ Right-clicking on the activity monitor will reset the totals.]]
|
|||||||
local wizardPage = UI.WizardPage {
|
local wizardPage = UI.WizardPage {
|
||||||
title = 'Status Monitor',
|
title = 'Status Monitor',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
[1] = UI.TextArea {
|
[1] = UI.TextArea {
|
||||||
x = 2, ex = -2, y = 2, ey = 6,
|
x = 2, ex = -2, y = 2, ey = 6,
|
||||||
marginRight = 0,
|
marginRight = 0,
|
||||||
@@ -84,8 +83,9 @@ local function createPage(node)
|
|||||||
parent = monitor,
|
parent = monitor,
|
||||||
tabs = UI.Tabs {
|
tabs = UI.Tabs {
|
||||||
[1] = UI.Tab {
|
[1] = UI.Tab {
|
||||||
tabTitle = 'Overview',
|
title = 'Overview',
|
||||||
backgroundColor = colors.black,
|
backgroundColor = colors.black,
|
||||||
|
noFill = true,
|
||||||
onlineLabel = UI.Text {
|
onlineLabel = UI.Text {
|
||||||
x = 2, y = 2,
|
x = 2, y = 2,
|
||||||
value = 'Storage Status',
|
value = 'Storage Status',
|
||||||
@@ -135,13 +135,15 @@ local function createPage(node)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
[2] = UI.Tab {
|
[2] = UI.Tab {
|
||||||
tabTitle = 'Stats',
|
title = 'Stats',
|
||||||
|
noFill = true,
|
||||||
textArea = UI.TextArea {
|
textArea = UI.TextArea {
|
||||||
y = 3,
|
y = 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[3] = UI.Tab {
|
[3] = UI.Tab {
|
||||||
tabTitle = 'Storage',
|
title = 'Storage',
|
||||||
|
noFill = true,
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 2,
|
y = 2,
|
||||||
columns = {
|
columns = {
|
||||||
@@ -149,13 +151,14 @@ local function createPage(node)
|
|||||||
{ heading = 'Size', key = 'size', width = 5 },
|
{ heading = 'Size', key = 'size', width = 5 },
|
||||||
{ heading = 'Used', key = 'used', width = 5 },
|
{ heading = 'Used', key = 'used', width = 5 },
|
||||||
{ heading = 'Perc', key = 'perc', width = 5 },
|
{ heading = 'Perc', key = 'perc', width = 5 },
|
||||||
-- TODO: add % to each number
|
|
||||||
},
|
},
|
||||||
sortColumn = 'name',
|
sortColumn = 'name',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[4] = UI.Tab {
|
[4] = UI.Tab {
|
||||||
tabTitle = 'Offline',
|
title = 'Offline',
|
||||||
|
noFill = true,
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 2,
|
y = 2,
|
||||||
columns = {
|
columns = {
|
||||||
@@ -165,13 +168,15 @@ local function createPage(node)
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
[5] = UI.Tab {
|
[5] = UI.Tab {
|
||||||
tabTitle = 'Activity',
|
title = 'Activity',
|
||||||
|
noFill = true,
|
||||||
term = UI.Embedded {
|
term = UI.Embedded {
|
||||||
--visible = true,
|
--visible = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[6] = UI.Tab {
|
[6] = UI.Tab {
|
||||||
tabTitle = 'Tasks',
|
title = 'Tasks',
|
||||||
|
noFill = true,
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 2,
|
y = 2,
|
||||||
values = context.tasks,
|
values = context.tasks,
|
||||||
@@ -219,7 +224,7 @@ local function createPage(node)
|
|||||||
name = n.displayName or n.name,
|
name = n.displayName or n.name,
|
||||||
size = n.adapter.__size,
|
size = n.adapter.__size,
|
||||||
used = n.adapter.__used,
|
used = n.adapter.__used,
|
||||||
perc = math.floor(n.adapter.__used / n.adapter.__size * 100),
|
perc = tostring(math.floor(n.adapter.__used / n.adapter.__size * 100)) .. "%",
|
||||||
updated = updated,
|
updated = updated,
|
||||||
})
|
})
|
||||||
totals.usedSlots = totals.usedSlots + n.adapter.__used
|
totals.usedSlots = totals.usedSlots + n.adapter.__used
|
||||||
@@ -444,7 +449,7 @@ Unlocked Slots : %d of %d (%d%%)
|
|||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
if event.type == 'tab_activate' then
|
if event.type == 'tab_activate' then
|
||||||
local state = Milo:getState('statusState') or { }
|
local state = Milo:getState('statusState') or { }
|
||||||
state[node.name] = event.activated.tabTitle
|
state[node.name] = event.activated.title
|
||||||
Milo:setState('statusState', state)
|
Milo:setState('statusState', state)
|
||||||
end
|
end
|
||||||
return UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
@@ -467,7 +472,7 @@ Unlocked Slots : %d of %d (%d%%)
|
|||||||
-- restore active tab
|
-- restore active tab
|
||||||
local tabState = Milo:getState('statusState') or { }
|
local tabState = Milo:getState('statusState') or { }
|
||||||
if tabState[node.name] then
|
if tabState[node.name] then
|
||||||
page.tabs:selectTab(Util.find(page.tabs, 'tabTitle', tabState[node.name]))
|
page.tabs:selectTab(Util.find(page.tabs, 'title', tabState[node.name]))
|
||||||
end
|
end
|
||||||
|
|
||||||
return page
|
return page
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ local device = _G.device
|
|||||||
local storageView = UI.WizardPage {
|
local storageView = UI.WizardPage {
|
||||||
title = 'Storage Options - General',
|
title = 'Storage Options - General',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
x = 2, ex = -2, y = 1, ey = -2,
|
x = 2, ex = -2, y = 1, ey = -2,
|
||||||
manualControls = true,
|
manualControls = true,
|
||||||
@@ -62,7 +61,6 @@ UI:getPage('nodeWizard').wizard:add({ storageGeneral = storageView })
|
|||||||
local lockView = UI.WizardPage {
|
local lockView = UI.WizardPage {
|
||||||
title = 'Storage Options - Locking',
|
title = 'Storage Options - Locking',
|
||||||
index = 3,
|
index = 3,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
x = 2, ex = -2, y = 1, ey = 3,
|
x = 2, ex = -2, y = 1, ey = 3,
|
||||||
manualControls = true,
|
manualControls = true,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ local context = Milo:getContext()
|
|||||||
local wizardPage = UI.WizardPage {
|
local wizardPage = UI.WizardPage {
|
||||||
title = 'Transfer Inventory',
|
title = 'Transfer Inventory',
|
||||||
index = 2,
|
index = 2,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 2, ey = -2,
|
y = 2, ey = -2,
|
||||||
values = context.storage.nodes,
|
values = context.storage.nodes,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user