模块:AtlasData
来自夜幕之下 Wiki - Reign of Nightfall 中文资料站
更多操作
此模块的文档可以在模块:AtlasData/doc创建
-- Module:AtlasData
-- Sandbox atlas renderer backed by Directus cards.
local p = {}
local function trim(value)
if value == nil then return "" end
return mw.text.trim(tostring(value))
end
local function parseJson(str)
if not str or str == "" then return nil end
local ok, result = pcall(mw.text.jsonDecode, str)
if ok then return result else return nil end
end
local function normalizeTags(value)
if not value then return {} end
if type(value) == "table" then return value end
if type(value) == "string" then
local parsed = parseJson(value)
if type(parsed) == "table" then return parsed end
end
return {}
end
local function contains(list, value)
for _, item in ipairs(list) do
if trim(item) == value then return true end
end
return false
end
local function tagSetFromCards(cards)
local set = {}
for _, card in ipairs(cards) do
for _, tag in ipairs(normalizeTags(card.tags)) do
local name = trim(tag)
if name ~= "" then set[name] = true end
end
end
return set
end
local function uniqueCharacterOptions(cards)
local seen, rows = {}, {}
for _, card in ipairs(cards) do
local name = trim(card.character and card.character.name)
if name ~= "" and not seen[name] then
seen[name] = true
table.insert(rows, name)
end
end
return rows
end
local function acquisitionKey(card)
local key = trim(card.acquisition_type)
if key == "limited" then return "limited" end
return "permanent"
end
local function firstText(...)
local values = {...}
for _, value in ipairs(values) do
local text = trim(value)
if text ~= "" and text ~= "—" then return text end
end
return ""
end
local function fileIcon(name, size)
local text = trim(name)
if text == "" then return "" end
return "[[文件:图标_" .. text .. ".png|" .. (size or "24x24px") .. "|link=]]"
end
local function slug(text)
text = trim(text)
if text == "" then return "" end
return text
end
local professionOptions = { "特攻", "先锋", "歼灭", "支援" }
local desireOptions = { "恨欲", "恶欲", "惰欲", "爱欲", "私欲", "贪欲", "食欲" }
local tagOptions = {
"群攻", "单体", "持续伤害", "治疗", "护盾", "增益", "减益", "控制",
"驱散", "召唤", "反击", "斩杀", "欲火", "生存", "行动条", "限定",
"击破", "位移", "自恢复", "减攻", "暴击", "击退"
}
local function fetchCards(frame)
local url = "https://data.saltedkiss.org/items/cards"
.. "?fields=id,stylename,rarity,tags,acquisition_type,release_date,character.name,profession.name,desire.name"
.. ",intel,supply,execute,strategy"
.. ",attribute_growths.status,attribute_growths.sort,attribute_growths.attr_rank"
.. ",attribute_growths.base_hp,attribute_growths.base_atk,attribute_growths.base_def"
.. ",skill_normal_attack.name,skill_normal_attack.type,skill_normal_attack.weapon,skill_normal_attack.description,skill_normal_attack.description_template,skill_normal_attack.levels.*"
.. ",skill_passive.name,skill_passive.type,skill_passive.trigger_type,skill_passive.trigger_value,skill_passive.tags,skill_passive.description,skill_passive.description_template,skill_passive.levels.*"
.. ",skill_ultimate.name,skill_ultimate.type,skill_ultimate.desire_cost,skill_ultimate.tags,skill_ultimate.description,skill_ultimate.description_template,skill_ultimate.levels.*"
.. ",collection_refine_pool"
.. ",collection_refine_pools.status,collection_refine_pools.sort"
.. ",collection_refine_pools.pool_id,collection_refine_pools.name"
.. ",collection_refine_pools.terms.status,collection_refine_pools.terms.sort"
.. ",collection_refine_pools.terms.effect_id,collection_refine_pools.terms.name"
.. ",collection_refine_pools.terms.effect_type,collection_refine_pools.terms.attr_id"
.. ",collection_refine_pools.terms.attr_value,collection_refine_pools.terms.description"
.. ",collection_refine_pools.terms.quality,collection_refine_pools.terms.normal_weight"
.. "&limit=-1&sort=-rarity,stylename"
.. "&deep[attribute_growths][_filter][status][_eq]=published"
.. "&deep[attribute_growths][_sort]=sort"
.. "&deep[collection_refine_pools][_filter][status][_eq]=published"
.. "&deep[collection_refine_pools][_sort]=sort"
.. "&deep[collection_refine_pools][terms][_filter][status][_eq]=published"
.. "&deep[collection_refine_pools][terms][_sort]=effect_id"
local raw = frame:preprocess(
"{{#get_web_data:url=" .. url
.. "|format=text|data=responseText=__text}}"
.. "{{#external_value:responseText}}"
)
raw = trim(raw)
local parsed = parseJson(raw)
if parsed and type(parsed.data) == "table" then return parsed.data end
return {}
end
local function cardStatus(card)
local releaseDate = trim(card.release_date)
if releaseDate ~= "" then return "published" end
return "draft"
end
local function rarityClass(rarity, styleName)
local n = tonumber(rarity) or 0
if n >= 6 then return "atlas_card-proto--r6-crop-edge-3" end
if n == 5 then return "atlas_card-proto--r5-crop-edge-4" end
return "atlas_card-proto--r4-keep-edge-4"
end
local function atlasStyleFile(styleName, rarity)
local suffix = (tonumber(rarity) or 0) >= 6 and "_2" or ""
return "图鉴_风格_" .. trim(styleName) .. suffix .. ".png"
end
local function cardFaceFile(styleName, face)
local suffix = tostring(face or "1") == "2" and "_2" or ""
return "卡面_" .. trim(styleName) .. suffix .. ".png"
end
local function makePreviewThumbCache(node, styleName, rarity)
local cache = node:tag("div"):addClass("atlas_card-preview-cache")
cache:tag("div")
:attr("data-atlas-preview-thumb", "1")
:attr("data-atlas-preview-face-source", "1")
:wikitext("[[文件:" .. cardFaceFile(styleName, "1") .. "|640x329px|link=]]")
if (tonumber(rarity) or 0) >= 6 then
cache:tag("div")
:attr("data-atlas-preview-thumb", "1")
:attr("data-atlas-preview-face-source", "2")
:wikitext("[[文件:" .. cardFaceFile(styleName, "2") .. "|640x329px|link=]]")
end
end
local function makeFilterOption(parent, key, label, iconName)
local opt = parent:tag("span")
:addClass("atlas_frame-filter-option")
:attr("role", "button")
:attr("tabindex", "0")
:attr("data-atlas-filter", key)
if iconName then
opt:tag("span"):addClass("atlas_frame-filter-icon"):wikitext(fileIcon(iconName, "26x26px"))
end
opt:tag("span"):wikitext(label)
return opt
end
local function makePanelFilterOption(parent, key, label)
return makeFilterOption(parent, key, label):addClass("atlas_frame-panel-option")
end
local function makePanelSection(panel, key, label)
local section = panel:tag("div")
:addClass("atlas_frame-tag-section")
:addClass("atlas_frame-tag-section--" .. key)
section:tag("span"):addClass("atlas_frame-tag-section-label"):wikitext(label)
return section
end
local function firstGrowth(card)
if not card or type(card.attribute_growths) ~= "table" then return {} end
return card.attribute_growths[1] or {}
end
local function iconFileName(name)
local text = trim(name)
if text == "" or text == "—" then return "" end
return "图标_" .. text .. ".png"
end
local function skillSummary(skill)
if type(skill) ~= "table" then return "" end
local text = tostring(skill.description_template or skill.description or "")
text = mw.text.trim(text:gsub("<.->", ""):gsub("%s+", " "))
if text == "" then return "" end
if mw.ustring.len(text) > 86 then
text = mw.ustring.sub(text, 1, 84) .. "…"
end
return text
end
local function skillTagsText(skill)
if type(skill) ~= "table" or not skill.tags then return "" end
if type(skill.tags) == "string" then return skill.tags end
if type(skill.tags) ~= "table" then return "" end
local out = {}
for _, tag in ipairs(skill.tags) do
if tag ~= nil and tostring(tag) ~= "" then table.insert(out, tostring(tag)) end
end
return table.concat(out, "、")
end
local function atlasTriggerLabel(triggerType, triggerValue)
local t = trim(triggerType)
local v = trim(triggerValue)
if t == "normal_attack" then
return "每进行" .. (v ~= "" and v or "?") .. "次普攻"
end
return t ~= "" and t or v
end
local function skillMetaText(skill, skillType)
if type(skill) ~= "table" then return "" end
if skillType == "normal_attack" then
local weapon = trim(skill.weapon)
return weapon ~= "" and ("使用武器 " .. weapon) or ""
elseif skillType == "passive" then
local label = atlasTriggerLabel(skill.trigger_type, skill.trigger_value)
return label ~= "" and ("触发条件 " .. label) or ""
elseif skillType == "ultimate" then
local cost = trim(skill.desire_cost)
return cost ~= "" and ("欲火消耗 " .. cost) or ""
end
return ""
end
local function asJsonArray(value)
if value == nil then return "[]" end
if type(value) == "string" then return value end
if type(value) == "table" then return mw.text.jsonEncode(value) end
return "[]"
end
local function skillParamRows(levelsValue)
local rows = levelsValue
if type(rows) == "string" then rows = parseJson(rows) end
if type(rows) ~= "table" then return {}, {} end
local ordered, byIndex = {}, {}
for idx, row in ipairs(rows) do
if type(row) == "table" then
local paramIndex = tonumber(row.source_param_index or row.sort or idx) or idx
row.__param_index = paramIndex
table.insert(ordered, row)
byIndex[paramIndex] = row
end
end
table.sort(ordered, function(a, b)
local ai = tonumber(a.__param_index) or 9999
local bi = tonumber(b.__param_index) or 9999
if ai ~= bi then return ai < bi end
return tostring(a.name or "") < tostring(b.name or "")
end)
return ordered, byIndex
end
local function skillParamValue(paramRow, level)
if not paramRow or type(paramRow.levels) ~= "table" then return "—" end
local lv = tonumber(level) or 1
local value = paramRow.levels[lv]
if value == nil or value == "" then value = paramRow.levels[1] end
if value == nil or value == "" then return "—" end
return tostring(value)
end
local function addSkillDescriptionText(node, text)
if text and text ~= "" then node:wikitext(text) end
end
local function makeAtlasSkillDescription(skillData, displayLevel)
local wrap = mw.html.create("div"):addClass("atlas_frame-preview-skill-effect card_content_skill_effect")
local template = ""
if type(skillData) == "table" then
template = mw.text.trim(tostring(skillData.description_template or ""))
if template == "" then template = mw.text.trim(tostring(skillData.description or "")) end
end
if template == "" then
wrap:wikitext("—")
return wrap
end
local _, paramsByIndex = skillParamRows(skillData.levels)
local pos = 1
while true do
local pStart, pEnd, pIndex = string.find(template, "{p(%d+)}", pos)
local fStart, fEnd, effectName = string.find(template, "{fx:([^}]+)}", pos)
if not pStart and not fStart then break end
local useParam = pStart and (not fStart or pStart < fStart)
local startPos = useParam and pStart or fStart
local endPos = useParam and pEnd or fEnd
addSkillDescriptionText(wrap, string.sub(template, pos, startPos - 1))
if useParam then
local index = tonumber(pIndex)
local value = skillParamValue(paramsByIndex[index], displayLevel or 1)
local span = wrap:tag("span")
:addClass("card_content_skill-param")
:attr("data-skill-param-index", tostring(index))
:attr("data-skill-param-level", tostring(displayLevel or 1))
span:wikitext(value)
span:tag("sup")
:addClass("card_content_skill-param-index")
:wikitext(tostring(index))
else
local label = mw.text.trim(effectName or "")
wrap:tag("span")
:addClass("card_content_skill-effect-term")
:attr("data-skill-effect", label)
:wikitext("[" .. label .. "]")
end
pos = endPos + 1
end
addSkillDescriptionText(wrap, string.sub(template, pos))
return wrap
end
local collectionQualityMeta = {
[1] = { code = "C", class = "style_collection-quality--c" },
[2] = { code = "B", class = "style_collection-quality--b" },
[3] = { code = "A", class = "style_collection-quality--a" },
[4] = { code = "S", class = "style_collection-quality--s" },
}
local collectionPercentAttrs = {
[8] = true, [9] = true, [11] = true, [12] = true,
[13] = true, [14] = true, [21] = true, [22] = true,
[23] = true, [24] = true, [58] = true, [59] = true, [60] = true,
}
local function collectionRows(input)
if type(input) ~= "table" then return {} end
return input
end
local function collectionFormatPercent(raw)
local n = (tonumber(raw) or 0) / 100
local text
if math.floor(n) == n then
text = string.format("%.0f", n)
elseif math.floor(n * 10) == n * 10 then
text = string.format("%.1f", n)
else
text = string.format("%.2f", n)
end
return "+" .. text .. "%"
end
local function collectionFormatAttr(attrId, raw)
local n = tonumber(raw)
if not n then return "—" end
if collectionPercentAttrs[tonumber(attrId)] then
return collectionFormatPercent(n)
end
return "+" .. mw.getContentLanguage():formatNum(math.floor(n))
end
local function collectionTermDescription(term)
if not term then return "—" end
if tonumber(term.effect_type) == 1 and term.attr_id and term.attr_value then
return firstText(term.name, "属性") .. " " .. collectionFormatAttr(term.attr_id, term.attr_value)
end
return firstText(term.description, "效果描述待补充")
end
local function collectionPreviewTerms(card)
local terms = {}
local pools = collectionRows(card.collection_refine_pools)
for _, pool in ipairs(pools) do
for _, term in ipairs(collectionRows(pool.terms)) do
if type(term) == "table" and (tonumber(term.quality) or 0) >= 3 then
table.insert(terms, term)
end
end
end
table.sort(terms, function(a, b)
local aq = tonumber(a.quality) or 0
local bq = tonumber(b.quality) or 0
if aq ~= bq then return aq > bq end
local aw = tonumber(a.normal_weight) or 0
local bw = tonumber(b.normal_weight) or 0
if aw ~= bw then return aw > bw end
local ae = tonumber(a.effect_id) or 999
local be = tonumber(b.effect_id) or 999
if ae ~= be then return ae < be end
return tostring(a.name or "") < tostring(b.name or "")
end)
return terms
end
local function makeCollectionPreviewCache(node, card)
local cache = node:tag("div"):addClass("atlas_card-collection-preview-cache")
for _, term in ipairs(collectionPreviewTerms(card or {})) do
local quality = collectionQualityMeta[tonumber(term.quality)] or collectionQualityMeta[1]
local item = cache:tag("div")
:addClass("atlas_frame-preview-term")
:attr("data-atlas-collection-term-source", "1")
item:tag("span")
:addClass("style_collection-quality " .. quality.class)
:wikitext(quality.code)
local body = item:tag("span"):addClass("atlas_frame-preview-term-body")
body:tag("span"):addClass("atlas_frame-preview-term-name"):wikitext(firstText(term.name, "词条"))
body:tag("span"):addClass("atlas_frame-preview-term-desc"):wikitext(collectionTermDescription(term))
end
end
local function makeSkillPreviewCache(node, skills)
local cache = node:tag("div"):addClass("atlas_card-skill-preview-cache")
for _, row in ipairs(skills) do
cache:tag("div")
:attr("data-atlas-skill-desc-source", row[1])
:node(makeAtlasSkillDescription(row[2], 1))
end
end
local function makeCard(card)
local rarity = tonumber(card.rarity) or 0
local styleName = firstText(card.stylename, "未知风格")
local charName = firstText(card.character and card.character.name, "—")
local profName = firstText(card.profession and card.profession.name, "—")
local desireName = firstText(card.desire and card.desire.name, "—")
local tags = normalizeTags(card.tags)
local status = cardStatus(card)
local tagText = table.concat(tags, " ")
local searchText = table.concat({ styleName, charName, profName, desireName, tagText }, " ")
local imageFile = atlasStyleFile(styleName, rarity)
local acquisition = acquisitionKey(card)
local acquisitionLabel = acquisition == "limited" and "限定" or "常驻"
local skillNA = card.skill_normal_attack or {}
local skillPA = card.skill_passive or {}
local skillUL = card.skill_ultimate or {}
skillNA.levels = asJsonArray(skillNA.levels)
skillPA.levels = asJsonArray(skillPA.levels)
skillUL.levels = asJsonArray(skillUL.levels)
local node = mw.html.create("div")
:addClass("atlas_card-proto")
:addClass(rarityClass(rarity, styleName))
:addClass("atlas_card-item")
:attr("role", "button")
:attr("tabindex", "0")
:attr("data-atlas-card", "1")
:attr("data-atlas-card-id", trim(card.id))
:attr("data-atlas-status", status)
:attr("data-atlas-danger", tostring(rarity))
:attr("data-atlas-profession", slug(profName))
:attr("data-atlas-desire", slug(desireName))
:attr("data-atlas-character", slug(charName))
:attr("data-atlas-acquisition", acquisition)
:attr("data-atlas-page-title", styleName)
:attr("data-atlas-style-name", styleName)
:attr("data-atlas-character-name", charName)
:attr("data-atlas-profession-name", profName)
:attr("data-atlas-desire-name", desireName)
:attr("data-atlas-profession-icon", iconFileName(profName))
:attr("data-atlas-desire-icon", iconFileName(desireName))
:attr("data-atlas-acquisition-label", acquisitionLabel)
:attr("data-atlas-info-intel", firstText(card.intel, "—"))
:attr("data-atlas-info-supply", firstText(card.supply, "—"))
:attr("data-atlas-info-execute", firstText(card.execute, "—"))
:attr("data-atlas-info-strategy", firstText(card.strategy, "—"))
:attr("data-atlas-skill-normal-name", firstText(skillNA.name, "—"))
:attr("data-atlas-skill-normal-type", firstText(skillNA.type, "—"))
:attr("data-atlas-skill-normal-meta", skillMetaText(skillNA, "normal_attack"))
:attr("data-atlas-skill-normal-desc", skillSummary(skillNA))
:attr("data-atlas-skill-passive-name", firstText(skillPA.name, "—"))
:attr("data-atlas-skill-passive-type", firstText(skillPA.type, "—"))
:attr("data-atlas-skill-passive-meta", skillMetaText(skillPA, "passive"))
:attr("data-atlas-skill-passive-tags", skillTagsText(skillPA))
:attr("data-atlas-skill-passive-desc", skillSummary(skillPA))
:attr("data-atlas-skill-ultimate-name", firstText(skillUL.name, "—"))
:attr("data-atlas-skill-ultimate-type", firstText(skillUL.type, "—"))
:attr("data-atlas-skill-ultimate-meta", skillMetaText(skillUL, "ultimate"))
:attr("data-atlas-skill-ultimate-tags", skillTagsText(skillUL))
:attr("data-atlas-skill-ultimate-desc", skillSummary(skillUL))
:attr("data-atlas-tag-text", tagText)
:attr("data-atlas-tags", table.concat(tags, "|"))
:attr("data-atlas-search-text", searchText)
node:tag("div")
:addClass("atlas_card-proto-img")
:wikitext("[[文件:" .. imageFile .. "|420px|link=]]")
makePreviewThumbCache(node, styleName, rarity)
makeSkillPreviewCache(node, {
{ "normal", skillNA },
{ "passive", skillPA },
{ "ultimate", skillUL },
})
makeCollectionPreviewCache(node, card)
local info = node:tag("div"):addClass("atlas_card-proto-info")
info:tag("div")
:addClass("atlas_card-proto-meta")
:tag("span")
:addClass("atlas_card-proto-character")
:wikitext(charName)
local titleRow = info:tag("div"):addClass("atlas_card-proto-title-row")
local prof = titleRow:tag("span"):addClass("atlas_card-proto-prof")
prof:tag("span"):addClass("atlas_card-proto-prof-icon"):wikitext(fileIcon(profName, "24x24px"))
prof:tag("span"):wikitext(profName)
titleRow:tag("span")
:addClass("atlas_card-proto-desire")
:tag("span")
:addClass("atlas_card-proto-desire-icon")
:wikitext(fileIcon(desireName, "24x24px"))
titleRow:tag("span")
:addClass("atlas_card-proto-title")
:wikitext(styleName)
return node
end
function p.render(frame)
local cards = fetchCards(frame)
local availableTags = tagSetFromCards(cards)
local characterOptions = uniqueCharacterOptions(cards)
local total, published, draft = #cards, 0, 0
for _, card in ipairs(cards) do
if cardStatus(card) == "published" then published = published + 1 else draft = draft + 1 end
end
local root = mw.html.create("div")
:addClass("atlas_frame-root")
:attr("data-atlas-frame-sandbox", "1")
local hero = root:tag("div"):addClass("atlas_frame-hero")
hero:tag("div"):addClass("atlas_frame-hero-art card_fullscreen-img")
:wikitext("[[文件:卡面_复仇童谣.png|2400px|link=]]")
local heroInner = hero:tag("div"):addClass("atlas_frame-hero-inner")
heroInner:tag("div"):addClass("atlas_frame-kicker"):wikitext("Wiki Atlas Prototype")
heroInner:tag("div"):addClass("atlas_frame-title")
:wikitext("风格图鉴")
:tag("span"):addClass("atlas_frame-title-sub"):wikitext("STYLE")
local status = hero:tag("div")
:addClass("atlas_frame-status")
:attr("role", "group")
:attr("aria-label", "图鉴状态筛选")
local statusRows = {
{ "all", "全部角色", total, true },
{ "published", "已实装", published, false },
{ "draft", "未实装", draft, false },
}
for _, item in ipairs(statusRows) do
local row = status:tag("div")
:addClass("atlas_frame-status-item")
:attr("role", "button")
:attr("tabindex", "0")
:attr("data-atlas-status", item[1])
if item[4] then row:addClass("is-active") end
row:tag("span"):wikitext(item[2])
row:tag("span"):addClass("atlas_frame-status-num"):wikitext(tostring(item[3]))
end
local surface = root:tag("div"):addClass("atlas_frame-surface-wrap")
:tag("div"):addClass("atlas_frame-surface")
:tag("div"):addClass("atlas_frame-shell")
local toolbar = surface:tag("div"):addClass("atlas_frame-toolbar")
local primary = toolbar:tag("div"):addClass("atlas_frame-filter-row atlas_frame-filter-row--primary")
local left = primary:tag("div")
:addClass("atlas_frame-filter-left")
:attr("role", "group")
:attr("aria-label", "危险度和职业筛选")
local danger = left:tag("div")
:addClass("atlas_frame-filter-group atlas_frame-filter-group--danger")
:attr("data-atlas-filter-group", "danger")
danger:tag("span"):addClass("atlas_frame-filter-label"):wikitext("危险度")
makeFilterOption(danger, "danger-all", "全部"):addClass("is-active")
makeFilterOption(danger, "danger-6", "6")
makeFilterOption(danger, "danger-5", "5")
makeFilterOption(danger, "danger-4", "4")
local profGroup = left:tag("div")
:addClass("atlas_frame-filter-group atlas_frame-filter-group--profession")
:attr("data-atlas-filter-group", "profession")
profGroup:tag("span"):addClass("atlas_frame-filter-label"):wikitext("职业")
makeFilterOption(profGroup, "profession-all", "全部"):addClass("is-active")
for _, name in ipairs(professionOptions) do
makeFilterOption(profGroup, "profession-" .. name, name, name)
end
local previewToggle = primary:tag("div")
:addClass("atlas_frame-preview-toggle")
:attr("role", "switch")
:attr("tabindex", "0")
:attr("aria-checked", "false")
:attr("data-atlas-preview-toggle", "1")
previewToggle:tag("span"):addClass("atlas_frame-preview-toggle-track")
:tag("span"):addClass("atlas_frame-preview-toggle-thumb")
previewToggle:tag("span"):addClass("atlas_frame-preview-toggle-text"):wikitext("预览模式")
local search = primary:tag("div"):addClass("atlas_frame-search"):attr("role", "search")
search:tag("span"):addClass("atlas_frame-search-mark")
search:tag("div")
:addClass("atlas_frame-search-input")
:attr("role", "textbox")
:attr("tabindex", "0")
:attr("contenteditable", "true")
:attr("spellcheck", "false")
:attr("aria-label", "搜索角色名或风格名")
:attr("data-atlas-placeholder", "搜索角色名或风格名")
:attr("data-atlas-search", "1")
local desire = toolbar:tag("div")
:addClass("atlas_frame-filter-row atlas_frame-filter-row--desire")
:attr("role", "group")
:attr("aria-label", "欲望筛选")
:attr("data-atlas-filter-group", "desire")
desire:tag("span"):addClass("atlas_frame-filter-label"):wikitext("欲望")
makeFilterOption(desire, "desire-all", "全部"):addClass("is-active")
for _, name in ipairs(desireOptions) do
makeFilterOption(desire, "desire-" .. name, name, name)
end
local tagsRow = toolbar:tag("div"):addClass("atlas_frame-filter-row atlas_frame-filter-row--tags")
local toggle = tagsRow:tag("div")
:addClass("atlas_frame-tag-toggle")
:attr("role", "button")
:attr("tabindex", "0")
:attr("data-atlas-tag-toggle", "1")
:attr("aria-expanded", "false")
toggle:tag("span"):wikitext("展开标签筛选")
toggle:tag("span"):addClass("atlas_frame-tag-toggle-line")
toggle:tag("span"):addClass("atlas_frame-tag-toggle-mark"):wikitext("+")
local panel = tagsRow:tag("div"):addClass("atlas_frame-tag-panel"):attr("data-atlas-tag-panel", "1")
local characterPanel = makePanelSection(panel, "character", "角色")
:attr("data-atlas-filter-group", "character")
for _, name in ipairs(characterOptions) do
makePanelFilterOption(characterPanel, "character-" .. name, name)
end
local acquisitionPanel = makePanelSection(panel, "acquisition", "获取途径")
:attr("data-atlas-filter-group", "acquisition")
makePanelFilterOption(acquisitionPanel, "acquisition-permanent", "常驻")
makePanelFilterOption(acquisitionPanel, "acquisition-limited", "限定")
local positionPanel = makePanelSection(panel, "position", "定位")
for _, name in ipairs(tagOptions) do
local opt = positionPanel:tag("span")
:addClass("atlas_frame-tag-option")
:attr("role", "button")
:attr("tabindex", "0")
:attr("data-atlas-tag", name)
if not availableTags[name] then opt:addClass("is-disabled") end
opt:wikitext(name)
end
local layout = surface:tag("div"):addClass("atlas_frame-layout")
local grid = layout:tag("div")
:addClass("atlas_frame-grid atlas_frame-grid--card-protos")
:attr("aria-label", "图鉴卡片列表")
for _, card in ipairs(cards) do
grid:node(makeCard(card))
end
grid:tag("div")
:addClass("atlas_frame-empty")
:attr("data-atlas-empty", "1")
:attr("hidden", "hidden")
:wikitext("没有符合条件的风格")
local preview = layout:tag("div"):addClass("atlas_frame-preview"):attr("data-atlas-preview-panel", "1")
preview:tag("div")
:addClass("atlas_frame-preview-empty")
:attr("data-atlas-preview-empty", "1")
:wikitext("请选择风格预览")
local previewHead = preview:tag("div"):addClass("atlas_frame-preview-head")
previewHead:tag("div"):addClass("atlas_frame-preview-title"):attr("data-atlas-preview-title", "1"):wikitext("详情预览")
previewHead:tag("div")
:addClass("atlas_frame-preview-open")
:attr("role", "button")
:attr("tabindex", "0")
:attr("aria-disabled", "true")
:attr("data-atlas-preview-open", "1")
:wikitext("打开风格页")
previewHead:tag("div"):addClass("atlas_frame-preview-mode"):attr("data-atlas-preview-mode-label", "1"):wikitext("默认点击会在新窗口打开风格页")
local artWrap = preview:tag("div"):addClass("atlas_frame-preview-art-wrap")
artWrap:tag("div"):addClass("atlas_frame-preview-art"):attr("data-atlas-preview-art", "1")
local faceSwitch = artWrap:tag("div"):addClass("atlas_frame-preview-face-switch"):attr("data-atlas-preview-face-switch", "1")
faceSwitch:tag("div"):addClass("atlas_frame-preview-face")
:attr("role", "button"):attr("tabindex", "0"):attr("data-atlas-preview-face", "1"):wikitext("Ⅰ")
faceSwitch:tag("div"):addClass("atlas_frame-preview-face is-active")
:attr("role", "button"):attr("tabindex", "0"):attr("data-atlas-preview-face", "2"):wikitext("Ⅱ")
local previewMeta = preview:tag("div"):addClass("atlas_frame-preview-meta")
local metaTop = previewMeta:tag("div"):addClass("atlas_frame-preview-meta-top")
metaTop:tag("div"):addClass("atlas_frame-preview-name"):attr("data-atlas-preview-style", "1"):wikitext("未选择")
local metaTax = metaTop:tag("div"):addClass("atlas_frame-preview-taxonomy")
local metaProf = metaTax:tag("div"):addClass("atlas_frame-preview-taxonomy-item")
metaProf:tag("span"):addClass("atlas_frame-preview-stat-icon"):attr("data-atlas-preview-profession-icon", "1")
metaProf:tag("span"):addClass("atlas_frame-preview-stat-text"):attr("data-atlas-preview-profession-text", "1"):wikitext("—")
local metaDesire = metaTax:tag("div"):addClass("atlas_frame-preview-taxonomy-item")
metaDesire:tag("span"):addClass("atlas_frame-preview-stat-icon"):attr("data-atlas-preview-desire-icon", "1")
metaDesire:tag("span"):addClass("atlas_frame-preview-stat-text"):attr("data-atlas-preview-desire-text", "1"):wikitext("—")
local metaSub = previewMeta:tag("div"):addClass("atlas_frame-preview-meta-sub")
metaSub:tag("div"):addClass("atlas_frame-preview-sub"):attr("data-atlas-preview-sub", "1"):wikitext("开启预览模式后,点击卡片会在这里显示横屏卡面和简单信息。")
metaSub:tag("div"):addClass("atlas_frame-preview-tags card_content_style-tags card_content_skill-tags")
:attr("data-atlas-preview-tags", "1"):wikitext("—")
metaSub:tag("div"):addClass("atlas_frame-preview-acquisition"):attr("data-atlas-preview-acquisition", "1"):wikitext("—")
local infoLine = preview:tag("div"):addClass("atlas_frame-preview-info-line card_content_info")
local dangerItem = infoLine:tag("div"):addClass("atlas_frame-preview-info-item atlas_frame-preview-info-item--danger card_content_info-item")
dangerItem:tag("div"):addClass("atlas_frame-preview-danger-label"):wikitext("")
dangerItem:tag("div"):addClass("atlas_frame-preview-stars"):attr("data-atlas-preview-danger", "1"):wikitext("—")
local attrRows = {
{ "情报", "intel" },
{ "物资", "supply" },
{ "执行", "execute" },
{ "策略", "strategy" },
}
for _, row in ipairs(attrRows) do
local item = infoLine:tag("div"):addClass("atlas_frame-preview-info-item card_content_info-item")
item:tag("div"):wikitext(row[1])
item:tag("div"):attr("data-atlas-preview-attr-" .. row[2], "1"):wikitext("—")
end
local skills = preview:tag("div"):addClass("atlas_frame-preview-skills"):attr("data-atlas-preview-skills", "1")
local skillRows = {
{ "普攻", "normal" },
{ "被动", "passive" },
{ "终结", "ultimate" },
}
for _, row in ipairs(skillRows) do
local skill = skills:tag("div"):addClass("atlas_frame-preview-skill"):attr("data-atlas-preview-skill", row[2])
local head = skill:tag("div"):addClass("atlas_frame-preview-skill-head")
head:tag("b"):addClass("atlas_frame-preview-skill-name"):attr("data-atlas-preview-skill-" .. row[2] .. "-name", "1"):wikitext("—")
head:tag("span"):addClass("atlas_frame-preview-skill-type card_accent--type"):attr("data-atlas-preview-skill-" .. row[2] .. "-type", "1"):wikitext(row[1])
head:tag("span"):addClass("atlas_frame-preview-skill-meta"):attr("data-atlas-preview-skill-" .. row[2] .. "-meta", "1"):wikitext("—")
skill:tag("div"):addClass("atlas_frame-preview-skill-desc"):attr("data-atlas-preview-skill-" .. row[2] .. "-desc", "1"):wikitext("—")
end
local collectionPreview = preview:tag("div")
:addClass("atlas_frame-preview-collection")
:attr("data-atlas-preview-collection", "1")
collectionPreview:tag("div"):addClass("atlas_frame-preview-collection-title"):wikitext("映像词条")
collectionPreview:tag("div")
:addClass("atlas_frame-preview-collection-list")
:attr("data-atlas-preview-collection-list", "1")
:wikitext("—")
preview:tag("div"):addClass("atlas_frame-preview-copy"):attr("data-atlas-preview-copy", "1")
:wikitext("默认状态下点击卡片会在新窗口打开对应风格页面。")
return tostring(root)
end
return p