Last active
July 31, 2025 06:06
-
-
Save thejoester/b298b852b5cae2af5d8ab39416124696 to your computer and use it in GitHub Desktop.
FoundryVTT: Import .csv into roll table for v12 or v13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| ****************************************************************** | |
| Macro Title: CSV-to-RollTable CSV Importer for Foundry VTT | |
| Author: TheJoester (https://github.com/thejoester) | |
| Last updated 30-July-2025 | |
| License: MIT License | |
| Description: | |
| Promts to select a .csv file, choose a result column, and | |
| auto-generate a RollTable from its contents. | |
| ****************************************************************** | |
| */ | |
| (async () => { | |
| // Step 1: Initial info dialog (still fine to use confirm here) | |
| const firstDialogResult = await new Promise(resolve => { | |
| new foundry.applications.api.DialogV2({ | |
| window: { title: "RollTable CSV Importer", resizable: false }, | |
| content: ` | |
| <p>This macro lets you import a <strong>.csv file</strong> to create a new <strong>RollTable</strong>.</p> | |
| <ul> | |
| <li>You choose which column becomes the result text.</li> | |
| <li>Each row becomes a RollTable result.</li> | |
| <li>Weights and ranges are auto-generated.</li> | |
| </ul> | |
| <p>Click <strong>Import .CSV</strong> to continue.</p> | |
| `, | |
| buttons: [{ | |
| action: "import", | |
| label: "Import .CSV", | |
| default: true, | |
| callback: () => resolve(true) | |
| }], | |
| submit: () => resolve(false) // fallback if user closes dialog | |
| }).render(true); | |
| }); | |
| if (!firstDialogResult) return; | |
| // Step 2: Trigger file input picker for CSV file selection | |
| const fileInput = document.createElement("input"); | |
| fileInput.type = "file"; | |
| fileInput.accept = ".csv"; | |
| fileInput.click(); | |
| // Wait for user to select a file | |
| const file = await new Promise(resolve => { | |
| fileInput.addEventListener("change", () => resolve(fileInput.files[0])); | |
| }); | |
| if (!file) { | |
| ui.notifications.warn("No file selected."); | |
| return; | |
| } | |
| // Step 3: Read file contents as text and parse into lines | |
| const text = await file.text(); | |
| const lines = text.split(/\r?\n/).filter(l => l.trim().length); | |
| if (lines.length === 0) { | |
| ui.notifications.error("CSV appears empty."); | |
| return; | |
| } | |
| // Step 4: Grab first 3 lines as a preview sample | |
| const sampleLines = lines.slice(0, 3).map(l => l.split(",")); | |
| const maxCols = Math.max(...sampleLines.map(row => row.length)); | |
| const headerRow = sampleLines[0]; | |
| // Step 5: Build preview + input dialog content HTML | |
| const previewHTML = ` | |
| <div style="min-width: 600px; max-height: 70vh; overflow-y: auto; padding-right: 1em;"> | |
| <p><strong>CSV Preview (first 3 rows):</strong></p> | |
| <div style="overflow-x: auto;"> | |
| <table class="pf2e" style="width:100%"> | |
| ${sampleLines.map(row => `<tr>${row.map(cell => `<td>${cell}</td>`).join("")}</tr>`).join("")} | |
| </table> | |
| </div> | |
| <div class="form-group"> | |
| <label><input type="checkbox" name="hasHeader" checked> First row is a header</label> | |
| </div> | |
| <div class="form-group" style="display: flex; align-items: center; gap: 0.5em;"> | |
| <label for="columnSelect" style="white-space: nowrap; width: 140px;">Select Column:</label> | |
| <select name="columnSelect" style="flex: 1;"> | |
| ${Array.from({ length: maxCols }).map((_, i) => { | |
| const name = headerRow[i]?.trim() || `Column ${i + 1}`; | |
| return `<option value="${i}">${name}</option>`; | |
| }).join("")} | |
| </select> | |
| </div> | |
| <div class="form-group" style="display: flex; align-items: center; gap: 0.5em;"> | |
| <label for="tableName" style="white-space: nowrap; width: 140px;">RollTable Name:</label> | |
| <input type="text" name="tableName" value="${file.name.replace(/\.csv$/i, '')}" style="flex: 1;"> | |
| </div> | |
| </div> | |
| `; | |
| // Step 6: Show dialog to choose which column to use, and name the RollTable | |
| let selectedColumn, hasHeader, tableName; | |
| const dialog = new foundry.applications.api.DialogV2({ | |
| window: { | |
| title: "Import RollTable from CSV", | |
| resizable: true | |
| }, | |
| content: previewHTML, | |
| buttons: [{ | |
| action: "create", | |
| label: "Create RollTable", | |
| default: true, | |
| callback: (event, button, dialog) => { | |
| const form = button.form; | |
| selectedColumn = parseInt(form.columnSelect.value); | |
| hasHeader = form.hasHeader.checked; | |
| tableName = form.tableName.value.trim(); | |
| if (!tableName) { | |
| ui.notifications.warn("Please enter a name for the RollTable."); | |
| return false; // Prevent dialog from closing | |
| } | |
| return "create"; | |
| } | |
| }, { | |
| action: "cancel", | |
| label: "Cancel" | |
| }], | |
| // Step 7: On submit, build and create the RollTable if confirmed | |
| submit: async result => { | |
| if (result !== "create") { | |
| ui.notifications.info("RollTable creation cancelled."); | |
| return; | |
| } | |
| // Parse full CSV into rows | |
| const dataRows = lines.map(l => l.split(",")); | |
| if (hasHeader) dataRows.shift(); | |
| // Extract result text from selected column, build RollTableResult[] array | |
| const rollResults = dataRows | |
| .map(row => row[selectedColumn]?.trim()) | |
| .filter(text => text?.length > 0) | |
| .map((text, idx) => ({ | |
| _id: foundry.utils.randomID(), | |
| type: 0, // Text result | |
| text, | |
| img: "icons/svg/d20.svg", | |
| weight: 1, | |
| range: [idx + 1, idx + 1], | |
| drawn: false | |
| })); | |
| if (rollResults.length === 0) { | |
| ui.notifications.error("No valid rows found to import."); | |
| return; | |
| } | |
| // Create the RollTable document with generated entries | |
| await RollTable.create({ | |
| name: tableName, | |
| results: rollResults, | |
| formula: `1d${rollResults.length}` | |
| }); | |
| ui.notifications.info(`Created RollTable "${tableName}" with ${rollResults.length} results.`); | |
| } | |
| }); | |
| dialog.render(true); | |
| })(); |
Author
thejoester
commented
Jul 14, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment