File size: 2,892 Bytes
831819e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
export const esc = (value) =>
  String(value ?? "").replace(
    /[&<>"']/g,
    (c) =>
      ({
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&#39;",
      })[c],
  );

export const number = (value) =>
  Number.isFinite(value)
    ? value.toLocaleString("zh-CN", { maximumFractionDigits: 1 })
    : "—";

export function icon(name, label, attributes = "") {
  return `<button type="button" class="icon-button synth-icon" aria-label="${esc(label)}" title="${esc(label)}" ${attributes}><i data-lucide="${name}" aria-hidden="true"></i></button>`;
}

export function external(label, url) {
  return `<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${esc(label)}<i data-lucide="arrow-up-right" aria-hidden="true"></i></a>`;
}

export function section(title, body, id = "") {
  return `<section class="section" ${id ? `id="${id}"` : ""}><h2>${esc(title)}</h2>${body}</section>`;
}

export function table(headings, rows, className = "") {
  return `<div class="table-scroll ${className}" tabindex="0" role="region" aria-label="${esc(headings.join("、"))}"><table><thead><tr>${headings.map((h) => `<th scope="col">${esc(h)}</th>`).join("")}</tr></thead><tbody>${rows.join("")}</tbody></table></div>`;
}

export function empty(message = "没有匹配的数据集", reset = false) {
  return `<div class="empty-state"><i data-lucide="search-x" aria-hidden="true"></i><p>${esc(message)}</p>${reset ? '<button type="button" class="text-button" data-clear-filters>重置筛选</button>' : ""}</div>`;
}

export function enhance() {
  window.lucide?.createIcons();
}

export function fact(label, value) {
  return `<div><dt>${esc(label)}</dt><dd>${value}</dd></div>`;
}

export function tier(id, meta) {
  const value = meta.tier[id];
  return `<span class="tier tier-${esc(id)}" title="${esc(value?.name + " · " + value?.desc)}">${esc(id === "-" ? "—" : id)}</span>`;
}

export function status(id, meta) {
  return `<span class="status status-${esc(id)}" title="${esc(meta.status[id]?.desc)}">${esc(meta.status[id]?.name || id)}</span>`;
}

export function thumbnail(dataset, context) {
  const preview = context.previews[dataset.id]?.[0];
  const figure = context.figures[dataset.id]?.figures?.[0]?.images?.[0];
  return preview || figure
    ? `<img class="dataset-thumb" src="${esc(preview?.rgb || figure.src)}" alt="${esc(dataset.name)}${preview ? "样例" : "官方图"}" loading="lazy" decoding="async">`
    : '<span class="dataset-thumb no-thumb"><i data-lucide="images" aria-hidden="true"></i></span>';
}

export async function readJSON(path, { optional = false, fallback = {} } = {}) {
  try {
    const response = await fetch(path);
    if (!response.ok) throw new Error(`${path}: ${response.status}`);
    return await response.json();
  } catch (error) {
    if (!optional) throw error;
    return fallback;
  }
}