local ui = require("ui") local ensure_width = require("cc.strings").ensure_width local term_stack = require("term-stack") local LEFT_KEY = {keys.left , {"ctrl", keys.h}} local DOWN_KEY = {keys.down , {"ctrl", keys.j}} local UP_KEY = {keys.up , {"ctrl", keys.k}} local RIGHT_KEY = {keys.right, {"ctrl", keys.l}} local REQUEST_KEY = {"ctrl", keys.space} local HELP_KEY = keys.f1 local STATISTICS_KEY = keys.f2 local REFRESH_KEY = keys.f5 local dbg_file = fs.combine(shell.dir(), "logs.txt") if dbg_file then fs.delete(dbg_file) end local function dbg(...) if dbg_file then local out = io.open(dbg_file, "a") if not out then return end for i = 1, select("#", ...) do local value = select(i, ...) if type(value) == "table" then out:write(textutils.serialise(value)) else out:write(tostring(value)) out:write(" ") end end out:write("\n") out:close() else local pretty = require("cc.pretty") for i = 1, select("#", ...) do local value = select(i, ...) pretty.pretty_print(value) end end end local function is_inventory(peripheral_name) local types = {peripheral.getType(peripheral_name)} for _, t in ipairs(types) do if t == "inventory" then return true end end return false end local function remove_value(array, value) for i, v in ipairs(array) do if v == value then table.remove(array, i) return end end end local function has_in_array(value, array) for _, v in ipairs(array) do if v == value then return true end end end local function list_inventories() local inventories = {} for _, name in ipairs(peripheral.getNames()) do if is_inventory(name) and not has_in_array(name, { "top", "left", "right", "back", "bottom", "front" }) then table.insert(inventories, name) end end return inventories end local function list_items(inventories) local items = {} for _, name in ipairs(inventories) do for slot, item in pairs(peripheral.call(name, "list")) do items[item.name] = items[item.name] or {} table.insert(items[item.name], { count = item.count, slot = slot, peripheral = name }) end end for name, item_collection in pairs(items) do local item = item_collection[1] local detail = peripheral.call(item.peripheral, "getItemDetail", item.slot) items[name].displayName = detail.displayName items[name].stack_size = detail.maxCount item_collection.count = 0 for _, slot in ipairs(item_collection) do item_collection.count = item_collection.count + slot.count end end return items end local function get_item_counts(items) local item_counts = {} for name, item_collection in pairs(items) do item_counts[name] = item_collection.count end return item_counts end local function vline(x, y, height) for i=1, height do term.setCursorPos(x, y+i-1) term.write("|") end end local function clamp(x, min, max) return math.min(math.max(x, min), max) end local function rect(x, y, w, h) return { x = x, y = y, w = w, h = h or w } end local function vsplit(area, x) local left_area = rect(area.x, area.y, x - area.x, area.h) local right_area = rect(area.x + left_area.w+1, area.y, area.w - left_area.w-1, area.h) return left_area, right_area end local main_view = { event = {} } function main_view:list_shown_names(all_items, name_filter, max_names) name_filter = name_filter:lower() local names = {} for name, count in pairs(all_items) do local displayName = self.items[name].displayName:lower() if displayName:find(name_filter) and count > 0 then table.insert(names, name) end end table.sort(names, function(a, b) return all_items[a] > all_items[b] end) if #names > max_names then local names_subset = {} for i=1, max_names do table.insert(names_subset, names[i]) end names = names_subset end return names end function main_view:display_item_list(x, y, w, all_items, shown_names, highlight_row) term_stack.push_cursor(x, y) local max_count = 0 for _, name in pairs(shown_names) do local count = all_items[name] max_count = math.max(max_count, count) end local max_count_width = math.floor(math.log(max_count, 10) + 1) for row, name in pairs(shown_names) do local count = all_items[name] local item_info = self.items[name] local count_width = math.floor(math.log(count, 10) + 1) if row == highlight_row then term.setTextColor(colors.yellow) else term.setTextColor(colors.white) end term.setCursorPos((max_count_width+1 - count_width), row) term.write(count) term.setCursorPos(max_count_width+2, row) term.write(ensure_width(item_info.displayName, w-max_count_width-1)) end term_stack.pop_cursor() end function main_view:move_items(item_name, amount, destination) local item_collection = self.items[item_name] local moved_amount = 0 while moved_amount < amount and item_collection.count > 0 do local slot_details = item_collection[#item_collection] local needed_amount = amount - moved_amount local transferred_amount = peripheral.call(slot_details.peripheral, "pushItems", destination, slot_details.slot, needed_amount) moved_amount = moved_amount + transferred_amount slot_details.count = slot_details.count - transferred_amount item_collection.count = item_collection.count - transferred_amount if slot_details.count == 0 then table.remove(item_collection) end end return moved_amount end function main_view:refresh_shown_names(store, area) store.shown_names = main_view:list_shown_names(store.all_items, store.search_bar or "", area.h-1) store.selected_row = clamp(store.selected_row or 1, 1, #store.shown_names) end function main_view:display_items_with_search(store, area, active) store.search_bar = store.search_bar or "" store.selected_row = store.selected_row or 0 store.all_items = store.all_items or {} store.shown_names = store.shown_names or main_view:list_shown_names(store.all_items, store.search_bar, area.h-1) term_stack.push_cursor(area.x, area.y) local new_search_bar = ui:textbox(area.w, active, "Search", store.search_bar) if new_search_bar ~= store.search_bar then store.search_bar = new_search_bar self:refresh_shown_names(store, area) end if active then if ui.event[1] == "key" then local key = ui.event[2] if key == keys.down or (main_view.ctrl_down and key == keys.j) then store.selected_row = math.min(store.selected_row + 1, #store.shown_names) elseif key == keys.up or (main_view.ctrl_down and key == keys.k) then store.selected_row = math.max(store.selected_row - 1, 1) end end end term.setBackgroundColor(colors.black) main_view:display_item_list(1, 2, area.w, store.all_items, store.shown_names, store.selected_row) term_stack.pop_cursor() end local function main() main_view.result_inventory = "minecraft:barrel_4" local inventories = list_inventories() remove_value(inventories, main_view.result_inventory) main_view.items = list_items(inventories) local update_interval = 0.05 local main_area = rect(1, 1, term.getSize()) local split_x = math.floor(main_area.w*0.5) local left_area, right_area = vsplit(main_area, split_x) local is_left_active = true local left_store = { all_items = get_item_counts(main_view.items) } local right_store = { all_items = {} } local update_timer = os.startTimer(update_interval) while true do ---@diagnostic disable-next-line: missing-parameter ui.event = {os.pullEvent()} local has_selected_items = false for _, count in pairs(right_store.all_items) do if count > 0 then has_selected_items = true break end end if not has_selected_items and not is_left_active then is_left_active = true end local event = ui.event[1] if event == "timer" then local timer = ui.event[2] if timer == update_timer then update_timer = os.startTimer(update_interval) end elseif event == "key" then local key = ui.event[2] if key == keys.rightCtrl or key == keys.leftCtrl then main_view.ctrl_down = true elseif key == keys.tab then is_left_active = not is_left_active elseif key == keys.f5 then main_view.items = list_items(inventories) left_store.all_items = get_item_counts(main_view.items) right_store.all_items = {} main_view:refresh_shown_names(left_store, left_area) main_view:refresh_shown_names(right_store, right_area) update_timer = os.startTimer(update_interval) end if main_view.ctrl_down then if key == keys.space then if has_selected_items then for name, count in pairs(right_store.all_items) do local transferred_count = main_view:move_items(name, count, main_view.result_inventory) left_store.all_items[name] = left_store.all_items[name] - transferred_count end right_store.all_items = {} main_view:refresh_shown_names(left_store, left_area) main_view:refresh_shown_names(right_store, right_area) else local target_item = left_store.shown_names[left_store.selected_row] if target_item then local item_collection = main_view.items[target_item] local transferred_count = main_view:move_items(target_item, item_collection.stack_size, main_view.result_inventory) left_store.all_items[target_item] = left_store.all_items[target_item] - transferred_count main_view:refresh_shown_names(left_store, left_area) end end end if is_left_active then if key == keys.l then local target_item = left_store.shown_names[left_store.selected_row] if target_item then local stack_size = main_view.items[target_item].stack_size local transferred_count = math.min(left_store.all_items[target_item], stack_size) left_store.all_items[target_item] = left_store.all_items[target_item] - transferred_count right_store.all_items[target_item] = (right_store.all_items[target_item] or 0) + transferred_count main_view:refresh_shown_names(left_store, left_area) main_view:refresh_shown_names(right_store, right_area) end end else if key == keys.h then local target_item = right_store.shown_names[right_store.selected_row] if target_item then local stack_size = main_view.items[target_item].stack_size local transferred_count = math.min(right_store.all_items[target_item], stack_size) left_store.all_items[target_item] = left_store.all_items[target_item] + transferred_count right_store.all_items[target_item] = right_store.all_items[target_item] - transferred_count main_view:refresh_shown_names(left_store, left_area) main_view:refresh_shown_names(right_store, right_area) end end end end elseif event == "key_up" then local key = ui.event[2] if key == keys.rightCtrl or key == keys.leftCtrl then main_view.ctrl_down = false end end term.setBackgroundColor(colors.black) term.clear() if has_selected_items then term.setTextColor(colors.lightGray) vline(split_x, main_area.y, main_area.h) main_view:display_items_with_search(left_store, left_area, is_left_active) main_view:display_items_with_search(right_store, right_area, not is_left_active) else main_view:display_items_with_search(left_store, main_area, is_left_active) end end end main(...)