-- AD Guide -- Fælles guide-motor (ren Lua, ingen spil-API) for "Opsæt mark" og "Opsæt flow": ét trin ad gangen, -- trin der ikke er relevante springes over, tælling "Trin 3 af 7" over de synlige trin, og [Ret] der -- åbner ét enkelt trin. Motoren kender ikke marker eller flows; en trin-definition giver rækkefølgen og -- hvornår et trin vises: -- def = {order = {stepId, ...}, isVisible = function(stepId, guide) -> bool} -- Guiden er en tabel {def, draft, context, step, single, returnTo, snapshot}; alle funktioner returnerer -- en ny guide (kladden kopieres), så [Annullér] aldrig har ændret noget. ADGuide = {} -- Kopi af rene data (tabeller uden cykler); funktioner og tal/tekster deles. function ADGuide.deepCopy(value) if type(value) ~= "table" then return value end local copy = {} for key, item in pairs(value) do copy[key] = ADGuide.deepCopy(item) end return copy end -- Ny liste med item tilføjet (on) eller fjernet; aldrig dubletter. function ADGuide.toggleItem(list, item, on) local result, found = {}, false for _, value in ipairs(list or {}) do if value == item then found = true if on then table.insert(result, value) end else table.insert(result, value) end end if on and not found then table.insert(result, item) end return result end local function clone(guide, changes) local copy = {} for key, value in pairs(guide) do copy[key] = value end for key, value in pairs(changes or {}) do if value == false and (key == "snapshot") then copy[key] = nil else copy[key] = value end end return copy end local function isVisible(guide, step) return guide.def.isVisible == nil or guide.def.isVisible(step, guide) == true end function ADGuide.visibleSteps(guide) local steps = {} for _, step in ipairs(guide.def.order) do if isVisible(guide, step) then table.insert(steps, step) end end return steps end local function orderIndex(guide, step) for index, id in ipairs(guide.def.order) do if id == step then return index end end return 0 end -- Første synlige trin efter (dir = 1) eller før (dir = -1) det aktuelle i rækkefølgen. local function neighbour(guide, dir) local order = guide.def.order local index = orderIndex(guide, guide.step) + dir while index >= 1 and index <= #order do if isVisible(guide, order[index]) then return order[index] end index = index + dir end return nil end -- options = {step = id, single = bool, returnTo = "close"|"summary"} function ADGuide.new(def, draft, context, options) options = options or {} local guide = {def = def, draft = draft, context = context, step = options.step, single = options.single == true, returnTo = options.returnTo or "close"} if guide.step == nil then guide.step = ADGuide.visibleSteps(guide)[1] or def.order[1] end if guide.single then guide.snapshot = ADGuide.deepCopy(draft) end return guide end -- index (nil hvis det aktuelle trin ikke er synligt), total synlige trin. function ADGuide.position(guide) local steps = ADGuide.visibleSteps(guide) local index = nil for position, step in ipairs(steps) do if step == guide.step then index = position end end return index, #steps end -- Sidste trin: ingen synlige efter, eller et [Ret]-trin der gemmer direkte. function ADGuide.isLast(guide) if guide.single and guide.returnTo == "close" then return true end if guide.single then return false end return neighbour(guide, 1) == nil end -- Næste: event "done" når der gemmes (sidste trin, eller [Ret] fra fanens oversigt). function ADGuide.next(guide) if guide.single then if guide.returnTo == "summary" then return clone(guide, {step = "summary", single = false, snapshot = false}), nil end return guide, "done" end local step = neighbour(guide, 1) if step == nil then return guide, "done" end return clone(guide, {step = step}), nil end -- Tilbage: event "cancel" når guiden lukkes (første trin, eller [Ret] fra fanens oversigt). -- I et [Ret]-trin fra guidens eget oversigts-trin fortrydes trinnets ændringer. function ADGuide.back(guide) if guide.single then if guide.returnTo == "summary" then return clone(guide, {step = "summary", single = false, draft = guide.snapshot or guide.draft, snapshot = false}), nil end return guide, "cancel" end local step = neighbour(guide, -1) if step == nil then return guide, "cancel" end return clone(guide, {step = step}), nil end -- [Ret]: åbn kun ét trin. returnTo "summary" (guidens oversigt) eller "close" (gem og luk). function ADGuide.jump(guide, step, returnTo) return clone(guide, {step = step, single = true, returnTo = returnTo or "close", snapshot = ADGuide.deepCopy(guide.draft)}) end function ADGuide.withDraft(guide, draft) return clone(guide, {draft = draft}) end function ADGuide.withContext(guide, context) return clone(guide, {context = context}) end -- label = {key = "...", args = {...}} (oversættes) eller {text = "..."} (færdig tekst); valgfri prefix -- (færdig tekst foran, fx "Mælk: " foran den oversatte politik). function ADGuide.labelText(label, getText) if type(label) ~= "table" then return "" end if label.prefix ~= nil then local rest = {} for key, value in pairs(label) do if key ~= "prefix" then rest[key] = value end end return tostring(label.prefix) .. ADGuide.labelText(rest, getText) end if label.text ~= nil then return tostring(label.text) end local template = getText(label.key) if label.args == nil or #label.args == 0 then return tostring(template) end local unpackFn = table.unpack or unpack local ok, text = pcall(string.format, template, unpackFn(label.args)) return ok and text or tostring(template) end -- Resumé-linje {step, title, parts} -> "Afgrøde: Behold det der gror, så igen". function ADGuide.lineText(line, getText) local parts = {} for _, part in ipairs(line.parts or {}) do local text = ADGuide.labelText(part, getText) if text ~= "" then table.insert(parts, text) end end return ADGuide.labelText(line.title, getText) .. ": " .. (#parts > 0 and table.concat(parts, ", ") or "-") end