// полный снимок доски: все задачи и связи, обе раскладки относительно дорожек (layout — обычная, layoutByDate — «по датам»; h только если задана вручную), // порядок и свёртка дорожек, фильтры. Текущую раскладку компонент сохраняет в хранилище (saveLayout) перед вызовом import { JIRA_URL } from "./constants.ts"; import { colWidths, foldedSet, laneOrder, savedLayout } from "./storage.ts"; import type { BoardModel, Filters, Visible } from "./types.ts"; type Saved = Record; export function exportJson(model: BoardModel, vis: Visible, f: Filters, now: string): unknown { const savedPos = (savedLayout(false) || {}) as Saved, savedDate = (savedLayout(true) || {}) as Saved; const shown = new Set(vis.nodes.map((n) => n.key)); const fromSaved = (saved: Saved, k: string) => { const r = saved[k]; if (!r) return null; const o: { x: number; ry: number; w?: number; h?: number } = { x: r.x, ry: r.ry }; if (typeof r.w === "number") o.w = r.w; // запись без w допустима (как в эталоне) if (typeof r.h === "number") o.h = r.h; return o; }; const nodes = model.nodes.filter((n) => !n.stub).map((n) => ({ key: n.key, title: n.title, status: n.status, priority: n.prio, kind: n.kind, due: n.due || null, assignee: n.who || null, sprint: n.sprint || null, epic: n.epic, url: `${JIRA_URL}/browse/${n.key}`, visible: shown.has(n.key), layout: fromSaved(savedPos, n.key), layoutByDate: fromSaved(savedDate, n.key), })); const order = laneOrder(); return { format: "arm-ka-links/1", exportedAt: now, source: model.fileName, filters: { sprints: [...f.sprints], assignees: [...f.who], neighbors: f.neighbors, onlyLinked: f.onlyLinked, onlyHand: f.onlyHand, criticalPath: f.crit, byDate: f.byDate, hiddenKinds: [...f.hideKind], hiddenEdges: [...f.hideEdge] }, laneOrder: order.length ? order : vis.lanes.map((l) => l.name), collapsedLanes: [...foldedSet()], dateColumns: colWidths(), nodes, links: model.links.map((l) => ({ from: l.src, to: l.dst, type: l.type })), }; } // имя файла выгрузки: links-<имя исходного файла без .csv>-<дата>.json export function exportFileName(model: BoardModel, now: string): string { return `links-${(model.fileName || "board").replace(/[^\wА-Яа-яЁё.-]+/g, "_").replace(/\.csv$/i, "")}-${now.slice(0, 10)}.json`; }