-- AD Flow View -- Rene regler for Flows-fanens oprydning (ingen spil-API): tavlens grupper og rækkefølge, hvilke -- traktorer og bygninger der vises som standard, og hvad "Vis alle" tilføjer. ADFlowView = {} -- Naturlig sortering: tal i navne sammenlignes som tal ("Biogas 9" < "Biogas 10"). local function naturalKey(text) local parts = {} local value = string.lower(tostring(text or "")) local position = 1 while position <= #value do local digits = string.match(value, "^%d+", position) if digits ~= nil then table.insert(parts, {number = tonumber(digits)}) position = position + #digits else local chunk = string.match(value, "^%D+", position) table.insert(parts, {text = chunk}) position = position + #chunk end end return parts end function ADFlowView.naturalLess(a, b) local ka, kb = naturalKey(a), naturalKey(b) for index = 1, math.max(#ka, #kb) do local pa, pb = ka[index], kb[index] if pa == nil then return true end if pb == nil then return false end if pa.number ~= nil and pb.number ~= nil then if pa.number ~= pb.number then return pa.number < pb.number end elseif pa.number ~= nil or pb.number ~= nil then return pa.number ~= nil elseif pa.text ~= pb.text then return pa.text < pb.text end end return false end local function sortedBy(list, field) local copy = {} for _, item in ipairs(list or {}) do table.insert(copy, item) end table.sort(copy, function(a, b) if a[field] ~= b[field] then return ADFlowView.naturalLess(a[field], b[field]) end return tostring(a.id) < tostring(b.id) end) return copy end -- Tavlen: forsyning ("Ind til bygning") først, så udkørsel ("Ud fra bygning"); hver gruppe efter navn. -- {{kind, runs}, ...}; tomme grupper udelades. function ADFlowView.boardGroups(runs) local byKind = {supply = {}, outbound = {}} for _, run in ipairs(runs or {}) do table.insert(byKind[run.kind == "outbound" and "outbound" or "supply"], run) end local groups = {} for _, kind in ipairs({"supply", "outbound"}) do if #byKind[kind] > 0 then table.insert(groups, {kind = kind, runs = sortedBy(byKind[kind], "name")}) end end return groups end -- Samme rækkefølge som tavlen, som én liste (flow-vælgeren). function ADFlowView.orderedRuns(runs) local result = {} for _, group in ipairs(ADFlowView.boardGroups(runs)) do for _, run in ipairs(group.runs) do table.insert(result, run) end end return result end -- Traktorer: vehicles = {{id, label, state = "in"|"free"|"other"|"blocked"}}. Standard: flowets egne, -- så ledige der kan bruges. showAll tilføjer traktorer i andre flows og låste (vogn passer ikke). local STATE_ORDER = {"in", "free", "other", "blocked"} function ADFlowView.vehicleRows(vehicles, showAll) local byState = {} for _, entry in ipairs(vehicles or {}) do byState[entry.state] = byState[entry.state] or {} table.insert(byState[entry.state], entry) end local rows = {} for _, state in ipairs(STATE_ORDER) do if showAll or state == "in" or state == "free" then for _, entry in ipairs(sortedBy(byState[state], "label")) do table.insert(rows, entry) end end end return rows end function ADFlowView.hiddenCount(all, shown) return #(all or {}) - #(shown or {}) end -- Bygninger: buildings = {{id, name, hasMarker}}, chosen = {[id] = true}. Valgte først; bygninger uden -- markør kun hvis valgte eller showAll (de kan alligevel ikke bruges før en markør er sat). function ADFlowView.buildingRows(buildings, chosen, showAll) local shown, withoutMarker = {}, {} for _, building in ipairs(buildings or {}) do if (chosen ~= nil and chosen[building.id]) or building.hasMarker then table.insert(shown, building) elseif showAll then table.insert(withoutMarker, building) end end -- fast rækkefølge: bygningsnavn, så markørnavn (label) — et tryk må ikke flytte rækken under markøren local function byNameThenLabel(list) local copy = {} for _, item in ipairs(list) do table.insert(copy, item) end table.sort(copy, function(a, b) if a.name ~= b.name then return ADFlowView.naturalLess(a.name, b.name) end if (a.label or "") ~= (b.label or "") then return ADFlowView.naturalLess(a.label or "", b.label or "") end return tostring(a.id) < tostring(b.id) end) return copy end local rows = {} for _, list in ipairs({byNameThenLabel(shown), byNameThenLabel(withoutMarker)}) do for _, building in ipairs(list) do table.insert(rows, building) end end return rows end -- "Chicken Coop x3 + Cow Barn": ens navne slås sammen med antal, i rækkefølge efter første forekomst. function ADFlowView.joinCounted(names, separator, empty) local order, counts = {}, {} for _, name in ipairs(names or {}) do local key = tostring(name) if counts[key] == nil then counts[key] = 0 table.insert(order, key) end counts[key] = counts[key] + 1 end if #order == 0 then return empty end local parts = {} for _, key in ipairs(order) do table.insert(parts, counts[key] > 1 and string.format("%s x%d", key, counts[key]) or key) end return table.concat(parts, separator) end -- Lang tekst -> linjer på højst maxChars (ombrydes ved mellemrum; indrykning gentages på hver linje, -- så en indrykket forklaring forbliver indrykket). Ét ord længere end linjen deles ikke. function ADFlowView.wrapText(text, maxChars) text = tostring(text or "") if #text <= maxChars then return {text} end local indent = string.match(text, "^%s*") or "" local lines, line = {}, indent for word in string.gmatch(string.sub(text, #indent + 1), "%S+") do if line ~= indent and #line + 1 + #word > maxChars then table.insert(lines, line) line = indent .. word else line = line == indent and (indent .. word) or (line .. " " .. word) end end if line ~= indent then table.insert(lines, line) end return lines end -- ---- status pr. flow (Flows-listen) ---- ADFlowView.LEVEL_COLORS = { running = {0.30, 0.75, 0.35, 1}, waiting = {0.95, 0.80, 0.25, 1}, error = {0.90, 0.35, 0.30, 1}, stopped = {0.55, 0.55, 0.55, 1}, } -- s = {running, total, stopped, canStop, warning, reason, lastStartFailed} -> "running"|"waiting"|"error"|"stopped" function ADFlowView.flowStatus(s) if s.stopped then return "stopped" end if s.warning ~= nil then return "error" end if (s.running or 0) == 0 and s.lastStartFailed then return "error" end if (s.running or 0) > 0 and s.reason == nil then return "running" end if (s.running or 0) > 0 or s.canStop then return "waiting" end return "stopped" end