sbuffkin_hexmaker/src/hex-map/HexExportModal.ts
isaprettycoolguy@protonmail.com 6aa885b5b2 feat: export module — PDF/Markdown for notes, hexes, maps, tables, workflows (1.2.0)
Adds a unified export pipeline under src/export/ with exporters for single notes,
structured hexes, maps (with reference table), random tables, and workflows
(with rolled samples). PDF rendering reuses Obsidian's MarkdownRenderer + theme
CSS; map PNG/PDF goes through a dedicated canvas renderer.

UI surface:
- "Export" tab in MapModal (PNG/PDF with overlay + size options)
- "Export" link in HexEditorModal + HexExportModal
- "Export PDF/Markdown" links in RandomTableView header
- "Export…" button in WorkflowEditorModal + WorkflowExportModal
- Commands: export current note (PDF/MD), current hex, current workflow
- File-menu items on any .md file: Export to PDF / Markdown

Other:
- New `exportFolder` setting (defaults to {worldFolder}/exports)
- hexGeometry: add hexSize / hexCenter / hexPolygonPoints / gridBoundingBox
- HexTableView + HexEditorModal: first-wins on duplicate palette names
  (matches HexMapView's .find() behaviour)
- Tests: unit + integration suites for all exporters; sim-vault fixture

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 20:46:41 -04:00

115 lines
3.7 KiB
TypeScript

/**
* Export options modal for a single hex (Phase 1.5).
*
* Currently exposes file name + GM-sections toggle + format buttons. The
* architecture is set up to grow: future options (per-section toggles,
* "include related random tables", linked-note expansions, etc.) slot in as
* new rows in `renderOptions`. The orchestrator wires those toggles into
* `SingleHexExportOptions` without needing structural changes.
*/
import { App, TFile } from "obsidian";
import { HexmakerModal } from "../HexmakerModal";
import type HexmakerPlugin from "../HexmakerPlugin";
import {
exportHexAsPdf,
exportHexAsMarkdown,
} from "../export/exporters/singleHex";
export class HexExportModal extends HexmakerModal {
constructor(
app: App,
private plugin: HexmakerPlugin,
private hexFile: TFile,
private defaultName?: string,
) {
super(app);
}
onOpen(): void {
this.titleEl.setText(`Export hex: ${this.hexFile.basename}`);
this.makeDraggable();
this.render();
}
private render(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("duckmage-hex-export-modal");
const baseName = this.defaultName ?? this.hexFile.basename;
contentEl.createEl("p", {
cls: "duckmage-export-tab-hint",
text: "Export this hex as a structured PDF or Markdown file. Each section is rendered as its own block with generous spacing.",
});
const optsForm = contentEl.createDiv({ cls: "duckmage-export-tab-options" });
// File name
const nameRow = optsForm.createDiv({ cls: "duckmage-export-tab-row" });
nameRow.createEl("label", {
text: "File name",
cls: "duckmage-export-tab-label",
});
const nameInput = nameRow.createEl("input", {
type: "text",
cls: "duckmage-export-tab-text",
attr: { placeholder: baseName },
});
nameInput.value = baseName;
// Include-GM toggle. Default OFF — GM-only sections should be opt-in.
const gmRow = optsForm.createDiv({ cls: "duckmage-export-tab-row" });
const gmCb = gmRow.createEl("input", {
type: "checkbox",
cls: "duckmage-export-tab-checkbox",
});
gmCb.checked = false;
/* eslint-disable obsidianmd/ui/sentence-case -- GM is an acronym; Hidden/Secret are section names */
gmRow.createEl("label", {
text: "Include GM-only sections (Hidden, Secret)",
cls: "duckmage-export-tab-label",
});
/* eslint-enable obsidianmd/ui/sentence-case */
gmRow.addEventListener("click", (e) => {
if (e.target instanceof HTMLInputElement) return;
gmCb.checked = !gmCb.checked;
});
// Live filename preview
const preview = contentEl.createDiv({ cls: "duckmage-export-tab-preview" });
const buildStem = (): string =>
nameInput.value.trim() || baseName;
const updatePreview = () => {
preview.setText(`Output: ${buildStem()}.pdf / ${buildStem()}.md`);
};
nameInput.addEventListener("input", updatePreview);
updatePreview();
// Action buttons
const actions = contentEl.createDiv({ cls: "duckmage-export-tab-actions" });
const pdfBtn = actions.createEl("button", {
cls: "mod-cta",
text: "Export PDF",
});
pdfBtn.addEventListener("click", () => {
void exportHexAsPdf(this.plugin, this.hexFile, {
outputName: buildStem(),
includeGmSections: gmCb.checked,
});
this.close();
});
const mdBtn = actions.createEl("button", {
cls: "mod-cta",
text: "Export Markdown",
});
mdBtn.addEventListener("click", () => {
void exportHexAsMarkdown(this.plugin, this.hexFile, {
outputName: buildStem(),
includeGmSections: gmCb.checked,
});
this.close();
});
}
}