fix(kæde): pausede kæder kan genoptages, træet folder ud og teksten passer i cellerne
This commit is contained in:
@@ -1,31 +1,73 @@
|
|||||||
-- AD Chain View (ren logik)
|
-- AD Chain View (ren logik)
|
||||||
-- Rækkerne til fanen Kæder ud fra ADChain.last: rod -> noder rekursivt (kilde, behov pr. måned, flowstatus),
|
-- Rækkerne til fanen Kæder ud fra ADChain.last. Hver rod er foldet sammen som standard (én linje med samlet
|
||||||
-- forslag/auto-skift under afgrøden, og nederst frie marker + antal automatiske skift. Ingen spiladgang:
|
-- status + antal forslag); foldet ud vises varerne rekursivt (kilde, behov pr. måned, status, lager). Forslag
|
||||||
-- titleOf(navn) -> visningstitel, fmt(liter) -> kompakt tal, textOf(l10n-nøgle) -> tekst gives udefra.
|
-- vises altid (under roden eller under afgrøden). Pausede rødder (som modellen ikke længere ser som rødder)
|
||||||
-- Række: {kind="root"|"node"|"suggest"|"dismiss"|"auto"|"info"|"empty", indent, text, level, cells={c1..c5},
|
-- får en grå linje med [Genoptag]. Nederst frie marker + antal automatiske skift. Ingen spiladgang:
|
||||||
-- action={kind="approve"|"dismiss"|"pause"|"resume", fieldId|rootKey}, fillType}
|
-- titleOf(navn) -> titel, fmt(liter) -> kompakt tal, textOf(l10n-nøgle) -> tekst gives udefra.
|
||||||
|
-- Række: {kind="header"|"root"|"node"|"suggest"|"auto"|"info"|"empty", indent, level, cells={c1..c5}, text,
|
||||||
|
-- actions={[celle]={kind="approve"|"dismiss"|"pause"|"resume", fieldId|rootKey}}, toggle=rootKey, fillType}
|
||||||
|
|
||||||
ADChainView = {}
|
ADChainView = {}
|
||||||
ADChainView.INDENT = " "
|
ADChainView.INDENT = " "
|
||||||
-- nøgler ud over spu_tab_chains/spu_chain_empty/... som rækkerne bruger (til l10n-testen)
|
-- Tegnbudget pr. celle i flowListPrefab (gui/SmartPickupFrame.xml: textMaxWidth 192/162/62/244/92 px).
|
||||||
ADChainView.TEXT_KEYS = {"spu_chain_perMonth", "spu_chain_noField", "spu_chain_buySource", "spu_chain_cycle",
|
-- Antaget ~11,5 px pr. tegn (spuInfoText: 800 px ~ 70 tegn), fed ~12,5 px; c3 har kun tal (~10 px pr. tegn).
|
||||||
"spu_chain_fieldShort", "spu_chain_flowOk", "spu_chain_flowNone", "spu_chain_flowStopped", "spu_chain_approve",
|
ADChainView.CELL_CHARS = {15, 14, 6, 21, 8}
|
||||||
"spu_chain_dismiss", "spu_chain_switches", "spu_chain_rootOk", "spu_chain_rootError", "spu_chain_rootWaiting",
|
ADChainView.MAX_SOURCE_NAMES = 2
|
||||||
"spu_chain_rootPaused"}
|
-- "stramt": lager for en mark-afgrøde under så mange måneders behov -> gul
|
||||||
|
ADChainView.TIGHT_MONTHS = 3
|
||||||
|
-- nøgler rækkerne bruger (til l10n-testen)
|
||||||
|
ADChainView.TEXT_KEYS = {"spu_chain_empty", "spu_chain_approve", "spu_chain_dismiss", "spu_chain_auto",
|
||||||
|
"spu_chain_flowOk", "spu_chain_flowMissing", "spu_chain_flowNone", "spu_chain_flowStopped", "spu_chain_bottleneck",
|
||||||
|
"spu_chain_buy", "spu_chain_cycle", "spu_chain_fieldShort", "spu_chain_tight", "spu_chain_noField", "spu_chain_buySource",
|
||||||
|
"spu_chain_pause", "spu_chain_resume", "spu_chain_rootOk", "spu_chain_rootError", "spu_chain_rootWaiting",
|
||||||
|
"spu_chain_rootPaused", "spu_chain_rootSummary", "spu_chain_free", "spu_chain_switches", "spu_chain_gone", "spu_chain_col_item",
|
||||||
|
"spu_chain_col_source", "spu_chain_col_perMonth", "spu_chain_col_status", "spu_chain_col_stock"}
|
||||||
|
|
||||||
local FLOW_KEYS = {ok = "spu_chain_flowOk", missing = "spu_chain_flowNone", stopped = "spu_chain_flowStopped", noVehicle = "spu_chain_flowMissing"}
|
local FLOW_KEYS = {ok = "spu_chain_flowOk", missing = "spu_chain_flowNone", stopped = "spu_chain_flowStopped", noVehicle = "spu_chain_flowMissing"}
|
||||||
|
local ROOT_STATUS = {running = "spu_chain_rootOk", error = "spu_chain_rootError", waiting = "spu_chain_rootWaiting", stopped = "spu_chain_rootPaused"}
|
||||||
|
|
||||||
local function identity(value) return tostring(value) end
|
local function identity(value) return tostring(value) end
|
||||||
|
|
||||||
local function plannedFruit(field)
|
-- ---- UTF-8-sikker længde/afkortning (æøå og → er flere bytes) ----
|
||||||
if field.targetFruit ~= nil and field.targetFruit ~= "" then return field.targetFruit end
|
|
||||||
return field.fruit
|
function ADChainView.textLength(text)
|
||||||
|
local count = 0
|
||||||
|
for _ in tostring(text):gmatch("[^\128-\191]") do count = count + 1 end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
local function utf8Prefix(text, chars)
|
||||||
|
local result, count = {}, 0
|
||||||
|
for char in text:gmatch("[^\128-\191][\128-\191]*") do
|
||||||
|
if count >= chars then break end
|
||||||
|
table.insert(result, char)
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return table.concat(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Afkort til celle-budgettet med ".." til sidst.
|
||||||
|
function ADChainView.fit(text, chars)
|
||||||
|
text = tostring(text or "")
|
||||||
|
if ADChainView.textLength(text) <= chars then return text end
|
||||||
|
return utf8Prefix(text, math.max(0, chars - 2)) .. ".."
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fitCells(cells)
|
||||||
|
local result = {}
|
||||||
|
for index = 1, 5 do result[index] = ADChainView.fit(cells[index] or "", ADChainView.CELL_CHARS[index]) end
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
local function haText(ha)
|
local function haText(ha)
|
||||||
return string.format("%.0f", ha or 0)
|
return string.format("%.0f", ha or 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function plannedFruit(field)
|
||||||
|
if field.targetFruit ~= nil and field.targetFruit ~= "" then return field.targetFruit end
|
||||||
|
return field.fruit
|
||||||
|
end
|
||||||
|
|
||||||
local function edgeKey(node)
|
local function edgeKey(node)
|
||||||
return tostring(node.consumer) .. "|" .. tostring(node.fillType)
|
return tostring(node.consumer) .. "|" .. tostring(node.fillType)
|
||||||
end
|
end
|
||||||
@@ -36,7 +78,17 @@ local function shortageByFruit(last)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Alle noder under roden (dybde først).
|
local function stockOf(last, fruit)
|
||||||
|
local stock = last.data ~= nil and last.data.stock or nil
|
||||||
|
return stock ~= nil and stock[fruit] or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Mark-afgrøde uden mangel men med lager under TIGHT_MONTHS måneders behov.
|
||||||
|
local function isTight(node, last, short)
|
||||||
|
if node.source ~= "field" or node.fruit == nil or short[node.fruit] ~= nil then return false end
|
||||||
|
return (stockOf(last, node.fruit) or 0) < ADChainView.TIGHT_MONTHS * (node.perMonth or 0)
|
||||||
|
end
|
||||||
|
|
||||||
local function eachNode(nodes, fn, depth)
|
local function eachNode(nodes, fn, depth)
|
||||||
for _, node in ipairs(nodes or {}) do
|
for _, node in ipairs(nodes or {}) do
|
||||||
fn(node, depth or 1)
|
fn(node, depth or 1)
|
||||||
@@ -56,14 +108,22 @@ function ADChainView.rootLevel(root, last)
|
|||||||
if node.fruit ~= nil and short[node.fruit] ~= nil then hasShortage = true end
|
if node.fruit ~= nil and short[node.fruit] ~= nil then hasShortage = true end
|
||||||
if (node.shortPerMonth or 0) > 0 then hasShortage = true end
|
if (node.shortPerMonth or 0) > 0 then hasShortage = true end
|
||||||
local status = node.consumer ~= nil and flowStatus[edgeKey(node)] or nil
|
local status = node.consumer ~= nil and flowStatus[edgeKey(node)] or nil
|
||||||
if status ~= nil and status ~= "ok" then hasWaiting = true end
|
if (status ~= nil and status ~= "ok") or isTight(node, last, short) then hasWaiting = true end
|
||||||
end)
|
end)
|
||||||
if hasShortage then return "error" end
|
if hasShortage then return "error" end
|
||||||
if hasWaiting then return "waiting" end
|
if hasWaiting then return "waiting" end
|
||||||
return "running"
|
return "running"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Kilden som tekst: fabrikker / stalde / marker med planlagt afgrøde / køb.
|
-- Højst MAX_SOURCE_NAMES navne + " +k".
|
||||||
|
local function joinCapped(names)
|
||||||
|
local shown = {}
|
||||||
|
for index = 1, math.min(#names, ADChainView.MAX_SOURCE_NAMES) do shown[index] = names[index] end
|
||||||
|
local text = table.concat(shown, ", ")
|
||||||
|
if #names > #shown then text = text .. " +" .. tostring(#names - #shown) end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
local function sourceText(node, last, textOf)
|
local function sourceText(node, last, textOf)
|
||||||
local names = {}
|
local names = {}
|
||||||
if node.source == "production" then
|
if node.source == "production" then
|
||||||
@@ -72,9 +132,7 @@ local function sourceText(node, last, textOf)
|
|||||||
for _, herd in ipairs(node.herds or {}) do table.insert(names, tostring(herd.buildingName)) end
|
for _, herd in ipairs(node.herds or {}) do table.insert(names, tostring(herd.buildingName)) end
|
||||||
elseif node.source == "field" then
|
elseif node.source == "field" then
|
||||||
for _, field in ipairs(last.fields or {}) do
|
for _, field in ipairs(last.fields or {}) do
|
||||||
if plannedFruit(field) == node.fruit then
|
if plannedFruit(field) == node.fruit then table.insert(names, "F" .. tostring(field.id)) end
|
||||||
table.insert(names, string.format("F%s (%s ha)", tostring(field.id), haText(field.areaHa)))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
if #names == 0 then return textOf("spu_chain_noField") end
|
if #names == 0 then return textOf("spu_chain_noField") end
|
||||||
elseif node.source == "buy" then
|
elseif node.source == "buy" then
|
||||||
@@ -82,29 +140,26 @@ local function sourceText(node, last, textOf)
|
|||||||
else
|
else
|
||||||
return "-"
|
return "-"
|
||||||
end
|
end
|
||||||
return table.concat(names, ", ")
|
return joinCapped(names)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Nodens status (tekst, niveau): mangel/flaskehals/køb/kredsløb først, ellers flowets status.
|
-- Nodens status (tekst, niveau): kredsløb/flaskehals/køb/mangel (rød) > flow ikke ok (gul) > stramt lager (gul) > flow ok.
|
||||||
local function nodeStatus(node, last, titleOf, fmt, textOf, short)
|
local function nodeStatus(node, last, fmt, textOf, short)
|
||||||
local title = titleOf(node.fillType)
|
|
||||||
if node.source == "cycle" then return textOf("spu_chain_cycle"), "error" end
|
if node.source == "cycle" then return textOf("spu_chain_cycle"), "error" end
|
||||||
if (node.shortPerMonth or 0) > 0 then
|
if (node.shortPerMonth or 0) > 0 then
|
||||||
if node.source == "production" then
|
local key = node.source == "production" and "spu_chain_bottleneck" or "spu_chain_buy"
|
||||||
return string.format(textOf("spu_chain_bottleneck"), title, fmt(node.shortPerMonth)), "error"
|
return string.format(textOf(key), fmt(node.shortPerMonth)), "error"
|
||||||
end
|
|
||||||
return string.format(textOf("spu_chain_buy"), title, fmt(node.shortPerMonth)), "error"
|
|
||||||
end
|
end
|
||||||
local shortage = node.fruit ~= nil and short[node.fruit] or nil
|
local shortage = node.fruit ~= nil and short[node.fruit] or nil
|
||||||
if shortage ~= nil then
|
if shortage ~= nil then return string.format(textOf("spu_chain_fieldShort"), haText(shortage.missingHa)), "error" end
|
||||||
return string.format(textOf("spu_chain_fieldShort"), haText(shortage.missingHa)), "error"
|
|
||||||
end
|
|
||||||
local status = node.consumer ~= nil and (last.flowStatus or {})[edgeKey(node)] or nil
|
local status = node.consumer ~= nil and (last.flowStatus or {})[edgeKey(node)] or nil
|
||||||
if status == nil then return "", "running" end
|
if status ~= nil and status ~= "ok" then return textOf(FLOW_KEYS[status] or "spu_chain_flowNone"), "waiting" end
|
||||||
return textOf(FLOW_KEYS[status] or "spu_chain_flowNone"), status == "ok" and "running" or "waiting"
|
if isTight(node, last, short) then return textOf("spu_chain_tight"), "waiting" end
|
||||||
|
if status == "ok" then return textOf("spu_chain_flowOk"), "running" end
|
||||||
|
return "", "running"
|
||||||
end
|
end
|
||||||
|
|
||||||
local function joinCells(cells)
|
local function joinText(cells)
|
||||||
local parts = {}
|
local parts = {}
|
||||||
for _, cell in ipairs(cells) do
|
for _, cell in ipairs(cells) do
|
||||||
if cell ~= nil and cell ~= "" then table.insert(parts, cell) end
|
if cell ~= nil and cell ~= "" then table.insert(parts, cell) end
|
||||||
@@ -112,51 +167,98 @@ local function joinCells(cells)
|
|||||||
return table.concat(parts, " · ")
|
return table.concat(parts, " · ")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function makeRow(kind, indent, level, cells, action)
|
local function makeRow(kind, indent, level, cells, actions, toggle)
|
||||||
return {kind = kind, indent = indent, level = level, cells = cells, action = action, text = joinCells(cells)}
|
local fitted = fitCells(cells)
|
||||||
|
return {kind = kind, indent = indent, level = level, cells = fitted, actions = actions or {}, toggle = toggle, text = joinText(fitted)}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Forslag (godkend + afvis) og auto-skift for afgrøden, lige under dens node.
|
local function suggestRow(action, indent, titleOf, textOf)
|
||||||
local function actionRows(rows, fruit, indent, last, titleOf, textOf, shown)
|
|
||||||
if fruit == nil or shown[fruit] then return end
|
|
||||||
shown[fruit] = true
|
|
||||||
local pad = string.rep(ADChainView.INDENT, indent)
|
local pad = string.rep(ADChainView.INDENT, indent)
|
||||||
for _, action in ipairs(last.actions or {}) do
|
local from = (action.from ~= nil and action.from ~= "") and titleOf(action.from) or "-"
|
||||||
if action.to == fruit then
|
local cells = {pad .. "F" .. tostring(action.fieldId) .. " " .. from, "→ " .. titleOf(action.to), haText(action.hectares) .. " ha"}
|
||||||
local fromTitle = (action.from ~= nil and action.from ~= "") and titleOf(action.from) or "-"
|
if action.mode == "suggest" then
|
||||||
if action.mode == "suggest" then
|
cells[4], cells[5] = textOf("spu_chain_approve"), textOf("spu_chain_dismiss")
|
||||||
local text = string.format(textOf("spu_chain_suggest"), action.fieldId, fromTitle, titleOf(action.to), action.hectares or 0)
|
return makeRow("suggest", indent, "waiting", cells,
|
||||||
table.insert(rows, makeRow("suggest", indent, "waiting", {pad, "", "", text, textOf("spu_chain_approve")},
|
{[4] = {kind = "approve", fieldId = action.fieldId}, [5] = {kind = "dismiss", fieldId = action.fieldId}})
|
||||||
{kind = "approve", fieldId = action.fieldId}))
|
|
||||||
table.insert(rows, makeRow("dismiss", indent, "waiting", {pad, "", "", "", textOf("spu_chain_dismiss")},
|
|
||||||
{kind = "dismiss", fieldId = action.fieldId}))
|
|
||||||
else
|
|
||||||
local text = string.format(textOf("spu_chain_auto"), action.fieldId, titleOf(action.to))
|
|
||||||
table.insert(rows, makeRow("auto", indent, "running", {pad, "", "", text, ""}, nil))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
cells[4] = textOf("spu_chain_auto")
|
||||||
|
return makeRow("auto", indent, "running", cells)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ROOT_STATUS = {running = "spu_chain_rootOk", error = "spu_chain_rootError", waiting = "spu_chain_rootWaiting", stopped = "spu_chain_rootPaused"}
|
-- Handlinger for de afgrøder (i rækkefølge) der hentes fra mark under roden.
|
||||||
|
local function rootFruits(root)
|
||||||
|
local fruits, seen = {}, {}
|
||||||
|
eachNode(root.children, function(node)
|
||||||
|
if node.source == "field" and node.fruit ~= nil and not seen[node.fruit] then
|
||||||
|
seen[node.fruit] = true
|
||||||
|
table.insert(fruits, node.fruit)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return fruits
|
||||||
|
end
|
||||||
|
|
||||||
local function rootRows(rows, root, last, titleOf, fmt, textOf, short)
|
local function actionsFor(last, fruit, modes)
|
||||||
|
local list = {}
|
||||||
|
for _, action in ipairs(last.actions or {}) do
|
||||||
|
if action.to == fruit and modes[action.mode] then table.insert(list, action) end
|
||||||
|
end
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rootRows(rows, root, last, ctx, isExpanded)
|
||||||
local level = ADChainView.rootLevel(root, last)
|
local level = ADChainView.rootLevel(root, last)
|
||||||
local paused = (last.paused or {})[root.key] == true
|
local paused = (last.paused or {})[root.key] == true
|
||||||
table.insert(rows, makeRow("root", 0, level, {titleOf(root.name), tostring(root.buildingName or ""), "",
|
local suggestions = {}
|
||||||
textOf(ROOT_STATUS[level]), textOf(paused and "spu_chain_resume" or "spu_chain_pause")},
|
for _, fruit in ipairs(rootFruits(root)) do
|
||||||
{kind = paused and "resume" or "pause", rootKey = root.key}))
|
for _, action in ipairs(actionsFor(last, fruit, {suggest = true})) do table.insert(suggestions, action) end
|
||||||
|
end
|
||||||
|
local status = ctx.textOf(ROOT_STATUS[level])
|
||||||
|
if #suggestions > 0 then status = string.format(ctx.textOf("spu_chain_rootSummary"), status, #suggestions) end
|
||||||
|
local marker = (#(root.children or {}) > 0) and (isExpanded and "– " or "+ ") or ""
|
||||||
|
table.insert(rows, makeRow("root", 0, level, {marker .. ctx.titleOf(root.name), tostring(root.buildingName or ""), "", status,
|
||||||
|
ctx.textOf(paused and "spu_chain_resume" or "spu_chain_pause")},
|
||||||
|
{[5] = {kind = paused and "resume" or "pause", rootKey = root.key}}, root.key))
|
||||||
|
if not isExpanded then
|
||||||
|
for _, action in ipairs(suggestions) do table.insert(rows, suggestRow(action, 1, ctx.titleOf, ctx.textOf)) end
|
||||||
|
return
|
||||||
|
end
|
||||||
local shown = {}
|
local shown = {}
|
||||||
eachNode(root.children, function(node, depth)
|
eachNode(root.children, function(node, depth)
|
||||||
local status, nodeLevel = nodeStatus(node, last, titleOf, fmt, textOf, short)
|
local text, nodeLevel = nodeStatus(node, last, ctx.fmt, ctx.textOf, ctx.short)
|
||||||
local row = makeRow("node", depth, nodeLevel, {string.rep(ADChainView.INDENT, depth) .. titleOf(node.fillType),
|
local stock = node.source == "field" and stockOf(last, node.fruit) or nil
|
||||||
sourceText(node, last, textOf), string.format(textOf("spu_chain_perMonth"), fmt(node.perMonth or 0)), status, ""}, nil)
|
local row = makeRow("node", depth, nodeLevel, {string.rep(ADChainView.INDENT, depth) .. ctx.titleOf(node.fillType),
|
||||||
|
sourceText(node, last, ctx.textOf), ctx.fmt(node.perMonth or 0), text, stock ~= nil and ctx.fmt(stock) or ""})
|
||||||
row.fillType = node.fillType
|
row.fillType = node.fillType
|
||||||
table.insert(rows, row)
|
table.insert(rows, row)
|
||||||
if node.source == "field" then actionRows(rows, node.fruit, depth + 1, last, titleOf, textOf, shown) end
|
if node.source == "field" and node.fruit ~= nil and not shown[node.fruit] then
|
||||||
|
shown[node.fruit] = true
|
||||||
|
for _, action in ipairs(actionsFor(last, node.fruit, {suggest = true, auto = true})) do
|
||||||
|
table.insert(rows, suggestRow(action, depth + 1, ctx.titleOf, ctx.textOf))
|
||||||
|
end
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Pausede rødder: recipesWithPaused slukker dem, så modellen ser dem ikke som rødder -> grå linje med [Genoptag].
|
||||||
|
local function pausedRows(rows, last, ctx)
|
||||||
|
local present, keys = {}, {}
|
||||||
|
for _, root in ipairs(last.chain ~= nil and last.chain.roots or {}) do present[root.key] = true end
|
||||||
|
for key in pairs(last.paused or {}) do
|
||||||
|
if not present[key] then table.insert(keys, key) end
|
||||||
|
end
|
||||||
|
table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
|
||||||
|
for _, key in ipairs(keys) do
|
||||||
|
local recipe
|
||||||
|
for _, candidate in ipairs(last.data ~= nil and last.data.recipes or {}) do
|
||||||
|
if candidate.key == key then recipe = candidate end
|
||||||
|
end
|
||||||
|
local name = recipe ~= nil and recipe.name ~= nil and ctx.titleOf(recipe.name) or tostring(key)
|
||||||
|
table.insert(rows, makeRow("root", 0, "stopped", {name, recipe ~= nil and tostring(recipe.buildingName or "") or "", "",
|
||||||
|
ctx.textOf("spu_chain_rootPaused"), ctx.textOf("spu_chain_resume")}, {[5] = {kind = "resume", rootKey = key}}))
|
||||||
|
end
|
||||||
|
return #keys
|
||||||
|
end
|
||||||
|
|
||||||
local function footerRows(rows, last, textOf)
|
local function footerRows(rows, last, textOf)
|
||||||
local free = {}
|
local free = {}
|
||||||
for _, field in ipairs(last.fields or {}) do
|
for _, field in ipairs(last.fields or {}) do
|
||||||
@@ -170,18 +272,25 @@ local function footerRows(rows, last, textOf)
|
|||||||
table.insert(rows, {kind = "info", indent = 0, text = string.format(textOf("spu_chain_switches"), last.switchCount or 0)})
|
table.insert(rows, {kind = "info", indent = 0, text = string.format(textOf("spu_chain_switches"), last.switchCount or 0)})
|
||||||
end
|
end
|
||||||
|
|
||||||
function ADChainView.rows(last, titleOf, fmt, textOf)
|
-- expanded = {[rodnøgle] = true} for udfoldede rødder (alle andre er foldet sammen).
|
||||||
textOf, fmt = textOf or identity, fmt or identity
|
function ADChainView.rows(last, titleOf, fmt, textOf, expanded)
|
||||||
|
textOf, fmt, expanded = textOf or identity, fmt or identity, expanded or {}
|
||||||
local titles = last ~= nil and last.titles or {}
|
local titles = last ~= nil and last.titles or {}
|
||||||
titleOf = titleOf or function(name) return (titles or {})[name] or tostring(name) end
|
titleOf = titleOf or function(name) return (titles or {})[name] or tostring(name) end
|
||||||
local rows = {}
|
local rows = {}
|
||||||
local roots = last ~= nil and last.chain ~= nil and last.chain.roots or {}
|
local roots = last ~= nil and last.chain ~= nil and last.chain.roots or {}
|
||||||
if #roots == 0 then
|
local hasPaused = last ~= nil and next(last.paused or {}) ~= nil
|
||||||
|
if #roots == 0 and not hasPaused then
|
||||||
table.insert(rows, {kind = "empty", indent = 0, text = textOf("spu_chain_empty")})
|
table.insert(rows, {kind = "empty", indent = 0, text = textOf("spu_chain_empty")})
|
||||||
end
|
end
|
||||||
if last == nil then return rows end
|
if last == nil then return rows end
|
||||||
local short = shortageByFruit(last)
|
if #roots > 0 or hasPaused then
|
||||||
for _, root in ipairs(roots) do rootRows(rows, root, last, titleOf, fmt, textOf, short) end
|
table.insert(rows, makeRow("header", 0, nil, {textOf("spu_chain_col_item"), textOf("spu_chain_col_source"),
|
||||||
|
textOf("spu_chain_col_perMonth"), textOf("spu_chain_col_status"), textOf("spu_chain_col_stock")}))
|
||||||
|
end
|
||||||
|
local ctx = {titleOf = titleOf, fmt = fmt, textOf = textOf, short = shortageByFruit(last)}
|
||||||
|
for _, root in ipairs(roots) do rootRows(rows, root, last, ctx, expanded[root.key] == true) end
|
||||||
|
pausedRows(rows, last, ctx)
|
||||||
footerRows(rows, last, textOf)
|
footerRows(rows, last, textOf)
|
||||||
return rows
|
return rows
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,21 +26,46 @@ function SmartPickupFrame:chainTitle(last, name)
|
|||||||
return title or self:fruitTitle(name)
|
return title or self:fruitTitle(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Klik på en kæde-knap: udfør, evaluér straks (ny visning) og genopbyg.
|
-- Klik på en kæde-knap: udfør, evaluér straks (ny visning) og genopbyg. Et forslag der ikke længere findes
|
||||||
|
-- (approve/dismiss -> false) meldes i statuslinjen som i "Kræver handling".
|
||||||
function SmartPickupFrame:onChainAction(action)
|
function SmartPickupFrame:onChainAction(action)
|
||||||
|
local ok = true
|
||||||
if action.kind == "approve" then
|
if action.kind == "approve" then
|
||||||
ADChain.approve(action.fieldId)
|
ok = ADChain.approve(action.fieldId)
|
||||||
elseif action.kind == "dismiss" then
|
elseif action.kind == "dismiss" then
|
||||||
ADChain.dismiss(action.fieldId)
|
ok = ADChain.dismiss(action.fieldId)
|
||||||
elseif action.kind == "pause" or action.kind == "resume" then
|
elseif action.kind == "pause" or action.kind == "resume" then
|
||||||
ADChain.setPaused(action.rootKey, action.kind == "pause")
|
ADChain.setPaused(action.rootKey, action.kind == "pause")
|
||||||
end
|
end
|
||||||
|
if ok == false then
|
||||||
|
local text = self.i18n:getText("spu_chain_gone")
|
||||||
|
self.statusOverride, self.statusOverrideTicks = text, SmartPickupFrame.STATUS_OVERRIDE_TICKS
|
||||||
|
if ADFieldJobs ~= nil and ADFieldJobs.notify ~= nil then ADFieldJobs.notify(text, false) end
|
||||||
|
end
|
||||||
local adEnv = self.getAdEnv()
|
local adEnv = self.getAdEnv()
|
||||||
if adEnv ~= nil then self:chainLast(adEnv, true) end
|
if adEnv ~= nil then self:chainLast(adEnv, true) end
|
||||||
self:requestRebuild()
|
self:requestRebuild()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Række med prik + c1..c5 (flowListPrefab); knappen er c5 (klik i c5 = handling, som Flows-listen).
|
-- Klik på rodens linje uden for knappen: fold ud/sammen (huskes kun mens spillet kører).
|
||||||
|
function SmartPickupFrame:toggleChainRoot(rootKey)
|
||||||
|
self.chainExpanded = self.chainExpanded or {}
|
||||||
|
self.chainExpanded[rootKey] = not self.chainExpanded[rootKey] or nil
|
||||||
|
self:requestRebuild()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hvilken celle blev der klikket i? Højeste celle hvis venstre kant ligger til venstre for klikket.
|
||||||
|
local function clickedCell(frame, cells)
|
||||||
|
if frame.lastClickX == nil then return nil end
|
||||||
|
for index = #cells, 1, -1 do
|
||||||
|
local cell = cells[index]
|
||||||
|
if cell ~= nil and cell.absPosition ~= nil and frame.lastClickX >= cell.absPosition[1] then return index end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Række med prik + c1..c5 (flowListPrefab). Knapper er celler (entry.actions[celle], som [Start]/[Stop] i
|
||||||
|
-- Flows-listen); et klik andetsteds på en rodlinje folder den ud/sammen.
|
||||||
function SmartPickupFrame:addChainRow(entry)
|
function SmartPickupFrame:addChainRow(entry)
|
||||||
local unpackFn = table.unpack or unpack
|
local unpackFn = table.unpack or unpack
|
||||||
local row = self:addRow(self.flowListPrefab, {kind = "chainRow"})
|
local row = self:addRow(self.flowListPrefab, {kind = "chainRow"})
|
||||||
@@ -48,21 +73,22 @@ function SmartPickupFrame:addChainRow(entry)
|
|||||||
for index = 1, 5 do cells[index] = row:getDescendantByName("c" .. index) end
|
for index = 1, 5 do cells[index] = row:getDescendantByName("c" .. index) end
|
||||||
SmartPickupFrame.setCells(cells, entry.cells)
|
SmartPickupFrame.setCells(cells, entry.cells)
|
||||||
local dot = row:getDescendantByName("dot")
|
local dot = row:getDescendantByName("dot")
|
||||||
local color = ADFlowView.LEVEL_COLORS[entry.level or "running"]
|
local color = entry.level ~= nil and ADFlowView.LEVEL_COLORS[entry.level] or nil
|
||||||
if dot ~= nil and dot.setImageColor ~= nil and color ~= nil then
|
if dot ~= nil and dot.setImageColor ~= nil and color ~= nil then dot:setImageColor(nil, unpackFn(color)) end
|
||||||
dot:setImageColor(nil, unpackFn(color))
|
-- kun rod og varer har en status-prik (overskrift, forslag og auto-skift har ingen)
|
||||||
-- rækker uden egen status (forslag/afvis/auto) får ingen prik — kun rod og noder har status
|
if dot ~= nil and dot.setVisible ~= nil and entry.kind ~= "root" and entry.kind ~= "node" then dot:setVisible(false) end
|
||||||
if entry.kind ~= "root" and entry.kind ~= "node" and dot.setVisible ~= nil then dot:setVisible(false) end
|
if cells[4] ~= nil and cells[4].setTextColor ~= nil and color ~= nil and entry.level ~= "running" and entry.kind ~= "suggest" then
|
||||||
end
|
|
||||||
if cells[4] ~= nil and cells[4].setTextColor ~= nil and color ~= nil and entry.level ~= "running" then
|
|
||||||
cells[4]:setTextColor(unpackFn(color))
|
cells[4]:setTextColor(unpackFn(color))
|
||||||
end
|
end
|
||||||
if entry.action ~= nil then
|
local actions, toggle = entry.actions or {}, entry.toggle
|
||||||
local action, actionCell = entry.action, cells[5]
|
if next(actions) ~= nil or toggle ~= nil then
|
||||||
self:makeRowClickable(row, function(frame)
|
self:makeRowClickable(row, function(frame)
|
||||||
local onButton = frame.lastClickX == nil or actionCell == nil or actionCell.absPosition == nil
|
local action = actions[clickedCell(frame, cells) or 0]
|
||||||
or frame.lastClickX >= actionCell.absPosition[1]
|
if action ~= nil then
|
||||||
if onButton then frame:onChainAction(action) end
|
frame:onChainAction(action)
|
||||||
|
elseif toggle ~= nil then
|
||||||
|
frame:toggleChainRoot(toggle)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return row
|
return row
|
||||||
@@ -72,7 +98,7 @@ function SmartPickupFrame:fillChainRows(adEnv)
|
|||||||
local i18n = self.i18n
|
local i18n = self.i18n
|
||||||
local last = self:chainLast(adEnv, false)
|
local last = self:chainLast(adEnv, false)
|
||||||
local rows = ADChainView.rows(last, function(name) return self:chainTitle(last, name) end,
|
local rows = ADChainView.rows(last, function(name) return self:chainTitle(last, name) end,
|
||||||
SmartPickupFrame.formatCompact, function(key) return i18n:getText(key) end)
|
SmartPickupFrame.formatCompact, function(key) return i18n:getText(key) end, self.chainExpanded or {})
|
||||||
for _, entry in ipairs(rows) do
|
for _, entry in ipairs(rows) do
|
||||||
if entry.kind == "info" or entry.kind == "empty" then
|
if entry.kind == "info" or entry.kind == "empty" then
|
||||||
self:addTextRow(entry.text)
|
self:addTextRow(entry.text)
|
||||||
|
|||||||
@@ -473,6 +473,7 @@ function SmartPickupFrame:rebuild()
|
|||||||
if adEnv ~= nil then
|
if adEnv ~= nil then
|
||||||
self:fillChainRows(adEnv)
|
self:fillChainRows(adEnv)
|
||||||
end
|
end
|
||||||
|
self.statusRow = self:addTextRow(self.statusOverride or "")
|
||||||
self.monthRebuildTicks = SmartPickupFrame.CHAIN_REBUILD_TICKS
|
self.monthRebuildTicks = SmartPickupFrame.CHAIN_REBUILD_TICKS
|
||||||
self:colorRows()
|
self:colorRows()
|
||||||
self.boxLayout:invalidateLayout()
|
self.boxLayout:invalidateLayout()
|
||||||
@@ -653,6 +654,12 @@ function SmartPickupFrame:refreshLive()
|
|||||||
end
|
end
|
||||||
if self.currentTab == SmartPickupFrame.TAB_MONTH or self.currentTab == SmartPickupFrame.TAB_FIELDSILOS
|
if self.currentTab == SmartPickupFrame.TAB_MONTH or self.currentTab == SmartPickupFrame.TAB_FIELDSILOS
|
||||||
or self.currentTab == SmartPickupFrame.TAB_CHAINS then
|
or self.currentTab == SmartPickupFrame.TAB_CHAINS then
|
||||||
|
if self.currentTab == SmartPickupFrame.TAB_CHAINS then
|
||||||
|
-- "forslaget gælder ikke længere" står STATUS_OVERRIDE_TICKS sekunder
|
||||||
|
self.statusOverrideTicks = (self.statusOverrideTicks or 0) - 1
|
||||||
|
if self.statusOverrideTicks <= 0 then self.statusOverride = nil end
|
||||||
|
self:setStatusText(self.statusOverride or "")
|
||||||
|
end
|
||||||
self.monthRebuildTicks = (self.monthRebuildTicks or 0) - 1
|
self.monthRebuildTicks = (self.monthRebuildTicks or 0) - 1
|
||||||
if self.monthRebuildTicks <= 0 then self:requestRebuild() end
|
if self.monthRebuildTicks <= 0 then self:requestRebuild() end
|
||||||
self:refreshButtons()
|
self:refreshButtons()
|
||||||
|
|||||||
@@ -577,27 +577,33 @@
|
|||||||
<text name="spu_item_share" text="%d %% af varen"/>
|
<text name="spu_item_share" text="%d %% af varen"/>
|
||||||
<text name="spu_tab_chains" text="Kæder"/>
|
<text name="spu_tab_chains" text="Kæder"/>
|
||||||
<text name="spu_chain_empty" text="Ingen kæder: slå et produkt til i en fabrik"/>
|
<text name="spu_chain_empty" text="Ingen kæder: slå et produkt til i en fabrik"/>
|
||||||
<text name="spu_chain_suggest" text="Forslag: F%d %s → %s (%.1f ha)"/>
|
<text name="spu_chain_col_item" text="Vare"/>
|
||||||
<text name="spu_chain_approve" text="[Godkend]"/>
|
<text name="spu_chain_col_source" text="Kilde"/>
|
||||||
|
<text name="spu_chain_col_perMonth" text="l/md"/>
|
||||||
|
<text name="spu_chain_col_status" text="Status"/>
|
||||||
|
<text name="spu_chain_col_stock" text="Lager"/>
|
||||||
|
<text name="spu_chain_approve" text="Forslag [Godkend]"/>
|
||||||
<text name="spu_chain_dismiss" text="[Afvis]"/>
|
<text name="spu_chain_dismiss" text="[Afvis]"/>
|
||||||
<text name="spu_chain_auto" text="F%d skiftet til %s (fri til kæden)"/>
|
<text name="spu_chain_auto" text="skiftet (fri mark)"/>
|
||||||
|
<text name="spu_chain_gone" text="Forslaget gælder ikke længere"/>
|
||||||
<text name="spu_chain_flowOk" text="flow kører"/>
|
<text name="spu_chain_flowOk" text="flow kører"/>
|
||||||
<text name="spu_chain_flowMissing" text="flow mangler traktor/vogn"/>
|
<text name="spu_chain_flowMissing" text="mangler traktor/vogn"/>
|
||||||
<text name="spu_chain_flowNone" text="intet flow endnu"/>
|
<text name="spu_chain_flowNone" text="intet flow"/>
|
||||||
<text name="spu_chain_flowStopped" text="flow stoppet"/>
|
<text name="spu_chain_flowStopped" text="flow stoppet"/>
|
||||||
<text name="spu_chain_bottleneck" text="flaskehals: %s mangler %s/md"/>
|
<text name="spu_chain_bottleneck" text="flaskehals: -%s/md"/>
|
||||||
<text name="spu_chain_buy" text="%s skal købes (%s/md)"/>
|
<text name="spu_chain_buy" text="skal købes: %s/md"/>
|
||||||
<text name="spu_chain_cycle" text="kredsløb i kæden"/>
|
<text name="spu_chain_cycle" text="kredsløb"/>
|
||||||
<text name="spu_chain_fieldShort" text="mangler %s ha"/>
|
<text name="spu_chain_fieldShort" text="mangler %s ha"/>
|
||||||
<text name="spu_chain_perMonth" text="%s/md"/>
|
<text name="spu_chain_tight" text="lager < 3 md"/>
|
||||||
<text name="spu_chain_noField" text="ingen mark"/>
|
<text name="spu_chain_noField" text="ingen mark"/>
|
||||||
<text name="spu_chain_buySource" text="køb"/>
|
<text name="spu_chain_buySource" text="køb"/>
|
||||||
<text name="spu_chain_pause" text="[Pause]"/>
|
<text name="spu_chain_pause" text="[Pause]"/>
|
||||||
<text name="spu_chain_resume" text="[Genoptag]"/>
|
<text name="spu_chain_resume" text="[Start]"/>
|
||||||
<text name="spu_chain_rootOk" text="dækket"/>
|
<text name="spu_chain_rootOk" text="dækket"/>
|
||||||
<text name="spu_chain_rootError" text="mangler noget"/>
|
<text name="spu_chain_rootError" text="mangel"/>
|
||||||
<text name="spu_chain_rootWaiting" text="venter på flow"/>
|
<text name="spu_chain_rootWaiting" text="venter"/>
|
||||||
<text name="spu_chain_rootPaused" text="på pause"/>
|
<text name="spu_chain_rootPaused" text="på pause"/>
|
||||||
|
<text name="spu_chain_rootSummary" text="%s · %d forslag"/>
|
||||||
<text name="spu_chain_free" text="Marker fri til kæden: %s"/>
|
<text name="spu_chain_free" text="Marker fri til kæden: %s"/>
|
||||||
<text name="spu_chain_switches" text="Automatiske skift siden indlæsning: %d"/>
|
<text name="spu_chain_switches" text="Automatiske skift siden indlæsning: %d"/>
|
||||||
<text name="spu_attention_chainSuggest" text="Kæde: F%d %s → %s"/>
|
<text name="spu_attention_chainSuggest" text="Kæde: F%d %s → %s"/>
|
||||||
|
|||||||
@@ -577,27 +577,33 @@
|
|||||||
<text name="spu_item_share" text="%d %% of the good"/>
|
<text name="spu_item_share" text="%d %% of the good"/>
|
||||||
<text name="spu_tab_chains" text="Chains"/>
|
<text name="spu_tab_chains" text="Chains"/>
|
||||||
<text name="spu_chain_empty" text="No chains: enable a product in a factory"/>
|
<text name="spu_chain_empty" text="No chains: enable a product in a factory"/>
|
||||||
<text name="spu_chain_suggest" text="Suggestion: F%d %s → %s (%.1f ha)"/>
|
<text name="spu_chain_col_item" text="Goods"/>
|
||||||
<text name="spu_chain_approve" text="[Approve]"/>
|
<text name="spu_chain_col_source" text="Source"/>
|
||||||
<text name="spu_chain_dismiss" text="[Dismiss]"/>
|
<text name="spu_chain_col_perMonth" text="l/mo"/>
|
||||||
<text name="spu_chain_auto" text="F%d switched to %s (free for the chain)"/>
|
<text name="spu_chain_col_status" text="Status"/>
|
||||||
|
<text name="spu_chain_col_stock" text="Stock"/>
|
||||||
|
<text name="spu_chain_approve" text="Suggest [Approve]"/>
|
||||||
|
<text name="spu_chain_dismiss" text="[Reject]"/>
|
||||||
|
<text name="spu_chain_auto" text="switched (free field)"/>
|
||||||
|
<text name="spu_chain_gone" text="The suggestion no longer applies"/>
|
||||||
<text name="spu_chain_flowOk" text="flow running"/>
|
<text name="spu_chain_flowOk" text="flow running"/>
|
||||||
<text name="spu_chain_flowMissing" text="flow lacks tractor/trailer"/>
|
<text name="spu_chain_flowMissing" text="no tractor/trailer"/>
|
||||||
<text name="spu_chain_flowNone" text="no flow yet"/>
|
<text name="spu_chain_flowNone" text="no flow"/>
|
||||||
<text name="spu_chain_flowStopped" text="flow stopped"/>
|
<text name="spu_chain_flowStopped" text="flow stopped"/>
|
||||||
<text name="spu_chain_bottleneck" text="bottleneck: %s short %s/mo"/>
|
<text name="spu_chain_bottleneck" text="bottleneck: -%s/mo"/>
|
||||||
<text name="spu_chain_buy" text="%s must be bought (%s/mo)"/>
|
<text name="spu_chain_buy" text="must buy: %s/mo"/>
|
||||||
<text name="spu_chain_cycle" text="loop in the chain"/>
|
<text name="spu_chain_cycle" text="loop"/>
|
||||||
<text name="spu_chain_fieldShort" text="short %s ha"/>
|
<text name="spu_chain_fieldShort" text="short %s ha"/>
|
||||||
<text name="spu_chain_perMonth" text="%s/mo"/>
|
<text name="spu_chain_tight" text="stock < 3 mo"/>
|
||||||
<text name="spu_chain_noField" text="no field"/>
|
<text name="spu_chain_noField" text="no field"/>
|
||||||
<text name="spu_chain_buySource" text="buy"/>
|
<text name="spu_chain_buySource" text="buy"/>
|
||||||
<text name="spu_chain_pause" text="[Pause]"/>
|
<text name="spu_chain_pause" text="[Pause]"/>
|
||||||
<text name="spu_chain_resume" text="[Resume]"/>
|
<text name="spu_chain_resume" text="[Resume]"/>
|
||||||
<text name="spu_chain_rootOk" text="covered"/>
|
<text name="spu_chain_rootOk" text="covered"/>
|
||||||
<text name="spu_chain_rootError" text="something is short"/>
|
<text name="spu_chain_rootError" text="short"/>
|
||||||
<text name="spu_chain_rootWaiting" text="waiting for flow"/>
|
<text name="spu_chain_rootWaiting" text="waiting"/>
|
||||||
<text name="spu_chain_rootPaused" text="paused"/>
|
<text name="spu_chain_rootPaused" text="paused"/>
|
||||||
|
<text name="spu_chain_rootSummary" text="%s · %d sugg."/>
|
||||||
<text name="spu_chain_free" text="Fields free for the chain: %s"/>
|
<text name="spu_chain_free" text="Fields free for the chain: %s"/>
|
||||||
<text name="spu_chain_switches" text="Automatic switches since load: %d"/>
|
<text name="spu_chain_switches" text="Automatic switches since load: %d"/>
|
||||||
<text name="spu_attention_chainSuggest" text="Chain: F%d %s → %s"/>
|
<text name="spu_attention_chainSuggest" text="Chain: F%d %s → %s"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user