Implement search functionality with keyboard support and pagination in inventory dashboard
This commit is contained in:
@@ -151,6 +151,34 @@ local touchZones = {}
|
||||
local pendingZones = {}
|
||||
local needsRedraw = true
|
||||
|
||||
local currentPage = 1
|
||||
local totalPages = 1
|
||||
local searchQuery = ""
|
||||
local showKeyboard = false
|
||||
|
||||
-- Keyboard layout
|
||||
local kbRows = {
|
||||
{"Q","W","E","R","T","Y","U","I","O","P"},
|
||||
{"A","S","D","F","G","H","J","K","L"},
|
||||
{"Z","X","C","V","B","N","M"},
|
||||
}
|
||||
|
||||
-- Get items filtered by search query
|
||||
local function getFilteredItems()
|
||||
local filtered = {}
|
||||
for _, item in ipairs(cache.itemList) do
|
||||
if searchQuery == "" then
|
||||
table.insert(filtered, item)
|
||||
else
|
||||
local lower = item.name:lower():gsub("minecraft:", ""):gsub("_", " ")
|
||||
if lower:find(searchQuery:lower(), 1, true) then
|
||||
table.insert(filtered, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
return filtered
|
||||
end
|
||||
|
||||
local function addZone(x1, y1, x2, y2, action, data)
|
||||
table.insert(pendingZones, {
|
||||
x1 = x1, y1 = y1, x2 = x2, y2 = y2,
|
||||
@@ -294,9 +322,63 @@ local function drawDashboard()
|
||||
local sx1, sy1, sx2, sy2 = drawButton(scanX, 5, refreshTxt, refreshFg, refreshBg, 1, 1)
|
||||
addZone(sx1, sy1, sx2, sy2, "scan", nil)
|
||||
|
||||
-- ===== Row 6: blank spacer =====
|
||||
-- ===== Search bar + Pagination (row 6) =====
|
||||
monFill(6, colors.black)
|
||||
|
||||
-- Keyboard toggle button
|
||||
local kbLabel = showKeyboard and " X " or " ? "
|
||||
local kbBg = showKeyboard and colors.red or colors.purple
|
||||
monWrite(2, 6, kbLabel, colors.white, kbBg)
|
||||
addZone(2, 6, 4, 6, "kb_toggle", nil)
|
||||
|
||||
-- Search query display
|
||||
local queryDisplay = searchQuery
|
||||
if showKeyboard then
|
||||
queryDisplay = queryDisplay .. "|"
|
||||
elseif queryDisplay == "" then
|
||||
queryDisplay = "search..."
|
||||
end
|
||||
local fieldW = math.floor(w * 0.4)
|
||||
if fieldW < 10 then fieldW = 10 end
|
||||
local displayText = queryDisplay:sub(1, fieldW)
|
||||
displayText = displayText .. string.rep("_", math.max(0, fieldW - #displayText))
|
||||
monWrite(6, 6, displayText,
|
||||
(searchQuery == "" and not showKeyboard) and colors.gray or colors.white,
|
||||
colors.black)
|
||||
addZone(6, 6, 5 + fieldW, 6, "kb_toggle", nil)
|
||||
|
||||
-- Filter items
|
||||
local filteredItems = getFilteredItems()
|
||||
|
||||
-- Pagination
|
||||
local maxRows = h - 10
|
||||
if maxRows < 1 then maxRows = 1 end
|
||||
totalPages = math.max(1, math.ceil(#filteredItems / maxRows))
|
||||
if currentPage > totalPages then currentPage = totalPages end
|
||||
if currentPage < 1 then currentPage = 1 end
|
||||
|
||||
-- Page controls (right side of row 6)
|
||||
local pageStr = string.format("Pg %d/%d", currentPage, totalPages)
|
||||
local navW = 3 + 1 + #pageStr + 1 + 3
|
||||
local navX = w - navW
|
||||
|
||||
if currentPage > 1 then
|
||||
monWrite(navX, 6, " < ", colors.white, colors.gray)
|
||||
addZone(navX, 6, navX + 2, 6, "page_prev", nil)
|
||||
else
|
||||
monWrite(navX, 6, " < ", colors.lightGray, colors.black)
|
||||
end
|
||||
|
||||
monWrite(navX + 4, 6, pageStr, colors.lightGray, colors.black)
|
||||
|
||||
local nextX = navX + 4 + #pageStr + 1
|
||||
if currentPage < totalPages then
|
||||
monWrite(nextX, 6, " > ", colors.white, colors.gray)
|
||||
addZone(nextX, 6, nextX + 2, 6, "page_next", nil)
|
||||
else
|
||||
monWrite(nextX, 6, " > ", colors.lightGray, colors.black)
|
||||
end
|
||||
|
||||
-- ===== Column headers (row 7) =====
|
||||
local row = 7
|
||||
monFill(row, colors.gray)
|
||||
@@ -307,74 +389,140 @@ local function drawDashboard()
|
||||
monWrite(w - 1, row, ">", colors.lightGray, colors.gray)
|
||||
row = row + 1
|
||||
|
||||
-- ===== Item rows =====
|
||||
local itemList = cache.itemList
|
||||
-- ===== Item rows (paginated + filtered) =====
|
||||
local maxCount = 0
|
||||
for _, item in ipairs(itemList) do
|
||||
for _, item in ipairs(filteredItems) do
|
||||
if item.total > maxCount then maxCount = item.total end
|
||||
end
|
||||
if maxCount == 0 then maxCount = 1 end
|
||||
|
||||
local maxRows = h - row - 3
|
||||
for i, item in ipairs(itemList) do
|
||||
if i > maxRows then break end
|
||||
local startIdx = (currentPage - 1) * maxRows + 1
|
||||
local endIdx = math.min(startIdx + maxRows - 1, #filteredItems)
|
||||
|
||||
local y = row
|
||||
local short = item.name:gsub("^minecraft:", ""):gsub("_", " ")
|
||||
short = short:sub(1,1):upper() .. short:sub(2)
|
||||
|
||||
local maxNameLen = w - 30
|
||||
if #short > maxNameLen then
|
||||
short = short:sub(1, maxNameLen - 2) .. ".."
|
||||
if #filteredItems == 0 then
|
||||
monFill(8, colors.black)
|
||||
monFill(9, colors.black)
|
||||
if searchQuery ~= "" then
|
||||
monCenter(9, "No items match \"" .. searchQuery .. "\"", colors.gray, colors.black)
|
||||
else
|
||||
monCenter(9, "No items in storage", colors.gray, colors.black)
|
||||
end
|
||||
row = 10
|
||||
else
|
||||
for i = startIdx, endIdx do
|
||||
local item = filteredItems[i]
|
||||
local y = row
|
||||
local short = item.name:gsub("^minecraft:", ""):gsub("_", " ")
|
||||
short = short:sub(1,1):upper() .. short:sub(2)
|
||||
|
||||
local rowBg = (i % 2 == 0) and colors.gray or colors.black
|
||||
monFill(y, rowBg)
|
||||
local maxNameLen = w - 30
|
||||
if #short > maxNameLen then
|
||||
short = short:sub(1, maxNameLen - 2) .. ".."
|
||||
end
|
||||
|
||||
monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg)
|
||||
monWrite(5, y, short, colors.white, rowBg)
|
||||
monWrite(w - 22, y, tostring(item.total), colors.yellow, rowBg)
|
||||
local rowBg = ((i - startIdx) % 2 == 0) and colors.black or colors.gray
|
||||
monFill(y, rowBg)
|
||||
|
||||
local ratio = item.total / maxCount
|
||||
local barColor = colors.lime
|
||||
if ratio < 0.25 then barColor = colors.red
|
||||
elseif ratio < 0.5 then barColor = colors.orange
|
||||
monWrite(2, y, string.format("%2d", i), colors.lightBlue, rowBg)
|
||||
monWrite(5, y, short, colors.white, rowBg)
|
||||
monWrite(w - 22, y, tostring(item.total), colors.yellow, rowBg)
|
||||
|
||||
local ratio = item.total / maxCount
|
||||
local barColor = colors.lime
|
||||
if ratio < 0.25 then barColor = colors.red
|
||||
elseif ratio < 0.5 then barColor = colors.orange
|
||||
end
|
||||
monBar(w - 14, y, 12, ratio, barColor, rowBg == colors.gray and colors.lightGray or colors.gray)
|
||||
|
||||
monWrite(w - 1, y, ">", colors.orange, rowBg)
|
||||
addZone(1, y, w, y, "order", item.name)
|
||||
|
||||
row = row + 1
|
||||
end
|
||||
monBar(w - 14, y, 12, ratio, barColor, rowBg == colors.gray and colors.lightGray or colors.gray)
|
||||
|
||||
monWrite(w - 1, y, ">", colors.orange, rowBg)
|
||||
addZone(1, y, w, y, "order", i)
|
||||
end
|
||||
|
||||
-- Fill remaining empty item rows
|
||||
local lastItemRow = h - 3
|
||||
while row <= lastItemRow do
|
||||
monFill(row, colors.black)
|
||||
row = row + 1
|
||||
end
|
||||
|
||||
-- ===== Status message =====
|
||||
local msgY = h - 2
|
||||
monFill(msgY, colors.black)
|
||||
if statusTimer > 0 and #statusMessage > 0 then
|
||||
monCenter(msgY, statusMessage, statusColor, colors.black)
|
||||
if showKeyboard then
|
||||
-- ===== Keyboard overlay (bottom 3 rows: h-2, h-1, h) =====
|
||||
local keyW = 3
|
||||
local keyGap = 1
|
||||
|
||||
local kbDefs = {
|
||||
{ keys = kbRows[1], specials = {{ label = " Bksp ", action = "kb_bksp", bg = colors.red }} },
|
||||
{ keys = kbRows[2], specials = {{ label = " Done ", action = "kb_done", bg = colors.green }} },
|
||||
{ keys = kbRows[3], specials = {
|
||||
{ label = " Space ", action = "kb_space", bg = colors.lightGray },
|
||||
{ label = " Clr ", action = "kb_clear", bg = colors.orange },
|
||||
}},
|
||||
}
|
||||
|
||||
for rowIdx, def in ipairs(kbDefs) do
|
||||
local y = h - 3 + rowIdx
|
||||
monFill(y, colors.black)
|
||||
|
||||
-- Calculate total row width for centering
|
||||
local keysW = #def.keys * keyW + math.max(0, #def.keys - 1) * keyGap
|
||||
local specialsW = 0
|
||||
for _, sp in ipairs(def.specials) do
|
||||
specialsW = specialsW + keyGap + #sp.label
|
||||
end
|
||||
local rowW = keysW + specialsW
|
||||
local x = math.floor((w - rowW) / 2) + 1
|
||||
|
||||
-- Draw letter keys
|
||||
for ki, key in ipairs(def.keys) do
|
||||
monWrite(x, y, " " .. key .. " ", colors.white, colors.gray)
|
||||
addZone(x, y, x + keyW - 1, y, "kb_key", key:lower())
|
||||
x = x + keyW
|
||||
if ki < #def.keys then x = x + keyGap end
|
||||
end
|
||||
|
||||
-- Draw special keys
|
||||
for _, sp in ipairs(def.specials) do
|
||||
x = x + keyGap
|
||||
monWrite(x, y, sp.label, colors.white, sp.bg)
|
||||
addZone(x, y, x + #sp.label - 1, y, sp.action, nil)
|
||||
x = x + #sp.label
|
||||
end
|
||||
end
|
||||
else
|
||||
-- ===== Status message (h-2) =====
|
||||
monFill(h - 2, colors.black)
|
||||
if statusTimer > 0 and #statusMessage > 0 then
|
||||
monCenter(h - 2, statusMessage, statusColor, colors.black)
|
||||
end
|
||||
|
||||
-- ===== Footer (h-1) =====
|
||||
monFill(h - 1, colors.gray)
|
||||
local footerLeft = string.format(" Total: %d items | %d types ",
|
||||
cache.grandTotal, #cache.itemList)
|
||||
monWrite(2, h - 1, footerLeft, colors.white, colors.gray)
|
||||
|
||||
if searchQuery ~= "" then
|
||||
local filterNote = string.format("| Showing %d ", #filteredItems)
|
||||
monWrite(2 + #footerLeft + 1, h - 1, filterNote, colors.yellow, colors.gray)
|
||||
end
|
||||
|
||||
local timeStr = textutils.formatTime(os.time(), true)
|
||||
monWrite(w - #timeStr - 1, h - 1, timeStr, colors.lightGray, colors.gray)
|
||||
|
||||
-- ===== Bottom accent (h) =====
|
||||
monFill(h, colors.blue)
|
||||
local bottomMsg = " Tap item to order "
|
||||
if activity.dispensing then
|
||||
bottomMsg = " DISPENSING... "
|
||||
elseif activity.sorting then
|
||||
bottomMsg = " SORTING BARREL... "
|
||||
end
|
||||
monCenter(h, bottomMsg, colors.lightBlue, colors.blue)
|
||||
end
|
||||
|
||||
-- ===== Footer =====
|
||||
local footerY = h - 1
|
||||
monFill(footerY, colors.gray)
|
||||
monWrite(2, footerY,
|
||||
string.format(" Total: %d items | %d types ", cache.grandTotal, #itemList),
|
||||
colors.white, colors.gray)
|
||||
|
||||
local timeStr = textutils.formatTime(os.time(), true)
|
||||
monWrite(w - #timeStr - 1, footerY, timeStr, colors.lightGray, colors.gray)
|
||||
|
||||
-- Bottom accent
|
||||
monFill(h, colors.blue)
|
||||
local bottomMsg = " Tap item to order "
|
||||
if activity.dispensing then
|
||||
bottomMsg = " DISPENSING... "
|
||||
elseif activity.sorting then
|
||||
bottomMsg = " SORTING BARREL... "
|
||||
end
|
||||
monCenter(h, bottomMsg, colors.lightBlue, colors.blue)
|
||||
|
||||
-- Flush to monitor
|
||||
draw.setVisible(true)
|
||||
|
||||
@@ -512,22 +660,18 @@ local function handleTouch(x, y)
|
||||
if action == "amount" then
|
||||
selectedAmount = data
|
||||
print("[UI] Amount set to " .. data)
|
||||
-- Instant visual feedback: just redraw (no peripheral calls)
|
||||
needsRedraw = true
|
||||
|
||||
elseif action == "order" then
|
||||
local idx = data
|
||||
if cache.itemList[idx] then
|
||||
local item = cache.itemList[idx]
|
||||
local short = item.name:gsub("^minecraft:", ""):gsub("_", " ")
|
||||
-- Immediate feedback
|
||||
local itemName = data
|
||||
if itemName then
|
||||
local short = itemName:gsub("^minecraft:", ""):gsub("_", " ")
|
||||
statusMessage = string.format("Ordering %s x%d...", short, selectedAmount)
|
||||
statusColor = colors.cyan
|
||||
statusTimer = 10
|
||||
activity.dispensing = true
|
||||
needsRedraw = true
|
||||
-- Actual order happens next — draw will flush before peripheral calls
|
||||
orderItem(item.name, selectedAmount)
|
||||
orderItem(itemName, selectedAmount)
|
||||
end
|
||||
|
||||
elseif action == "scan" then
|
||||
@@ -536,6 +680,57 @@ local function handleTouch(x, y)
|
||||
statusTimer = 3
|
||||
needsRedraw = true
|
||||
print("[UI] Manual refresh")
|
||||
|
||||
elseif action == "kb_toggle" then
|
||||
showKeyboard = not showKeyboard
|
||||
print("[UI] Keyboard " .. (showKeyboard and "open" or "closed"))
|
||||
needsRedraw = true
|
||||
|
||||
elseif action == "kb_key" then
|
||||
if #searchQuery < 30 then
|
||||
searchQuery = searchQuery .. data
|
||||
end
|
||||
currentPage = 1
|
||||
needsRedraw = true
|
||||
|
||||
elseif action == "kb_bksp" then
|
||||
if #searchQuery > 0 then
|
||||
searchQuery = searchQuery:sub(1, -2)
|
||||
end
|
||||
currentPage = 1
|
||||
needsRedraw = true
|
||||
|
||||
elseif action == "kb_space" then
|
||||
if #searchQuery < 30 then
|
||||
searchQuery = searchQuery .. " "
|
||||
end
|
||||
currentPage = 1
|
||||
needsRedraw = true
|
||||
|
||||
elseif action == "kb_done" then
|
||||
showKeyboard = false
|
||||
print("[UI] Keyboard closed")
|
||||
needsRedraw = true
|
||||
|
||||
elseif action == "kb_clear" then
|
||||
searchQuery = ""
|
||||
currentPage = 1
|
||||
print("[UI] Search cleared")
|
||||
needsRedraw = true
|
||||
|
||||
elseif action == "page_prev" then
|
||||
if currentPage > 1 then
|
||||
currentPage = currentPage - 1
|
||||
print("[UI] Page " .. currentPage)
|
||||
end
|
||||
needsRedraw = true
|
||||
|
||||
elseif action == "page_next" then
|
||||
if currentPage < totalPages then
|
||||
currentPage = currentPage + 1
|
||||
print("[UI] Page " .. currentPage)
|
||||
end
|
||||
needsRedraw = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user